diff --git a/bindings/GNUmakefile b/bindings/GNUmakefile index 45350faa..c26d6c4f 100644 --- a/bindings/GNUmakefile +++ b/bindings/GNUmakefile @@ -50,7 +50,7 @@ virtio_SRCS := virtio/boot.S virtio/start.c $(common_SRCS) \ virtio/platform.c virtio/platform_intr.c \ virtio/pci.c virtio/serial.c virtio/time.c virtio/virtio_ring.c \ virtio/virtio_net.c virtio/virtio_blk.c virtio/tscclock.c \ - virtio/clock_subr.c virtio/pvclock.c + virtio/clock_subr.c virtio/pvclock.c virtio/acpi.c muen_SRCS := muen/start.c $(common_SRCS) $(common_hvt_SRCS) \ muen/channel.c muen/reader.c muen/writer.c muen/muen-block.c \ diff --git a/bindings/virtio/acpi.c b/bindings/virtio/acpi.c new file mode 100644 index 00000000..cb173de7 --- /dev/null +++ b/bindings/virtio/acpi.c @@ -0,0 +1,301 @@ +/* + * Copyright (c) 2015-2019 Contributors as noted in the AUTHORS file + * + * This file is part of Solo5, a sandboxed execution environment. + * + * Permission to use, copy, modify, and/or distribute this software + * for any purpose with or without fee is hereby granted, provided + * that the above copyright notice and this permission notice appear + * in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL + * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE + * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* This is not a full ACPI implementation. + + A full ACPI implementation involves a turing incomplete byte-code interpreter + of third party code with full access to the system. This is controversial, + to say the least. This implementation may be just enough to shut down a + running system. + */ + +#include "bindings.h" + +bool acpi_detected = false; + +/* This was adapted from kaworu's example in +https://forum.osdev.org/viewtopic.php?t=16990 */ + +#define byte uint8_t +#define word uint16_t +#define dword uint32_t + +dword SMI_CMD; +byte ACPI_ENABLE; +byte ACPI_DISABLE; +dword PM1a_CNT; +dword PM1b_CNT; +word SLP_TYPa; +word SLP_TYPb; +word SLP_EN; +word SCI_EN; +byte PM1_CNT_LEN; + +struct RSDPtr { + byte Signature[8]; + byte CheckSum; + byte OemID[6]; + byte Revision; + dword *RsdtAddress; +}; + +struct FACP { + byte Signature[4]; + dword Length; + byte unneded1[40 - 8]; + dword *DSDT; + byte unneded2[48 - 44]; + dword SMI_CMD; + byte ACPI_ENABLE; + byte ACPI_DISABLE; + byte unneded3[64 - 54]; + dword PM1a_CNT_BLK; + dword PM1b_CNT_BLK; + byte unneded4[89 - 72]; + byte PM1_CNT_LEN; +}; + +/* check if the given address has a valid header */ +static unsigned int *acpi_check_rsdptr(unsigned int *ptr) +{ + char *sig = "RSD PTR "; + struct RSDPtr *rsdp = (struct RSDPtr *) ptr; + byte *bptr; + byte check = 0; + uint32_t i; + + if (memcmp(sig, rsdp, 8) == 0) { + // check checksum rsdpd + bptr = (byte *) ptr; + for (i=0; iRsdtAddress; + } + } + return NULL; +} + +/* finds the acpi header and returns the address of the rsdt */ +static unsigned int *acpi_get_rsdptr(void) +{ + uint32_t *addr; + uint32_t *rsdp; + + /* search below the 1mb mark for RSDP signature */ + for (addr = (uint32_t *) 0x000E0000; addr < (uint32_t *)0x00100000; addr += 0x10/sizeof(addr)) { + rsdp = acpi_check_rsdptr(addr); + if (rsdp != NULL) + return rsdp; + } + /* at address 0x40:0x0E is the RM segment of the ebda */ + uint32_t *ebda = (uint32_t *)((0x40E * 0x10) & 0x000FFFFF); // transform segment into linear address + + /* search Extended BIOS Data Area for the Root System Description Pointer signature */ + for (addr = ebda; addr < (uint32_t *)ebda+1024; addr+= 0x10/sizeof(addr)) { + rsdp = acpi_check_rsdptr(addr); + if (rsdp != NULL) + return rsdp; + } + return NULL; +} + +/* checks for a given header and validates checksum */ +static int acpi_check_header(uint32_t *ptr, char *sig) +{ + if (memcmp(ptr, sig, 4) == 0) { + char *checkPtr = (char *)ptr; + int len = *(ptr + 1); + char check = 0; + while (0 < len--) { + check += *checkPtr; + checkPtr++; + } + if (check == 0) + return 0; + } + return -1; +} + +static int acpi_enable(void) +{ + log(INFO, "Solo5: acpi_enable\n"); + if ((inw(PM1a_CNT) & SCI_EN) == 0) { + /* check if acpi can be enabled */ + if (SMI_CMD != 0 && ACPI_ENABLE != 0) { + outb(SMI_CMD, ACPI_ENABLE); /* send acpi enable command */ + /* give 3 seconds time to enable acpi */ + cpu_wasteful_milli_sleep(3000); + int i = 0; + if (PM1b_CNT != 0) { + for (; i<300; i++ ) { + if ( (inw((unsigned int)PM1b_CNT) &SCI_EN) == 1 ) + break; + cpu_wasteful_milli_sleep(10); + } + } + if (i<300) { + log(INFO, "Solo5: enabled ACPI\n"); + return 0; + } else { + log(INFO, "Solo5: timed out trying to enable ACPI\n"); + return -1; + } + } else { + log(INFO, "Solo5: no known way to enable ACPI.\n"); + return -1; + } + } else { + log(INFO, "Solo5: ACPI was already enabled\n"); + return 0; + } +} + + +// +// bytecode of the \_S5 object +// ----------------------------------------- +// | (optional) | | | | +// NameOP | \ | _ | S | 5 | _ +// 08 | 5A | 5F | 53 | 35 | 5F +// +// ----------------------------------------------------------------------------------------------------------- +// | | | ( SLP_TYPa ) | ( SLP_TYPb ) | ( Reserved ) | (Reserved ) +// PackageOP | PkgLength | NumElements | byteprefix Num | byteprefix Num | byteprefix Num | byteprefix Num +// 12 | 0A | 04 | 0A 05 | 0A 05 | 0A 05 | 0A 05 +// +//----this-structure-was-also-seen---------------------- +// PackageOP | PkgLength | NumElements | +// 12 | 06 | 04 | 00 00 00 00 +// +// (Pkglength bit 6-7 encode additional PkgLength bytes [shouldn't be the case here]) +// +static int acpi_init(void) +{ + log(INFO, "Solo5: acpi_init()\n"); + uint32_t *ptr = acpi_get_rsdptr(); + int entrys; + + if (ptr == NULL) { + log(WARN, "Solo5: no rsdptr\n"); + return -1; + } + if (acpi_check_header(ptr, "RSDT") != 0) { + log(WARN, "Solo5: RSDT not found\n"); + return -1; + } + // the RSDT contains an unknown number of pointers to acpi tables + entrys = *(ptr + 1); + entrys = (entrys-36) /4; + ptr += 36/4; // skip header information + log(INFO, "Solo5: %d ACPI table entries\n", entrys); + while (0DSDT, "DSDT") == 0) + { + // search the \_S5 package in the DSDT + char *S5Addr = (char *) facp->DSDT +36; // skip header + int dsdtLength = *(facp->DSDT+1) -36; + while (0 < dsdtLength--) { + if (memcmp(S5Addr, "_S5_", 4) == 0) + break; + S5Addr++; + } + // check if \_S5 was found + if (dsdtLength > 0) + { + // check for valid AML structure + if ( (*(S5Addr-1) == 0x08 + || ( *(S5Addr-2) == 0x08 && *(S5Addr-1) == '\\') ) + && *(S5Addr+4) == 0x12 ) { + S5Addr += 5; + S5Addr += ((*S5Addr &0xC0)>>6) +2; // calculate PkgLength size + + if (*S5Addr == 0x0A) + S5Addr++; // skip byteprefix + SLP_TYPa = *(S5Addr)<<10; + S5Addr++; + + if (*S5Addr == 0x0A) + S5Addr++; // skip byteprefix + SLP_TYPb = *(S5Addr)<<10; + + SMI_CMD = facp->SMI_CMD; + + ACPI_ENABLE = facp->ACPI_ENABLE; + ACPI_DISABLE = facp->ACPI_DISABLE; + + PM1a_CNT = facp->PM1a_CNT_BLK; + PM1b_CNT = facp->PM1b_CNT_BLK; + + PM1_CNT_LEN = facp->PM1_CNT_LEN; + + SLP_EN = 1<<13; + SCI_EN = 1; + + return 0; + } else { + log(WARN, "Solo5: \\_S5 parse error.\n"); + } + } else { + log(WARN, "Solo5: \\_S5 not present.\n"); + } + } else { + log(WARN, "Solo5: DSDT invalid.\n"); + } + } + ptr++; + } + log(INFO, "Solo5: no valid FACP present.\n"); + return -1; +} + +void acpi_poweroff(void) +{ + log(INFO, "Solo5: ACPI poweroff initiated\n"); + if (!acpi_detected) { + log(WARN, "Solo5: acpi_poweroff() called but ACPI poweroff is not available?\n"); + return; + } + log(INFO, "Solo5: init ACPI\n"); + acpi_init(); + /* SCI_EN is set to 1 if acpi shutdown is possible */ + if (SCI_EN == 0) { + log(WARN, "Solo5: ACPI shutdown is not possible (SCI_EN == 0)\n"); + return; + } + log(WARN, "Solo5: initiating ACPI poweroff\n"); + acpi_enable(); + /* send the shutdown command */ + outw(PM1a_CNT, SLP_TYPa | SLP_EN ); + if (PM1b_CNT != 0) + outw(PM1b_CNT, SLP_TYPb | SLP_EN); + + log(WARN, "Solo5: ACPI poweroff failed.\n"); +} diff --git a/bindings/virtio/bindings.h b/bindings/virtio/bindings.h index 16ccf0d7..efcab34a 100644 --- a/bindings/virtio/bindings.h +++ b/bindings/virtio/bindings.h @@ -48,17 +48,23 @@ int tscclock_init(void); uint64_t tscclock_monotonic(void); uint64_t tscclock_epochoffset(void); void cpu_block(uint64_t until); +void cpu_wasteful_milli_sleep(uint64_t millis); -/* pci.c: only enumerate for now */ +/* pci.c: only enumerate for now (except with a hack for poweroff). */ struct pci_config_info { uint8_t bus; uint8_t dev; + uint8_t fun; uint16_t vendor_id; + uint16_t device_id; uint16_t subsys_id; uint16_t base; uint8_t irq; }; +extern bool acpi_detected; +void acpi_poweroff(void); + void pci_enumerate(void); /* virtio.c: mostly net for now */ diff --git a/bindings/virtio/pci.c b/bindings/virtio/pci.c index 9354f704..02ad69e3 100644 --- a/bindings/virtio/pci.c +++ b/bindings/virtio/pci.c @@ -23,12 +23,14 @@ #define PCI_CONFIG_ADDR 0xCF8 #define PCI_CONFIG_DATA 0xCFC -/* 8 bits for bus number, 5 bits for devices */ +/* 8 bits for bus number, 5 bits for devices, 3 bits for functions */ #define PCI_MAX_BUSES (1 << 8) #define PCI_MAX_DEVICES (1 << 5) +#define PCI_MAX_FUNCTIONS (1 << 3) #define PCI_BUS_SHIFT (16) #define PCI_DEVICE_SHIFT (11) +#define PCI_FUNCTION_SHIFT (8) #define PCI_ENABLE_BIT (1 << 31) #define PCI_CONF_SUBSYS_ID 0x2c @@ -43,7 +45,6 @@ #define PCI_CONF_IOBAR_SHFT 0x0 #define PCI_CONF_IOBAR_MASK ~0x3 - #define PCI_CONF_READ(type, ret, a, s) do { \ uint32_t _conf_data; \ outl(PCI_CONFIG_ADDR, (a) | PCI_CONF_##s); \ @@ -52,7 +53,6 @@ *(ret) = (type) _conf_data; \ } while (0) - static uint32_t net_devices_found; static uint32_t blk_devices_found; @@ -64,59 +64,95 @@ static void virtio_config(struct pci_config_info *pci) /* we only support one net device and one blk device */ switch (pci->subsys_id) { case PCI_CONF_SUBSYS_NET: - log(INFO, "Solo5: PCI:%02x:%02x: virtio-net device, base=0x%x, irq=%u\n", - pci->bus, pci->dev, pci->base, pci->irq); + log(INFO, "Solo5: PCI:%02x:%02x.%02x: virtio-net device, base=0x%x, irq=%u\n", + pci->bus, pci->dev, pci->fun, pci->base, pci->irq); if (!net_devices_found++) virtio_config_network(pci); else - log(WARN, "Solo5: PCI:%02x:%02x: not configured\n", pci->bus, - pci->dev); + log(WARN, "Solo5: PCI:%02x:%02x.%02x: not configured\n", pci->bus, + pci->dev, pci->fun); break; case PCI_CONF_SUBSYS_BLK: - log(INFO, "Solo5: PCI:%02x:%02x: virtio-block device, base=0x%x, irq=%u\n", - pci->bus, pci->dev, pci->base, pci->irq); + log(INFO, "Solo5: PCI:%02x:%02x.%02x: virtio-block device, base=0x%x, irq=%u\n", + pci->bus, pci->dev, pci->fun, pci->base, pci->irq); if (!blk_devices_found++) virtio_config_block(pci); else - log(WARN, "Solo5: PCI:%02x:%02x: not configured\n", pci->bus, - pci->dev); + log(WARN, "Solo5: PCI:%02x:%02x.%02x: not configured\n", pci->bus, + pci->dev, pci->fun); break; default: - log(WARN, "Solo5: PCI:%02x:%02x: unknown virtio device (0x%x)\n", - pci->bus, pci->dev, pci->subsys_id); + log(WARN, "Solo5: PCI:%02x:%02x.%02x: unknown virtio device (0x%x)\n", + pci->bus, pci->dev, pci->fun, pci->subsys_id); return; } } +#define DEVICE_ID_INTEL_PIIX4 0x7113 + +/* The virtio driver can be used to run unikernels on Google Compute Engine. + According to the Google docs, the guest OS will see an Intel PIIX4 + controller if run as a guest under its platform. + + Use its presence the presence of this controller to figure out if ACPI + functions are available. */ +static void intel_config(struct pci_config_info *pci) +{ + switch (pci->device_id) { + case DEVICE_ID_INTEL_PIIX4: + log(INFO, "Solo5: PCI:%02x:%02x.%02x: Intel 82371AB/EB/MB PIIX4 ACPI, irq=%u\n", + pci->bus, pci->dev, pci->fun, pci->irq); + acpi_detected = true; + break; + default: + break; + } +} + +#define VENDOR_INTEL 0x8086 #define VENDOR_QUMRANET_VIRTIO 0x1af4 void pci_enumerate(void) { uint32_t bus; uint8_t dev; + uint8_t fun; for (bus = 0; bus < PCI_MAX_BUSES; bus++) { for (dev = 0; dev < PCI_MAX_DEVICES; dev++) { - uint32_t config_addr, config_data; - struct pci_config_info pci; - - config_addr = (PCI_ENABLE_BIT) - | (bus << PCI_BUS_SHIFT) - | (dev << PCI_DEVICE_SHIFT); - - outl(PCI_CONFIG_ADDR, config_addr); - config_data = inl(PCI_CONFIG_DATA); - - pci.bus = bus; - pci.dev = dev; - pci.vendor_id = config_data & 0xffff; + for (fun = 0; fun < PCI_MAX_FUNCTIONS; fun++) { + uint32_t config_addr, config_data; + uint16_t vendor_id; + struct pci_config_info pci; + + config_addr = (PCI_ENABLE_BIT) + | (bus << PCI_BUS_SHIFT) + | (dev << PCI_DEVICE_SHIFT) + | (fun << PCI_FUNCTION_SHIFT); + + outl(PCI_CONFIG_ADDR, config_addr); + config_data = inl(PCI_CONFIG_DATA); + + vendor_id = config_data & 0xffff; + if (vendor_id == 0xffff) { + /* This means there's no device here. */ + continue; + } + pci.bus = bus; + pci.dev = dev; + pci.fun = fun; + pci.vendor_id = vendor_id; + pci.device_id = (config_data >> 16) & 0xffff; - if (pci.vendor_id == VENDOR_QUMRANET_VIRTIO) { PCI_CONF_READ(uint16_t, &pci.subsys_id, config_addr, SUBSYS_ID); PCI_CONF_READ(uint16_t, &pci.base, config_addr, IOBAR); PCI_CONF_READ(uint8_t, &pci.irq, config_addr, IRQ); - virtio_config(&pci); + if (pci.vendor_id == VENDOR_QUMRANET_VIRTIO) { + virtio_config(&pci); + } else if (pci.vendor_id == VENDOR_INTEL) { + intel_config(&pci); + } } } } diff --git a/bindings/virtio/platform.c b/bindings/virtio/platform.c index c3684833..9cbf954a 100644 --- a/bindings/virtio/platform.c +++ b/bindings/virtio/platform.c @@ -107,10 +107,10 @@ void platform_exit(int status __attribute__((unused)), */ outw(0x501, 41); - /* - * If we got here, there is no way to initiate "shutdown" on virtio without - * ACPI, so just halt. - */ + if (acpi_detected == true) + acpi_poweroff(); + + /* If we got here, ACPI poweroff wasn't supported (or failed). So just halt. */ platform_puts("Solo5: Halted\n", 14); cpu_halt(); } diff --git a/bindings/virtio/tscclock.c b/bindings/virtio/tscclock.c index 345472eb..422f0c31 100644 --- a/bindings/virtio/tscclock.c +++ b/bindings/virtio/tscclock.c @@ -329,3 +329,14 @@ void cpu_block(uint64_t until) { "cli;\n"); cpu_intr_depth = d; } + +/* This is only here for timing while configuring devices on either startup or + shutdown. Do not generally use. */ +void cpu_wasteful_milli_sleep(uint64_t millis) +{ + uint64_t now_ns = solo5_clock_monotonic(); + uint64_t until_ns = now_ns + (millis * 1000000); + while (solo5_clock_monotonic() <= until_ns) { + /* Spin! */ + } +} diff --git a/bindings/virtio/virtio_blk.c b/bindings/virtio/virtio_blk.c index 0fa9a729..5b4adef2 100644 --- a/bindings/virtio/virtio_blk.c +++ b/bindings/virtio/virtio_blk.c @@ -162,9 +162,9 @@ void virtio_config_block(struct pci_config_info *pci) outl(pci->base + VIRTIO_PCI_GUEST_FEATURES, guest_features); virtio_blk_sectors = inq(pci->base + VIRTIO_PCI_CONFIG_OFF); - log(INFO, "Solo5: PCI:%02x:%02x: configured, capacity=%llu sectors, " + log(INFO, "Solo5: PCI:%02x:%02x.%02x: configured, capacity=%llu sectors, " "features=0x%x\n", - pci->bus, pci->dev, (unsigned long long)virtio_blk_sectors, + pci->bus, pci->dev, pci->fun, (unsigned long long)virtio_blk_sectors, host_features); virtq_init_rings(pci->base, &blkq, 0); diff --git a/bindings/virtio/virtio_net.c b/bindings/virtio/virtio_net.c index 7e246f7c..c3947794 100644 --- a/bindings/virtio/virtio_net.c +++ b/bindings/virtio/virtio_net.c @@ -181,8 +181,8 @@ void virtio_config_network(struct pci_config_info *pci) virtio_net_mac[3], virtio_net_mac[4], virtio_net_mac[5]); - log(INFO, "Solo5: PCI:%02x:%02x: configured, mac=%s, features=0x%x\n", - pci->bus, pci->dev, virtio_net_mac_str, host_features); + log(INFO, "Solo5: PCI:%02x:%02x.%02x: configured, mac=%s, features=0x%x\n", + pci->bus, pci->dev, pci->fun, virtio_net_mac_str, host_features); /* * 7. Perform device-specific setup, including discovery of virtqueues for