Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/stand/efi/libefi/eficom.c
34860 views
1
/*-
2
* Copyright (c) 1998 Michael Smith ([email protected])
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 <stand.h>
27
#include <sys/errno.h>
28
#include <bootstrap.h>
29
#include <stdbool.h>
30
31
#include <efi.h>
32
#include <efilib.h>
33
34
static EFI_GUID serial = SERIAL_IO_PROTOCOL;
35
36
#define COMC_TXWAIT 0x40000 /* transmit timeout */
37
38
#define PNP0501 0x501 /* 16550A-compatible COM port */
39
40
struct serial {
41
uint64_t newbaudrate;
42
uint64_t baudrate;
43
uint32_t timeout;
44
uint32_t receivefifodepth;
45
uint32_t databits;
46
EFI_PARITY_TYPE parity;
47
EFI_STOP_BITS_TYPE stopbits;
48
int ioaddr; /* index in handles array */
49
EFI_HANDLE currdev; /* current serial device */
50
EFI_HANDLE condev; /* EFI Console device */
51
SERIAL_IO_INTERFACE *sio;
52
};
53
54
static void comc_probe(struct console *);
55
static int comc_init(int);
56
static void comc_putchar(int);
57
static int comc_getchar(void);
58
static int comc_ischar(void);
59
static bool comc_setup(void);
60
static int comc_parse_intval(const char *, unsigned *);
61
static int comc_port_set(struct env_var *, int, const void *);
62
static int comc_speed_set(struct env_var *, int, const void *);
63
64
static struct serial *comc_port;
65
extern struct console efi_console;
66
67
struct console eficom = {
68
.c_name = "eficom",
69
.c_desc = "serial port",
70
.c_flags = 0,
71
.c_probe = comc_probe,
72
.c_init = comc_init,
73
.c_out = comc_putchar,
74
.c_in = comc_getchar,
75
.c_ready = comc_ischar,
76
};
77
78
#if defined(__aarch64__) && __FreeBSD_version < 1500000
79
static void comc_probe_compat(struct console *);
80
struct console comconsole = {
81
.c_name = "comconsole",
82
.c_desc = "serial port",
83
.c_flags = 0,
84
.c_probe = comc_probe_compat,
85
.c_init = comc_init,
86
.c_out = comc_putchar,
87
.c_in = comc_getchar,
88
.c_ready = comc_ischar,
89
};
90
#endif
91
92
static EFI_STATUS
93
efi_serial_init(EFI_HANDLE **handlep, int *nhandles)
94
{
95
UINTN bufsz = 0;
96
EFI_STATUS status;
97
EFI_HANDLE *handles;
98
99
/*
100
* get buffer size
101
*/
102
*nhandles = 0;
103
handles = NULL;
104
status = BS->LocateHandle(ByProtocol, &serial, NULL, &bufsz, handles);
105
if (status != EFI_BUFFER_TOO_SMALL)
106
return (status);
107
108
if ((handles = malloc(bufsz)) == NULL)
109
return (ENOMEM);
110
111
*nhandles = (int)(bufsz / sizeof (EFI_HANDLE));
112
/*
113
* get handle array
114
*/
115
status = BS->LocateHandle(ByProtocol, &serial, NULL, &bufsz, handles);
116
if (EFI_ERROR(status)) {
117
free(handles);
118
*nhandles = 0;
119
} else
120
*handlep = handles;
121
return (status);
122
}
123
124
/*
125
* Find serial device number from device path.
126
* Return -1 if not found.
127
*/
128
static int
129
efi_serial_get_index(EFI_DEVICE_PATH *devpath, int idx)
130
{
131
ACPI_HID_DEVICE_PATH *acpi;
132
CHAR16 *text;
133
134
while (!IsDevicePathEnd(devpath)) {
135
if (DevicePathType(devpath) == MESSAGING_DEVICE_PATH &&
136
DevicePathSubType(devpath) == MSG_UART_DP)
137
return (idx);
138
139
if (DevicePathType(devpath) == ACPI_DEVICE_PATH &&
140
(DevicePathSubType(devpath) == ACPI_DP ||
141
DevicePathSubType(devpath) == ACPI_EXTENDED_DP)) {
142
143
acpi = (ACPI_HID_DEVICE_PATH *)devpath;
144
if (acpi->HID == EISA_PNP_ID(PNP0501)) {
145
return (acpi->UID);
146
}
147
}
148
149
devpath = NextDevicePathNode(devpath);
150
}
151
return (-1);
152
}
153
154
/*
155
* The order of handles from LocateHandle() is not known, we need to
156
* iterate handles, pick device path for handle, and check the device
157
* number.
158
*/
159
static EFI_HANDLE
160
efi_serial_get_handle(int port, EFI_HANDLE condev)
161
{
162
EFI_STATUS status;
163
EFI_HANDLE *handles, handle;
164
EFI_DEVICE_PATH *devpath;
165
int index, nhandles;
166
167
if (port == -1)
168
return (NULL);
169
170
handles = NULL;
171
nhandles = 0;
172
status = efi_serial_init(&handles, &nhandles);
173
if (EFI_ERROR(status))
174
return (NULL);
175
176
/*
177
* We have console handle, set ioaddr for it.
178
*/
179
if (condev != NULL) {
180
for (index = 0; index < nhandles; index++) {
181
if (condev == handles[index]) {
182
devpath = efi_lookup_devpath(condev);
183
comc_port->ioaddr =
184
efi_serial_get_index(devpath, index);
185
efi_close_devpath(condev);
186
free(handles);
187
return (condev);
188
}
189
}
190
}
191
192
handle = NULL;
193
for (index = 0; handle == NULL && index < nhandles; index++) {
194
devpath = efi_lookup_devpath(handles[index]);
195
if (port == efi_serial_get_index(devpath, index))
196
handle = (handles[index]);
197
efi_close_devpath(handles[index]);
198
}
199
200
/*
201
* In case we did fail to identify the device by path, use port as
202
* array index. Note, we did check port == -1 above.
203
*/
204
if (port < nhandles && handle == NULL)
205
handle = handles[port];
206
207
free(handles);
208
return (handle);
209
}
210
211
static EFI_HANDLE
212
comc_get_con_serial_handle(const char *name)
213
{
214
EFI_HANDLE handle;
215
EFI_DEVICE_PATH *node;
216
EFI_STATUS status;
217
char *buf, *ep;
218
size_t sz;
219
220
buf = NULL;
221
sz = 0;
222
status = efi_global_getenv(name, buf, &sz);
223
if (status == EFI_BUFFER_TOO_SMALL) {
224
buf = malloc(sz);
225
if (buf == NULL)
226
return (NULL);
227
status = efi_global_getenv(name, buf, &sz);
228
}
229
if (status != EFI_SUCCESS) {
230
free(buf);
231
return (NULL);
232
}
233
234
ep = buf + sz;
235
node = (EFI_DEVICE_PATH *)buf;
236
while ((char *)node < ep) {
237
status = BS->LocateDevicePath(&serial, &node, &handle);
238
if (status == EFI_SUCCESS) {
239
free(buf);
240
return (handle);
241
}
242
243
/* Sanity check the node before moving to the next node. */
244
if (DevicePathNodeLength(node) < sizeof(*node))
245
break;
246
247
/* Start of next device path in list. */
248
node = NextDevicePathNode(node);
249
}
250
free(buf);
251
return (NULL);
252
}
253
254
/*
255
* Called from cons_probe() to see if this device is available.
256
* Return immediately on x86, except for hyperv, since it interferes with
257
* common configurations otherwise (yes, this is just firewalling the bug).
258
*/
259
static void
260
comc_probe(struct console *sc)
261
{
262
EFI_STATUS status;
263
EFI_HANDLE handle;
264
char name[20];
265
char value[20];
266
unsigned val;
267
char *env, *buf, *ep;
268
size_t sz;
269
270
#ifdef __amd64__
271
/*
272
* This driver tickles issues on a number of different firmware loads.
273
* It is only required for HyperV, and is only known to work on HyperV,
274
* so only allow it on HyperV.
275
*/
276
env = getenv("smbios.bios.version");
277
if (env == NULL || strncmp(env, "Hyper-V", 7) != 0) {
278
return;
279
}
280
#endif
281
282
if (comc_port == NULL) {
283
comc_port = calloc(1, sizeof (struct serial));
284
if (comc_port == NULL)
285
return;
286
}
287
288
/* Use defaults from firmware */
289
comc_port->databits = 8;
290
comc_port->parity = DefaultParity;
291
comc_port->stopbits = DefaultStopBits;
292
293
handle = NULL;
294
env = getenv("efi_com_port");
295
if (comc_parse_intval(env, &val) == CMD_OK) {
296
comc_port->ioaddr = val;
297
} else {
298
/*
299
* efi_com_port is not set, we need to select default.
300
* First, we consult ConOut variable to see if
301
* we have serial port redirection. If not, we just
302
* pick first device.
303
*/
304
handle = comc_get_con_serial_handle("ConOut");
305
comc_port->condev = handle;
306
}
307
308
handle = efi_serial_get_handle(comc_port->ioaddr, handle);
309
if (handle != NULL) {
310
comc_port->currdev = handle;
311
status = BS->OpenProtocol(handle, &serial,
312
(void**)&comc_port->sio, IH, NULL,
313
EFI_OPEN_PROTOCOL_GET_PROTOCOL);
314
315
if (EFI_ERROR(status)) {
316
comc_port->sio = NULL;
317
} else {
318
comc_port->newbaudrate =
319
comc_port->baudrate = comc_port->sio->Mode->BaudRate;
320
comc_port->timeout = comc_port->sio->Mode->Timeout;
321
comc_port->receivefifodepth =
322
comc_port->sio->Mode->ReceiveFifoDepth;
323
comc_port->databits = comc_port->sio->Mode->DataBits;
324
comc_port->parity = comc_port->sio->Mode->Parity;
325
comc_port->stopbits = comc_port->sio->Mode->StopBits;
326
}
327
}
328
329
/*
330
* If there's no sio, then the device isn't there, so just return since
331
* the present flags aren't yet set.
332
*/
333
if (comc_port->sio == NULL) {
334
free(comc_port);
335
comc_port = NULL;
336
return;
337
}
338
339
if (env != NULL)
340
unsetenv("efi_com_port");
341
snprintf(value, sizeof (value), "%u", comc_port->ioaddr);
342
env_setenv("efi_com_port", EV_VOLATILE, value,
343
comc_port_set, env_nounset);
344
345
env = getenv("efi_com_speed");
346
if (env == NULL)
347
/* fallback to comconsole setting */
348
env = getenv("comconsole_speed");
349
350
if (comc_parse_intval(env, &val) == CMD_OK)
351
comc_port->newbaudrate = val;
352
353
if (env != NULL)
354
unsetenv("efi_com_speed");
355
snprintf(value, sizeof (value), "%ju", (uintmax_t)comc_port->baudrate);
356
env_setenv("efi_com_speed", EV_VOLATILE, value,
357
comc_speed_set, env_nounset);
358
359
if (comc_setup()) {
360
sc->c_flags = C_PRESENTIN | C_PRESENTOUT;
361
} else {
362
sc->c_flags &= ~(C_PRESENTIN | C_PRESENTOUT);
363
free(comc_port);
364
comc_port = NULL;
365
}
366
}
367
368
#if defined(__aarch64__) && __FreeBSD_version < 1500000
369
static void
370
comc_probe_compat(struct console *sc)
371
{
372
comc_probe(&eficom);
373
if (eficom.c_flags & (C_PRESENTIN | C_PRESENTOUT)) {
374
printf("comconsole: comconsole device name is deprecated, switch to eficom\n");
375
}
376
/*
377
* Note: We leave the present bits unset in sc to avoid ghosting.
378
*/
379
}
380
#endif
381
382
/*
383
* Called when the console is selected in cons_change. If we didn't detect the
384
* device, comc_port will be NULL, and comc_setup will fail. It may be called
385
* even when the device isn't present as a 'fallback' console or when listed
386
* specifically in console env, so we have to reset the c_flags in those case to
387
* say it's not present.
388
*/
389
static int
390
comc_init(int arg __unused)
391
{
392
if (comc_setup())
393
return (0);
394
395
eficom.c_flags &= ~(C_ACTIVEIN | C_ACTIVEOUT);
396
return (1);
397
}
398
399
static void
400
comc_putchar(int c)
401
{
402
int wait;
403
EFI_STATUS status;
404
UINTN bufsz = 1;
405
char cb = c;
406
407
if (comc_port->sio == NULL)
408
return;
409
410
for (wait = COMC_TXWAIT; wait > 0; wait--) {
411
status = comc_port->sio->Write(comc_port->sio, &bufsz, &cb);
412
if (status != EFI_TIMEOUT)
413
break;
414
}
415
}
416
417
static int
418
comc_getchar(void)
419
{
420
EFI_STATUS status;
421
UINTN bufsz = 1;
422
char c;
423
424
425
/*
426
* if this device is also used as ConIn, some firmwares
427
* fail to return all input via SIO protocol.
428
*/
429
if (comc_port->currdev == comc_port->condev) {
430
if ((efi_console.c_flags & C_ACTIVEIN) == 0)
431
return (efi_console.c_in());
432
return (-1);
433
}
434
435
if (comc_port->sio == NULL)
436
return (-1);
437
438
status = comc_port->sio->Read(comc_port->sio, &bufsz, &c);
439
if (EFI_ERROR(status) || bufsz == 0)
440
return (-1);
441
442
return (c);
443
}
444
445
static int
446
comc_ischar(void)
447
{
448
EFI_STATUS status;
449
uint32_t control;
450
451
/*
452
* if this device is also used as ConIn, some firmwares
453
* fail to return all input via SIO protocol.
454
*/
455
if (comc_port->currdev == comc_port->condev) {
456
if ((efi_console.c_flags & C_ACTIVEIN) == 0)
457
return (efi_console.c_ready());
458
return (0);
459
}
460
461
if (comc_port->sio == NULL)
462
return (0);
463
464
status = comc_port->sio->GetControl(comc_port->sio, &control);
465
if (EFI_ERROR(status))
466
return (0);
467
468
return (!(control & EFI_SERIAL_INPUT_BUFFER_EMPTY));
469
}
470
471
static int
472
comc_parse_intval(const char *value, unsigned *valp)
473
{
474
unsigned n;
475
char *ep;
476
477
if (value == NULL || *value == '\0')
478
return (CMD_ERROR);
479
480
errno = 0;
481
n = strtoul(value, &ep, 10);
482
if (errno != 0 || *ep != '\0')
483
return (CMD_ERROR);
484
*valp = n;
485
486
return (CMD_OK);
487
}
488
489
static int
490
comc_port_set(struct env_var *ev, int flags, const void *value)
491
{
492
unsigned port;
493
SERIAL_IO_INTERFACE *sio;
494
EFI_HANDLE handle;
495
EFI_STATUS status;
496
497
if (value == NULL || comc_port == NULL)
498
return (CMD_ERROR);
499
500
if (comc_parse_intval(value, &port) != CMD_OK)
501
return (CMD_ERROR);
502
503
handle = efi_serial_get_handle(port, NULL);
504
if (handle == NULL) {
505
printf("no handle\n");
506
return (CMD_ERROR);
507
}
508
509
status = BS->OpenProtocol(handle, &serial,
510
(void**)&sio, IH, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
511
512
if (EFI_ERROR(status)) {
513
printf("OpenProtocol: %lu\n", EFI_ERROR_CODE(status));
514
return (CMD_ERROR);
515
}
516
517
comc_port->currdev = handle;
518
comc_port->ioaddr = port;
519
comc_port->sio = sio;
520
521
(void) comc_setup();
522
523
env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL);
524
return (CMD_OK);
525
}
526
527
static int
528
comc_speed_set(struct env_var *ev, int flags, const void *value)
529
{
530
unsigned speed;
531
532
if (value == NULL || comc_port == NULL)
533
return (CMD_ERROR);
534
535
if (comc_parse_intval(value, &speed) != CMD_OK)
536
return (CMD_ERROR);
537
538
comc_port->newbaudrate = speed;
539
if (comc_setup())
540
env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL);
541
542
return (CMD_OK);
543
}
544
545
/*
546
* In case of error, we also reset ACTIVE flags, so the console
547
* framefork will try alternate consoles.
548
*/
549
static bool
550
comc_setup(void)
551
{
552
EFI_STATUS status;
553
char *ev;
554
555
/*
556
* If the device isn't active, or there's no port present.
557
*/
558
if ((eficom.c_flags & (C_ACTIVEIN | C_ACTIVEOUT)) == 0 || comc_port == NULL)
559
return (false);
560
561
if (comc_port->sio->Reset != NULL) {
562
status = comc_port->sio->Reset(comc_port->sio);
563
if (EFI_ERROR(status))
564
return (false);
565
}
566
567
/*
568
* Avoid setting the baud rate on Hyper-V. Also, only set the baud rate
569
* if the baud rate has changed from the default. And pass in '0' or
570
* DefaultFoo when we're not changing those values. Some EFI
571
* implementations get cranky when you set things to the values reported
572
* back even when they are unchanged.
573
*/
574
if (comc_port->sio->SetAttributes != NULL &&
575
comc_port->newbaudrate != comc_port->baudrate) {
576
ev = getenv("smbios.bios.version");
577
if (ev != NULL && strncmp(ev, "Hyper-V", 7) != 0) {
578
status = comc_port->sio->SetAttributes(comc_port->sio,
579
comc_port->newbaudrate, 0, 0, DefaultParity, 0,
580
DefaultStopBits);
581
if (EFI_ERROR(status))
582
return (false);
583
comc_port->baudrate = comc_port->newbaudrate;
584
}
585
}
586
587
#ifdef EFI_FORCE_RTS
588
if (comc_port->sio->GetControl != NULL && comc_port->sio->SetControl != NULL) {
589
UINT32 control;
590
591
status = comc_port->sio->GetControl(comc_port->sio, &control);
592
if (EFI_ERROR(status))
593
return (false);
594
control |= EFI_SERIAL_REQUEST_TO_SEND;
595
(void) comc_port->sio->SetControl(comc_port->sio, control);
596
}
597
#endif
598
/* Mark this port usable. */
599
eficom.c_flags |= (C_PRESENTIN | C_PRESENTOUT);
600
return (true);
601
}
602
603