1/*
2 * Copyright 2004-2008, Axel D��rfler, axeld@pinc-software.de.
3 * Copyright 2013-2014, Fredrik Holmqvist, fredrik.holmqvist@gmail.com.
4 * Copyright 2016, Jessica Hamilton, jessica.l.hamilton@gmail.com.
5 * Distributed under the terms of the MIT License.
6 */
7
8
9#include "efi_platform.h"
10#include <efi/protocol/serial-io.h>
11#include "serial.h"
12
13#include <boot/platform.h>
14#include <arch/cpu.h>
15#include <arch/generic/debug_uart.h>
16#include <arch/generic/debug_uart_8250.h>
17#include <boot/stage2.h>
18#include <boot/stdio.h>
19
20#include <string.h>
21
22
23static efi_guid sSerialIOProtocolGUID = EFI_SERIAL_IO_PROTOCOL_GUID;
24static const uint32 kSerialBaudRate = 115200;
25
26static efi_serial_io_protocol *sEFISerialIO = NULL;
27static bool sSerialEnabled = false;
28static bool sEFIAvailable = true;
29
30
31DebugUART* gUART = NULL;
32bool gUARTSkipInit = false;
33
34
35static void
36serial_putc(char ch)
37{
38	if (!sSerialEnabled)
39		return;
40
41	// First we use EFI serial_io output if available
42	if (sEFISerialIO != NULL) {
43		size_t bufSize = 1;
44		sEFISerialIO->Write(sEFISerialIO, &bufSize, &ch);
45		return;
46	}
47
48#ifdef DEBUG
49	// If we don't have EFI serial_io, fallback to EFI stdio
50	if (sEFIAvailable) {
51		char16_t ucsBuffer[2];
52		ucsBuffer[0] = ch;
53		ucsBuffer[1] = 0;
54		kSystemTable->ConOut->OutputString(kSystemTable->ConOut, ucsBuffer);
55		return;
56	}
57#endif
58
59	// If EFI services are unavailable... try any UART
60	// this can happen when serial_io is unavailable, or EFI
61	// is exiting
62	if (gUART != NULL) {
63		gUART->PutChar(ch);
64		return;
65	}
66}
67
68
69extern "C" void
70serial_puts(const char* string, size_t size)
71{
72	if (!sSerialEnabled)
73		return;
74
75	while (size-- != 0) {
76		char ch = string[0];
77
78		if (ch == '\n') {
79			serial_putc('\r');
80			serial_putc('\n');
81		} else if (ch != '\r')
82			serial_putc(ch);
83
84		string++;
85	}
86}
87
88
89extern "C" void
90serial_disable(void)
91{
92	sSerialEnabled = false;
93}
94
95
96extern "C" void
97serial_enable(void)
98{
99	sSerialEnabled = true;
100	if ((gUART != NULL) && !gUARTSkipInit)
101		gUART->InitPort(kSerialBaudRate);
102}
103
104
105extern "C" void
106serial_init(void)
107{
108	if (sEFIAvailable) {
109		// Check for EFI Serial
110		efi_status status = kSystemTable->BootServices->LocateProtocol(
111			&sSerialIOProtocolGUID, NULL, (void**)&sEFISerialIO);
112
113		if (status != EFI_SUCCESS)
114			sEFISerialIO = NULL;
115
116		if (sEFISerialIO != NULL) {
117			// Setup serial, 0, 0 = Default Receive FIFO queue and default timeout
118			status = sEFISerialIO->SetAttributes(sEFISerialIO, kSerialBaudRate, 0, 0, NoParity, 8,
119				OneStopBit);
120
121			if (status != EFI_SUCCESS)
122				sEFISerialIO = NULL;
123
124			// serial_io was successful.
125			return;
126		}
127	}
128
129#if defined(__i386__) || defined(__x86_64__)
130	// On x86, we can try to setup COM1 as a gUART too
131	// while this serial port may not physically exist,
132	// the location is fixed on the x86 arch.
133	// TODO: We could also try to pull from acpi?
134	if (gUART == NULL) {
135		gUART = arch_get_uart_8250(0x3f8, 1843200);
136
137		// TODO: convert over to exclusively arch_args.uart?
138		memset(gKernelArgs.platform_args.serial_base_ports, 0,
139			sizeof(uint16) * MAX_SERIAL_PORTS);
140		gKernelArgs.platform_args.serial_base_ports[0] = 0x3f8;
141	}
142#endif
143
144	if (gUART != NULL)
145		gUART->InitEarly();
146}
147
148
149extern "C" void
150serial_kernel_handoff(void)
151{
152	// The console was provided by boot services, disable it ASAP
153	stdout = NULL;
154	stderr = NULL;
155
156	// Disconnect from EFI serial_io services is important as we leave the bootloader
157	sEFISerialIO = NULL;
158	sEFIAvailable = false;
159}
160