efi/serial: Refactor EFI bios serial services

* Convert x86 direct port I/O to our standard gUART.
* sSerial is plainly EFI Serial or EFI Stdout
* gUART is plainly hardware serial now

Change-Id: I5730fbc0ce3427851e4e2d1d0aa8c55f7765e444
Reviewed-on: https://review.haiku-os.org/c/haiku/+/5982
Reviewed-by: Alex von Gluck IV <kallisti5@unixzen.com>
Reviewed-by: Adrien Destugues <pulkomandy@pulkomandy.tk>
This commit is contained in:
Alexander von Gluck IV 2023-01-04 11:18:28 -06:00 committed by Alex von Gluck IV
parent 762b00e999
commit 81a892eddf
11 changed files with 115 additions and 93 deletions

View File

@ -1,5 +1,7 @@
SubDir HAIKU_TOP src system boot arch x86 ;
SubDirC++Flags -fno-rtti ;
local defines = $(DEFINES) ;
local platform ;
@ -24,6 +26,11 @@ for platform in [ MultiBootSubDirSetup bios_ia32 efi pxe_ia32 ] {
arch_elf.cpp
;
local kernelGenericDriverSources =
debug_uart.cpp
debug_uart_8250.cpp
;
local kernelArchSpecificSources ;
local kernelLibArchSpecificSources ;
if $(TARGET_ARCH) = x86_64 && $(TARGET_BOOT_PLATFORM) = efi {
@ -41,6 +48,7 @@ for platform in [ MultiBootSubDirSetup bios_ia32 efi pxe_ia32 ] {
BootMergeObject [ FGristFiles boot_arch_$(TARGET_KERNEL_ARCH).o ] :
$(kernelArchSources)
$(kernelArchSpecificSources)
$(kernelGenericDriverSources)
$(kernelLibArchSpecificSources)
$(librootOsArchSources)
arch_cpu.cpp
@ -48,8 +56,10 @@ for platform in [ MultiBootSubDirSetup bios_ia32 efi pxe_ia32 ] {
: -std=c++11 # additional flags
;
SEARCH on [ FGristFiles $(kernelGenericDriverSources) ]
= [ FDirName $(HAIKU_TOP) src system kernel arch generic ] ;
SEARCH on [ FGristFiles $(kernelArchSources) ]
= [ FDirName $(HAIKU_TOP) src system kernel arch x86 ] ;
= [ FDirName $(HAIKU_TOP) src system kernel arch $(TARGET_KERNEL_ARCH_DIR) ] ;
if $(TARGET_ARCH) = x86_64 && $(TARGET_BOOT_PLATFORM) = efi {
SEARCH on [ FGristFiles $(kernelArchSpecificSources) ]

View File

@ -165,16 +165,14 @@ arch_start_kernel(addr_t kernelEntry)
// A changing memory map shouldn't affect the generated page tables, as
// they only needed to know about the maximum address, not any specific
// entry.
dprintf("Calling ExitBootServices. So long, EFI!\n");
serial_disable();
while (true) {
if (kBootServices->ExitBootServices(kImage, mapKey) == EFI_SUCCESS) {
// If the console was provided by boot services, disable it.
stdout = NULL;
stderr = NULL;
// Also switch to legacy serial output
// (may not work on all systems)
serial_switch_to_legacy();
dprintf("Switched to legacy serial output\n");
// Disconnect from EFI serial_io / stdio services
serial_kernel_handoff();
dprintf("Unhooked from EFI serial services\n");
break;
}
@ -189,6 +187,8 @@ arch_start_kernel(addr_t kernelEntry)
arch_mmu_post_efi_setup(memoryMapSize, memoryMap,
descriptorSize, descriptorVersion);
serial_enable();
// Copy final kernel args
// This should be the last step before jumping to the kernel
// as there are some fixups happening to kernel_args even in the last minute

View File

@ -136,16 +136,14 @@ arch_start_kernel(addr_t kernelEntry)
// A changing memory map shouldn't affect the generated page tables, as
// they only needed to know about the maximum address, not any specific
// entry.
dprintf("Calling ExitBootServices. So long, EFI!\n");
serial_disable();
while (true) {
if (kBootServices->ExitBootServices(kImage, map_key) == EFI_SUCCESS) {
// The console was provided by boot services, disable it.
stdout = NULL;
stderr = NULL;
// Can we adjust gKernelArgs.platform_args.serial_base_ports[0]
// to something fixed in qemu for debugging?
serial_switch_to_legacy();
dprintf("Switched to legacy serial output\n");
// Disconnect from EFI serial_io / stdio services
serial_kernel_handoff();
dprintf("Unhooked from EFI serial services\n");
break;
}
@ -159,6 +157,7 @@ arch_start_kernel(addr_t kernelEntry)
// Update EFI, generate final kernel physical memory map, etc.
arch_mmu_post_efi_setup(memory_map_size, memory_map, descriptor_size, descriptor_version);
serial_enable();
switch (el) {
case 1:

View File

@ -119,16 +119,14 @@ arch_start_kernel(addr_t kernelEntry)
// A changing memory map shouldn't affect the generated page tables, as
// they only needed to know about the maximum address, not any specific
// entry.
dprintf("Calling ExitBootServices. So long, EFI!\n");
serial_disable();
while (true) {
if (kBootServices->ExitBootServices(kImage, map_key) == EFI_SUCCESS) {
// The console was provided by boot services, disable it.
stdout = NULL;
stderr = NULL;
// Also switch to legacy serial output
// (may not work on all systems)
serial_switch_to_legacy();
dprintf("Switched to legacy serial output\n");
// Disconnect from EFI serial_io / stdio services
serial_kernel_handoff();
dprintf("Unhooked from EFI serial services\n");
break;
}
@ -145,6 +143,8 @@ arch_start_kernel(addr_t kernelEntry)
arch_mmu_post_efi_setup(memory_map_size, memory_map,
descriptor_size, descriptor_version);
serial_enable();
dprintf("[PRE] SetSatp()\n");
SetSatp(satp);
dprintf("[POST] SetSatp()\n");

View File

@ -130,16 +130,14 @@ arch_start_kernel(addr_t kernelEntry)
// A changing memory map shouldn't affect the generated page tables, as
// they only needed to know about the maximum address, not any specific
// entry.
dprintf("Calling ExitBootServices. So long, EFI!\n");
serial_disable();
while (true) {
if (kBootServices->ExitBootServices(kImage, mapKey) == EFI_SUCCESS) {
// The console was provided by boot services, disable it.
stdout = NULL;
stderr = NULL;
// Also switch to legacy serial output
// (may not work on all systems)
serial_switch_to_legacy();
dprintf("Switched to legacy serial output\n");
// Disconnect from EFI serial_io / stdio services
serial_kernel_handoff();
dprintf("Unhooked from EFI serial services\n");
break;
}
@ -154,6 +152,8 @@ arch_start_kernel(addr_t kernelEntry)
arch_mmu_post_efi_setup(memoryMapSize, memoryMap,
descriptorSize, descriptorVersion);
serial_enable();
// Copy final kernel args
// This should be the last step before jumping to the kernel
// as there are some fixups happening to kernel_args even in the last minute

View File

@ -143,16 +143,14 @@ arch_start_kernel(addr_t kernelEntry)
// A changing memory map shouldn't affect the generated page tables, as
// they only needed to know about the maximum address, not any specific
// entry.
dprintf("Calling ExitBootServices. So long, EFI!\n");
serial_disable();
while (true) {
if (kBootServices->ExitBootServices(kImage, map_key) == EFI_SUCCESS) {
// The console was provided by boot services, disable it.
stdout = NULL;
stderr = NULL;
// Also switch to legacy serial output
// (may not work on all systems)
serial_switch_to_legacy();
dprintf("Switched to legacy serial output\n");
// Disconnect from EFI serial_io / stdio services
serial_kernel_handoff();
dprintf("Unhooked from EFI serial services\n");
break;
}
@ -167,6 +165,9 @@ arch_start_kernel(addr_t kernelEntry)
arch_mmu_post_efi_setup(memory_map_size, memory_map,
descriptor_size, descriptor_version);
// Restart serial. gUART only until we get into the kernel
serial_enable();
smp_boot_other_cpus(final_pml4, kernelEntry, (addr_t)&gKernelArgs);
// Enter the kernel!

View File

@ -13,6 +13,7 @@
#include <boot/platform.h>
#include <arch/cpu.h>
#include <arch/generic/debug_uart.h>
#include <arch/generic/debug_uart_8250.h>
#include <boot/stage2.h>
#include <boot/stdio.h>
@ -22,24 +23,9 @@
static efi_guid sSerialIOProtocolGUID = EFI_SERIAL_IO_PROTOCOL_GUID;
static const uint32 kSerialBaudRate = 115200;
static efi_serial_io_protocol *sSerial = NULL;
static efi_serial_io_protocol *sEFISerialIO = NULL;
static bool sSerialEnabled = false;
static bool sSerialUsesEFI = true;
enum serial_register_offsets {
SERIAL_TRANSMIT_BUFFER = 0,
SERIAL_RECEIVE_BUFFER = 0,
SERIAL_DIVISOR_LATCH_LOW = 0,
SERIAL_DIVISOR_LATCH_HIGH = 1,
SERIAL_FIFO_CONTROL = 2,
SERIAL_LINE_CONTROL = 3,
SERIAL_MODEM_CONTROL = 4,
SERIAL_LINE_STATUS = 5,
SERIAL_MODEM_STATUS = 6,
};
static uint16 sSerialBasePort = 0x3f8;
static bool sEFIAvailable = true;
DebugUART* gUART = NULL;
@ -51,16 +37,16 @@ serial_putc(char ch)
if (!sSerialEnabled)
return;
if (sSerialUsesEFI && sSerial != NULL) {
// First we use EFI serial_io output if available
if (sEFISerialIO != NULL) {
size_t bufSize = 1;
sSerial->Write(sSerial, &bufSize, &ch);
sEFISerialIO->Write(sEFISerialIO, &bufSize, &ch);
return;
}
#ifdef TRACE_DEBUG
if (sSerialUsesEFI) {
// To aid in early bring-up on EFI platforms, where the
// serial_io protocol isn't working/available.
#ifdef DEBUG
// If we don't have EFI serial_io, fallback to EFI stdio
if (sEFIAvailable) {
char16_t ucsBuffer[2];
ucsBuffer[0] = ch;
ucsBuffer[1] = 0;
@ -69,24 +55,20 @@ serial_putc(char ch)
}
#endif
// If EFI services are unavailable... try any UART
// this can happen when serial_io is unavailable, or EFI
// is exiting
if (gUART != NULL) {
gUART->PutChar(ch);
return;
}
#if defined(__i386__) || defined(__x86_64__)
while ((in8(sSerialBasePort + SERIAL_LINE_STATUS) & 0x20) == 0)
asm volatile ("pause;");
out8(ch, sSerialBasePort + SERIAL_TRANSMIT_BUFFER);
#endif
}
extern "C" void
serial_puts(const char* string, size_t size)
{
if (!sSerialEnabled || (sSerial == NULL && sSerialUsesEFI))
if (!sSerialEnabled)
return;
while (size-- != 0) {
@ -114,50 +96,60 @@ extern "C" void
serial_enable(void)
{
sSerialEnabled = true;
if (gUART != NULL)
gUART->InitPort(kSerialBaudRate);
}
extern "C" void
serial_init(void)
{
// Check for EFI Serial
efi_status status = kSystemTable->BootServices->LocateProtocol(
&sSerialIOProtocolGUID, NULL, (void**)&sSerial);
&sSerialIOProtocolGUID, NULL, (void**)&sEFISerialIO);
if (status != EFI_SUCCESS || sSerial == NULL) {
sSerial = NULL;
return;
if (status != EFI_SUCCESS)
sEFISerialIO = NULL;
if (sEFISerialIO != NULL) {
// Setup serial, 0, 0 = Default Receive FIFO queue and default timeout
status = sEFISerialIO->SetAttributes(sEFISerialIO, kSerialBaudRate, 0, 0, NoParity, 8,
OneStopBit);
if (status != EFI_SUCCESS)
sEFISerialIO = NULL;
// serial_io was successful.
}
// Setup serial, 0, 0 = Default Receive FIFO queue and default timeout
status = sSerial->SetAttributes(sSerial, kSerialBaudRate, 0, 0, NoParity, 8,
OneStopBit);
if (status != EFI_SUCCESS) {
sSerial = NULL;
return;
#if defined(__i386__) || defined(__x86_64__)
// On x86, we can try to setup COM1 as a gUART too
// while this serial port may not physically exist,
// the location is fixed on the x86 arch.
// TODO: We could also try to pull from acpi?
if (gUART == NULL) {
gUART = arch_get_uart_8250(0x3f8, 1843200);
gUART->InitEarly();
}
#endif
}
extern "C" void
serial_switch_to_legacy(void)
serial_kernel_handoff(void)
{
sSerial = NULL;
sSerialUsesEFI = false;
// The console was provided by boot services, disable it ASAP
stdout = NULL;
stderr = NULL;
// Disconnect from EFI serial_io services is important as we leave the bootloader
sEFISerialIO = NULL;
sEFIAvailable = false;
#if defined(__i386__) || defined(__x86_64__)
// TODO: convert over to exclusively arch_args.uart?
memset(gKernelArgs.platform_args.serial_base_ports, 0,
sizeof(uint16) * MAX_SERIAL_PORTS);
gKernelArgs.platform_args.serial_base_ports[0] = sSerialBasePort;
uint16 divisor = uint16(115200 / kSerialBaudRate);
out8(0x80, sSerialBasePort + SERIAL_LINE_CONTROL);
// set divisor latch access bit
out8(divisor & 0xf, sSerialBasePort + SERIAL_DIVISOR_LATCH_LOW);
out8(divisor >> 8, sSerialBasePort + SERIAL_DIVISOR_LATCH_HIGH);
out8(3, sSerialBasePort + SERIAL_LINE_CONTROL);
// 8N1
gKernelArgs.platform_args.serial_base_ports[0] = 0x3f8;
#endif
}

View File

@ -26,7 +26,7 @@ extern void serial_puts(const char *string, size_t size);
extern void serial_disable(void);
extern void serial_enable(void);
extern void serial_switch_to_legacy(void);
extern void serial_kernel_handoff(void);
#ifdef __cplusplus
}

View File

@ -256,6 +256,8 @@ efi_main(efi_handle image, efi_system_table *systemTable)
acpi_init();
#ifdef _BOOT_FDT_SUPPORT
dtb_init();
// Start serial a second time to give any uart from DTB a chance to start up
serial_enable();
#endif
timer_init();
smp_init();

View File

@ -13,6 +13,12 @@ DebugUART::Out8(int reg, uint8 value)
#if defined(__ARM__) || defined(__aarch64__)
// 32-bit aligned
*((uint8 *)Base() + reg * sizeof(uint32)) = value;
#elif defined(__i386__) || defined(__x86_64__)
// outb for access to IO space.
if ((Base() + reg) <= 0xFFFF)
__asm__ volatile ("outb %%al,%%dx" : : "a" (value), "d" (Base() + reg));
else
*((uint8 *)Base() + reg) = value;
#else
*((uint8 *)Base() + reg) = value;
#endif
@ -25,6 +31,14 @@ DebugUART::In8(int reg)
#if defined(__ARM__) || defined(__aarch64__)
// 32-bit aligned
return *((uint8 *)Base() + reg * sizeof(uint32));
#elif defined(__i386__) || defined(__x86_64__)
// inb for access to IO space.
if ((Base() + reg) <= 0xFFFF) {
uint8 _v;
__asm__ volatile ("inb %%dx,%%al" : "=a" (_v) : "d" (Base() + reg));
return _v;
}
return *((uint8 *)Base() + reg);
#else
return *((uint8 *)Base() + reg);
#endif

View File

@ -97,6 +97,10 @@ local archGenericSources =
msi.cpp
pic.cpp
# serial
debug_uart.cpp
debug_uart_8250.cpp
# paging
X86PagingMethod.cpp
X86PagingStructures.cpp