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