diff --git a/usr.sbin/bhyve/Makefile b/usr.sbin/bhyve/Makefile --- a/usr.sbin/bhyve/Makefile +++ b/usr.sbin/bhyve/Makefile @@ -15,9 +15,11 @@ BHYVE_SYSDIR?=${SRCTOP} SRCS= \ + acpi_device.c \ atkbdc.c \ acpi.c \ audio.c \ + basl.c \ bhyvegc.c \ bhyverun.c \ block_if.c \ @@ -61,6 +63,8 @@ post.c \ ps2kbd.c \ ps2mouse.c \ + qemu_fwcfg.c \ + qemu_loader.c \ rfb.c \ rtc.c \ smbiostbl.c \ diff --git a/usr.sbin/bhyve/acpi.h b/usr.sbin/bhyve/acpi.h --- a/usr.sbin/bhyve/acpi.h +++ b/usr.sbin/bhyve/acpi.h @@ -31,6 +31,8 @@ #ifndef _ACPI_H_ #define _ACPI_H_ +#include "acpi_device.h" + #define SCI_INT 9 #define SMI_CMD 0xb2 @@ -55,6 +57,7 @@ int acpi_build(struct vmctx *ctx, int ncpu); void acpi_raise_gpe(struct vmctx *ctx, unsigned bit); +int acpi_tables_add_device(const struct acpi_device *const dev); void dsdt_line(const char *fmt, ...); void dsdt_fixed_ioport(uint16_t iobase, uint16_t length); void dsdt_fixed_irq(uint8_t irq); diff --git a/usr.sbin/bhyve/acpi.c b/usr.sbin/bhyve/acpi.c --- a/usr.sbin/bhyve/acpi.c +++ b/usr.sbin/bhyve/acpi.c @@ -38,20 +38,6 @@ * * The tables are placed in the guest's ROM area just below 1MB physical, * above the MPTable. - * - * Layout (No longer correct at FADT and beyond due to properly - * calculating the size of the MADT to allow for changes to - * VM_MAXCPU above 21 which overflows this layout.) - * ------ - * RSDP -> 0xf2400 (36 bytes fixed) - * RSDT -> 0xf2440 (36 bytes + 4*7 table addrs, 4 used) - * XSDT -> 0xf2480 (36 bytes + 8*7 table addrs, 4 used) - * MADT -> 0xf2500 (depends on #CPUs) - * FADT -> 0xf2600 (268 bytes) - * HPET -> 0xf2740 (56 bytes) - * MCFG -> 0xf2780 (60 bytes) - * FACS -> 0xf27C0 (64 bytes) - * DSDT -> 0xf2800 (variable - can go up to 0x100000) */ #include @@ -61,6 +47,7 @@ #include #include +#include #include #include #include @@ -73,44 +60,21 @@ #include "bhyverun.h" #include "acpi.h" +#include "basl.h" #include "pci_emul.h" #include "vmgenc.h" -/* - * Define the base address of the ACPI tables, the sizes of some tables, - * and the offsets to the individual tables, - */ -#define BHYVE_ACPI_BASE 0xf2400 -#define RSDT_OFFSET 0x040 -#define XSDT_OFFSET 0x080 -#define MADT_OFFSET 0x100 -/* - * The MADT consists of: - * 44 Fixed Header - * 8 * maxcpu Processor Local APIC entries - * 12 I/O APIC entry - * 2 * 10 Interrupt Source Override entries - * 6 Local APIC NMI entry - */ -#define MADT_SIZE roundup2((44 + basl_ncpu*8 + 12 + 2*10 + 6), 0x100) -#define FADT_OFFSET (MADT_OFFSET + MADT_SIZE) -#define FADT_SIZE 0x140 -#define HPET_OFFSET (FADT_OFFSET + FADT_SIZE) -#define HPET_SIZE 0x40 -#define MCFG_OFFSET (HPET_OFFSET + HPET_SIZE) -#define MCFG_SIZE 0x40 -#define FACS_OFFSET (MCFG_OFFSET + MCFG_SIZE) -#define FACS_SIZE 0x40 -#define DSDT_OFFSET (FACS_OFFSET + FACS_SIZE) - #define BHYVE_ASL_TEMPLATE "bhyve.XXXXXXX" #define BHYVE_ASL_SUFFIX ".aml" #define BHYVE_ASL_COMPILER "/usr/sbin/iasl" +#define BHYVE_ADDRESS_IOAPIC 0xFEC00000 +#define BHYVE_ADDRESS_HPET 0xFED00000 +#define BHYVE_ADDRESS_LAPIC 0xFEE00000 + static int basl_keep_temps; static int basl_verbose_iasl; static int basl_ncpu; -static uint32_t basl_acpi_base = BHYVE_ACPI_BASE; static uint32_t hpet_capabilities; /* @@ -127,6 +91,9 @@ static int dsdt_indent_level; static int dsdt_error; +struct basl_table *rsdt; +struct basl_table *xsdt; + struct basl_fio { int fd; FILE *fp; @@ -139,512 +106,28 @@ #define EFFLUSH(x) \ if (fflush(x) != 0) goto err_exit; -static int -basl_fwrite_rsdp(FILE *fp) -{ - EFPRINTF(fp, "/*\n"); - EFPRINTF(fp, " * bhyve RSDP template\n"); - EFPRINTF(fp, " */\n"); - EFPRINTF(fp, "[0008]\t\tSignature : \"RSD PTR \"\n"); - EFPRINTF(fp, "[0001]\t\tChecksum : 43\n"); - EFPRINTF(fp, "[0006]\t\tOem ID : \"BHYVE \"\n"); - EFPRINTF(fp, "[0001]\t\tRevision : 02\n"); - EFPRINTF(fp, "[0004]\t\tRSDT Address : %08X\n", - basl_acpi_base + RSDT_OFFSET); - EFPRINTF(fp, "[0004]\t\tLength : 00000024\n"); - EFPRINTF(fp, "[0008]\t\tXSDT Address : 00000000%08X\n", - basl_acpi_base + XSDT_OFFSET); - EFPRINTF(fp, "[0001]\t\tExtended Checksum : 00\n"); - EFPRINTF(fp, "[0003]\t\tReserved : 000000\n"); - - EFFLUSH(fp); - - return (0); - -err_exit: - return (errno); -} - -static int -basl_fwrite_rsdt(FILE *fp) -{ - EFPRINTF(fp, "/*\n"); - EFPRINTF(fp, " * bhyve RSDT template\n"); - EFPRINTF(fp, " */\n"); - EFPRINTF(fp, "[0004]\t\tSignature : \"RSDT\"\n"); - EFPRINTF(fp, "[0004]\t\tTable Length : 00000000\n"); - EFPRINTF(fp, "[0001]\t\tRevision : 01\n"); - EFPRINTF(fp, "[0001]\t\tChecksum : 00\n"); - EFPRINTF(fp, "[0006]\t\tOem ID : \"BHYVE \"\n"); - EFPRINTF(fp, "[0008]\t\tOem Table ID : \"BVRSDT \"\n"); - EFPRINTF(fp, "[0004]\t\tOem Revision : 00000001\n"); - /* iasl will fill in the compiler ID/revision fields */ - EFPRINTF(fp, "[0004]\t\tAsl Compiler ID : \"xxxx\"\n"); - EFPRINTF(fp, "[0004]\t\tAsl Compiler Revision : 00000000\n"); - EFPRINTF(fp, "\n"); - - /* Add in pointers to the MADT, FADT and HPET */ - EFPRINTF(fp, "[0004]\t\tACPI Table Address 0 : %08X\n", - basl_acpi_base + MADT_OFFSET); - EFPRINTF(fp, "[0004]\t\tACPI Table Address 1 : %08X\n", - basl_acpi_base + FADT_OFFSET); - EFPRINTF(fp, "[0004]\t\tACPI Table Address 2 : %08X\n", - basl_acpi_base + HPET_OFFSET); - EFPRINTF(fp, "[0004]\t\tACPI Table Address 3 : %08X\n", - basl_acpi_base + MCFG_OFFSET); - - EFFLUSH(fp); - - return (0); - -err_exit: - return (errno); -} - -static int -basl_fwrite_xsdt(FILE *fp) -{ - EFPRINTF(fp, "/*\n"); - EFPRINTF(fp, " * bhyve XSDT template\n"); - EFPRINTF(fp, " */\n"); - EFPRINTF(fp, "[0004]\t\tSignature : \"XSDT\"\n"); - EFPRINTF(fp, "[0004]\t\tTable Length : 00000000\n"); - EFPRINTF(fp, "[0001]\t\tRevision : 01\n"); - EFPRINTF(fp, "[0001]\t\tChecksum : 00\n"); - EFPRINTF(fp, "[0006]\t\tOem ID : \"BHYVE \"\n"); - EFPRINTF(fp, "[0008]\t\tOem Table ID : \"BVXSDT \"\n"); - EFPRINTF(fp, "[0004]\t\tOem Revision : 00000001\n"); - /* iasl will fill in the compiler ID/revision fields */ - EFPRINTF(fp, "[0004]\t\tAsl Compiler ID : \"xxxx\"\n"); - EFPRINTF(fp, "[0004]\t\tAsl Compiler Revision : 00000000\n"); - EFPRINTF(fp, "\n"); - - /* Add in pointers to the MADT, FADT and HPET */ - EFPRINTF(fp, "[0004]\t\tACPI Table Address 0 : 00000000%08X\n", - basl_acpi_base + MADT_OFFSET); - EFPRINTF(fp, "[0004]\t\tACPI Table Address 1 : 00000000%08X\n", - basl_acpi_base + FADT_OFFSET); - EFPRINTF(fp, "[0004]\t\tACPI Table Address 2 : 00000000%08X\n", - basl_acpi_base + HPET_OFFSET); - EFPRINTF(fp, "[0004]\t\tACPI Table Address 3 : 00000000%08X\n", - basl_acpi_base + MCFG_OFFSET); - - EFFLUSH(fp); - - return (0); - -err_exit: - return (errno); -} +/* + * A list for additional ACPI devices like a TPM. + */ +struct acpi_device_list_entry { + SLIST_ENTRY(acpi_device_list_entry) chain; + const struct acpi_device *dev; +}; +SLIST_HEAD(acpi_device_list, + acpi_device_list_entry) acpi_devices = SLIST_HEAD_INITIALIZER(acpi_devices); -static int -basl_fwrite_madt(FILE *fp) +int +acpi_tables_add_device(const struct acpi_device *const dev) { - int i; - - EFPRINTF(fp, "/*\n"); - EFPRINTF(fp, " * bhyve MADT template\n"); - EFPRINTF(fp, " */\n"); - EFPRINTF(fp, "[0004]\t\tSignature : \"APIC\"\n"); - EFPRINTF(fp, "[0004]\t\tTable Length : 00000000\n"); - EFPRINTF(fp, "[0001]\t\tRevision : 01\n"); - EFPRINTF(fp, "[0001]\t\tChecksum : 00\n"); - EFPRINTF(fp, "[0006]\t\tOem ID : \"BHYVE \"\n"); - EFPRINTF(fp, "[0008]\t\tOem Table ID : \"BVMADT \"\n"); - EFPRINTF(fp, "[0004]\t\tOem Revision : 00000001\n"); - - /* iasl will fill in the compiler ID/revision fields */ - EFPRINTF(fp, "[0004]\t\tAsl Compiler ID : \"xxxx\"\n"); - EFPRINTF(fp, "[0004]\t\tAsl Compiler Revision : 00000000\n"); - EFPRINTF(fp, "\n"); - - EFPRINTF(fp, "[0004]\t\tLocal Apic Address : FEE00000\n"); - EFPRINTF(fp, "[0004]\t\tFlags (decoded below) : 00000001\n"); - EFPRINTF(fp, "\t\t\tPC-AT Compatibility : 1\n"); - EFPRINTF(fp, "\n"); - - /* Add a Processor Local APIC entry for each CPU */ - for (i = 0; i < basl_ncpu; i++) { - EFPRINTF(fp, "[0001]\t\tSubtable Type : 00\n"); - EFPRINTF(fp, "[0001]\t\tLength : 08\n"); - /* iasl expects hex values for the proc and apic id's */ - EFPRINTF(fp, "[0001]\t\tProcessor ID : %02x\n", i); - EFPRINTF(fp, "[0001]\t\tLocal Apic ID : %02x\n", i); - EFPRINTF(fp, "[0004]\t\tFlags (decoded below) : 00000001\n"); - EFPRINTF(fp, "\t\t\tProcessor Enabled : 1\n"); - EFPRINTF(fp, "\t\t\tRuntime Online Capable : 0\n"); - EFPRINTF(fp, "\n"); + struct acpi_device_list_entry *const entry = calloc(1, sizeof(*entry)); + if (entry == NULL) { + return (ENOMEM); } - /* Always a single IOAPIC entry, with ID 0 */ - EFPRINTF(fp, "[0001]\t\tSubtable Type : 01\n"); - EFPRINTF(fp, "[0001]\t\tLength : 0C\n"); - /* iasl expects a hex value for the i/o apic id */ - EFPRINTF(fp, "[0001]\t\tI/O Apic ID : %02x\n", 0); - EFPRINTF(fp, "[0001]\t\tReserved : 00\n"); - EFPRINTF(fp, "[0004]\t\tAddress : fec00000\n"); - EFPRINTF(fp, "[0004]\t\tInterrupt : 00000000\n"); - EFPRINTF(fp, "\n"); - - /* Legacy IRQ0 is connected to pin 2 of the IOAPIC */ - EFPRINTF(fp, "[0001]\t\tSubtable Type : 02\n"); - EFPRINTF(fp, "[0001]\t\tLength : 0A\n"); - EFPRINTF(fp, "[0001]\t\tBus : 00\n"); - EFPRINTF(fp, "[0001]\t\tSource : 00\n"); - EFPRINTF(fp, "[0004]\t\tInterrupt : 00000002\n"); - EFPRINTF(fp, "[0002]\t\tFlags (decoded below) : 0005\n"); - EFPRINTF(fp, "\t\t\tPolarity : 1\n"); - EFPRINTF(fp, "\t\t\tTrigger Mode : 1\n"); - EFPRINTF(fp, "\n"); - - EFPRINTF(fp, "[0001]\t\tSubtable Type : 02\n"); - EFPRINTF(fp, "[0001]\t\tLength : 0A\n"); - EFPRINTF(fp, "[0001]\t\tBus : 00\n"); - EFPRINTF(fp, "[0001]\t\tSource : %02X\n", SCI_INT); - EFPRINTF(fp, "[0004]\t\tInterrupt : %08X\n", SCI_INT); - EFPRINTF(fp, "[0002]\t\tFlags (decoded below) : 0000\n"); - EFPRINTF(fp, "\t\t\tPolarity : 3\n"); - EFPRINTF(fp, "\t\t\tTrigger Mode : 3\n"); - EFPRINTF(fp, "\n"); - - /* Local APIC NMI is connected to LINT 1 on all CPUs */ - EFPRINTF(fp, "[0001]\t\tSubtable Type : 04\n"); - EFPRINTF(fp, "[0001]\t\tLength : 06\n"); - EFPRINTF(fp, "[0001]\t\tProcessor ID : FF\n"); - EFPRINTF(fp, "[0002]\t\tFlags (decoded below) : 0005\n"); - EFPRINTF(fp, "\t\t\tPolarity : 1\n"); - EFPRINTF(fp, "\t\t\tTrigger Mode : 1\n"); - EFPRINTF(fp, "[0001]\t\tInterrupt Input LINT : 01\n"); - EFPRINTF(fp, "\n"); - - EFFLUSH(fp); - - return (0); - -err_exit: - return (errno); -} - -static int -basl_fwrite_fadt(FILE *fp) -{ - EFPRINTF(fp, "/*\n"); - EFPRINTF(fp, " * bhyve FADT template\n"); - EFPRINTF(fp, " */\n"); - EFPRINTF(fp, "[0004]\t\tSignature : \"FACP\"\n"); - EFPRINTF(fp, "[0004]\t\tTable Length : 0000010C\n"); - EFPRINTF(fp, "[0001]\t\tRevision : 05\n"); - EFPRINTF(fp, "[0001]\t\tChecksum : 00\n"); - EFPRINTF(fp, "[0006]\t\tOem ID : \"BHYVE \"\n"); - EFPRINTF(fp, "[0008]\t\tOem Table ID : \"BVFACP \"\n"); - EFPRINTF(fp, "[0004]\t\tOem Revision : 00000001\n"); - /* iasl will fill in the compiler ID/revision fields */ - EFPRINTF(fp, "[0004]\t\tAsl Compiler ID : \"xxxx\"\n"); - EFPRINTF(fp, "[0004]\t\tAsl Compiler Revision : 00000000\n"); - EFPRINTF(fp, "\n"); - - EFPRINTF(fp, "[0004]\t\tFACS Address : %08X\n", - basl_acpi_base + FACS_OFFSET); - EFPRINTF(fp, "[0004]\t\tDSDT Address : %08X\n", - basl_acpi_base + DSDT_OFFSET); - EFPRINTF(fp, "[0001]\t\tModel : 01\n"); - EFPRINTF(fp, "[0001]\t\tPM Profile : 00 [Unspecified]\n"); - EFPRINTF(fp, "[0002]\t\tSCI Interrupt : %04X\n", - SCI_INT); - EFPRINTF(fp, "[0004]\t\tSMI Command Port : %08X\n", - SMI_CMD); - EFPRINTF(fp, "[0001]\t\tACPI Enable Value : %02X\n", - BHYVE_ACPI_ENABLE); - EFPRINTF(fp, "[0001]\t\tACPI Disable Value : %02X\n", - BHYVE_ACPI_DISABLE); - EFPRINTF(fp, "[0001]\t\tS4BIOS Command : 00\n"); - EFPRINTF(fp, "[0001]\t\tP-State Control : 00\n"); - EFPRINTF(fp, "[0004]\t\tPM1A Event Block Address : %08X\n", - PM1A_EVT_ADDR); - EFPRINTF(fp, "[0004]\t\tPM1B Event Block Address : 00000000\n"); - EFPRINTF(fp, "[0004]\t\tPM1A Control Block Address : %08X\n", - PM1A_CNT_ADDR); - EFPRINTF(fp, "[0004]\t\tPM1B Control Block Address : 00000000\n"); - EFPRINTF(fp, "[0004]\t\tPM2 Control Block Address : 00000000\n"); - EFPRINTF(fp, "[0004]\t\tPM Timer Block Address : %08X\n", - IO_PMTMR); - EFPRINTF(fp, "[0004]\t\tGPE0 Block Address : %08X\n", IO_GPE0_BLK); - EFPRINTF(fp, "[0004]\t\tGPE1 Block Address : 00000000\n"); - EFPRINTF(fp, "[0001]\t\tPM1 Event Block Length : 04\n"); - EFPRINTF(fp, "[0001]\t\tPM1 Control Block Length : 02\n"); - EFPRINTF(fp, "[0001]\t\tPM2 Control Block Length : 00\n"); - EFPRINTF(fp, "[0001]\t\tPM Timer Block Length : 04\n"); - EFPRINTF(fp, "[0001]\t\tGPE0 Block Length : %02x\n", IO_GPE0_LEN); - EFPRINTF(fp, "[0001]\t\tGPE1 Block Length : 00\n"); - EFPRINTF(fp, "[0001]\t\tGPE1 Base Offset : 00\n"); - EFPRINTF(fp, "[0001]\t\t_CST Support : 00\n"); - EFPRINTF(fp, "[0002]\t\tC2 Latency : 0000\n"); - EFPRINTF(fp, "[0002]\t\tC3 Latency : 0000\n"); - EFPRINTF(fp, "[0002]\t\tCPU Cache Size : 0000\n"); - EFPRINTF(fp, "[0002]\t\tCache Flush Stride : 0000\n"); - EFPRINTF(fp, "[0001]\t\tDuty Cycle Offset : 00\n"); - EFPRINTF(fp, "[0001]\t\tDuty Cycle Width : 00\n"); - EFPRINTF(fp, "[0001]\t\tRTC Day Alarm Index : 00\n"); - EFPRINTF(fp, "[0001]\t\tRTC Month Alarm Index : 00\n"); - EFPRINTF(fp, "[0001]\t\tRTC Century Index : 32\n"); - EFPRINTF(fp, "[0002]\t\tBoot Flags (decoded below) : 0000\n"); - EFPRINTF(fp, "\t\t\tLegacy Devices Supported (V2) : 0\n"); - EFPRINTF(fp, "\t\t\t8042 Present on ports 60/64 (V2) : 0\n"); - EFPRINTF(fp, "\t\t\tVGA Not Present (V4) : 1\n"); - EFPRINTF(fp, "\t\t\tMSI Not Supported (V4) : 0\n"); - EFPRINTF(fp, "\t\t\tPCIe ASPM Not Supported (V4) : 1\n"); - EFPRINTF(fp, "\t\t\tCMOS RTC Not Present (V5) : 0\n"); - EFPRINTF(fp, "[0001]\t\tReserved : 00\n"); - EFPRINTF(fp, "[0004]\t\tFlags (decoded below) : 00000000\n"); - EFPRINTF(fp, "\t\t\tWBINVD instruction is operational (V1) : 1\n"); - EFPRINTF(fp, "\t\t\tWBINVD flushes all caches (V1) : 0\n"); - EFPRINTF(fp, "\t\t\tAll CPUs support C1 (V1) : 1\n"); - EFPRINTF(fp, "\t\t\tC2 works on MP system (V1) : 0\n"); - EFPRINTF(fp, "\t\t\tControl Method Power Button (V1) : 0\n"); - EFPRINTF(fp, "\t\t\tControl Method Sleep Button (V1) : 1\n"); - EFPRINTF(fp, "\t\t\tRTC wake not in fixed reg space (V1) : 0\n"); - EFPRINTF(fp, "\t\t\tRTC can wake system from S4 (V1) : 0\n"); - EFPRINTF(fp, "\t\t\t32-bit PM Timer (V1) : 1\n"); - EFPRINTF(fp, "\t\t\tDocking Supported (V1) : 0\n"); - EFPRINTF(fp, "\t\t\tReset Register Supported (V2) : 1\n"); - EFPRINTF(fp, "\t\t\tSealed Case (V3) : 0\n"); - EFPRINTF(fp, "\t\t\tHeadless - No Video (V3) : 1\n"); - EFPRINTF(fp, "\t\t\tUse native instr after SLP_TYPx (V3) : 0\n"); - EFPRINTF(fp, "\t\t\tPCIEXP_WAK Bits Supported (V4) : 0\n"); - EFPRINTF(fp, "\t\t\tUse Platform Timer (V4) : 0\n"); - EFPRINTF(fp, "\t\t\tRTC_STS valid on S4 wake (V4) : 0\n"); - EFPRINTF(fp, "\t\t\tRemote Power-on capable (V4) : 0\n"); - EFPRINTF(fp, "\t\t\tUse APIC Cluster Model (V4) : 0\n"); - EFPRINTF(fp, "\t\t\tUse APIC Physical Destination Mode (V4) : 1\n"); - EFPRINTF(fp, "\t\t\tHardware Reduced (V5) : 0\n"); - EFPRINTF(fp, "\t\t\tLow Power S0 Idle (V5) : 0\n"); - EFPRINTF(fp, "\n"); - - EFPRINTF(fp, - "[0012]\t\tReset Register : [Generic Address Structure]\n"); - EFPRINTF(fp, "[0001]\t\tSpace ID : 01 [SystemIO]\n"); - EFPRINTF(fp, "[0001]\t\tBit Width : 08\n"); - EFPRINTF(fp, "[0001]\t\tBit Offset : 00\n"); - EFPRINTF(fp, "[0001]\t\tEncoded Access Width : 01 [Byte Access:8]\n"); - EFPRINTF(fp, "[0008]\t\tAddress : 0000000000000CF9\n"); - EFPRINTF(fp, "\n"); - - EFPRINTF(fp, "[0001]\t\tValue to cause reset : 06\n"); - EFPRINTF(fp, "[0002]\t\tARM Flags (decoded below): 0000\n"); - EFPRINTF(fp, "\t\t\tPSCI Compliant : 0\n"); - EFPRINTF(fp, "\t\t\tMust use HVC for PSCI : 0\n"); - EFPRINTF(fp, "[0001]\t\tFADT Minor Revision : 01\n"); - EFPRINTF(fp, "[0008]\t\tFACS Address : 00000000%08X\n", - basl_acpi_base + FACS_OFFSET); - EFPRINTF(fp, "[0008]\t\tDSDT Address : 00000000%08X\n", - basl_acpi_base + DSDT_OFFSET); - EFPRINTF(fp, - "[0012]\t\tPM1A Event Block : [Generic Address Structure]\n"); - EFPRINTF(fp, "[0001]\t\tSpace ID : 01 [SystemIO]\n"); - EFPRINTF(fp, "[0001]\t\tBit Width : 20\n"); - EFPRINTF(fp, "[0001]\t\tBit Offset : 00\n"); - EFPRINTF(fp, "[0001]\t\tEncoded Access Width : 02 [Word Access:16]\n"); - EFPRINTF(fp, "[0008]\t\tAddress : 00000000%08X\n", - PM1A_EVT_ADDR); - EFPRINTF(fp, "\n"); - - EFPRINTF(fp, - "[0012]\t\tPM1B Event Block : [Generic Address Structure]\n"); - EFPRINTF(fp, "[0001]\t\tSpace ID : 01 [SystemIO]\n"); - EFPRINTF(fp, "[0001]\t\tBit Width : 00\n"); - EFPRINTF(fp, "[0001]\t\tBit Offset : 00\n"); - EFPRINTF(fp, - "[0001]\t\tEncoded Access Width : 00 [Undefined/Legacy]\n"); - EFPRINTF(fp, "[0008]\t\tAddress : 0000000000000000\n"); - EFPRINTF(fp, "\n"); - - EFPRINTF(fp, - "[0012]\t\tPM1A Control Block : [Generic Address Structure]\n"); - EFPRINTF(fp, "[0001]\t\tSpace ID : 01 [SystemIO]\n"); - EFPRINTF(fp, "[0001]\t\tBit Width : 10\n"); - EFPRINTF(fp, "[0001]\t\tBit Offset : 00\n"); - EFPRINTF(fp, "[0001]\t\tEncoded Access Width : 02 [Word Access:16]\n"); - EFPRINTF(fp, "[0008]\t\tAddress : 00000000%08X\n", - PM1A_CNT_ADDR); - EFPRINTF(fp, "\n"); - - EFPRINTF(fp, - "[0012]\t\tPM1B Control Block : [Generic Address Structure]\n"); - EFPRINTF(fp, "[0001]\t\tSpace ID : 01 [SystemIO]\n"); - EFPRINTF(fp, "[0001]\t\tBit Width : 00\n"); - EFPRINTF(fp, "[0001]\t\tBit Offset : 00\n"); - EFPRINTF(fp, - "[0001]\t\tEncoded Access Width : 00 [Undefined/Legacy]\n"); - EFPRINTF(fp, "[0008]\t\tAddress : 0000000000000000\n"); - EFPRINTF(fp, "\n"); - - EFPRINTF(fp, - "[0012]\t\tPM2 Control Block : [Generic Address Structure]\n"); - EFPRINTF(fp, "[0001]\t\tSpace ID : 01 [SystemIO]\n"); - EFPRINTF(fp, "[0001]\t\tBit Width : 08\n"); - EFPRINTF(fp, "[0001]\t\tBit Offset : 00\n"); - EFPRINTF(fp, - "[0001]\t\tEncoded Access Width : 00 [Undefined/Legacy]\n"); - EFPRINTF(fp, "[0008]\t\tAddress : 0000000000000000\n"); - EFPRINTF(fp, "\n"); - - /* Valid for bhyve */ - EFPRINTF(fp, - "[0012]\t\tPM Timer Block : [Generic Address Structure]\n"); - EFPRINTF(fp, "[0001]\t\tSpace ID : 01 [SystemIO]\n"); - EFPRINTF(fp, "[0001]\t\tBit Width : 20\n"); - EFPRINTF(fp, "[0001]\t\tBit Offset : 00\n"); - EFPRINTF(fp, - "[0001]\t\tEncoded Access Width : 03 [DWord Access:32]\n"); - EFPRINTF(fp, "[0008]\t\tAddress : 00000000%08X\n", - IO_PMTMR); - EFPRINTF(fp, "\n"); - - EFPRINTF(fp, "[0012]\t\tGPE0 Block : [Generic Address Structure]\n"); - EFPRINTF(fp, "[0001]\t\tSpace ID : 01 [SystemIO]\n"); - EFPRINTF(fp, "[0001]\t\tBit Width : %02x\n", IO_GPE0_LEN * 8); - EFPRINTF(fp, "[0001]\t\tBit Offset : 00\n"); - EFPRINTF(fp, "[0001]\t\tEncoded Access Width : 01 [Byte Access:8]\n"); - EFPRINTF(fp, "[0008]\t\tAddress : %016X\n", IO_GPE0_BLK); - EFPRINTF(fp, "\n"); - - EFPRINTF(fp, "[0012]\t\tGPE1 Block : [Generic Address Structure]\n"); - EFPRINTF(fp, "[0001]\t\tSpace ID : 01 [SystemIO]\n"); - EFPRINTF(fp, "[0001]\t\tBit Width : 00\n"); - EFPRINTF(fp, "[0001]\t\tBit Offset : 00\n"); - EFPRINTF(fp, - "[0001]\t\tEncoded Access Width : 00 [Undefined/Legacy]\n"); - EFPRINTF(fp, "[0008]\t\tAddress : 0000000000000000\n"); - EFPRINTF(fp, "\n"); - - EFPRINTF(fp, - "[0012]\t\tSleep Control Register : [Generic Address Structure]\n"); - EFPRINTF(fp, "[0001]\t\tSpace ID : 01 [SystemIO]\n"); - EFPRINTF(fp, "[0001]\t\tBit Width : 08\n"); - EFPRINTF(fp, "[0001]\t\tBit Offset : 00\n"); - EFPRINTF(fp, "[0001]\t\tEncoded Access Width : 01 [Byte Access:8]\n"); - EFPRINTF(fp, "[0008]\t\tAddress : 0000000000000000\n"); - EFPRINTF(fp, "\n"); - - EFPRINTF(fp, - "[0012]\t\tSleep Status Register : [Generic Address Structure]\n"); - EFPRINTF(fp, "[0001]\t\tSpace ID : 01 [SystemIO]\n"); - EFPRINTF(fp, "[0001]\t\tBit Width : 08\n"); - EFPRINTF(fp, "[0001]\t\tBit Offset : 00\n"); - EFPRINTF(fp, "[0001]\t\tEncoded Access Width : 01 [Byte Access:8]\n"); - EFPRINTF(fp, "[0008]\t\tAddress : 0000000000000000\n"); - - EFFLUSH(fp); - - return (0); - -err_exit: - return (errno); -} - -static int -basl_fwrite_hpet(FILE *fp) -{ - EFPRINTF(fp, "/*\n"); - EFPRINTF(fp, " * bhyve HPET template\n"); - EFPRINTF(fp, " */\n"); - EFPRINTF(fp, "[0004]\t\tSignature : \"HPET\"\n"); - EFPRINTF(fp, "[0004]\t\tTable Length : 00000000\n"); - EFPRINTF(fp, "[0001]\t\tRevision : 01\n"); - EFPRINTF(fp, "[0001]\t\tChecksum : 00\n"); - EFPRINTF(fp, "[0006]\t\tOem ID : \"BHYVE \"\n"); - EFPRINTF(fp, "[0008]\t\tOem Table ID : \"BVHPET \"\n"); - EFPRINTF(fp, "[0004]\t\tOem Revision : 00000001\n"); - - /* iasl will fill in the compiler ID/revision fields */ - EFPRINTF(fp, "[0004]\t\tAsl Compiler ID : \"xxxx\"\n"); - EFPRINTF(fp, "[0004]\t\tAsl Compiler Revision : 00000000\n"); - EFPRINTF(fp, "\n"); - - EFPRINTF(fp, "[0004]\t\tHardware Block ID : %08X\n", hpet_capabilities); - EFPRINTF(fp, - "[0012]\t\tTimer Block Register : [Generic Address Structure]\n"); - EFPRINTF(fp, "[0001]\t\tSpace ID : 00 [SystemMemory]\n"); - EFPRINTF(fp, "[0001]\t\tBit Width : 00\n"); - EFPRINTF(fp, "[0001]\t\tBit Offset : 00\n"); - EFPRINTF(fp, - "[0001]\t\tEncoded Access Width : 00 [Undefined/Legacy]\n"); - EFPRINTF(fp, "[0008]\t\tAddress : 00000000FED00000\n"); - EFPRINTF(fp, "\n"); - - EFPRINTF(fp, "[0001]\t\tSequence Number : 00\n"); - EFPRINTF(fp, "[0002]\t\tMinimum Clock Ticks : 0000\n"); - EFPRINTF(fp, "[0004]\t\tFlags (decoded below) : 00000001\n"); - EFPRINTF(fp, "\t\t\t4K Page Protect : 1\n"); - EFPRINTF(fp, "\t\t\t64K Page Protect : 0\n"); - EFPRINTF(fp, "\n"); - - EFFLUSH(fp); - - return (0); - -err_exit: - return (errno); -} - -static int -basl_fwrite_mcfg(FILE *fp) -{ - EFPRINTF(fp, "/*\n"); - EFPRINTF(fp, " * bhyve MCFG template\n"); - EFPRINTF(fp, " */\n"); - EFPRINTF(fp, "[0004]\t\tSignature : \"MCFG\"\n"); - EFPRINTF(fp, "[0004]\t\tTable Length : 00000000\n"); - EFPRINTF(fp, "[0001]\t\tRevision : 01\n"); - EFPRINTF(fp, "[0001]\t\tChecksum : 00\n"); - EFPRINTF(fp, "[0006]\t\tOem ID : \"BHYVE \"\n"); - EFPRINTF(fp, "[0008]\t\tOem Table ID : \"BVMCFG \"\n"); - EFPRINTF(fp, "[0004]\t\tOem Revision : 00000001\n"); - - /* iasl will fill in the compiler ID/revision fields */ - EFPRINTF(fp, "[0004]\t\tAsl Compiler ID : \"xxxx\"\n"); - EFPRINTF(fp, "[0004]\t\tAsl Compiler Revision : 00000000\n"); - EFPRINTF(fp, "[0008]\t\tReserved : 0\n"); - EFPRINTF(fp, "\n"); - - EFPRINTF(fp, "[0008]\t\tBase Address : %016lX\n", pci_ecfg_base()); - EFPRINTF(fp, "[0002]\t\tSegment Group Number : 0000\n"); - EFPRINTF(fp, "[0001]\t\tStart Bus Number : 00\n"); - EFPRINTF(fp, "[0001]\t\tEnd Bus Number : FF\n"); - EFPRINTF(fp, "[0004]\t\tReserved : 0\n"); - EFFLUSH(fp); - return (0); -err_exit: - return (errno); -} - -static int -basl_fwrite_facs(FILE *fp) -{ - EFPRINTF(fp, "/*\n"); - EFPRINTF(fp, " * bhyve FACS template\n"); - EFPRINTF(fp, " */\n"); - EFPRINTF(fp, "[0004]\t\tSignature : \"FACS\"\n"); - EFPRINTF(fp, "[0004]\t\tLength : 00000040\n"); - EFPRINTF(fp, "[0004]\t\tHardware Signature : 00000000\n"); - EFPRINTF(fp, "[0004]\t\t32 Firmware Waking Vector : 00000000\n"); - EFPRINTF(fp, "[0004]\t\tGlobal Lock : 00000000\n"); - EFPRINTF(fp, "[0004]\t\tFlags (decoded below) : 00000000\n"); - EFPRINTF(fp, "\t\t\tS4BIOS Support Present : 0\n"); - EFPRINTF(fp, "\t\t\t64-bit Wake Supported (V2) : 0\n"); - EFPRINTF(fp, - "[0008]\t\t64 Firmware Waking Vector : 0000000000000000\n"); - EFPRINTF(fp, "[0001]\t\tVersion : 02\n"); - EFPRINTF(fp, "[0003]\t\tReserved : 000000\n"); - EFPRINTF(fp, "[0004]\t\tOspmFlags (decoded below) : 00000000\n"); - EFPRINTF(fp, "\t\t\t64-bit Wake Env Required (V2) : 0\n"); - - EFFLUSH(fp); + entry->dev = dev; + SLIST_INSERT_HEAD(&acpi_devices, entry, chain); return (0); - -err_exit: - return (errno); } /* @@ -731,8 +214,10 @@ dsdt_line("/*"); dsdt_line(" * bhyve DSDT template"); dsdt_line(" */"); - dsdt_line("DefinitionBlock (\"bhyve_dsdt.aml\", \"DSDT\", 2," - "\"BHYVE \", \"BVDSDT \", 0x00000001)"); + dsdt_line("DefinitionBlock (\"bhyve_dsdt.aml\", \"%s\", 0x%02x," + "\"%s\", \"%s\", 0x%08x)", + ACPI_SIG_DSDT, BASL_REVISION_DSDT, BASL_OEM_ID, + BASL_OEM_TABLE_ID_DSDT, BASL_OEM_REVISION_DSDT); dsdt_line("{"); dsdt_line(" Name (_S5, Package ()"); dsdt_line(" {"); @@ -760,6 +245,11 @@ vmgenc_write_dsdt(); + const struct acpi_device_list_entry *entry; + SLIST_FOREACH(entry, &acpi_devices, chain) { + acpi_device_write_dsdt(entry->dev); + } + dsdt_line("}"); if (dsdt_error != 0) @@ -835,26 +325,33 @@ } static int -basl_load(struct vmctx *ctx, int fd, uint64_t off) +basl_load(struct vmctx *ctx, int fd) { struct stat sb; - void *gaddr; + void *addr; if (fstat(fd, &sb) < 0) return (errno); - gaddr = paddr_guest2host(ctx, basl_acpi_base + off, sb.st_size); - if (gaddr == NULL) + addr = calloc(1, sb.st_size); + if (addr == NULL) return (EFAULT); - if (read(fd, gaddr, sb.st_size) < 0) + if (read(fd, addr, sb.st_size) < 0) return (errno); + struct basl_table *table; + + uint8_t name[ACPI_NAMESEG_SIZE + 1] = { 0 }; + memcpy(name, addr, sizeof(name) - 1 /* last char is '\0' */); + BASL_EXEC(basl_table_create(&table, ctx, name, BASL_TABLE_ALIGNMENT)); + BASL_EXEC(basl_table_append_bytes(table, addr, sb.st_size)); + return (0); } static int -basl_compile(struct vmctx *ctx, int (*fwrite_section)(FILE *), uint64_t offset) +basl_compile(struct vmctx *ctx, int (*fwrite_section)(FILE *)) { struct basl_fio io[2]; static char iaslbuf[3*MAXPATHLEN + 10]; @@ -888,7 +385,7 @@ * Copy the aml output file into guest * memory at the specified location */ - err = basl_load(ctx, io[1].fd, offset); + err = basl_load(ctx, io[1].fd); } } basl_end(&io[0], &io[1]); @@ -943,6 +440,429 @@ return (err); } +static int +build_dsdt(struct vmctx *const ctx) +{ + BASL_EXEC(basl_compile(ctx, basl_fwrite_dsdt)); + + return (0); +} + +static int +build_facs(struct vmctx *const ctx) +{ + struct basl_table *facs; + + BASL_EXEC(basl_table_create(&facs, ctx, ACPI_SIG_FACS, + BASL_TABLE_ALIGNMENT_FACS)); + + /* Signature */ + BASL_EXEC( + basl_table_append_bytes(facs, ACPI_SIG_FACS, ACPI_NAMESEG_SIZE)); + /* Length */ + BASL_EXEC(basl_table_append_length(facs, 4)); + /* Hardware Signature */ + BASL_EXEC(basl_table_append_int(facs, 0, 4)); + /* Firmware Waking Vector */ + BASL_EXEC(basl_table_append_int(facs, 0, 4)); + /* Global Lock */ + BASL_EXEC(basl_table_append_int(facs, 0, 4)); + /* Flags */ + BASL_EXEC(basl_table_append_int(facs, 0, 4)); + /* Extended Firmware Waking Vector */ + BASL_EXEC(basl_table_append_int(facs, 0, 8)); + /* Version */ + BASL_EXEC(basl_table_append_int(facs, 2, 1)); + /* Reserved */ + BASL_EXEC(basl_table_append_int(facs, 0, 3)); + /* OSPM Flags */ + BASL_EXEC(basl_table_append_int(facs, 0, 4)); + /* Reserved */ + const uint8_t reserved[24] = { 0 }; + BASL_EXEC(basl_table_append_bytes(facs, reserved, 24)); + + return (0); +} + +static int +build_fadt(struct vmctx *const ctx) +{ + struct basl_table *fadt; + + BASL_EXEC( + basl_table_create(&fadt, ctx, ACPI_SIG_FADT, BASL_TABLE_ALIGNMENT)); + + /* Header */ + BASL_EXEC( + basl_table_append_header(fadt, ACPI_SIG_FADT, BASL_REVISION_FADT, + BASL_OEM_ID, BASL_OEM_TABLE_ID_FADT, BASL_OEM_REVISION_FADT)); + /* FACS Address */ + BASL_EXEC(basl_table_append_pointer(fadt, ACPI_SIG_FACS, + ACPI_RSDT_ENTRY_SIZE)); + /* DSDT Address */ + BASL_EXEC(basl_table_append_pointer(fadt, ACPI_SIG_DSDT, + ACPI_RSDT_ENTRY_SIZE)); + /* Eeserved */ + BASL_EXEC(basl_table_append_int(fadt, 0, 1)); + /* Preferred_PM_Profile [Unspecified] */ + BASL_EXEC(basl_table_append_int(fadt, 0, 1)); + /* SCI Interrupt */ + BASL_EXEC(basl_table_append_int(fadt, SCI_INT, 2)); + /* SMI Command Port */ + BASL_EXEC(basl_table_append_int(fadt, SMI_CMD, 4)); + /* ACPI Enable Value */ + BASL_EXEC(basl_table_append_int(fadt, BHYVE_ACPI_ENABLE, 1)); + /* ACPI Disable Value */ + BASL_EXEC(basl_table_append_int(fadt, BHYVE_ACPI_DISABLE, 1)); + /* S4BIOS Command */ + BASL_EXEC(basl_table_append_int(fadt, 0, 1)); + /* P-State Control */ + BASL_EXEC(basl_table_append_int(fadt, 0, 1)); + /* PM1A Event Block Address */ + BASL_EXEC(basl_table_append_int(fadt, PM1A_EVT_ADDR, 4)); + /* PM1B Event Block Address */ + BASL_EXEC(basl_table_append_int(fadt, 0, 4)); + /* PM1A Control Block Address */ + BASL_EXEC(basl_table_append_int(fadt, PM1A_CNT_ADDR, 4)); + /* PM1B Control Block Address */ + BASL_EXEC(basl_table_append_int(fadt, 0, 4)); + /* PM2 Control Block Address */ + BASL_EXEC(basl_table_append_int(fadt, 0, 4)); + /* PM Timer Block Address */ + BASL_EXEC(basl_table_append_int(fadt, IO_PMTMR, 4)); + /* GPE0 Block Address */ + BASL_EXEC(basl_table_append_int(fadt, IO_GPE0_BLK, 4)); + /* GPE1 Block Address */ + BASL_EXEC(basl_table_append_int(fadt, 0, 4)); + /* PM1 Event Block Length */ + BASL_EXEC(basl_table_append_int(fadt, 4, 1)); + /* PM1 Control Block Length */ + BASL_EXEC(basl_table_append_int(fadt, 2, 1)); + /* PM2 Control Block Length */ + BASL_EXEC(basl_table_append_int(fadt, 0, 1)); + /* PM Timer Block Length */ + BASL_EXEC(basl_table_append_int(fadt, 4, 1)); + /* GPE0 Block Length */ + BASL_EXEC(basl_table_append_int(fadt, IO_GPE0_LEN, 1)); + /* GPE1 Block Length */ + BASL_EXEC(basl_table_append_int(fadt, 0, 1)); + /* GPE1 Base Offset */ + BASL_EXEC(basl_table_append_int(fadt, 0, 1)); + /* _CST Support */ + BASL_EXEC(basl_table_append_int(fadt, 0, 1)); + /* C2 Latency */ + BASL_EXEC(basl_table_append_int(fadt, 0, 2)); + /* C3 Latency */ + BASL_EXEC(basl_table_append_int(fadt, 0, 2)); + /* CPU Cache Size */ + BASL_EXEC(basl_table_append_int(fadt, 0, 2)); + /* Cache Flush Stride */ + BASL_EXEC(basl_table_append_int(fadt, 0, 2)); + /* Duty Cycle Offset */ + BASL_EXEC(basl_table_append_int(fadt, 0, 1)); + /* Duty Cycle Width */ + BASL_EXEC(basl_table_append_int(fadt, 0, 1)); + /* RTC Day Alarm Index */ + BASL_EXEC(basl_table_append_int(fadt, 0, 1)); + /* RTC Month Alarm Index */ + BASL_EXEC(basl_table_append_int(fadt, 0, 1)); + /* RTC Centyr Index */ + BASL_EXEC(basl_table_append_int(fadt, 32, 1)); + /* Boot Flags */ + BASL_EXEC(basl_table_append_int(fadt, + ACPI_FADT_NO_VGA | ACPI_FADT_NO_ASPM, 2)); + /* Reserved */ + BASL_EXEC(basl_table_append_int(fadt, 0, 1)); + /* Flags */ + BASL_EXEC(basl_table_append_int(fadt, + ACPI_FADT_WBINVD | ACPI_FADT_C1_SUPPORTED | ACPI_FADT_SLEEP_BUTTON | + ACPI_FADT_32BIT_TIMER | ACPI_FADT_RESET_REGISTER | + ACPI_FADT_HEADLESS | ACPI_FADT_APIC_PHYSICAL, + 4)); + /* Reset Register */ + BASL_EXEC(basl_table_append_gas(fadt, ACPI_ADR_SPACE_SYSTEM_IO, 8, 0, + ACPI_GAS_ACCESS_WIDTH_BYTE, 0xCF9)); + /* Reset Value */ + BASL_EXEC(basl_table_append_int(fadt, 6, 1)); + /* ARM Boot Architecture Flags */ + BASL_EXEC(basl_table_append_int(fadt, 0, 2)); + /* FADT Minor Version */ + BASL_EXEC(basl_table_append_int(fadt, 1, 1)); + /* Extended FACS Address */ + BASL_EXEC(basl_table_append_pointer(fadt, ACPI_SIG_FACS, + ACPI_XSDT_ENTRY_SIZE)); + /* Extended DSDT Address */ + BASL_EXEC(basl_table_append_pointer(fadt, ACPI_SIG_DSDT, + ACPI_XSDT_ENTRY_SIZE)); + /* Extended PM1A Event Block Address */ + BASL_EXEC(basl_table_append_gas(fadt, ACPI_ADR_SPACE_SYSTEM_IO, 0x20, 0, + ACPI_GAS_ACCESS_WIDTH_WORD, PM1A_EVT_ADDR)); + /* Extended PM1B Event Block Address */ + BASL_EXEC(basl_table_append_gas(fadt, ACPI_ADR_SPACE_SYSTEM_IO, 0, 0, + ACPI_GAS_ACCESS_WIDTH_UNDEFINED, 0)); + /* Extended PM1A Control Block Address */ + BASL_EXEC(basl_table_append_gas(fadt, ACPI_ADR_SPACE_SYSTEM_IO, 0x10, 0, + ACPI_GAS_ACCESS_WIDTH_WORD, PM1A_CNT_ADDR)); + /* Extended PM1B Control Block Address */ + BASL_EXEC(basl_table_append_gas(fadt, ACPI_ADR_SPACE_SYSTEM_IO, 0, 0, + ACPI_GAS_ACCESS_WIDTH_UNDEFINED, 0)); + /* Extended PM2 Control Block Address */ + BASL_EXEC(basl_table_append_gas(fadt, ACPI_ADR_SPACE_SYSTEM_IO, 8, 0, + ACPI_GAS_ACCESS_WIDTH_UNDEFINED, 0)); + /* Extended PM Timer Block Address */ + BASL_EXEC(basl_table_append_gas(fadt, ACPI_ADR_SPACE_SYSTEM_IO, 0x20, 0, + ACPI_GAS_ACCESS_WIDTH_DWORD, IO_PMTMR)); + /* Extended GPE0 Block Address */ + BASL_EXEC(basl_table_append_gas(fadt, ACPI_ADR_SPACE_SYSTEM_IO, + IO_GPE0_LEN * 8, 0, ACPI_GAS_ACCESS_WIDTH_BYTE, IO_GPE0_BLK)); + /* Extended GPE1 Block Address */ + BASL_EXEC(basl_table_append_gas(fadt, ACPI_ADR_SPACE_SYSTEM_IO, 0, 0, + ACPI_GAS_ACCESS_WIDTH_UNDEFINED, 0)); + /* Sleep Control Register Address */ + BASL_EXEC(basl_table_append_gas(fadt, ACPI_ADR_SPACE_SYSTEM_IO, 8, 0, + ACPI_GAS_ACCESS_WIDTH_BYTE, 0)); + /* Sleep Status Register Address */ + BASL_EXEC(basl_table_append_gas(fadt, ACPI_ADR_SPACE_SYSTEM_IO, 8, 0, + ACPI_GAS_ACCESS_WIDTH_BYTE, 0)); + /* Hypervisor Vendor Identity */ + BASL_EXEC(basl_table_append_int(fadt, 0, 8)); + + BASL_EXEC(basl_table_append_pointer(rsdt, ACPI_SIG_FADT, + ACPI_RSDT_ENTRY_SIZE)); + BASL_EXEC(basl_table_append_pointer(xsdt, ACPI_SIG_FADT, + ACPI_XSDT_ENTRY_SIZE)); + + return (0); +} + +static int +build_hpet(struct vmctx *const ctx) +{ + struct basl_table *hpet; + + BASL_EXEC( + basl_table_create(&hpet, ctx, ACPI_SIG_HPET, BASL_TABLE_ALIGNMENT)); + + /* Header */ + BASL_EXEC( + basl_table_append_header(hpet, ACPI_SIG_HPET, BASL_REVISION_HPET, + BASL_OEM_ID, BASL_OEM_TABLE_ID_HPET, BASL_OEM_REVISION_HPET)); + /* Hardware Block ID */ + BASL_EXEC(basl_table_append_int(hpet, hpet_capabilities, 4)); + /* Timer Block Register */ + BASL_EXEC(basl_table_append_gas(hpet, ACPI_ADR_SPACE_SYSTEM_MEMORY, 0, + 0, 0, BHYVE_ADDRESS_HPET)); + /* Sequence Number */ + BASL_EXEC(basl_table_append_int(hpet, 0, 1)); + /* Minimum Clock Ticks */ + BASL_EXEC(basl_table_append_int(hpet, 0, 2)); + /* Flags */ + BASL_EXEC(basl_table_append_int(hpet, ACPI_HPET_PAGE_PROTECT4, 4)); + + BASL_EXEC(basl_table_append_pointer(rsdt, ACPI_SIG_HPET, + ACPI_RSDT_ENTRY_SIZE)); + BASL_EXEC(basl_table_append_pointer(xsdt, ACPI_SIG_HPET, + ACPI_XSDT_ENTRY_SIZE)); + + return (0); +} + +static int +build_madt(struct vmctx *const ctx) +{ + struct basl_table *madt; + + BASL_EXEC( + basl_table_create(&madt, ctx, ACPI_SIG_MADT, BASL_TABLE_ALIGNMENT)); + + /* Header */ + BASL_EXEC( + basl_table_append_header(madt, ACPI_SIG_MADT, BASL_REVISION_MADT, + BASL_OEM_ID, BASL_OEM_TABLE_ID_MADT, BASL_OEM_REVISION_MADT)); + /* Local Apic Address */ + BASL_EXEC(basl_table_append_int(madt, BHYVE_ADDRESS_LAPIC, 4)); + /* Flags */ + BASL_EXEC(basl_table_append_int(madt, ACPI_MADT_PCAT_COMPAT, 4)); + + /* Local APIC for each CPU */ + for (int i = 0; i < basl_ncpu; ++i) { + /* Type */ + BASL_EXEC( + basl_table_append_int(madt, ACPI_MADT_TYPE_LOCAL_APIC, 1)); + /* Length */ + BASL_EXEC(basl_table_append_int(madt, 8, 1)); + /* ACPI Processor UID */ + BASL_EXEC(basl_table_append_int(madt, i, 1)); + /* APIC ID */ + BASL_EXEC(basl_table_append_int(madt, i, 1)); + /* Flags */ + BASL_EXEC(basl_table_append_int(madt, ACPI_MADT_ENABLED, 4)); + } + + /* I/O APIC */ + /* Type */ + BASL_EXEC(basl_table_append_int(madt, ACPI_MADT_TYPE_IO_APIC, 1)); + /* Length */ + BASL_EXEC(basl_table_append_int(madt, 12, 1)); + /* I/O APIC ID */ + BASL_EXEC(basl_table_append_int(madt, 0, 1)); + /* Reserved */ + BASL_EXEC(basl_table_append_int(madt, 0, 1)); + /* I/O APIC Address */ + BASL_EXEC(basl_table_append_int(madt, BHYVE_ADDRESS_IOAPIC, 4)); + /* Interrupt Base */ + BASL_EXEC(basl_table_append_int(madt, 0, 4)); + + /* Legacy IRQ0 is connected to pin 2 of the I/O APIC */ + /* Type */ + BASL_EXEC( + basl_table_append_int(madt, ACPI_MADT_TYPE_INTERRUPT_OVERRIDE, 1)); + /* Length */ + BASL_EXEC(basl_table_append_int(madt, 10, 1)); + /* Bus */ + BASL_EXEC(basl_table_append_int(madt, 0, 1)); + /* Source */ + BASL_EXEC(basl_table_append_int(madt, 0, 1)); + /* Interrupt */ + BASL_EXEC(basl_table_append_int(madt, 2, 4)); + /* Flags */ + BASL_EXEC(basl_table_append_int(madt, + ACPI_MADT_POLARITY_ACTIVE_LOW | ACPI_MADT_TRIGGER_LEVEL, 2)); + + /* Type */ + BASL_EXEC( + basl_table_append_int(madt, ACPI_MADT_TYPE_INTERRUPT_OVERRIDE, 1)); + /* Length */ + BASL_EXEC(basl_table_append_int(madt, 10, 1)); + /* Bus */ + BASL_EXEC(basl_table_append_int(madt, 0, 1)); + /* Source */ + BASL_EXEC(basl_table_append_int(madt, SCI_INT, 1)); + /* Interrupt */ + BASL_EXEC(basl_table_append_int(madt, SCI_INT, 4)); + /* Flags */ + BASL_EXEC(basl_table_append_int(madt, + ACPI_MADT_POLARITY_ACTIVE_LOW | ACPI_MADT_TRIGGER_LEVEL, 2)); + + /* Local APIC NMI is conntected to LINT 1 on all CPUs */ + /* Type */ + BASL_EXEC( + basl_table_append_int(madt, ACPI_MADT_TYPE_LOCAL_APIC_NMI, 1)); + /* Length */ + BASL_EXEC(basl_table_append_int(madt, 6, 1)); + /* Processor UID */ + BASL_EXEC(basl_table_append_int(madt, 0xFF, 1)); + /* Flags */ + BASL_EXEC(basl_table_append_int(madt, + ACPI_MADT_POLARITY_ACTIVE_HIGH | ACPI_MADT_TRIGGER_EDGE, 2)); + /* Local APIC LINT */ + BASL_EXEC(basl_table_append_int(madt, 1, 1)); + + BASL_EXEC(basl_table_append_pointer(rsdt, ACPI_SIG_MADT, + ACPI_RSDT_ENTRY_SIZE)); + BASL_EXEC(basl_table_append_pointer(xsdt, ACPI_SIG_MADT, + ACPI_XSDT_ENTRY_SIZE)); + + return (0); +} + +static int +build_mcfg(struct vmctx *const ctx) +{ + struct basl_table *mcfg; + + BASL_EXEC( + basl_table_create(&mcfg, ctx, ACPI_SIG_MCFG, BASL_TABLE_ALIGNMENT)); + + /* Header */ + BASL_EXEC( + basl_table_append_header(mcfg, ACPI_SIG_MCFG, BASL_REVISION_MCFG, + BASL_OEM_ID, BASL_OEM_TABLE_ID_MCFG, BASL_OEM_REVISION_MCFG)); + /* Reserved */ + BASL_EXEC(basl_table_append_int(mcfg, 0, 8)); + /* Base Address */ + BASL_EXEC(basl_table_append_int(mcfg, pci_ecfg_base(), 8)); + /* Segment Group Number */ + BASL_EXEC(basl_table_append_int(mcfg, 0, 2)); + /* Start Bus Number */ + BASL_EXEC(basl_table_append_int(mcfg, 0, 1)); + /* End Bus Number */ + BASL_EXEC(basl_table_append_int(mcfg, 0xFF, 1)); + /* Reserved */ + BASL_EXEC(basl_table_append_int(mcfg, 0, 4)); + + BASL_EXEC(basl_table_append_pointer(rsdt, ACPI_SIG_MCFG, + ACPI_RSDT_ENTRY_SIZE)); + BASL_EXEC(basl_table_append_pointer(xsdt, ACPI_SIG_MCFG, + ACPI_XSDT_ENTRY_SIZE)); + + return (0); +} + +static int +build_rsdp(struct vmctx *const ctx) +{ + struct basl_table *rsdp; + + BASL_EXEC(basl_table_create(&rsdp, ctx, ACPI_RSDP_NAME, + BASL_TABLE_ALIGNMENT)); + + /* Signature */ + BASL_EXEC(basl_table_append_bytes(rsdp, ACPI_SIG_RSDP, + sizeof(ACPI_SIG_RSDP) - 1)); + /* Checksum (patched by guest) */ + BASL_EXEC(basl_table_append_checksum(rsdp, 0, 20)); + /* OEM Id */ + BASL_EXEC(basl_table_append_bytes(rsdp, BASL_OEM_ID, ACPI_OEM_ID_SIZE)); + /* Revision */ + BASL_EXEC(basl_table_append_int(rsdp, BASL_REVISION_RSDP, 1)); + /* RSDT Address (patched by guest) */ + BASL_EXEC(basl_table_append_pointer(rsdp, ACPI_SIG_RSDT, + ACPI_RSDT_ENTRY_SIZE)); + /* Length (patched by basl_finish) */ + BASL_EXEC(basl_table_append_length(rsdp, sizeof(UINT32))); + /* XSDT Address (patched by guest) */ + BASL_EXEC(basl_table_append_pointer(rsdp, ACPI_SIG_XSDT, + ACPI_XSDT_ENTRY_SIZE)); + /* Extended Checksum (patched by guest) */ + BASL_EXEC(basl_table_append_checksum(rsdp, 0, + BASL_TABLE_CHECKSUM_LEN_FULL_TABLE)); + /* Reserved */ + BASL_EXEC(basl_table_append_int(rsdp, 0, 3)); + + return (0); +} + +static int +build_rsdt(struct vmctx *const ctx) +{ + BASL_EXEC( + basl_table_create(&rsdt, ctx, ACPI_SIG_RSDT, BASL_TABLE_ALIGNMENT)); + + /* Header */ + BASL_EXEC( + basl_table_append_header(rsdt, ACPI_SIG_RSDT, BASL_REVISION_RSDT, + BASL_OEM_ID, BASL_OEM_TABLE_ID_RSDT, BASL_OEM_REVISION_RSDT)); + /* Pointers (added by other build_XXX funcs) */ + + return (0); +} + +static int +build_xsdt(struct vmctx *const ctx) +{ + BASL_EXEC( + basl_table_create(&xsdt, ctx, ACPI_SIG_XSDT, BASL_TABLE_ALIGNMENT)); + + /* Header */ + BASL_EXEC( + basl_table_append_header(xsdt, ACPI_SIG_XSDT, BASL_REVISION_XSDT, + BASL_OEM_ID, BASL_OEM_TABLE_ID_XSDT, BASL_OEM_REVISION_XSDT)); + /* Pointers (added by other build_XXX funcs) */ + + return (0); +} + int acpi_build(struct vmctx *ctx, int ncpu) { @@ -968,30 +888,29 @@ if (getenv("BHYVE_ACPI_KEEPTMPS")) basl_keep_temps = 1; - err = basl_make_templates(); + BASL_EXEC(basl_init()); + + BASL_EXEC(basl_make_templates()); /* * Run through all the ASL files, compiling them and * copying them into guest memory + * + * According to UEFI Specification v6.3 chapter 5.1 the FADT should be + * the first table pointed to by XSDT. For that reason, build it as + * first table after XSDT. */ - if (err == 0) - err = basl_compile(ctx, basl_fwrite_rsdp, 0); - if (err == 0) - err = basl_compile(ctx, basl_fwrite_rsdt, RSDT_OFFSET); - if (err == 0) - err = basl_compile(ctx, basl_fwrite_xsdt, XSDT_OFFSET); - if (err == 0) - err = basl_compile(ctx, basl_fwrite_madt, MADT_OFFSET); - if (err == 0) - err = basl_compile(ctx, basl_fwrite_fadt, FADT_OFFSET); - if (err == 0) - err = basl_compile(ctx, basl_fwrite_hpet, HPET_OFFSET); - if (err == 0) - err = basl_compile(ctx, basl_fwrite_mcfg, MCFG_OFFSET); - if (err == 0) - err = basl_compile(ctx, basl_fwrite_facs, FACS_OFFSET); - if (err == 0) - err = basl_compile(ctx, basl_fwrite_dsdt, DSDT_OFFSET); + BASL_EXEC(build_rsdp(ctx)); + BASL_EXEC(build_rsdt(ctx)); + BASL_EXEC(build_xsdt(ctx)); + BASL_EXEC(build_fadt(ctx)); + BASL_EXEC(build_madt(ctx)); + BASL_EXEC(build_hpet(ctx)); + BASL_EXEC(build_mcfg(ctx)); + BASL_EXEC(build_facs(ctx)); + BASL_EXEC(build_dsdt(ctx)); + + BASL_EXEC(basl_finish()); - return (err); + return (0); } diff --git a/usr.sbin/bhyve/acpi_device.h b/usr.sbin/bhyve/acpi_device.h new file mode 100644 --- /dev/null +++ b/usr.sbin/bhyve/acpi_device.h @@ -0,0 +1,42 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2021 Beckhoff Automation GmbH & Co. KG + * Author: Corvin Köhne + */ + +#pragma once + +#include + +struct vmctx; + +struct acpi_device; + +/** + * Creates an ACPI device. + * + * @param[out] new_dev Returns the newly create ACPI device. + * @param[in] vm_ctx VM context the ACPI device is created in. + * @param[in] name Name of the ACPI device. Should always be a NULL + * terminated string. + * @param[in] hid Hardware ID of the ACPI device. Should always be a NULL + * terminated string. + */ +int acpi_device_create(struct acpi_device **const new_dev, + struct vmctx *const vm_ctx, const char *const name, const char *const hid); +void acpi_device_destroy(struct acpi_device *const dev); + +/** + * @note: acpi_device_add_res_acpi_buffer doesn't ensure that no resources are + * added on an error condition. On error the caller should assume that + * the ACPI_BUFFER is partially added to the ACPI device. + */ +int acpi_device_add_res_acpi_buffer(struct acpi_device *const dev, + const ACPI_BUFFER resources); +int acpi_device_add_res_fixed_ioport(struct acpi_device *const dev, + const UINT16 port, UINT8 length); +int acpi_device_add_res_fixed_memory32(struct acpi_device *const dev, + const UINT8 write_protected, const UINT32 address, const UINT32 length); + +void acpi_device_write_dsdt(const struct acpi_device *const dev); diff --git a/usr.sbin/bhyve/acpi_device.c b/usr.sbin/bhyve/acpi_device.c new file mode 100644 --- /dev/null +++ b/usr.sbin/bhyve/acpi_device.c @@ -0,0 +1,240 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2021 Beckhoff Automation GmbH & Co. KG + * Author: Corvin Köhne + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include + +#include + +#include +#include +#include + +#include "acpi.h" +#include "acpi_device.h" + +/** + * List entry to enumerate all resources used by an ACPI device. + * + * @param chain Used to chain multiple elements together. + * @param type Type of the ACPI resource. + * @param data Data of the ACPI resource. + */ +struct acpi_resource_list_entry { + SLIST_ENTRY(acpi_resource_list_entry) chain; + UINT32 type; + ACPI_RESOURCE_DATA data; +}; + +/** + * Holds information about an ACPI device. + * + * @param vm_ctx VM context the ACPI device was created in. + * @param name Name of the ACPI device. + * @param hid Hardware ID of the ACPI device. + * @param crs Current resources used by the ACPI device. + */ +struct acpi_device { + struct vmctx *vm_ctx; + const char *name; + const char *hid; + SLIST_HEAD(acpi_resource_list, acpi_resource_list_entry) crs; +}; + +int +acpi_device_create(struct acpi_device **const new_dev, + struct vmctx *const vm_ctx, const char *const name, const char *const hid) +{ + if (new_dev == NULL || vm_ctx == NULL || name == NULL || hid == NULL) { + return (EINVAL); + } + + struct acpi_device *const dev = calloc(1, sizeof(*dev)); + if (dev == NULL) { + return (ENOMEM); + } + + dev->vm_ctx = vm_ctx; + dev->name = name; + dev->hid = hid; + SLIST_INIT(&dev->crs); + + /* current resources always contain an end tag */ + struct acpi_resource_list_entry *const crs_end_tag = calloc(1, + sizeof(*crs_end_tag)); + if (crs_end_tag == NULL) { + acpi_device_destroy(dev); + return (ENOMEM); + } + crs_end_tag->type = ACPI_RESOURCE_TYPE_END_TAG; + SLIST_INSERT_HEAD(&dev->crs, crs_end_tag, chain); + + const int error = acpi_tables_add_device(dev); + if (error) { + acpi_device_destroy(dev); + return (error); + } + + *new_dev = dev; + + return (0); +} + +void +acpi_device_destroy(struct acpi_device *const dev) +{ + if (dev == NULL) { + return; + } + + struct acpi_resource_list_entry *res; + while (!SLIST_EMPTY(&dev->crs)) { + res = SLIST_FIRST(&dev->crs); + SLIST_REMOVE_HEAD(&dev->crs, chain); + free(res); + } +} + +int +acpi_device_add_res_acpi_buffer(struct acpi_device *const dev, + const ACPI_BUFFER resources) +{ + if (dev == NULL) { + return (EINVAL); + } + + int error = 0; + size_t offset = 0; + while (offset < resources.Length) { + const ACPI_RESOURCE *const res = + (const ACPI_RESOURCE *)((UINT8 *)resources.Pointer + + offset); + switch (res->Type) { + case ACPI_RESOURCE_TYPE_FIXED_IO: + error = acpi_device_add_res_fixed_ioport(dev, + res->Data.FixedIo.Address, + res->Data.FixedIo.AddressLength); + break; + case ACPI_RESOURCE_TYPE_FIXED_MEMORY32: + error = acpi_device_add_res_fixed_memory32(dev, + res->Data.FixedMemory32.WriteProtect, + res->Data.FixedMemory32.Address, + res->Data.FixedMemory32.AddressLength); + break; + case ACPI_RESOURCE_TYPE_END_TAG: + break; + default: + warnx("%s: unknown resource type %d", __func__, + res->Type); + return (ENODEV); + } + if (error) { + break; + } + offset += res->Length; + } + + return (error); +} + +int +acpi_device_add_res_fixed_ioport(struct acpi_device *const dev, + const UINT16 port, const UINT8 length) +{ + if (dev == NULL) { + return (EINVAL); + } + + struct acpi_resource_list_entry *const res = calloc(1, sizeof(*res)); + if (res == NULL) { + return (ENOMEM); + } + + res->type = ACPI_RESOURCE_TYPE_FIXED_IO; + res->data.FixedIo.Address = port; + res->data.FixedIo.AddressLength = length; + + SLIST_INSERT_HEAD(&dev->crs, res, chain); + + return (0); +} + +int +acpi_device_add_res_fixed_memory32(struct acpi_device *const dev, + const UINT8 write_protected, const UINT32 address, const UINT32 length) +{ + if (dev == NULL) { + return (EINVAL); + } + + struct acpi_resource_list_entry *const res = calloc(1, sizeof(*res)); + if (res == NULL) { + return (ENOMEM); + } + + res->type = ACPI_RESOURCE_TYPE_FIXED_MEMORY32; + res->data.FixedMemory32.WriteProtect = write_protected; + res->data.FixedMemory32.Address = address; + res->data.FixedMemory32.AddressLength = length; + + SLIST_INSERT_HEAD(&dev->crs, res, chain); + + return (0); +} + +static void +acpi_device_write_dsdt_crs(const struct acpi_device *const dev) +{ + const struct acpi_resource_list_entry *res; + SLIST_FOREACH (res, &dev->crs, chain) { + switch (res->type) { + case ACPI_RESOURCE_TYPE_FIXED_IO: + dsdt_fixed_ioport(res->data.FixedIo.Address, + res->data.FixedIo.AddressLength); + break; + case ACPI_RESOURCE_TYPE_FIXED_MEMORY32: { + dsdt_fixed_mem32(res->data.FixedMemory32.Address, + res->data.FixedMemory32.AddressLength); + break; + } + case ACPI_RESOURCE_TYPE_END_TAG: + break; + default: + warnx("%s: unknown resource type %d", __func__, + res->type); + return; + } + } +} + +void +acpi_device_write_dsdt(const struct acpi_device *const dev) +{ + if (dev == NULL) { + return; + } + + dsdt_line(""); + dsdt_line(" Scope (\\_SB)"); + dsdt_line(" {"); + dsdt_line(" Device (%s)", dev->name); + dsdt_line(" {"); + dsdt_line(" Name (_HID, \"%s\")", dev->hid); + dsdt_line(" Name (_STA, 0x0F)"); + dsdt_line(" Name (_CRS, ResourceTemplate ()"); + dsdt_line(" {"); + dsdt_indent(4); + acpi_device_write_dsdt_crs(dev); + dsdt_unindent(4); + dsdt_line(" })"); + dsdt_line(" }"); + dsdt_line(" }"); +} diff --git a/usr.sbin/bhyve/basl.h b/usr.sbin/bhyve/basl.h new file mode 100644 --- /dev/null +++ b/usr.sbin/bhyve/basl.h @@ -0,0 +1,90 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2022 Beckhoff Automation GmbH & Co. KG + * Author: Corvin Köhne + */ + +#pragma once + +#include + +#include "qemu_fwcfg.h" + +#define ACPI_GAS_ACCESS_WIDTH_LEGACY 0 +#define ACPI_GAS_ACCESS_WIDTH_UNDEFINED 0 +#define ACPI_GAS_ACCESS_WIDTH_BYTE 1 +#define ACPI_GAS_ACCESS_WIDTH_WORD 2 +#define ACPI_GAS_ACCESS_WIDTH_DWORD 3 +#define ACPI_GAS_ACCESS_WIDTH_QWORD 4 + +#define BHYVE_ACPI_BASE 0xf2400 + +#define BASL_REVISION_DSDT 2 +#define BASL_REVISION_FADT 5 +#define BASL_REVISION_HPET 1 +#define BASL_REVISION_MADT 1 +#define BASL_REVISION_MCFG 1 +#define BASL_REVISION_RSDP 2 +#define BASL_REVISION_RSDT 1 +#define BASL_REVISION_XSDT 1 + +#define BASL_OEM_ID "BHYVE " + +#define BASL_OEM_TABLE_ID_DSDT "BVDSDT " +#define BASL_OEM_TABLE_ID_FADT "BVFACP " +#define BASL_OEM_TABLE_ID_HPET "BVHPET " +#define BASL_OEM_TABLE_ID_MADT "BVMADT " +#define BASL_OEM_TABLE_ID_MCFG "BVMCFG " +#define BASL_OEM_TABLE_ID_RSDT "BVRSDT " +#define BASL_OEM_TABLE_ID_XSDT "BVXSDT " + +#define BASL_OEM_REVISION_DSDT 1 +#define BASL_OEM_REVISION_FADT 1 +#define BASL_OEM_REVISION_HPET 1 +#define BASL_OEM_REVISION_MADT 1 +#define BASL_OEM_REVISION_MCFG 1 +#define BASL_OEM_REVISION_RSDT 1 +#define BASL_OEM_REVISION_XSDT 1 + +#define BASL_COMPILER_ID "BASL" + +#define BASL_COMPILER_REVISION 20220504 + +#define BASL_TABLE_ALIGNMENT 0x10 +#define BASL_TABLE_ALIGNMENT_FACS 0x40 + +#define BASL_TABLE_CHECKSUM_LEN_FULL_TABLE (-1) + +#define BASL_EXEC(x) \ + do { \ + const int error = (x); \ + if (error) { \ + warnc(error, \ + "BASL failed @ %s:%d\n Failed to execute %s", \ + __func__, __LINE__, #x); \ + return (error); \ + } \ + } while (0) + +struct basl_table; + +int basl_finish(); +int basl_init(); +int basl_table_append_bytes(struct basl_table *table, const void *bytes, + uint32_t len); +int basl_table_append_checksum(struct basl_table *table, uint32_t start, + uint32_t len); +int basl_table_append_gas(struct basl_table *table, uint8_t space_id, + uint8_t bit_width, uint8_t bit_offset, uint8_t access_width, + uint64_t address); +int basl_table_append_header(struct basl_table *table, + const uint8_t sign[ACPI_NAMESEG_SIZE], uint8_t rev, + const uint8_t oem_id[ACPI_OEM_ID_SIZE], + const uint8_t oem_table_id[ACPI_OEM_TABLE_ID_SIZE], uint32_t oem_revision); +int basl_table_append_int(struct basl_table *table, uint64_t val, uint8_t size); +int basl_table_append_length(struct basl_table *table, uint8_t size); +int basl_table_append_pointer(struct basl_table *table, + const uint8_t src_sign[ACPI_NAMESEG_SIZE], uint8_t size); +int basl_table_create(struct basl_table **table, struct vmctx *ctx, + const uint8_t name[QEMU_FWCFG_MAX_NAME], uint32_t alignment); diff --git a/usr.sbin/bhyve/basl.c b/usr.sbin/bhyve/basl.c new file mode 100644 --- /dev/null +++ b/usr.sbin/bhyve/basl.c @@ -0,0 +1,559 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2022 Beckhoff Automation GmbH & Co. KG + * Author: Corvin Köhne + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include "basl.h" +#include "qemu_loader.h" + +struct basl_table { + STAILQ_ENTRY(basl_table) chain; + struct vmctx *ctx; + uint8_t fwcfg_name[QEMU_FWCFG_MAX_NAME]; + void *data; + uint32_t len; + uint32_t off; + uint32_t alignment; +}; +STAILQ_HEAD(basl_table_list, basl_table) basl_tables = STAILQ_HEAD_INITIALIZER( + basl_tables); + +struct basl_table_checksum { + STAILQ_ENTRY(basl_table_checksum) chain; + struct basl_table *table; + uint32_t off; + uint32_t start; + uint32_t len; +}; +STAILQ_HEAD(basl_table_checksum_list, basl_table_checksum) basl_checksums = + STAILQ_HEAD_INITIALIZER(basl_checksums); + +struct basl_table_length { + STAILQ_ENTRY(basl_table_length) chain; + struct basl_table *table; + uint32_t off; + uint8_t size; +}; +STAILQ_HEAD(basl_table_length_list, + basl_table_length) basl_lengths = STAILQ_HEAD_INITIALIZER(basl_lengths); + +struct basl_table_pointer { + STAILQ_ENTRY(basl_table_pointer) chain; + struct basl_table *table; + uint8_t src_sign[ACPI_NAMESEG_SIZE]; + uint32_t off; + uint8_t size; +}; +STAILQ_HEAD(basl_table_pointer_list, + basl_table_pointer) basl_pointers = STAILQ_HEAD_INITIALIZER(basl_pointers); + +struct qemu_loader *basl_loader; + +static int +basl_dump_table(const struct basl_table *const table, const int mem) +{ + const ACPI_TABLE_HEADER *const header = table->data; + const uint8_t *data; + + if (!mem) { + data = table->data; + } else { + data = (uint8_t *)vm_map_gpa(table->ctx, + BHYVE_ACPI_BASE + table->off, table->len); + if (data == NULL) { + return (ENOMEM); + } + } + + printf("%c%c%c%c @ %8x (%s)\n\r", header->Signature[0], + header->Signature[1], header->Signature[2], header->Signature[3], + BHYVE_ACPI_BASE + table->off, mem ? "Memory" : "FwCfg"); + for (uint32_t i = 0; i < table->len; i += 0x10) { + printf("%08x: ", i); + for (uint32_t n = 0; n < 0x10; ++n) { + if (table->len <= i + n) { + printf(" "); + continue; + } + printf("%02x ", data[i + n]); + } + printf("| "); + for (uint32_t n = 0; n < 0x10; ++n) { + if (table->len <= i + n) { + printf(" "); + continue; + } + const uint8_t c = data[i + n]; + if (c < 0x20 || c >= 0x7F) { + printf("."); + } else { + printf("%c", c); + } + } + printf("\n\r"); + } + + return (0); +} + +static int +basl_dump(const int mem) +{ + struct basl_table *table; + STAILQ_FOREACH (table, &basl_tables, chain) { + BASL_EXEC(basl_dump_table(table, mem)); + } + + return (0); +} + +static int +basl_finish_alloc() +{ + struct basl_table *table; + uint32_t off = 0; + STAILQ_FOREACH (table, &basl_tables, chain) { + table->off = roundup2(off, table->alignment); + off = table->off + table->len; + if (off <= table->off) { + warnx("%s: invalid table length 0x%8x @ offset 0x%8x", + __func__, table->len, table->off); + return (EFAULT); + } + + /* + * Old guest bios versions search for ACPI tables in the guest + * memory and install them as is. Therefore, copy the tables + * into the guest memory. + */ + void *gva = vm_map_gpa(table->ctx, BHYVE_ACPI_BASE + table->off, + table->len); + if (gva == NULL) { + warnx("%s: could not map gpa [ 0x%16lx, 0x%16lx ]", + __func__, (uint64_t)BHYVE_ACPI_BASE + table->off, + (uint64_t)BHYVE_ACPI_BASE + table->off + + table->len); + return (ENOMEM); + } + memcpy(gva, table->data, table->len); + + /* Cause guest bios to copy the ACPI table into guest memory. */ + BASL_EXEC(qemu_fwcfg_add_file(table->fwcfg_name, table->len, + table->data)); + BASL_EXEC(qemu_loader_alloc(basl_loader, table->fwcfg_name, + table->alignment, QEMU_LOADER_ALLOC_HIGH)); + } + + return (0); +} + +static int +basl_finish_patch_checksums() +{ + struct basl_table_checksum *checksum; + STAILQ_FOREACH (checksum, &basl_checksums, chain) { + const struct basl_table *const table = checksum->table; + + uint32_t len = checksum->len; + if (len == BASL_TABLE_CHECKSUM_LEN_FULL_TABLE) { + len = table->len; + } + + /* + * Old guest bios versions search for ACPI tables in the guest + * memory and install them as is. Therefore, patch the checksum + * in the guest memory copies to a correct value. + */ + const uint64_t gpa = BHYVE_ACPI_BASE + table->off + + checksum->start; + if ((gpa < BHYVE_ACPI_BASE) || + (gpa < BHYVE_ACPI_BASE + table->off)) { + warnx("%s: invalid gpa (off 0x%8x start 0x%8x)", + __func__, table->off, checksum->start); + return (EFAULT); + } + + uint8_t *const gva = (uint8_t *)vm_map_gpa(table->ctx, gpa, + len); + if (gva == NULL) { + warnx("%s: could not map gpa [ 0x%16lx, 0x%16lx ]", + __func__, gpa, gpa + len); + return (ENOMEM); + } + uint8_t *const checksum_gva = gva + checksum->off; + if (checksum_gva < gva) { + warnx("%s: invalid checksum offset 0x%8x", __func__, + checksum->off); + return (EFAULT); + } + + uint8_t sum = 0; + for (uint32_t i = 0; i < len; ++i) { + sum += *(gva + i); + } + *checksum_gva = -sum; + + /* Cause guest bios to patch the checksum. */ + BASL_EXEC(qemu_loader_add_checksum(basl_loader, + table->fwcfg_name, checksum->off, checksum->start, len)); + } + + return (0); +} + +static struct basl_table * +basl_get_table_by_sign(const uint8_t sign[ACPI_NAMESEG_SIZE]) +{ + struct basl_table *table; + STAILQ_FOREACH (table, &basl_tables, chain) { + const ACPI_TABLE_HEADER *const header = + (const ACPI_TABLE_HEADER *)table->data; + if (strncmp(header->Signature, sign, + sizeof(header->Signature)) == 0) { + return (table); + } + } + + warnx("%s: %c%c%c%c not found", __func__, sign[0], sign[1], sign[2], + sign[3]); + return (NULL); +} + +static int +basl_finish_patch_pointers() +{ + struct basl_table_pointer *pointer; + STAILQ_FOREACH (pointer, &basl_pointers, chain) { + const struct basl_table *const table = pointer->table; + const struct basl_table *const src_table = + basl_get_table_by_sign(pointer->src_sign); + if (src_table == NULL) { + warnx("%s: could not find ACPI table %c%c%c%c", + __func__, pointer->src_sign[0], + pointer->src_sign[1], pointer->src_sign[2], + pointer->src_sign[3]); + return (EFAULT); + } + + /* + * Old guest bios versions search for ACPI tables in the guest + * memory and install them as is. Therefore, patch the pointers + * in the guest memory copies manually. + */ + const uint64_t gpa = BHYVE_ACPI_BASE + table->off; + if (gpa < BHYVE_ACPI_BASE) { + warnx("%s: table offset of 0x%8x is too large", + __func__, table->off); + return (EFAULT); + } + + uint8_t *const gva = (uint8_t *)vm_map_gpa(table->ctx, gpa, + table->len); + if (gva == NULL) { + warnx("%s: could not map gpa [ 0x%16lx, 0x%16lx ]", + __func__, gpa, gpa + table->len); + return (ENOMEM); + } + + uint64_t val_le = 0; + memcpy(&val_le, gva + pointer->off, pointer->size); + uint64_t val = le64toh(val_le); + + val += BHYVE_ACPI_BASE + src_table->off; + + val_le = htole64(val); + memcpy(gva + pointer->off, &val_le, pointer->size); + + /* Cause guest bios to patch the pointer. */ + BASL_EXEC( + qemu_loader_add_pointer(basl_loader, table->fwcfg_name, + src_table->fwcfg_name, pointer->off, pointer->size)); + } + + return (0); +} + +static int +basl_finish_set_length() +{ + struct basl_table_length *length; + STAILQ_FOREACH (length, &basl_lengths, chain) { + const struct basl_table *const table = length->table; + + uint32_t len_le = htole32(table->len); + + memcpy(table->data + length->off, &len_le, length->size); + } + + return (0); +} + +int +basl_finish() +{ + if (STAILQ_EMPTY(&basl_tables)) { + warnx("%s: no ACPI tables found", __func__); + return (EINVAL); + } + + BASL_EXEC(basl_finish_set_length()); + BASL_EXEC(basl_finish_alloc()); + BASL_EXEC(basl_finish_patch_pointers()); + BASL_EXEC(basl_finish_patch_checksums()); + BASL_EXEC(qemu_loader_finish(basl_loader)); + + return (0); +} + +int +basl_init() +{ + return (qemu_loader_create(&basl_loader, QEMU_FWCFG_FILE_TABLE_LOADER)); +} + +static int +basl_table_add_checksum(struct basl_table *const table, const uint32_t off, + const uint32_t start, const uint32_t len) +{ + struct basl_table_checksum *const checksum = calloc(1, + sizeof(struct basl_table_checksum)); + if (checksum == NULL) { + warnx("%s: failed to allocate checksum", __func__); + return (ENOMEM); + } + + checksum->table = table; + checksum->off = off; + checksum->start = start; + checksum->len = len; + + STAILQ_INSERT_TAIL(&basl_checksums, checksum, chain); + + return (0); +} + +static int +basl_table_add_length(struct basl_table *const table, const uint32_t off, + const uint8_t size) +{ + struct basl_table_length *const length = calloc(1, + sizeof(struct basl_table_length)); + if (length == NULL) { + warnx("%s: failed to allocate length", __func__); + return (ENOMEM); + } + + length->table = table; + length->off = off; + length->size = size; + + STAILQ_INSERT_TAIL(&basl_lengths, length, chain); + + return (0); +} + +static int +basl_table_add_pointer(struct basl_table *const table, + const uint8_t src_sign[ACPI_NAMESEG_SIZE], const uint32_t off, + const uint8_t size) +{ + struct basl_table_pointer *const pointer = calloc(1, + sizeof(struct basl_table_pointer)); + if (pointer == NULL) { + warnx("%s: failed to allocate pointer", __func__); + return (ENOMEM); + } + + pointer->table = table; + memcpy(pointer->src_sign, src_sign, sizeof(pointer->src_sign)); + pointer->off = off; + pointer->size = size; + + STAILQ_INSERT_TAIL(&basl_pointers, pointer, chain); + + return (0); +} + +int +basl_table_append_bytes(struct basl_table *const table, const void *const bytes, + const uint32_t len) +{ + if (table == NULL || bytes == NULL) { + return (EINVAL); + } + if (table->len + len <= table->len) { + warnx("%s: table too large (table->len 0x%8x len 0x%8x)", + __func__, table->len, len); + return (EFAULT); + } + + table->data = reallocf(table->data, table->len + len); + if (table->data == NULL) { + warnx("%s: failed to realloc table to length 0x%8x", __func__, + table->len + len); + table->len = 0; + return (ENOMEM); + } + void *const end = (uint8_t *)table->data + table->len; + table->len += len; + + memcpy(end, bytes, len); + + return (0); +} + +int +basl_table_append_checksum(struct basl_table *const table, const uint32_t start, + const uint32_t len) +{ + if (table == NULL) { + return (EINVAL); + } + + BASL_EXEC(basl_table_add_checksum(table, table->len, start, len)); + BASL_EXEC(basl_table_append_int(table, 0, 1)); + + return (0); +} + +int +basl_table_append_gas(struct basl_table *const table, const uint8_t space_id, + const uint8_t bit_width, const uint8_t bit_offset, + const uint8_t access_width, const uint64_t address) +{ + ACPI_GENERIC_ADDRESS gas_le = { + .SpaceId = space_id, + .BitWidth = bit_width, + .BitOffset = bit_offset, + .AccessWidth = access_width, + .Address = htole64(address), + }; + + return (basl_table_append_bytes(table, &gas_le, sizeof(gas_le))); +} + +int +basl_table_append_header(struct basl_table *const table, + const uint8_t sign[ACPI_NAMESEG_SIZE], const uint8_t rev, + const uint8_t oem_id[ACPI_OEM_ID_SIZE], + const uint8_t oem_table_id[ACPI_OEM_TABLE_ID_SIZE], + const uint32_t oem_revision) +{ + if (table == NULL || table->len != 0) { + return (EINVAL); + } + + ACPI_TABLE_HEADER header_le; + + memcpy(header_le.Signature, sign, sizeof(header_le.Signature)); + header_le.Length = 0; /* patched by basl_finish */ + header_le.Revision = rev; + header_le.Checksum = 0; /* patched by basl_finish */ + memcpy(header_le.OemId, oem_id, sizeof(header_le.OemId)); + memcpy(header_le.OemTableId, oem_table_id, sizeof(header_le.OemTableId)); + header_le.OemRevision = htole32(oem_revision); + static_assert(sizeof(header_le.AslCompilerId) == + sizeof(BASL_COMPILER_ID) - 1 /* Without '\0' */, + "Mismatching ASL compiler id size"); + memcpy(header_le.AslCompilerId, BASL_COMPILER_ID, + sizeof(header_le.AslCompilerId)); + header_le.AslCompilerRevision = htole32(BASL_COMPILER_REVISION); + + BASL_EXEC( + basl_table_append_bytes(table, &header_le, sizeof(header_le))); + + BASL_EXEC(basl_table_add_length(table, + offsetof(ACPI_TABLE_HEADER, Length), sizeof(header_le.Length))); + BASL_EXEC(basl_table_add_checksum(table, + offsetof(ACPI_TABLE_HEADER, Checksum), 0, + BASL_TABLE_CHECKSUM_LEN_FULL_TABLE)); + + return (0); +} + +int +basl_table_append_int(struct basl_table *const table, const uint64_t val, + const uint8_t size) +{ + if (size > sizeof(val)) { + return (EINVAL); + } + + const uint64_t val_le = htole64(val); + return (basl_table_append_bytes(table, &val_le, size)); +} + +int +basl_table_append_length(struct basl_table *const table, const uint8_t size) +{ + if (table == NULL || size > sizeof(table->len)) { + return (EINVAL); + } + + BASL_EXEC(basl_table_add_length(table, table->len, size)); + BASL_EXEC(basl_table_append_int(table, 0, size)); + + return (0); +} + +int +basl_table_append_pointer(struct basl_table *const table, + const uint8_t src_sign[ACPI_NAMESEG_SIZE], const uint8_t size) +{ + if (table == NULL || size > sizeof(UINT64)) { + return (EINVAL); + } + + BASL_EXEC(basl_table_add_pointer(table, src_sign, table->len, size)); + BASL_EXEC(basl_table_append_int(table, 0, size)); + + return (0); +} + +int +basl_table_create(struct basl_table **const table, struct vmctx *ctx, + const uint8_t name[QEMU_FWCFG_MAX_NAME], const uint32_t alignment) +{ + if (table == NULL) { + return (EINVAL); + } + + struct basl_table *const new_table = (struct basl_table *)calloc(1, + sizeof(struct basl_table)); + if (new_table == NULL) { + warnx("%s: failed to allocate table", __func__); + return (ENOMEM); + } + + new_table->ctx = ctx; + + snprintf(new_table->fwcfg_name, sizeof(new_table->fwcfg_name), + "etc/acpi/%s", name); + + new_table->alignment = alignment; + + STAILQ_INSERT_TAIL(&basl_tables, new_table, chain); + + *table = new_table; + + return (0); +} diff --git a/usr.sbin/bhyve/bhyve.8 b/usr.sbin/bhyve/bhyve.8 --- a/usr.sbin/bhyve/bhyve.8 +++ b/usr.sbin/bhyve/bhyve.8 @@ -45,6 +45,15 @@ .Op Cm ,threads= Ar n .Oc .Sm on +.Oo Fl f +.Sm off +.Ar name Cm \&, +.Oo +.Cm string No | Cm file +.Oc +.Cm \&= Ar data +.Sm on +.Oc .Oo .Sm off .Fl G\~ @@ -145,6 +154,16 @@ .Nm to exit when a guest issues an access to an I/O port that is not emulated. This is intended for debug purposes. +.It Fl f Ar name Ns Cm \&, Ns Oo Cm string Ns No | Ns Cm file Ns Oc Ns Cm \&= Ns Ar data +Add a fw_cfg file +.Ar name +to the fw_cfg interface. +If a +.Cm string +is specified, the fw_cfg file contains the string as data. +If a +.Cm file +is specified, bhyve reads the file and adds the file content as fw_cfg data. .It Fl G Xo .Sm off .Oo Ar w Oc diff --git a/usr.sbin/bhyve/bhyverun.c b/usr.sbin/bhyve/bhyverun.c --- a/usr.sbin/bhyve/bhyverun.c +++ b/usr.sbin/bhyve/bhyverun.c @@ -100,6 +100,7 @@ #include "pci_emul.h" #include "pci_irq.h" #include "pci_lpc.h" +#include "qemu_fwcfg.h" #include "smbiostbl.h" #ifdef BHYVE_SNAPSHOT #include "snapshot.h" @@ -1229,6 +1230,7 @@ set_config_bool("acpi_tables", false); set_config_value("memory.size", "256M"); set_config_bool("x86.strictmsr", true); + set_config_value("lpc.fwcfg", "bhyve"); } int @@ -1254,9 +1256,9 @@ progname = basename(argv[0]); #ifdef BHYVE_SNAPSHOT - optstr = "aehuwxACDHIPSWYk:o:p:G:c:s:m:l:K:U:r:"; + optstr = "aehuwxACDHIPSWYk:f:o:p:G:c:s:m:l:K:U:r:"; #else - optstr = "aehuwxACDHIPSWYk:o:p:G:c:s:m:l:K:U:"; + optstr = "aehuwxACDHIPSWYk:f:o:p:G:c:s:m:l:K:U:"; #endif while ((c = getopt(argc, argv, optstr)) != -1) { switch (c) { @@ -1284,6 +1286,11 @@ case 'C': set_config_bool("memory.guest_in_core", true); break; + case 'f': + if (qemu_fwcfg_parse_cmdline_arg(optarg) != 0) { + exit(1); + } + break; case 'G': parse_gdb_options(optarg); break; @@ -1460,6 +1467,17 @@ rtc_init(ctx); sci_init(ctx); + if (qemu_fwcfg_init(ctx) != 0) { + fprintf(stderr, "qemu fwcfg initialization error"); + exit(4); + } + + if (qemu_fwcfg_add_file("opt/bhyve/hw.ncpu", sizeof(guest_ncpus), + &guest_ncpus) != 0) { + fprintf(stderr, "Could not add qemu fwcfg opt/bhyve/hw.ncpu"); + exit(4); + } + /* * Exit if a device emulation finds an error in its initilization */ @@ -1543,8 +1561,9 @@ assert(error == 0); } - if (lpc_bootrom()) + if (lpc_bootrom() && (strcmp(lpc_fwcfg(), "bhyve") == 0)) { fwctl_init(); + } /* * Change the proc title to include the VM name. diff --git a/usr.sbin/bhyve/pci_lpc.h b/usr.sbin/bhyve/pci_lpc.h --- a/usr.sbin/bhyve/pci_lpc.h +++ b/usr.sbin/bhyve/pci_lpc.h @@ -72,5 +72,6 @@ char *lpc_pirq_name(int pin); void lpc_pirq_routed(void); const char *lpc_bootrom(void); +const char *lpc_fwcfg(void); #endif diff --git a/usr.sbin/bhyve/pci_lpc.c b/usr.sbin/bhyve/pci_lpc.c --- a/usr.sbin/bhyve/pci_lpc.c +++ b/usr.sbin/bhyve/pci_lpc.c @@ -110,10 +110,20 @@ set_config_value("lpc.bootrom", romfile); varfile = strsep(&str, ","); - if (varfile != NULL) { + if (varfile == NULL) { + error = 0; + goto done; + } + if (strchr(varfile, '=') == NULL) { set_config_value("lpc.bootvars", varfile); + } else { + /* varfile doesn't exist, it's another config + * option */ + pci_parse_legacy_config(find_config_node("lpc"), + varfile); } + pci_parse_legacy_config(find_config_node("lpc"), str); error = 0; goto done; } @@ -160,6 +170,12 @@ return (get_config_value("lpc.bootrom")); } +const char * +lpc_fwcfg(void) +{ + return (get_config_value("lpc.fwcfg")); +} + static void lpc_uart_intr_assert(void *arg) { diff --git a/usr.sbin/bhyve/qemu_fwcfg.h b/usr.sbin/bhyve/qemu_fwcfg.h new file mode 100644 --- /dev/null +++ b/usr.sbin/bhyve/qemu_fwcfg.h @@ -0,0 +1,26 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2021 Beckhoff Automation GmbH & Co. KG + * Author: Corvin Köhne + */ + +#pragma once + +#include + +#define QEMU_FWCFG_MAX_ARCHS 0x2 +#define QEMU_FWCFG_MAX_ENTRIES 0x3FFF +#define QEMU_FWCFG_MAX_NAME 56 + +#define QEMU_FWCFG_FILE_TABLE_LOADER "etc/table-loader" + +struct qemu_fwcfg_item { + uint32_t size; + uint8_t *data; +}; + +int qemu_fwcfg_add_file(const uint8_t name[QEMU_FWCFG_MAX_NAME], + const uint32_t size, void *const data); +int qemu_fwcfg_init(struct vmctx *const ctx); +int qemu_fwcfg_parse_cmdline_arg(const char *opt); diff --git a/usr.sbin/bhyve/qemu_fwcfg.c b/usr.sbin/bhyve/qemu_fwcfg.c new file mode 100644 --- /dev/null +++ b/usr.sbin/bhyve/qemu_fwcfg.c @@ -0,0 +1,562 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2021 Beckhoff Automation GmbH & Co. KG + * Author: Corvin Köhne + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "acpi_device.h" +#include "inout.h" +#include "pci_lpc.h" +#include "qemu_fwcfg.h" + +#define QEMU_FWCFG_ACPI_DEVICE_NAME "FWCF" +#define QEMU_FWCFG_ACPI_HARDWARE_ID "QEMU0002" + +#define QEMU_FWCFG_SELECTOR_PORT_NUMBER 0x510 +#define QEMU_FWCFG_SELECTOR_PORT_SIZE 1 +#define QEMU_FWCFG_SELECTOR_PORT_FLAGS IOPORT_F_INOUT +#define QEMU_FWCFG_DATA_PORT_NUMBER 0x511 +#define QEMU_FWCFG_DATA_PORT_SIZE 1 +#define QEMU_FWCFG_DATA_PORT_FLAGS \ + IOPORT_F_INOUT /* QEMU v2.4+ ignores writes */ + +#define QEMU_FWCFG_ARCHITECTURE_MASK 0x0001 +#define QEMU_FWCFG_INDEX_MASK 0x3FFF + +#define QEMU_FWCFG_SELECT_READ 0 +#define QEMU_FWCFG_SELECT_WRITE 1 + +#define QEMU_FWCFG_ARCHITECTURE_GENERIC 0 +#define QEMU_FWCFG_ARCHITECTURE_SPECIFIC 1 + +#define QEMU_FWCFG_INDEX_SIGNATURE 0x00 +#define QEMU_FWCFG_INDEX_ID 0x01 +#define QEMU_FWCFG_INDEX_FILE_DIR 0x19 + +#define QEMU_FWCFG_FIRST_FILE_INDEX 0x20 + +#define QEMU_FWCFG_MIN_FILES 10 + +#pragma pack(1) + +union qemu_fwcfg_selector { + struct { + uint16_t index : 14; + uint16_t writeable : 1; + /* + * 0 = generic | for all architectures + * 1 = specific | only for current architecture + */ + uint16_t architecture : 1; + }; + uint16_t bits; +}; + +struct qemu_fwcfg_signature { + uint8_t signature[4]; +}; + +struct qemu_fwcfg_id { + uint32_t interface : 1; /* always set */ + uint32_t DMA : 1; + uint32_t reserved : 30; +}; + +struct qemu_fwcfg_file { + uint32_t be_size; + uint16_t be_selector; + uint16_t reserved; + uint8_t name[QEMU_FWCFG_MAX_NAME]; +}; + +struct qemu_fwcfg_directory { + uint32_t be_count; + struct qemu_fwcfg_file files[0]; +}; + +struct qemu_fwcfg_softc { + struct acpi_device *acpi_dev; + + uint32_t data_offset; + union qemu_fwcfg_selector selector; + struct qemu_fwcfg_item items[QEMU_FWCFG_MAX_ARCHS] + [QEMU_FWCFG_MAX_ENTRIES]; + struct qemu_fwcfg_directory *directory; +}; + +#pragma pack() + +static struct qemu_fwcfg_softc sc; + +struct qemu_fwcfg_user_file { + STAILQ_ENTRY(qemu_fwcfg_user_file) chain; + uint8_t name[QEMU_FWCFG_MAX_NAME]; + uint32_t size; + void *data; +}; +STAILQ_HEAD(qemu_fwcfg_user_file_list, + qemu_fwcfg_user_file) user_files = STAILQ_HEAD_INITIALIZER(user_files); + +static int +qemu_fwcfg_selector_port_handler(struct vmctx *const ctx, const int vcpu, + const int in, const int port, const int bytes, uint32_t *const eax, + void *const arg) +{ + if (bytes != sizeof(uint16_t)) { + warnx("%s: invalid size (%d) of IO port access", __func__, + bytes); + return (-1); + } + + if (in) { + *eax = htole16(sc.selector.bits); + return (0); + } + + sc.data_offset = 0; + sc.selector.bits = le16toh(*eax); + + return (0); +} + +static int +qemu_fwcfg_data_port_handler(struct vmctx *const ctx, const int vcpu, + const int in, const int port, const int bytes, uint32_t *const eax, + void *const arg) +{ + if (bytes != sizeof(uint8_t)) { + warnx("%s: invalid size (%d) of IO port access", __func__, + bytes); + return (-1); + } + + if (!in) { + warnx("%s: Writes to qemu fwcfg data port aren't allowed", + __func__); + return (-1); + } + + /* get fwcfg item */ + struct qemu_fwcfg_item *const item = + &sc.items[sc.selector.architecture][sc.selector.index]; + if (item->data == NULL) { + warnx( + "%s: qemu fwcfg item doesn't exist (architecture %s index 0x%x)", + __func__, sc.selector.architecture ? "specific" : "generic", + sc.selector.index); + *eax = 0x00; + return (0); + } else if (sc.data_offset >= item->size) { + warnx( + "%s: qemu fwcfg item read exceeds size (architecture %s index 0x%x size 0x%x offset 0x%x)", + __func__, sc.selector.architecture ? "specific" : "generic", + sc.selector.index, item->size, sc.data_offset); + *eax = 0x00; + return (0); + } + + /* return item data */ + *eax = item->data[sc.data_offset]; + sc.data_offset++; + + return (0); +} + +static int +qemu_fwcfg_add_item(const uint16_t architecture, const uint16_t index, + const uint32_t size, void *const data) +{ + /* truncate architecture and index to their desired size */ + const uint16_t arch = architecture & QEMU_FWCFG_ARCHITECTURE_MASK; + const uint16_t idx = index & QEMU_FWCFG_INDEX_MASK; + + /* get pointer to item specified by selector */ + struct qemu_fwcfg_item *const fwcfg_item = &sc.items[arch][idx]; + + /* check if item is already used */ + if (fwcfg_item->data != NULL) { + warnx("%s: qemu fwcfg item exists (architecture %s index 0x%x)", + __func__, arch ? "specific" : "generic", idx); + return (-1); + } + + /* save data of the item */ + fwcfg_item->size = size; + fwcfg_item->data = data; + + return (0); +} + +static int +qemu_fwcfg_add_item_file_dir() +{ + /* alloc directory */ + const size_t size = sizeof(struct qemu_fwcfg_directory) + + QEMU_FWCFG_MIN_FILES * sizeof(struct qemu_fwcfg_file); + struct qemu_fwcfg_directory *const fwcfg_directory = calloc(1, size); + if (fwcfg_directory == NULL) { + return (-ENOMEM); + } + + /* init directory */ + sc.directory = fwcfg_directory; + + /* add directory */ + return qemu_fwcfg_add_item(QEMU_FWCFG_ARCHITECTURE_GENERIC, + QEMU_FWCFG_INDEX_FILE_DIR, sizeof(struct qemu_fwcfg_directory), (uint8_t *)sc.directory); +} + +static int +qemu_fwcfg_add_item_id() +{ + /* alloc id */ + struct qemu_fwcfg_id *const fwcfg_id = calloc(1, + sizeof(struct qemu_fwcfg_id)); + if (fwcfg_id == NULL) { + return (-ENOMEM); + } + + /* init id */ + fwcfg_id->interface = 1; + fwcfg_id->DMA = 0; + + /* + * QEMU specifies ID as little endian. + * Convert fwcfg_id to little endian. + */ + uint32_t *const le_fwcfg_id_ptr = (uint32_t *)fwcfg_id; + *le_fwcfg_id_ptr = htole32(*le_fwcfg_id_ptr); + + /* add id */ + return qemu_fwcfg_add_item(QEMU_FWCFG_ARCHITECTURE_GENERIC, + QEMU_FWCFG_INDEX_ID, sizeof(struct qemu_fwcfg_id), + (uint8_t *)fwcfg_id); +} + +static int +qemu_fwcfg_add_item_signature() +{ + /* alloc signature */ + struct qemu_fwcfg_signature *const fwcfg_signature = calloc(1, + sizeof(struct qemu_fwcfg_signature)); + if (fwcfg_signature == NULL) { + return (-ENOMEM); + } + + /* init signature */ + fwcfg_signature->signature[0] = 'Q'; + fwcfg_signature->signature[1] = 'E'; + fwcfg_signature->signature[2] = 'M'; + fwcfg_signature->signature[3] = 'U'; + + /* add signature */ + return qemu_fwcfg_add_item(QEMU_FWCFG_ARCHITECTURE_GENERIC, + QEMU_FWCFG_INDEX_SIGNATURE, sizeof(struct qemu_fwcfg_signature), + (uint8_t *)fwcfg_signature); +} + +static int +qemu_fwcfg_register_port(const char *const name, const int port, const int size, + const int flags, const inout_func_t handler) +{ + struct inout_port iop; + + bzero(&iop, sizeof(iop)); + iop.name = name; + iop.port = port; + iop.size = size; + iop.flags = flags; + iop.handler = handler; + + return register_inout(&iop); +} + +int +qemu_fwcfg_add_file(const uint8_t name[QEMU_FWCFG_MAX_NAME], const uint32_t size, + void *const data) +{ + /* + * QEMU specifies count as big endian. + * Convert it to host endian to work with it. + */ + const uint32_t count = be32toh(sc.directory->be_count) + 1; + + /* add file to items list */ + const uint32_t index = QEMU_FWCFG_FIRST_FILE_INDEX + count - 1; + const int error = qemu_fwcfg_add_item(QEMU_FWCFG_ARCHITECTURE_GENERIC, + index, size, data); + if (error != 0) { + return (error); + } + + /* + * files should be sorted alphabetical, get index for new file + */ + uint32_t file_index; + for (file_index = 0; file_index < count - 1; ++file_index) { + if (strcmp(name, sc.directory->files[file_index].name) < 0) + break; + } + + if (count > QEMU_FWCFG_MIN_FILES) { + /* alloc new file directory */ + const uint64_t new_size = sizeof(struct qemu_fwcfg_directory) + + count * sizeof(struct qemu_fwcfg_file); + struct qemu_fwcfg_directory *const new_directory = calloc(1, + new_size); + if (new_directory == NULL) { + warnx( + "%s: Unable to allocate a new qemu fwcfg files directory (count %d)", + __func__, count); + return (-ENOMEM); + } + + /* copy files below file_index to new directory */ + memcpy(new_directory->files, sc.directory->files, + file_index * sizeof(struct qemu_fwcfg_file)); + + /* copy files behind file_index to directory */ + memcpy(&new_directory->files[file_index + 1], + &sc.directory->files[file_index], + (count - file_index) * sizeof(struct qemu_fwcfg_file)); + + /* free old directory */ + free(sc.directory); + + /* set directory pointer to new directory */ + sc.directory = new_directory; + + /* adjust directory pointer */ + sc.items[0][QEMU_FWCFG_INDEX_FILE_DIR].data = (uint8_t *) + sc.directory; + } else { + /* shift files behind file_index */ + for (uint32_t i = QEMU_FWCFG_MIN_FILES - 1; i > file_index; --i) { + memcpy(&sc.directory->files[i], + &sc.directory->files[i - 1], + sizeof(struct qemu_fwcfg_file)); + } + } + + /* + * QEMU specifies count, size and index as big endian. + * Save these values in big endian to simplify guest reads of these + * values. + */ + sc.directory->be_count = htobe32(count); + sc.directory->files[file_index].be_size = htobe32(size); + sc.directory->files[file_index].be_selector = htobe16(index); + strcpy(sc.directory->files[file_index].name, name); + + /* set new size for the fwcfg_file_directory */ + sc.items[0][QEMU_FWCFG_INDEX_FILE_DIR].size = + sizeof(struct qemu_fwcfg_directory) + + count * sizeof(struct qemu_fwcfg_file); + + return (0); +} + +static int +qemu_fwcfg_add_user_files() +{ + const struct qemu_fwcfg_user_file *fwcfg_file; + STAILQ_FOREACH (fwcfg_file, &user_files, chain) { + const int error = qemu_fwcfg_add_file(fwcfg_file->name, + fwcfg_file->size, fwcfg_file->data); + if (error) + return (error); + } + + return (0); +} + +int +qemu_fwcfg_init(struct vmctx *const ctx) +{ + int error; + + error = acpi_device_create(&sc.acpi_dev, ctx, + QEMU_FWCFG_ACPI_DEVICE_NAME, QEMU_FWCFG_ACPI_HARDWARE_ID); + if (error) { + warnx("%s: failed to create ACPI device for QEMU FwCfg", + __func__); + goto done; + } + + error = acpi_device_add_res_fixed_ioport(sc.acpi_dev, + QEMU_FWCFG_SELECTOR_PORT_NUMBER, 2); + if (error) { + warnx("%s: failed to add fixed IO port for QEMU FwCfg", + __func__); + goto done; + } + + /* add common fwcfg items */ + if ((error = qemu_fwcfg_add_item_signature()) != 0) { + warnx("%s: Unable to add signature item", __func__); + goto done; + } + if ((error = qemu_fwcfg_add_item_id()) != 0) { + warnx("%s: Unable to add id item", __func__); + goto done; + } + if ((error = qemu_fwcfg_add_item_file_dir()) != 0) { + warnx("%s: Unable to add file_dir item", __func__); + goto done; + } + + /* + * Handlers for fwcfg ports can only be added if they are unused. That's + * only the case when fwcfg is set to qemu. + */ + if (strcmp(lpc_fwcfg(), "qemu") == 0) { + if ((error = qemu_fwcfg_register_port("qemu_fwcfg_selector", + QEMU_FWCFG_SELECTOR_PORT_NUMBER, + QEMU_FWCFG_SELECTOR_PORT_SIZE, + QEMU_FWCFG_SELECTOR_PORT_FLAGS, + qemu_fwcfg_selector_port_handler)) != 0) { + warnx( + "%s: Unable to register qemu fwcfg selector port 0x%x", + __func__, QEMU_FWCFG_SELECTOR_PORT_NUMBER); + goto done; + } + if ((error = qemu_fwcfg_register_port("qemu_fwcfg_data", + QEMU_FWCFG_DATA_PORT_NUMBER, QEMU_FWCFG_DATA_PORT_SIZE, + QEMU_FWCFG_DATA_PORT_FLAGS, + qemu_fwcfg_data_port_handler)) != 0) { + warnx( + "%s: Unable to register qemu fwcfg data port 0x%x", + __func__, QEMU_FWCFG_DATA_PORT_NUMBER); + goto done; + } + } + + if ((error = qemu_fwcfg_add_user_files()) != 0) { + warnx("%s: Unable to add user files", __func__); + goto done; + } + +done: + if (error) { + acpi_device_destroy(sc.acpi_dev); + } + + return (error); +} + +static void +qemu_fwcfg_usage(const char *opt) +{ + warnx("Invalid fw_cfg option \"%s\"", opt); + warnx("-f [name=],(string|file)="); +} + +/* + * Parses the cmdline argument for user defined fw_cfg items. The cmdline + * argument has the format: + * "-f [name=],(string|file)=" + * + * E.g.: "-f opt/com.page/example,string=Hello" + */ +int +qemu_fwcfg_parse_cmdline_arg(const char *opt) +{ + struct qemu_fwcfg_user_file *const fwcfg_file = malloc(sizeof(*fwcfg_file)); + if (fwcfg_file == NULL) { + warnx("Unable to allocate fw_cfg_user_file"); + return (-ENOMEM); + } + + /* get pointer to */ + const char *opt_ptr = opt; + /* If [name=] is specified, skip it */ + if (strncmp(opt_ptr, "name=", sizeof("name=") - 1) == 0) { + opt_ptr += sizeof("name=") - 1; + } + + /* get the end of */ + const char *opt_end = strchr(opt_ptr, ','); + if (opt_end == NULL) { + qemu_fwcfg_usage(opt); + return (-1); + } + + /* check if is too long */ + if (opt_end - opt_ptr > QEMU_FWCFG_MAX_NAME) { + warnx("fw_cfg name too long: \"%s\"", opt); + return (-1); + } + + /* save */ + strncpy(fwcfg_file->name, opt_ptr, opt_end - opt_ptr); + + /* set opt_ptr and opt_end to */ + opt_ptr = opt_end + 1; + opt_end = opt_ptr + strlen(opt_ptr); + + if (strncmp(opt_ptr, "string=", sizeof("string=") - 1) == 0) { + opt_ptr += sizeof("string=") - 1; + fwcfg_file->data = strdup(opt_ptr); + if (fwcfg_file->data == NULL) { + warnx(" Can't duplicate fw_cfg_user_file string \"%s\"", + opt_ptr); + return (-ENOMEM); + } + fwcfg_file->size = strlen(opt_ptr) + 1; + + } else if (strncmp(opt_ptr, "file=", sizeof("file=") - 1) == 0) { + opt_ptr += sizeof("file=") - 1; + + /* open file */ + const int fd = open(opt_ptr, O_RDONLY); + if (fd < 0) { + warnx("Can't open fw_cfg_user_file file \"%s\"", + opt_ptr); + return (-1); + } + + /* get file size */ + const uint64_t size = lseek(fd, 0, SEEK_END); + lseek(fd, 0, SEEK_SET); + + /* read file */ + fwcfg_file->data = malloc(size); + if (fwcfg_file->data == NULL) { + warnx( + "Can't allocate fw_cfg_user_file file \"%s\" (size: 0x%16lx)", + opt_ptr, size); + close(fd); + return (-ENOMEM); + } + fwcfg_file->size = read(fd, fwcfg_file->data, size); + + close(fd); + + } else { + qemu_fwcfg_usage(opt); + return (-1); + } + + STAILQ_INSERT_TAIL(&user_files, fwcfg_file, chain); + + return (0); +} diff --git a/usr.sbin/bhyve/qemu_loader.h b/usr.sbin/bhyve/qemu_loader.h new file mode 100644 --- /dev/null +++ b/usr.sbin/bhyve/qemu_loader.h @@ -0,0 +1,79 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2022 Beckhoff Automation GmbH & Co. KG + * Author: Corvin Köhne + */ + +#pragma once + +#include "qemu_fwcfg.h" + +struct qemu_loader; + +/* + * Some guest bios like seabios assume the RSDP to be located in the FSEG. Bhyve + * only supports OVMF which has no such requirement. + */ +enum qemu_loader_zone { + QEMU_LOADER_ALLOC_HIGH = 1, + QEMU_LOADER_ALLOC_FSEG, /* 0x0F000000 - 0x100000 */ +}; + +/** + * Loads a fwcfg item into guest memory. This command has to be issued before + * any subsequent command can be used. + * + * @param loader Qemu loader instance the command should be added to. + * @param name Name of the fwcfg item which should be allocated. + * @param alignment Alignment required by the data. + * @param zone Memory zone in which it should be loaded. + */ +int qemu_loader_alloc(struct qemu_loader *loader, + const uint8_t name[QEMU_FWCFG_MAX_NAME], uint32_t alignment, + enum qemu_loader_zone zone); +/** + * Calculates a checksum for @p name and writes it to @p name + @p off . The + * checksum calculation ranges from @p start to @p start + @p len. The checksum + * field is always one byte large and all bytes in the specified range, + * including the checksum, have to sum up to 0. + * + * @param loader Qemu loader instance the command should be added to. + * @param name Name of the fwcfg item which should be patched. + * @param off Offset into @p name . + * @param start Start offset of checksum calculation. + * @param len Length of the checksum calculation. + */ +int qemu_loader_add_checksum(struct qemu_loader *loader, + const uint8_t name[QEMU_FWCFG_MAX_NAME], uint32_t off, uint32_t start, + uint32_t len); +/** + * Adds the address of @p src_name to the value at @p dest_name + @p off . The + * size of the pointer is determined by @p dest_size and should be 1, 2, 4 or 8. + * + * @param loader Qemu loader instance the command should be added to. + * @param dest_name Name of the fwcfg item which should be patched. + * @param src_name Name of the fwcfg item which address should be written to + * @p dest_name + @p off. + * @param off Offset into @p dest_name . + * @param size Size of the pointer (1, 2, 4 or 8). + */ +int qemu_loader_add_pointer(struct qemu_loader *loader, + const uint8_t dest_name[QEMU_FWCFG_MAX_NAME], + const uint8_t src_name[QEMU_FWCFG_MAX_NAME], uint32_t off, uint8_t size); + +/** + * Creates a qemu loader instance. + * + * @param new_loader Returns the newly created qemu loader instance. + * @param fwcfg_name Name of the FwCfg item which represents the qemu loader + */ +int qemu_loader_create(struct qemu_loader **new_loader, + const uint8_t fwcfg_name[QEMU_FWCFG_MAX_NAME]); +/** + * Signals that all commands are written to the qemu loader. This function + * creates a proper FwCfg item and registers it. + * + * @param loader Qemu loader instance which should be finished. + */ +int qemu_loader_finish(struct qemu_loader *loader); diff --git a/usr.sbin/bhyve/qemu_loader.c b/usr.sbin/bhyve/qemu_loader.c new file mode 100644 --- /dev/null +++ b/usr.sbin/bhyve/qemu_loader.c @@ -0,0 +1,256 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2022 Beckhoff Automation GmbH & Co. KG + * Author: Corvin Köhne + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include "qemu_loader.h" + +struct qemu_loader_entry { + uint32_t cmd_le; + union { + struct { + uint8_t name[QEMU_FWCFG_MAX_NAME]; + uint32_t alignment_le; + uint8_t zone; + } alloc; + struct { + uint8_t dest_name[QEMU_FWCFG_MAX_NAME]; + uint8_t src_name[QEMU_FWCFG_MAX_NAME]; + uint32_t off_le; + uint8_t size; + } add_pointer; + struct { + uint8_t name[QEMU_FWCFG_MAX_NAME]; + uint32_t off_le; + uint32_t start_le; + uint32_t len_le; + } add_checksum; + struct { + uint8_t dest_name[QEMU_FWCFG_MAX_NAME]; + uint8_t src_name[QEMU_FWCFG_MAX_NAME]; + uint32_t dest_off_le; + uint32_t src_off_le; + uint8_t size; + } write_pointer; + + /* padding */ + uint8_t pad[124]; + }; +} __packed; + +enum qemu_loader_command { + QEMU_LOADER_CMD_ALLOC = 0x1, + QEMU_LOADER_CMD_ADD_POINTER = 0x2, + QEMU_LOADER_CMD_ADD_CHECKSUM = 0x3, + QEMU_LOADER_CMD_WRITE_POINTER = 0x4, +}; + +struct qemu_loader_element { + STAILQ_ENTRY(qemu_loader_element) chain; + struct qemu_loader_entry entry; +}; + +struct qemu_loader { + uint8_t fwcfg_name[QEMU_FWCFG_MAX_NAME]; + STAILQ_HEAD(qemu_loader_list, qemu_loader_element) list; +}; + +int +qemu_loader_alloc(struct qemu_loader *const loader, + const uint8_t name[QEMU_FWCFG_MAX_NAME], const uint32_t alignment, + const enum qemu_loader_zone zone) +{ + struct qemu_loader_element *const element = calloc(1, + sizeof(struct qemu_loader_element)); + if (element == NULL) { + warnx("%s: failed to allocate command", __func__); + return (ENOMEM); + } + + element->entry.cmd_le = htole32(QEMU_LOADER_CMD_ALLOC); + strncpy(element->entry.alloc.name, name, QEMU_FWCFG_MAX_NAME); + element->entry.alloc.alignment_le = htole32(alignment); + element->entry.alloc.zone = zone; + + /* + * The guest always works on copies of the fwcfg item, which where + * loaded into guest memory. Loading a fwcfg item is caused by ALLOC. + * For that reason, ALLOC should be scheduled in front of any other + * commands. + */ + STAILQ_INSERT_TAIL(&loader->list, element, chain); + + return (0); +} + +int +qemu_loader_add_checksum(struct qemu_loader *const loader, + const uint8_t name[QEMU_FWCFG_MAX_NAME], const uint32_t off, + const uint32_t start, const uint32_t len) +{ + struct qemu_loader_element *const element = calloc(1, + sizeof(struct qemu_loader_element)); + if (element == NULL) { + warnx("%s: failed to allocate command", __func__); + return (ENOMEM); + } + + element->entry.cmd_le = htole32(QEMU_LOADER_CMD_ADD_CHECKSUM); + strncpy(element->entry.add_checksum.name, name, QEMU_FWCFG_MAX_NAME); + element->entry.add_checksum.off_le = htole32(off); + element->entry.add_checksum.start_le = htole32(start); + element->entry.add_checksum.len_le = htole32(len); + + STAILQ_INSERT_TAIL(&loader->list, element, chain); + + return (0); +} + +int +qemu_loader_add_pointer(struct qemu_loader *const loader, + const uint8_t dest_name[QEMU_FWCFG_MAX_NAME], + const uint8_t src_name[QEMU_FWCFG_MAX_NAME], const uint32_t off, + const uint8_t size) +{ + struct qemu_loader_element *const element = calloc(1, + sizeof(struct qemu_loader_element)); + if (element == NULL) { + warnx("%s: failed to allocate command", __func__); + return (ENOMEM); + } + + element->entry.cmd_le = htole32(QEMU_LOADER_CMD_ADD_POINTER); + strncpy(element->entry.add_pointer.dest_name, dest_name, + QEMU_FWCFG_MAX_NAME); + strncpy(element->entry.add_pointer.src_name, src_name, + QEMU_FWCFG_MAX_NAME); + element->entry.add_pointer.off_le = htole32(off); + element->entry.add_pointer.size = size; + + STAILQ_INSERT_TAIL(&loader->list, element, chain); + + return (0); +} + +int +qemu_loader_create(struct qemu_loader **const new_loader, + const uint8_t fwcfg_name[QEMU_FWCFG_MAX_NAME]) +{ + if (new_loader == NULL) { + return (EINVAL); + } + + struct qemu_loader *const loader = (struct qemu_loader *)calloc(1, + sizeof(struct qemu_loader)); + if (loader == NULL) { + warnx("%s: failed to allocate loader", __func__); + return (ENOMEM); + } + + memcpy(loader, fwcfg_name, QEMU_FWCFG_MAX_NAME); + STAILQ_INIT(&loader->list); + + *new_loader = loader; + + return (0); +} + +static uint8_t * +qemu_loader_get_zone_name(const enum qemu_loader_zone zone) +{ + switch (zone) { + case QEMU_LOADER_ALLOC_HIGH: + return "HIGH"; + case QEMU_LOADER_ALLOC_FSEG: + return "FSEG"; + default: + return "Unknown"; + } +} + +static void +qemu_loader_dump_entry(const struct qemu_loader_entry *const entry) +{ + switch (le32toh(entry->cmd_le)) { + case QEMU_LOADER_CMD_ALLOC: + printf("CMD_ALLOC\n\r"); + printf(" name : %s\n\r", entry->alloc.name); + printf(" alignment: %8x\n\r", le32toh(entry->alloc.alignment_le)); + printf(" zone : %s\n\r", + qemu_loader_get_zone_name(entry->alloc.zone)); + break; + case QEMU_LOADER_CMD_ADD_POINTER: + printf("CMD_ADD_POINTER\n\r"); + printf(" dest_name: %s\n\r", entry->add_pointer.dest_name); + printf(" src_name : %s\n\r", entry->add_pointer.src_name); + printf(" off : %8x\n\r", le32toh(entry->add_pointer.off_le)); + printf(" size : %8x\n\r", entry->add_pointer.size); + break; + case QEMU_LOADER_CMD_ADD_CHECKSUM: + printf("CMD_ADD_CHECKSUM\n\r"); + printf(" name : %s\n\r", entry->add_checksum.name); + printf(" off : %8x\n\r", le32toh(entry->add_checksum.off_le)); + printf(" start : %8x\n\r", le32toh(entry->add_checksum.start_le)); + printf(" length : %8x\n\r", le32toh(entry->add_checksum.len_le)); + break; + case QEMU_LOADER_CMD_WRITE_POINTER: + printf("CMD_WRITE_POINTER\n\r"); + printf(" dest_name: %s\n\r", entry->write_pointer.dest_name); + printf(" src_name : %s\n\r", entry->write_pointer.src_name); + printf(" dest_off : %8x\n\r", le32toh(entry->write_pointer.dest_off_le)); + printf(" src_off : %8x\n\r", le32toh(entry->write_pointer.src_off_le)); + printf(" size : %8x\n\r", entry->write_pointer.size); + break; + default: + printf("UNKNOWN\n\r"); + break; + } +} + +int +qemu_loader_finish(struct qemu_loader *const loader) +{ + uint64_t len = 0; + struct qemu_loader_element *element; + STAILQ_FOREACH (element, &loader->list, chain) { + len += sizeof(struct qemu_loader_entry); + } + if (len == 0) { + warnx("%s: bios loader empty", __func__); + return (EFAULT); + } + + struct qemu_loader_entry *const data = calloc(1, len); + if (data == NULL) { + warnx("%s: failed to allocate fwcfg data", __func__); + return (ENOMEM); + } + + int i = 0; + STAILQ_FOREACH (element, &loader->list, chain) { + memcpy(&data[i], &element->entry, + sizeof(struct qemu_loader_entry)); + ++i; + } + + return (qemu_fwcfg_add_file(loader->fwcfg_name, len, data)); +}