blob: c0be9eb33411369fa3b7932ae9b7881333e3e9fb [file] [log] [blame]
// Compatibility Support Module (CSM) for UEFI / EDK-II
//
// Copyright © 2013 Intel Corporation
//
// This file may be distributed under the terms of the GNU LGPLv3 license.
#include "bregs.h" // struct bregs
#include "config.h" // CONFIG_*
#include "e820map.h" // e820_add
#include "farptr.h" // MAKE_FLATPTR
#include "hw/pci.h" // pci_to_bdf
#include "hw/pcidevice.h" // pci_probe_devices
#include "hw/pic.h" // pic_irqmask_read
#include "malloc.h" // malloc_csm_preinit
#include "memmap.h" // SYMBOL
#include "output.h" // dprintf
#include "paravirt.h" // qemu_preinit
#include "stacks.h" // wait_threads
#include "std/acpi.h" // RSDP_SIGNATURE
#include "std/bda.h" // struct bios_data_area_s
#include "std/optionrom.h" // struct rom_header
#include "util.h" // copy_smbios
#define UINT8 u8
#define UINT16 u16
#define UINT32 u32
#include "std/LegacyBios.h"
struct rsdp_descriptor csm_rsdp VARFSEG __aligned(16);
EFI_COMPATIBILITY16_TABLE csm_compat_table VARFSEG __aligned(16) = {
.Signature = 0x24454649,
.TableChecksum = 0 /* Filled in by checkrom.py */,
.TableLength = sizeof(csm_compat_table),
.Compatibility16CallSegment = SEG_BIOS,
.Compatibility16CallOffset = 0 /* Filled in by checkrom.py */,
.OemIdStringPointer = (u32)"SeaBIOS",
.AcpiRsdPtrPointer = (u32)&csm_rsdp,
};
EFI_TO_COMPATIBILITY16_INIT_TABLE *csm_init_table;
EFI_TO_COMPATIBILITY16_BOOT_TABLE *csm_boot_table;
static u16 PICMask = PIC_IRQMASK_DEFAULT;
extern void __csm_return(struct bregs *regs) __noreturn;
static void
csm_return(struct bregs *regs)
{
u32 rommax = rom_get_max();
dprintf(3, "handle_csm returning AX=%04x\n", regs->ax);
csm_compat_table.UmaAddress = rommax;
csm_compat_table.UmaSize = SYMBOL(final_readonly_start) - rommax;
PICMask = pic_irqmask_read();
__csm_return(regs);
}
static void
csm_maininit(struct bregs *regs)
{
interface_init();
pci_probe_devices();
csm_compat_table.PnPInstallationCheckSegment = SEG_BIOS;
csm_compat_table.PnPInstallationCheckOffset = get_pnp_offset();
regs->ax = 0;
csm_return(regs);
}
/* Legacy16InitializeYourself */
static void
handle_csm_0000(struct bregs *regs)
{
qemu_preinit();
dprintf(3, "Legacy16InitializeYourself table %04x:%04x\n", regs->es,
regs->bx);
csm_init_table = MAKE_FLATPTR(regs->es, regs->bx);
dprintf(3, "BiosLessThan1MB %08x\n", csm_init_table->BiosLessThan1MB);
dprintf(3, "HiPmmMemory %08x\n", csm_init_table->HiPmmMemory);
dprintf(3, "HiPmmMemorySize %08x\n", csm_init_table->HiPmmMemorySizeInBytes);
dprintf(3, "ReverseThunk %04x:%04x\n", csm_init_table->ReverseThunkCallSegment,
csm_init_table->ReverseThunkCallOffset);
dprintf(3, "NumE820Entries %08x\n", csm_init_table->NumberE820Entries);
dprintf(3, "OsMemoryAbove1M %08x\n", csm_init_table->OsMemoryAbove1Mb);
dprintf(3, "ThunkStart %08x\n", csm_init_table->ThunkStart);
dprintf(3, "ThunkSize %08x\n", csm_init_table->ThunkSizeInBytes);
dprintf(3, "LoPmmMemory %08x\n", csm_init_table->LowPmmMemory);
dprintf(3, "LoPmmMemorySize %08x\n", csm_init_table->LowPmmMemorySizeInBytes);
malloc_csm_preinit(csm_init_table->LowPmmMemory,
csm_init_table->LowPmmMemorySizeInBytes,
csm_init_table->HiPmmMemory,
csm_init_table->HiPmmMemorySizeInBytes);
reloc_preinit(csm_maininit, regs);
}
/* Legacy16UpdateBbs */
static void
handle_csm_0001(struct bregs *regs)
{
if (!CONFIG_BOOT) {
regs->ax = 1;
return;
}
dprintf(3, "Legacy16UpdateBbs table %04x:%04x\n", regs->es, regs->bx);
csm_boot_table = MAKE_FLATPTR(regs->es, regs->bx);
dprintf(3, "MajorVersion %04x\n", csm_boot_table->MajorVersion);
dprintf(3, "MinorVersion %04x\n", csm_boot_table->MinorVersion);
dprintf(3, "AcpiTable %08x\n", csm_boot_table->AcpiTable);
dprintf(3, "SmbiosTable %08x\n", csm_boot_table->SmbiosTable);
dprintf(3, "SmbiosTableLength %08x\n", csm_boot_table->SmbiosTableLength);
// dprintf(3, "SioData %08x\n", csm_boot_table->SioData);
dprintf(3, "DevicePathType %04x\n", csm_boot_table->DevicePathType);
dprintf(3, "PciIrqMask %04x\n", csm_boot_table->PciIrqMask);
dprintf(3, "NumberE820Entries %08x\n", csm_boot_table->NumberE820Entries);
// dprintf(3, "HddInfo %08x\n", csm_boot_table->HddInfo);
dprintf(3, "NumberBbsEntries %08x\n", csm_boot_table->NumberBbsEntries);
dprintf(3, "BBsTable %08x\n", csm_boot_table->BbsTable);
dprintf(3, "SmmTable %08x\n", csm_boot_table->SmmTable);
dprintf(3, "OsMemoryAbove1Mb %08x\n", csm_boot_table->OsMemoryAbove1Mb);
dprintf(3, "UnconventionalDeviceTable %08x\n", csm_boot_table->UnconventionalDeviceTable);
regs->ax = 0;
}
/* PrepareToBoot */
static void
handle_csm_0002(struct bregs *regs)
{
if (!CONFIG_BOOT) {
regs->ax = 1;
return;
}
dprintf(3, "PrepareToBoot table %04x:%04x\n", regs->es, regs->bx);
struct e820entry *p = (void *)csm_compat_table.E820Pointer;
int i;
for (i=0; i < csm_compat_table.E820Length / sizeof(struct e820entry); i++)
e820_add(p[i].start, p[i].size, p[i].type);
if (csm_init_table->HiPmmMemorySizeInBytes > BUILD_MAX_HIGHTABLE) {
u32 hi_pmm_end = csm_init_table->HiPmmMemory + csm_init_table->HiPmmMemorySizeInBytes;
e820_add(hi_pmm_end - BUILD_MAX_HIGHTABLE, BUILD_MAX_HIGHTABLE, E820_RESERVED);
}
// For PCIBIOS 1ab10e
if (csm_compat_table.IrqRoutingTablePointer &&
csm_compat_table.IrqRoutingTableLength) {
PirAddr = (void *)csm_compat_table.IrqRoutingTablePointer;
dprintf(3, "CSM PIRQ table at %p\n", PirAddr);
}
// For find_resume_vector()... and find_acpi_features()
if (csm_rsdp.signature == RSDP_SIGNATURE) {
RsdpAddr = &csm_rsdp;
dprintf(3, "CSM ACPI RSDP at %p\n", RsdpAddr);
find_acpi_features();
}
// SMBIOS table needs to be copied into the f-seg
// XX: OVMF doesn't seem to set SmbiosTableLength so don't check it
if (csm_boot_table->SmbiosTable && !SMBiosAddr)
copy_smbios((void *)csm_boot_table->SmbiosTable);
// MPTABLE is just there; we don't care where.
// EFI may have reinitialised the video using its *own* driver.
enable_vga_console();
// EFI fills this in for us. Zero it for now...
struct bios_data_area_s *bda = get_bda_ptr();
bda->hdcount = 0;
thread_setup();
mathcp_setup();
timer_setup();
clock_setup();
device_hardware_setup();
wait_threads();
interactive_bootmenu();
prepareboot();
regs->ax = 0;
}
/* Boot */
static void
handle_csm_0003(struct bregs *regs)
{
if (!CONFIG_BOOT) {
regs->ax = 1;
return;
}
dprintf(3, "Boot\n");
startBoot();
regs->ax = 1;
}
/* Legacy16DispatchOprom */
static void
handle_csm_0005(struct bregs *regs)
{
EFI_DISPATCH_OPROM_TABLE *table = MAKE_FLATPTR(regs->es, regs->bx);
struct rom_header *rom;
u16 bdf;
if (!CONFIG_OPTIONROMS) {
regs->ax = 1;
return;
}
dprintf(3, "Legacy16DispatchOprom rom %p\n", table);
dprintf(3, "OpromSegment %04x\n", table->OpromSegment);
dprintf(3, "RuntimeSegment %04x\n", table->RuntimeSegment);
dprintf(3, "PnPInstallationCheck %04x:%04x\n",
table->PnPInstallationCheckSegment,
table->PnPInstallationCheckOffset);
dprintf(3, "RuntimeSegment %04x\n", table->RuntimeSegment);
rom = MAKE_FLATPTR(table->OpromSegment, 0);
bdf = pci_bus_devfn_to_bdf(table->PciBus, table->PciDeviceFunction);
rom_reserve(rom->size * 512);
// XX PnP seg/ofs should never be other than default
callrom(rom, bdf);
rom_confirm(rom->size * 512);
regs->bx = 0; // FIXME
regs->ax = 0;
}
/* Legacy16GetTableAddress */
static void
handle_csm_0006(struct bregs *regs)
{
u16 size = regs->cx;
u16 align = regs->dx;
u16 region = regs->bx; // (1 for F000 seg, 2 for E000 seg, 0 for either)
void *chunk = NULL;
dprintf(3, "Legacy16GetTableAddress size %x align %x region %d\n",
size, align, region);
if (!region)
region = 3;
// DX = Required address alignment. Bit mapped.
// First non-zero bit from the right is the alignment.*/
if (align) {
align = 1 << __ffs(align);
if (align < MALLOC_MIN_ALIGN)
align = MALLOC_MIN_ALIGN;
} else {
align = MALLOC_MIN_ALIGN;
}
if (region & 2)
chunk = _malloc(&ZoneLow, size, align);
if (!chunk && (region & 1))
chunk = _malloc(&ZoneFSeg, size, align);
dprintf(3, "Legacy16GetTableAddress size %x align %x region %d yields %p\n",
size, align, region, chunk);
if (chunk) {
regs->ds = FLATPTR_TO_SEG(chunk);
regs->bx = FLATPTR_TO_OFFSET(chunk);
regs->ax = 0;
} else {
regs->ax = 1;
}
}
void VISIBLE32INIT
handle_csm(struct bregs *regs)
{
ASSERT32FLAT();
if (!CONFIG_CSM)
return;
dprintf(3, "handle_csm regs %p AX=%04x\n", regs, regs->ax);
code_mutable_preinit();
pic_irqmask_write(PICMask);
switch(regs->ax) {
case 0000: handle_csm_0000(regs); break;
case 0001: handle_csm_0001(regs); break;
case 0002: handle_csm_0002(regs); break;
case 0003: handle_csm_0003(regs); break;
// case 0004: handle_csm_0004(regs); break;
case 0005: handle_csm_0005(regs); break;
case 0006: handle_csm_0006(regs); break;
// case 0007: handle_csm_0007(regs); break;
// case 0008: hamdle_csm_0008(regs); break;
default: regs->al = 1;
}
csm_return(regs);
}
static int csm_prio_to_seabios(u16 csm_prio)
{
switch (csm_prio) {
case BBS_DO_NOT_BOOT_FROM:
case BBS_IGNORE_ENTRY:
return -1;
case BBS_LOWEST_PRIORITY:
case BBS_UNPRIORITIZED_ENTRY:
default:
// SeaBIOS default priorities start at 1, with 0 being used for
// an item explicitly selected from interactive_bootmenu().
// As in find_prio(), add 1 to the value being returned.
return csm_prio + 1;
}
}
int csm_bootprio_ata(struct pci_device *pci, int chanid, int slave)
{
if (!csm_boot_table)
return -1;
BBS_TABLE *bbs = (void *)csm_boot_table->BbsTable;
int index = 1 + (chanid * 2) + slave;
dprintf(3, "CSM bootprio for ATA%d,%d (index %d) is %d\n", chanid, slave,
index, bbs[index].BootPriority);
return csm_prio_to_seabios(bbs[index].BootPriority);
}
int csm_bootprio_fdc(struct pci_device *pci, int port, int fdid)
{
if (!csm_boot_table)
return -1;
BBS_TABLE *bbs = (void *)csm_boot_table->BbsTable;
dprintf(3, "CSM bootprio for FDC is %d\n", bbs[0].BootPriority);
return csm_prio_to_seabios(bbs[0].BootPriority);
}
int csm_bootprio_pci(struct pci_device *pci)
{
if (!csm_boot_table)
return -1;
BBS_TABLE *bbs = (void *)csm_boot_table->BbsTable;
int i;
for (i = 5; i < csm_boot_table->NumberBbsEntries; i++) {
if (pci->bdf == pci_to_bdf(bbs[i].Bus, bbs[i].Device, bbs[i].Function)) {
dprintf(3, "CSM bootprio for PCI(%d,%d,%d) is %d\n", bbs[i].Bus,
bbs[i].Device, bbs[i].Function, bbs[i].BootPriority);
return csm_prio_to_seabios(bbs[i].BootPriority);
}
}
return -1;
}