]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - stand/efi/loader/efiserialio.c
MFV r362990:
[FreeBSD/FreeBSD.git] / stand / efi / loader / efiserialio.c
1 /*-
2  * Copyright (c) 1998 Michael Smith (msmith@freebsd.org)
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
14  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
17  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
19  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
20  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
23  * SUCH DAMAGE.
24  */
25
26 #include <sys/cdefs.h>
27 __FBSDID("$FreeBSD$");
28
29 #include <stand.h>
30 #include <sys/errno.h>
31 #include <bootstrap.h>
32 #include <stdbool.h>
33
34 #include <efi.h>
35 #include <efilib.h>
36
37 #include "loader_efi.h"
38
39 static EFI_GUID serial = SERIAL_IO_PROTOCOL;
40
41 #define COMC_TXWAIT     0x40000         /* transmit timeout */
42
43 #ifndef COMSPEED
44 #define COMSPEED        9600
45 #endif
46
47 #define PNP0501         0x501           /* 16550A-compatible COM port */
48
49 struct serial {
50         uint64_t        baudrate;
51         uint8_t         databits;
52         EFI_PARITY_TYPE parity;
53         EFI_STOP_BITS_TYPE stopbits;
54         uint8_t         ignore_cd;      /* boolean */
55         uint8_t         rtsdtr_off;     /* boolean */
56         int             ioaddr;         /* index in handles array */
57         EFI_HANDLE      currdev;        /* current serial device */
58         EFI_HANDLE      condev;         /* EFI Console device */
59         SERIAL_IO_INTERFACE *sio;
60 };
61
62 static void     comc_probe(struct console *);
63 static int      comc_init(int);
64 static void     comc_putchar(int);
65 static int      comc_getchar(void);
66 static int      comc_ischar(void);
67 static bool     comc_setup(void);
68 static int      comc_parse_intval(const char *, unsigned *);
69 static int      comc_port_set(struct env_var *, int, const void *);
70 static int      comc_speed_set(struct env_var *, int, const void *);
71
72 static struct serial    *comc_port;
73 extern struct console efi_console;
74
75 struct console comconsole = {
76         .c_name = "comconsole",
77         .c_desc = "serial port",
78         .c_flags = 0,
79         .c_probe = comc_probe,
80         .c_init = comc_init,
81         .c_out = comc_putchar,
82         .c_in = comc_getchar,
83         .c_ready = comc_ischar,
84 };
85
86 static EFI_STATUS
87 efi_serial_init(EFI_HANDLE **handlep, int *nhandles)
88 {
89         UINTN bufsz = 0;
90         EFI_STATUS status;
91         EFI_HANDLE *handles;
92
93         /*
94          * get buffer size
95          */
96         *nhandles = 0;
97         handles = NULL;
98         status = BS->LocateHandle(ByProtocol, &serial, NULL, &bufsz, handles);
99         if (status != EFI_BUFFER_TOO_SMALL)
100                 return (status);
101
102         if ((handles = malloc(bufsz)) == NULL)
103                 return (ENOMEM);
104
105         *nhandles = (int)(bufsz / sizeof (EFI_HANDLE));
106         /*
107          * get handle array
108          */
109         status = BS->LocateHandle(ByProtocol, &serial, NULL, &bufsz, handles);
110         if (EFI_ERROR(status)) {
111                 free(handles);
112                 *nhandles = 0;
113         } else
114                 *handlep = handles;
115         return (status);
116 }
117
118 /*
119  * Find serial device number from device path.
120  * Return -1 if not found.
121  */
122 static int
123 efi_serial_get_index(EFI_DEVICE_PATH *devpath, int idx)
124 {
125         ACPI_HID_DEVICE_PATH  *acpi;
126         CHAR16 *text;
127
128         while (!IsDevicePathEnd(devpath)) {
129                 if (DevicePathType(devpath) == MESSAGING_DEVICE_PATH &&
130                     DevicePathSubType(devpath) == MSG_UART_DP)
131                         return (idx);
132
133                 if (DevicePathType(devpath) == ACPI_DEVICE_PATH &&
134                     (DevicePathSubType(devpath) == ACPI_DP ||
135                     DevicePathSubType(devpath) == ACPI_EXTENDED_DP)) {
136
137                         acpi = (ACPI_HID_DEVICE_PATH *)devpath;
138                         if (acpi->HID == EISA_PNP_ID(PNP0501)) {
139                                 return (acpi->UID);
140                         }
141                 }
142
143                 devpath = NextDevicePathNode(devpath);
144         }
145         return (-1);
146 }
147
148 /*
149  * The order of handles from LocateHandle() is not known, we need to
150  * iterate handles, pick device path for handle, and check the device
151  * number.
152  */
153 static EFI_HANDLE
154 efi_serial_get_handle(int port, EFI_HANDLE condev)
155 {
156         EFI_STATUS status;
157         EFI_HANDLE *handles, handle;
158         EFI_DEVICE_PATH *devpath;
159         int index, nhandles;
160
161         if (port == -1)
162                 return (NULL);
163
164         handles = NULL;
165         nhandles = 0;
166         status = efi_serial_init(&handles, &nhandles);
167         if (EFI_ERROR(status))
168                 return (NULL);
169
170         /*
171          * We have console handle, set ioaddr for it.
172          */
173         if (condev != NULL) {
174                 for (index = 0; index < nhandles; index++) {
175                         if (condev == handles[index]) {
176                                 devpath = efi_lookup_devpath(condev);
177                                 comc_port->ioaddr =
178                                     efi_serial_get_index(devpath, index);
179                                 efi_close_devpath(condev);
180                                 free(handles);
181                                 return (condev);
182                         }
183                 }
184         }
185
186         handle = NULL;
187         for (index = 0; handle == NULL && index < nhandles; index++) {
188                 devpath = efi_lookup_devpath(handles[index]);
189                 if (port == efi_serial_get_index(devpath, index))
190                         handle = (handles[index]);
191                 efi_close_devpath(handles[index]);
192         }
193
194         /*
195          * In case we did fail to identify the device by path, use port as
196          * array index. Note, we did check port == -1 above.
197          */
198         if (port < nhandles && handle == NULL)
199                 handle = handles[port];
200
201         free(handles);
202         return (handle);
203 }
204
205 static EFI_HANDLE
206 comc_get_con_serial_handle(const char *name)
207 {
208         EFI_HANDLE handle;
209         EFI_DEVICE_PATH *node;
210         EFI_STATUS status;
211         char *buf, *ep;
212         size_t sz;
213
214         buf = NULL;
215         sz = 0;
216         status = efi_global_getenv(name, buf, &sz);
217         if (status == EFI_BUFFER_TOO_SMALL) {
218                 buf = malloc(sz);
219                 if (buf == NULL)
220                         return (NULL);
221                 status = efi_global_getenv(name, buf, &sz);
222         }
223         if (status != EFI_SUCCESS) {
224                 free(buf);
225                 return (NULL);
226         }
227
228         ep = buf + sz;
229         node = (EFI_DEVICE_PATH *)buf;
230         while ((char *)node < ep) {
231                 status = BS->LocateDevicePath(&serial, &node, &handle);
232                 if (status == EFI_SUCCESS) {
233                         free(buf);
234                         return (handle);
235                 }
236
237                 /* Sanity check the node before moving to the next node. */
238                 if (DevicePathNodeLength(node) < sizeof(*node))
239                         break;
240
241                 /* Start of next device path in list. */
242                 node = NextDevicePathNode(node);
243         }
244         free(buf);
245         return (NULL);
246 }
247
248 static void
249 comc_probe(struct console *sc)
250 {
251         EFI_STATUS status;
252         EFI_HANDLE handle;
253         char name[20];
254         char value[20];
255         unsigned val;
256         char *env, *buf, *ep;
257         size_t sz;
258
259         if (comc_port == NULL) {
260                 comc_port = malloc(sizeof (struct serial));
261                 if (comc_port == NULL)
262                         return;
263         }
264         comc_port->baudrate = COMSPEED;
265         comc_port->ioaddr = 0;                  /* default port */
266         comc_port->databits = 8;                /* 8,n,1 */
267         comc_port->parity = NoParity;           /* 8,n,1 */
268         comc_port->stopbits = OneStopBit;       /* 8,n,1 */
269         comc_port->ignore_cd = 1;               /* ignore cd */
270         comc_port->rtsdtr_off = 0;              /* rts-dtr is on */
271         comc_port->sio = NULL;
272
273         handle = NULL;
274         env = getenv("efi_com_port");
275         if (comc_parse_intval(env, &val) == CMD_OK) {
276                 comc_port->ioaddr = val;
277         } else {
278                 /*
279                  * efi_com_port is not set, we need to select default.
280                  * First, we consult ConOut variable to see if
281                  * we have serial port redirection. If not, we just
282                  * pick first device.
283                  */
284                 handle = comc_get_con_serial_handle("ConOut");
285                 comc_port->condev = handle;
286         }
287
288         handle = efi_serial_get_handle(comc_port->ioaddr, handle);
289         if (handle != NULL) {
290                 comc_port->currdev = handle;
291                 status = BS->OpenProtocol(handle, &serial,
292                     (void**)&comc_port->sio, IH, NULL,
293                     EFI_OPEN_PROTOCOL_GET_PROTOCOL);
294
295                 if (EFI_ERROR(status))
296                         comc_port->sio = NULL;
297         }
298
299         if (env != NULL) 
300                 unsetenv("efi_com_port");
301         snprintf(value, sizeof (value), "%u", comc_port->ioaddr);
302         env_setenv("efi_com_port", EV_VOLATILE, value,
303             comc_port_set, env_nounset);
304
305         env = getenv("efi_com_speed");
306         if (comc_parse_intval(env, &val) == CMD_OK)
307                 comc_port->baudrate = val;
308
309         if (env != NULL)
310                 unsetenv("efi_com_speed");
311         snprintf(value, sizeof (value), "%ju", (uintmax_t)comc_port->baudrate);
312         env_setenv("efi_com_speed", EV_VOLATILE, value,
313             comc_speed_set, env_nounset);
314
315         comconsole.c_flags = 0;
316         if (comc_setup())
317                 sc->c_flags = C_PRESENTIN | C_PRESENTOUT;
318 }
319
320 static int
321 comc_init(int arg __unused)
322 {
323
324         if (comc_setup())
325                 return (CMD_OK);
326
327         comconsole.c_flags = 0;
328         return (CMD_ERROR);
329 }
330
331 static void
332 comc_putchar(int c)
333 {
334         int wait;
335         EFI_STATUS status;
336         UINTN bufsz = 1;
337         char cb = c;
338
339         if (comc_port->sio == NULL)
340                 return;
341
342         for (wait = COMC_TXWAIT; wait > 0; wait--) {
343                 status = comc_port->sio->Write(comc_port->sio, &bufsz, &cb);
344                 if (status != EFI_TIMEOUT)
345                         break;
346         }
347 }
348
349 static int
350 comc_getchar(void)
351 {
352         EFI_STATUS status;
353         UINTN bufsz = 1;
354         char c;
355
356
357         /*
358          * if this device is also used as ConIn, some firmwares
359          * fail to return all input via SIO protocol.
360          */
361         if (comc_port->currdev == comc_port->condev) {
362                 if ((efi_console.c_flags & C_ACTIVEIN) == 0)
363                         return (efi_console.c_in());
364                 return (-1);
365         }
366
367         if (comc_port->sio == NULL)
368                 return (-1);
369
370         status = comc_port->sio->Read(comc_port->sio, &bufsz, &c);
371         if (EFI_ERROR(status) || bufsz == 0)
372                 return (-1);
373
374         return (c);
375 }
376
377 static int
378 comc_ischar(void)
379 {
380         EFI_STATUS status;
381         uint32_t control;
382
383         /*
384          * if this device is also used as ConIn, some firmwares
385          * fail to return all input via SIO protocol.
386          */
387         if (comc_port->currdev == comc_port->condev) {
388                 if ((efi_console.c_flags & C_ACTIVEIN) == 0)
389                         return (efi_console.c_ready());
390                 return (0);
391         }
392
393         if (comc_port->sio == NULL)
394                 return (0);
395
396         status = comc_port->sio->GetControl(comc_port->sio, &control);
397         if (EFI_ERROR(status))
398                 return (0);
399
400         return (!(control & EFI_SERIAL_INPUT_BUFFER_EMPTY));
401 }
402
403 static int
404 comc_parse_intval(const char *value, unsigned *valp)
405 {
406         unsigned n;
407         char *ep;
408
409         if (value == NULL || *value == '\0')
410                 return (CMD_ERROR);
411
412         errno = 0;
413         n = strtoul(value, &ep, 10);
414         if (errno != 0 || *ep != '\0')
415                 return (CMD_ERROR);
416         *valp = n;
417
418         return (CMD_OK);
419 }
420
421 static int
422 comc_port_set(struct env_var *ev, int flags, const void *value)
423 {
424         unsigned port;
425         SERIAL_IO_INTERFACE *sio;
426         EFI_HANDLE handle;
427         EFI_STATUS status;
428
429         if (value == NULL)
430                 return (CMD_ERROR);
431
432         if (comc_parse_intval(value, &port) != CMD_OK) 
433                 return (CMD_ERROR);
434
435         handle = efi_serial_get_handle(port, NULL);
436         if (handle == NULL) {
437                 printf("no handle\n");
438                 return (CMD_ERROR);
439         }
440
441         status = BS->OpenProtocol(handle, &serial,
442             (void**)&sio, IH, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
443
444         if (EFI_ERROR(status)) {
445                 printf("OpenProtocol: %lu\n", EFI_ERROR_CODE(status));
446                 return (CMD_ERROR);
447         }
448
449         comc_port->currdev = handle;
450         comc_port->ioaddr = port;
451         comc_port->sio = sio;
452         
453         (void) comc_setup();
454
455         env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL);
456         return (CMD_OK);
457 }
458
459 static int
460 comc_speed_set(struct env_var *ev, int flags, const void *value)
461 {
462         unsigned speed;
463
464         if (value == NULL)
465                 return (CMD_ERROR);
466
467         if (comc_parse_intval(value, &speed) != CMD_OK) 
468                 return (CMD_ERROR);
469
470         comc_port->baudrate = speed;
471         (void) comc_setup();
472
473         env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL);
474
475         return (CMD_OK);
476 }
477
478 /*
479  * In case of error, we also reset ACTIVE flags, so the console
480  * framefork will try alternate consoles.
481  */
482 static bool
483 comc_setup(void)
484 {
485         EFI_STATUS status;
486         UINT32 control;
487
488         /* port is not usable */
489         if (comc_port->sio == NULL)
490                 return (false);
491
492         status = comc_port->sio->Reset(comc_port->sio);
493         if (EFI_ERROR(status))
494                 return (false);
495
496         status = comc_port->sio->SetAttributes(comc_port->sio,
497             comc_port->baudrate, 0, 0, comc_port->parity,
498             comc_port->databits, comc_port->stopbits);
499         if (EFI_ERROR(status))
500                 return (false);
501
502         status = comc_port->sio->GetControl(comc_port->sio, &control);
503         if (EFI_ERROR(status))
504                 return (false);
505         if (comc_port->rtsdtr_off) {
506                 control &= ~(EFI_SERIAL_REQUEST_TO_SEND |
507                     EFI_SERIAL_DATA_TERMINAL_READY);
508         } else {
509                 control |= EFI_SERIAL_REQUEST_TO_SEND;
510         }
511         (void) comc_port->sio->SetControl(comc_port->sio, control);
512         /* Mark this port usable. */
513         comconsole.c_flags |= (C_PRESENTIN | C_PRESENTOUT);
514         return (true);
515 }