diff options
104 files changed, 6580 insertions, 248 deletions
diff --git a/MAINTAINERS b/MAINTAINERS index 433cf66e8f..692628cd78 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2817,6 +2817,20 @@ F: hw/misc/ivshmem-flat.c F: include/hw/misc/ivshmem-flat.h F: docs/system/devices/ivshmem-flat.rst +UEFI variable service +M: Gerd Hoffmann <kraxel@redhat.com> +S: Maintained +F: hw/uefi/ +F: include/hw/uefi/ + +VMapple +M: Alexander Graf <agraf@csgraf.de> +M: Phil Dennis-Jordan <phil@philjordan.eu> +S: Maintained +F: hw/vmapple/* +F: include/hw/vmapple/* +F: docs/system/arm/vmapple.rst + Subsystems ---------- Overall Audio backends diff --git a/accel/Kconfig b/accel/Kconfig index 794e0d18d2..4263cab722 100644 --- a/accel/Kconfig +++ b/accel/Kconfig @@ -16,4 +16,5 @@ config KVM config XEN bool select FSDEV_9P if VIRTFS + select PCI_EXPRESS_GENERIC_BRIDGE select XEN_BUS diff --git a/contrib/vmapple/uuid.sh b/contrib/vmapple/uuid.sh new file mode 100755 index 0000000000..f5637221d2 --- /dev/null +++ b/contrib/vmapple/uuid.sh @@ -0,0 +1,12 @@ +#!/bin/sh +# +# Used for converting a guest provisioned using Virtualization.framework +# for use with the QEMU 'vmapple' aarch64 machine type. +# +# Extracts the Machine UUID from Virtualization.framework VM JSON file. +# (as produced by 'macosvm', passed as command line argument) +# +# SPDX-License-Identifier: GPL-2.0-or-later + +plutil -extract machineId raw "$1" | base64 -d | plutil -extract ECID raw - + diff --git a/docs/devel/index-internals.rst b/docs/devel/index-internals.rst index bca597c658..7a0678cbdd 100644 --- a/docs/devel/index-internals.rst +++ b/docs/devel/index-internals.rst @@ -20,6 +20,7 @@ Details about QEMU's various subsystems including how to add features to them. s390-cpu-topology s390-dasd-ipl tracing + uefi-vars vfio-iommufd writing-monitor-commands virtio-backends diff --git a/docs/devel/uefi-vars.rst b/docs/devel/uefi-vars.rst new file mode 100644 index 0000000000..0151a26a0a --- /dev/null +++ b/docs/devel/uefi-vars.rst @@ -0,0 +1,68 @@ +============== +UEFI variables +============== + +Guest UEFI variable management +============================== + +The traditional approach for UEFI Variable storage in qemu guests is +to work as close as possible to physical hardware. That means +providing pflash as storage and leaving the management of variables +and flash to the guest. + +Secure boot support comes with the requirement that the UEFI variable +storage must be protected against direct access by the OS. All update +requests must pass the sanity checks. (Parts of) the firmware must +run with a higher privilege level than the OS so this can be enforced +by the firmware. On x86 this has been implemented using System +Management Mode (SMM) in qemu and kvm, which again is the same +approach taken by physical hardware. Only privileged code running in +SMM mode is allowed to access flash storage. + +Communication with the firmware code running in SMM mode works by +serializing the requests to a shared buffer, then trapping into SMM +mode via SMI. The SMM code processes the request, stores the reply in +the same buffer and returns. + +Host UEFI variable service +========================== + +Instead of running the privileged code inside the guest we can run it +on the host. The serialization protocol can be reused. The +communication with the host uses a virtual device, which essentially +configures the shared buffer location and size, and traps to the host +to process the requests. + +The ``uefi-vars`` device implements the UEFI virtual device. It comes +in ``uefi-vars-x86`` and ``uefi-vars-sysbus`` flavours. The device +reimplements the handlers needed, specifically +``EfiSmmVariableProtocol`` and ``VarCheckPolicyLibMmiHandler``. It +also consumes events (``EfiEndOfDxeEventGroup``, +``EfiEventReadyToBoot`` and ``EfiEventExitBootServices``). + +The advantage of the approach is that we do not need a special +privilege level for the firmware to protect itself, i.e. it does not +depend on SMM emulation on x64, which allows the removal of a bunch of +complex code for SMM emulation from the linux kernel +(CONFIG_KVM_SMM=n). It also allows support for secure boot on arm +without implementing secure world (el3) emulation in kvm. + +Of course there are also downsides. The added device increases the +attack surface of the host, and we are adding some code duplication +because we have to reimplement some edk2 functionality in qemu. + +usage on x86_64 +--------------- + +.. code:: + + qemu-system-x86_64 \ + -device uefi-vars-x86,jsonfile=/path/to/vars.json + +usage on aarch64 +---------------- + +.. code:: + + qemu-system-aarch64 -M virt \ + -device uefi-vars-sysbus,jsonfile=/path/to/vars.json diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py index 5f96b46270..61997fd21a 100644 --- a/docs/sphinx/qapidoc.py +++ b/docs/sphinx/qapidoc.py @@ -254,10 +254,6 @@ class QAPISchemaGenRSTVisitor(QAPISchemaVisitor): section += dlnode return [section] - def _nodes_for_example(self, exampletext): - """Return list of doctree nodes for a code example snippet""" - return [nodes.literal_block(exampletext, exampletext)] - def _nodes_for_sections(self, doc): """Return list of doctree nodes for additional sections""" nodelist = [] @@ -275,10 +271,7 @@ class QAPISchemaGenRSTVisitor(QAPISchemaVisitor): continue snode = self._make_section(section.tag) - if section.tag.startswith('Example'): - snode += self._nodes_for_example(dedent(section.text)) - else: - self._parse_text_into_node(dedent(section.text), snode) + self._parse_text_into_node(dedent(section.text), snode) nodelist.append(snode) return nodelist @@ -421,6 +414,8 @@ class QAPISchemaGenRSTVisitor(QAPISchemaVisitor): node = self._start_new_heading(heading, len(leader)) if text == '': return + else: + node = nodes.container() self._parse_text_into_node(text, node) self._cur_doc = None diff --git a/docs/system/arm/vmapple.rst b/docs/system/arm/vmapple.rst new file mode 100644 index 0000000000..35c329ea5a --- /dev/null +++ b/docs/system/arm/vmapple.rst @@ -0,0 +1,65 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +VMApple machine emulation +======================================================================================== + +VMApple is the device model that the macOS built-in hypervisor called "Virtualization.framework" +exposes to Apple Silicon macOS guests. The "vmapple" machine model in QEMU implements the same +device model, but does not use any code from Virtualization.Framework. + +Prerequisites +------------- + +To run the vmapple machine model, you need to + + * Run on Apple Silicon + * Run on macOS 12.0 or above + * Have an already installed copy of a Virtualization.Framework macOS 12 virtual + machine. Note that newer versions than 12.x are currently NOT supported on + the guest side. I will assume that you installed it using the + `macosvm <https://github.com/s-u/macosvm>`__ CLI. + +First, we need to extract the UUID from the virtual machine that you installed. You can do this +by running the shell script in contrib/vmapple/uuid.sh on the macosvm.json file. + +.. code-block:: bash + :caption: uuid.sh script to extract the UUID from a macosvm.json file + + $ contrib/vmapple/uuid.sh "path/to/macosvm.json" + +Now we also need to trim the aux partition. It contains metadata that we can just discard: + +.. code-block:: bash + :caption: Command to trim the aux file + + $ dd if="aux.img" of="aux.img.trimmed" bs=$(( 0x4000 )) skip=1 + +How to run +---------- + +Then, we can launch QEMU with the Virtualization.Framework pre-boot environment and the readily +installed target disk images. I recommend to port forward the VM's ssh and vnc ports to the host +to get better interactive access into the target system: + +.. code-block:: bash + :caption: Example execution command line + + $ UUID="$(contrib/vmapple/uuid.sh 'macosvm.json')" + $ AVPBOOTER="/System/Library/Frameworks/Virtualization.framework/Resources/AVPBooter.vmapple2.bin" + $ AUX="aux.img.trimmed" + $ DISK="disk.img" + $ qemu-system-aarch64 \ + -serial mon:stdio \ + -m 4G \ + -accel hvf \ + -M vmapple,uuid="$UUID" \ + -bios "$AVPBOOTER" \ + -drive file="$AUX",if=pflash,format=raw \ + -drive file="$DISK",if=pflash,format=raw \ + -drive file="$AUX",if=none,id=aux,format=raw \ + -drive file="$DISK",if=none,id=root,format=raw \ + -device vmapple-virtio-blk-pci,variant=aux,drive=aux \ + -device vmapple-virtio-blk-pci,variant=root,drive=root \ + -netdev user,id=net0,ipv6=off,hostfwd=tcp::2222-:22,hostfwd=tcp::5901-:5900 \ + -device virtio-net-pci,netdev=net0 + diff --git a/docs/system/target-arm.rst b/docs/system/target-arm.rst index a43ec8f10e..b96a05a920 100644 --- a/docs/system/target-arm.rst +++ b/docs/system/target-arm.rst @@ -103,6 +103,7 @@ Board-specific documentation arm/stellaris arm/stm32 arm/virt + arm/vmapple arm/xenpvh arm/xlnx-versal-virt arm/xlnx-zynq diff --git a/hw/Kconfig b/hw/Kconfig index 1b4e9bb07f..9a86a6a28a 100644 --- a/hw/Kconfig +++ b/hw/Kconfig @@ -37,10 +37,12 @@ source smbios/Kconfig source ssi/Kconfig source timer/Kconfig source tpm/Kconfig +source uefi/Kconfig source ufs/Kconfig source usb/Kconfig source virtio/Kconfig source vfio/Kconfig +source vmapple/Kconfig source xen/Kconfig source watchdog/Kconfig diff --git a/hw/acpi/ghes.c b/hw/acpi/ghes.c index b709c177cd..b85bb48195 100644 --- a/hw/acpi/ghes.c +++ b/hw/acpi/ghes.c @@ -390,8 +390,8 @@ static void get_hw_error_offsets(uint64_t ghes_addr, *read_ack_register_addr = ghes_addr + sizeof(uint64_t); } -void ghes_record_cper_errors(const void *cper, size_t len, - uint16_t source_id, Error **errp) +static void ghes_record_cper_errors(const void *cper, size_t len, + uint16_t source_id, Error **errp) { uint64_t cper_addr = 0, read_ack_register_addr = 0, read_ack_register; AcpiGedState *acpi_ged_state; @@ -440,8 +440,6 @@ void ghes_record_cper_errors(const void *cper, size_t len, /* Write the generic error data entry into guest memory */ cpu_physical_memory_write(cper_addr, cper, len); - - return; } int acpi_ghes_memory_errors(uint16_t source_id, uint64_t physical_address) diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index faa00d1db3..15200a2d7e 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -2,6 +2,7 @@ config ARM_VIRT bool default y depends on ARM + depends on TCG || KVM || HVF imply PCI_DEVICES imply TEST_DEVICES imply VFIO_AMD_XGBE diff --git a/hw/arm/omap1.c b/hw/arm/omap1.c index ca2eb0d157..3ee10b4777 100644 --- a/hw/arm/omap1.c +++ b/hw/arm/omap1.c @@ -42,6 +42,7 @@ #include "qemu/cutils.h" #include "qemu/bcd.h" #include "target/arm/cpu-qom.h" +#include "trace.h" static inline void omap_log_badwidth(const char *funcname, hwaddr addr, int sz) { @@ -1731,7 +1732,7 @@ static void omap_clkm_write(void *opaque, hwaddr addr, case 0x18: /* ARM_SYSST */ if ((s->clkm.clocking_scheme ^ (value >> 11)) & 7) { s->clkm.clocking_scheme = (value >> 11) & 7; - printf("%s: clocking scheme set to %s\n", __func__, + trace_omap1_pwl_clocking_scheme( clkschemename[s->clkm.clocking_scheme]); } s->clkm.cold_start &= value & 0x3f; @@ -2335,7 +2336,7 @@ static void omap_pwl_update(struct omap_pwl_s *s) if (output != s->output) { s->output = output; - printf("%s: Backlight now at %i/256\n", __func__, output); + trace_omap1_pwl_backlight(output); } } @@ -2470,8 +2471,8 @@ static void omap_pwt_write(void *opaque, hwaddr addr, break; case 0x04: /* VRC */ if ((value ^ s->vrc) & 1) { - if (value & 1) - printf("%s: %iHz buzz on\n", __func__, (int) + if (value & 1) { + trace_omap1_pwt_buzz( /* 1.5 MHz from a 12-MHz or 13-MHz PWT_CLK */ ((omap_clk_getrate(s->clk) >> 3) / /* Pre-multiplexer divider */ @@ -2487,8 +2488,9 @@ static void omap_pwt_write(void *opaque, hwaddr addr, /* 80/127 divider */ ((value & (1 << 5)) ? 80 : 127) / (107 * 55 * 63 * 127))); - else - printf("%s: silence!\n", __func__); + } else { + trace_omap1_pwt_silence(); + } } s->vrc = value & 0x7f; break; @@ -2559,8 +2561,9 @@ static void omap_rtc_interrupts_update(struct omap_rtc_s *s) static void omap_rtc_alarm_update(struct omap_rtc_s *s) { s->alarm_ti = mktimegm(&s->alarm_tm); - if (s->alarm_ti == -1) - printf("%s: conversion failed\n", __func__); + if (s->alarm_ti == -1) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: conversion failed\n", __func__); + } } static uint64_t omap_rtc_read(void *opaque, hwaddr addr, unsigned size) @@ -2659,25 +2662,16 @@ static void omap_rtc_write(void *opaque, hwaddr addr, switch (offset) { case 0x00: /* SECONDS_REG */ -#ifdef ALMDEBUG - printf("RTC SEC_REG <-- %02x\n", value); -#endif s->ti -= s->current_tm.tm_sec; s->ti += from_bcd(value); return; case 0x04: /* MINUTES_REG */ -#ifdef ALMDEBUG - printf("RTC MIN_REG <-- %02x\n", value); -#endif s->ti -= s->current_tm.tm_min * 60; s->ti += from_bcd(value) * 60; return; case 0x08: /* HOURS_REG */ -#ifdef ALMDEBUG - printf("RTC HRS_REG <-- %02x\n", value); -#endif s->ti -= s->current_tm.tm_hour * 3600; if (s->pm_am) { s->ti += (from_bcd(value & 0x3f) & 12) * 3600; @@ -2687,17 +2681,11 @@ static void omap_rtc_write(void *opaque, hwaddr addr, return; case 0x0c: /* DAYS_REG */ -#ifdef ALMDEBUG - printf("RTC DAY_REG <-- %02x\n", value); -#endif s->ti -= s->current_tm.tm_mday * 86400; s->ti += from_bcd(value) * 86400; return; case 0x10: /* MONTHS_REG */ -#ifdef ALMDEBUG - printf("RTC MTH_REG <-- %02x\n", value); -#endif memcpy(&new_tm, &s->current_tm, sizeof(new_tm)); new_tm.tm_mon = from_bcd(value); ti[0] = mktimegm(&s->current_tm); @@ -2714,9 +2702,6 @@ static void omap_rtc_write(void *opaque, hwaddr addr, return; case 0x14: /* YEARS_REG */ -#ifdef ALMDEBUG - printf("RTC YRS_REG <-- %02x\n", value); -#endif memcpy(&new_tm, &s->current_tm, sizeof(new_tm)); new_tm.tm_year += from_bcd(value) - (new_tm.tm_year % 100); ti[0] = mktimegm(&s->current_tm); @@ -2736,25 +2721,16 @@ static void omap_rtc_write(void *opaque, hwaddr addr, return; /* Ignored */ case 0x20: /* ALARM_SECONDS_REG */ -#ifdef ALMDEBUG - printf("ALM SEC_REG <-- %02x\n", value); -#endif s->alarm_tm.tm_sec = from_bcd(value); omap_rtc_alarm_update(s); return; case 0x24: /* ALARM_MINUTES_REG */ -#ifdef ALMDEBUG - printf("ALM MIN_REG <-- %02x\n", value); -#endif s->alarm_tm.tm_min = from_bcd(value); omap_rtc_alarm_update(s); return; case 0x28: /* ALARM_HOURS_REG */ -#ifdef ALMDEBUG - printf("ALM HRS_REG <-- %02x\n", value); -#endif if (s->pm_am) s->alarm_tm.tm_hour = ((from_bcd(value & 0x3f)) % 12) + @@ -2765,33 +2741,21 @@ static void omap_rtc_write(void *opaque, hwaddr addr, return; case 0x2c: /* ALARM_DAYS_REG */ -#ifdef ALMDEBUG - printf("ALM DAY_REG <-- %02x\n", value); -#endif s->alarm_tm.tm_mday = from_bcd(value); omap_rtc_alarm_update(s); return; case 0x30: /* ALARM_MONTHS_REG */ -#ifdef ALMDEBUG - printf("ALM MON_REG <-- %02x\n", value); -#endif s->alarm_tm.tm_mon = from_bcd(value); omap_rtc_alarm_update(s); return; case 0x34: /* ALARM_YEARS_REG */ -#ifdef ALMDEBUG - printf("ALM YRS_REG <-- %02x\n", value); -#endif s->alarm_tm.tm_year = from_bcd(value); omap_rtc_alarm_update(s); return; case 0x40: /* RTC_CTRL_REG */ -#ifdef ALMDEBUG - printf("RTC CONTROL <-- %02x\n", value); -#endif s->pm_am = (value >> 3) & 1; s->auto_comp = (value >> 2) & 1; s->round = (value >> 1) & 1; @@ -2801,32 +2765,20 @@ static void omap_rtc_write(void *opaque, hwaddr addr, return; case 0x44: /* RTC_STATUS_REG */ -#ifdef ALMDEBUG - printf("RTC STATUSL <-- %02x\n", value); -#endif s->status &= ~((value & 0xc0) ^ 0x80); omap_rtc_interrupts_update(s); return; case 0x48: /* RTC_INTERRUPTS_REG */ -#ifdef ALMDEBUG - printf("RTC INTRS <-- %02x\n", value); -#endif s->interrupts = value; return; case 0x4c: /* RTC_COMP_LSB_REG */ -#ifdef ALMDEBUG - printf("RTC COMPLSB <-- %02x\n", value); -#endif s->comp_reg &= 0xff00; s->comp_reg |= 0x00ff & value; return; case 0x50: /* RTC_COMP_MSB_REG */ -#ifdef ALMDEBUG - printf("RTC COMPMSB <-- %02x\n", value); -#endif s->comp_reg &= 0x00ff; s->comp_reg |= 0xff00 & (value << 8); return; @@ -3024,8 +2976,9 @@ static void omap_mcbsp_source_tick(void *opaque) if (!s->rx_rate) return; - if (s->rx_req) - printf("%s: Rx FIFO overrun\n", __func__); + if (s->rx_req) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Rx FIFO overrun\n", __func__); + } s->rx_req = s->rx_rate << bps[(s->rcr[0] >> 5) & 7]; @@ -3070,8 +3023,9 @@ static void omap_mcbsp_sink_tick(void *opaque) if (!s->tx_rate) return; - if (s->tx_req) - printf("%s: Tx FIFO underrun\n", __func__); + if (s->tx_req) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Tx FIFO underrun\n", __func__); + } s->tx_req = s->tx_rate << bps[(s->xcr[0] >> 5) & 7]; @@ -3173,7 +3127,7 @@ static uint64_t omap_mcbsp_read(void *opaque, hwaddr addr, /* Fall through. */ case 0x02: /* DRR1 */ if (s->rx_req < 2) { - printf("%s: Rx FIFO underrun\n", __func__); + qemu_log_mask(LOG_GUEST_ERROR, "%s: Rx FIFO underrun\n", __func__); omap_mcbsp_rx_done(s); } else { s->tx_req -= 2; @@ -3278,8 +3232,9 @@ static void omap_mcbsp_writeh(void *opaque, hwaddr addr, } if (s->tx_req < 2) omap_mcbsp_tx_done(s); - } else - printf("%s: Tx FIFO overrun\n", __func__); + } else { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Tx FIFO overrun\n", __func__); + } return; case 0x08: /* SPCR2 */ @@ -3293,8 +3248,11 @@ static void omap_mcbsp_writeh(void *opaque, hwaddr addr, case 0x0a: /* SPCR1 */ s->spcr[0] &= 0x0006; s->spcr[0] |= 0xf8f9 & value; - if (value & (1 << 15)) /* DLB */ - printf("%s: Digital Loopback mode enable attempt\n", __func__); + if (value & (1 << 15)) { /* DLB */ + qemu_log_mask(LOG_UNIMP, + "%s: Digital Loopback mode enable attempt\n", + __func__); + } if (~value & 1) { /* RRST */ s->spcr[0] &= ~6; s->rx_req = 0; @@ -3325,13 +3283,19 @@ static void omap_mcbsp_writeh(void *opaque, hwaddr addr, return; case 0x18: /* MCR2 */ s->mcr[1] = value & 0x03e3; - if (value & 3) /* XMCM */ - printf("%s: Tx channel selection mode enable attempt\n", __func__); + if (value & 3) { /* XMCM */ + qemu_log_mask(LOG_UNIMP, + "%s: Tx channel selection mode enable attempt\n", + __func__); + } return; case 0x1a: /* MCR1 */ s->mcr[0] = value & 0x03e1; - if (value & 1) /* RMCM */ - printf("%s: Rx channel selection mode enable attempt\n", __func__); + if (value & 1) { /* RMCM */ + qemu_log_mask(LOG_UNIMP, + "%s: Rx channel selection mode enable attempt\n", + __func__); + } return; case 0x1c: /* RCERA */ s->rcer[0] = value & 0xffff; @@ -3412,8 +3376,9 @@ static void omap_mcbsp_writew(void *opaque, hwaddr addr, } if (s->tx_req < 4) omap_mcbsp_tx_done(s); - } else - printf("%s: Tx FIFO overrun\n", __func__); + } else { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Tx FIFO overrun\n", __func__); + } return; } @@ -3531,7 +3496,7 @@ static void omap_lpg_tick(void *opaque) timer_mod(s->tm, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + s->on); s->cycle = !s->cycle; - printf("%s: LED is %s\n", __func__, s->cycle ? "on" : "off"); + trace_omap1_lpg_led(s->cycle ? "on" : "off"); } static void omap_lpg_update(struct omap_lpg_s *s) @@ -3551,11 +3516,11 @@ static void omap_lpg_update(struct omap_lpg_s *s) } timer_del(s->tm); - if (on == period && s->on < s->period) - printf("%s: LED is on\n", __func__); - else if (on == 0 && s->on) - printf("%s: LED is off\n", __func__); - else if (on && (on != s->on || period != s->period)) { + if (on == period && s->on < s->period) { + trace_omap1_lpg_led("on"); + } else if (on == 0 && s->on) { + trace_omap1_lpg_led("off"); + } else if (on && (on != s->on || period != s->period)) { s->cycle = 0; s->on = on; s->period = period; diff --git a/hw/arm/omap_sx1.c b/hw/arm/omap_sx1.c index c6b0bed079..24b4043183 100644 --- a/hw/arm/omap_sx1.c +++ b/hw/arm/omap_sx1.c @@ -76,10 +76,6 @@ static uint64_t static_read(void *opaque, hwaddr offset, static void static_write(void *opaque, hwaddr offset, uint64_t value, unsigned size) { -#ifdef SPY - printf("%s: value %" PRIx64 " %u bytes written at 0x%x\n", - __func__, value, size, (int)offset); -#endif } static const MemoryRegionOps static_ops = { diff --git a/hw/arm/trace-events b/hw/arm/trace-events index 7790db780e..f49cae1656 100644 --- a/hw/arm/trace-events +++ b/hw/arm/trace-events @@ -1,5 +1,12 @@ # See docs/devel/tracing.rst for syntax documentation. +# omap1.c +omap1_pwl_clocking_scheme(const char *scheme) "omap1 CLKM: clocking scheme set to %s" +omap1_pwl_backlight(int output) "omap1 PWL: backlight now at %d/256" +omap1_pwt_buzz(int freq) "omap1 PWT: %dHz buzz on" +omap1_pwt_silence(void) "omap1 PWT: buzzer silenced" +omap1_lpg_led(const char *onoff) "omap1 LPG: LED is %s" + # virt-acpi-build.c virt_acpi_setup(void) "No fw cfg or ACPI disabled. Bailing out." diff --git a/hw/arm/versatilepb.c b/hw/arm/versatilepb.c index 941616cd25..35766445fa 100644 --- a/hw/arm/versatilepb.c +++ b/hw/arm/versatilepb.c @@ -27,6 +27,7 @@ #include "qom/object.h" #include "audio/audio.h" #include "target/arm/cpu-qom.h" +#include "qemu/log.h" #define VERSATILE_FLASH_ADDR 0x34000000 #define VERSATILE_FLASH_SIZE (64 * 1024 * 1024) @@ -110,7 +111,8 @@ static uint64_t vpb_sic_read(void *opaque, hwaddr offset, case 8: /* PICENABLE */ return s->pic_enable; default: - printf ("vpb_sic_read: Bad register offset 0x%x\n", (int)offset); + qemu_log_mask(LOG_GUEST_ERROR, + "vpb_sic_read: Bad register offset 0x%x\n", (int)offset); return 0; } } @@ -144,7 +146,8 @@ static void vpb_sic_write(void *opaque, hwaddr offset, vpb_sic_update_pic(s); break; default: - printf ("vpb_sic_write: Bad register offset 0x%x\n", (int)offset); + qemu_log_mask(LOG_GUEST_ERROR, + "vpb_sic_write: Bad register offset 0x%x\n", (int)offset); return; } vpb_sic_update(s); diff --git a/hw/arm/virt.c b/hw/arm/virt.c index ee69081ef4..904c698b14 100644 --- a/hw/arm/virt.c +++ b/hw/arm/virt.c @@ -82,6 +82,7 @@ #include "hw/mem/pc-dimm.h" #include "hw/mem/nvdimm.h" #include "hw/acpi/generic_event_device.h" +#include "hw/uefi/var-service-api.h" #include "hw/virtio/virtio-md-pci.h" #include "hw/virtio/virtio-iommu.h" #include "hw/char/pl011.h" @@ -3162,6 +3163,7 @@ static void virt_machine_class_init(ObjectClass *oc, void *data) machine_class_allow_dynamic_sysbus_dev(mc, TYPE_VFIO_AMD_XGBE); machine_class_allow_dynamic_sysbus_dev(mc, TYPE_RAMFB_DEVICE); machine_class_allow_dynamic_sysbus_dev(mc, TYPE_VFIO_PLATFORM); + machine_class_allow_dynamic_sysbus_dev(mc, TYPE_UEFI_VARS_SYSBUS); #ifdef CONFIG_TPM machine_class_allow_dynamic_sysbus_dev(mc, TYPE_TPM_TIS_SYSBUS); #endif diff --git a/hw/arm/xen-stubs.c b/hw/arm/xen-stubs.c index 34beb8b08c..5551584dc2 100644 --- a/hw/arm/xen-stubs.c +++ b/hw/arm/xen-stubs.c @@ -5,10 +5,9 @@ */ #include "qemu/osdep.h" -#include "qemu/error-report.h" #include "qapi/qapi-commands-migration.h" -#include "hw/boards.h" -#include "system/system.h" +#include "system/xen.h" +#include "hw/hw.h" #include "hw/xen/xen-hvm-common.h" #include "hw/xen/arch_hvm.h" diff --git a/hw/block/virtio-blk.c b/hw/block/virtio-blk.c index a1829e3abd..5135b4d8f1 100644 --- a/hw/block/virtio-blk.c +++ b/hw/block/virtio-blk.c @@ -50,7 +50,7 @@ static void virtio_blk_init_request(VirtIOBlock *s, VirtQueue *vq, req->mr_next = NULL; } -static void virtio_blk_req_complete(VirtIOBlockReq *req, unsigned char status) +void virtio_blk_req_complete(VirtIOBlockReq *req, unsigned char status) { VirtIOBlock *s = req->dev; VirtIODevice *vdev = VIRTIO_DEVICE(s); @@ -961,8 +961,18 @@ static int virtio_blk_handle_request(VirtIOBlockReq *req, MultiReqBuffer *mrb) break; } default: - virtio_blk_req_complete(req, VIRTIO_BLK_S_UNSUPP); - g_free(req); + { + /* + * Give subclasses a chance to handle unknown requests. This way the + * class lookup is not in the hot path. + */ + VirtIOBlkClass *vbk = VIRTIO_BLK_GET_CLASS(s); + if (!vbk->handle_unknown_request || + !vbk->handle_unknown_request(req, mrb, type)) { + virtio_blk_req_complete(req, VIRTIO_BLK_S_UNSUPP); + g_free(req); + } + } } return 0; } @@ -2029,6 +2039,7 @@ static const TypeInfo virtio_blk_info = { .instance_size = sizeof(VirtIOBlock), .instance_init = virtio_blk_instance_init, .class_init = virtio_blk_class_init, + .class_size = sizeof(VirtIOBlkClass), }; static void virtio_register_types(void) diff --git a/hw/char/bcm2835_aux.c b/hw/char/bcm2835_aux.c index 73ad593406..c6e7eccf7d 100644 --- a/hw/char/bcm2835_aux.c +++ b/hw/char/bcm2835_aux.c @@ -221,7 +221,7 @@ static int bcm2835_aux_can_receive(void *opaque) { BCM2835AuxState *s = opaque; - return s->read_count < BCM2835_AUX_RX_FIFO_LEN; + return BCM2835_AUX_RX_FIFO_LEN - s->read_count; } static void bcm2835_aux_put_fifo(void *opaque, uint8_t value) @@ -243,7 +243,9 @@ static void bcm2835_aux_put_fifo(void *opaque, uint8_t value) static void bcm2835_aux_receive(void *opaque, const uint8_t *buf, int size) { - bcm2835_aux_put_fifo(opaque, *buf); + for (int i = 0; i < size; i++) { + bcm2835_aux_put_fifo(opaque, buf[i]); + } } static const MemoryRegionOps bcm2835_aux_ops = { diff --git a/hw/char/imx_serial.c b/hw/char/imx_serial.c index 38b4865157..6f14f8403a 100644 --- a/hw/char/imx_serial.c +++ b/hw/char/imx_serial.c @@ -386,7 +386,8 @@ static void imx_serial_write(void *opaque, hwaddr offset, static int imx_can_receive(void *opaque) { IMXSerialState *s = (IMXSerialState *)opaque; - return s->ucr2 & UCR2_RXEN && fifo32_num_used(&s->rx_fifo) < FIFO_SIZE; + + return s->ucr2 & UCR2_RXEN ? fifo32_num_free(&s->rx_fifo) : 0; } static void imx_put_data(void *opaque, uint32_t value) @@ -417,7 +418,10 @@ static void imx_receive(void *opaque, const uint8_t *buf, int size) IMXSerialState *s = (IMXSerialState *)opaque; s->usr2 |= USR2_WAKE; - imx_put_data(opaque, *buf); + + for (int i = 0; i < size; i++) { + imx_put_data(opaque, buf[i]); + } } static void imx_event(void *opaque, QEMUChrEvent event) diff --git a/hw/char/mcf_uart.c b/hw/char/mcf_uart.c index 980a12fcb7..529c26be93 100644 --- a/hw/char/mcf_uart.c +++ b/hw/char/mcf_uart.c @@ -17,6 +17,8 @@ #include "chardev/char-fe.h" #include "qom/object.h" +#define FIFO_DEPTH 4 + struct mcf_uart_state { SysBusDevice parent_obj; @@ -27,7 +29,7 @@ struct mcf_uart_state { uint8_t imr; uint8_t bg1; uint8_t bg2; - uint8_t fifo[4]; + uint8_t fifo[FIFO_DEPTH]; uint8_t tb; int current_mr; int fifo_len; @@ -247,14 +249,16 @@ static void mcf_uart_reset(DeviceState *dev) static void mcf_uart_push_byte(mcf_uart_state *s, uint8_t data) { /* Break events overwrite the last byte if the fifo is full. */ - if (s->fifo_len == 4) + if (s->fifo_len == FIFO_DEPTH) { s->fifo_len--; + } s->fifo[s->fifo_len] = data; s->fifo_len++; s->sr |= MCF_UART_RxRDY; - if (s->fifo_len == 4) + if (s->fifo_len == FIFO_DEPTH) { s->sr |= MCF_UART_FFULL; + } mcf_uart_update(s); } @@ -277,14 +281,16 @@ static int mcf_uart_can_receive(void *opaque) { mcf_uart_state *s = (mcf_uart_state *)opaque; - return s->rx_enabled && (s->sr & MCF_UART_FFULL) == 0; + return s->rx_enabled ? FIFO_DEPTH - s->fifo_len : 0; } static void mcf_uart_receive(void *opaque, const uint8_t *buf, int size) { mcf_uart_state *s = (mcf_uart_state *)opaque; - mcf_uart_push_byte(s, buf[0]); + for (int i = 0; i < size; i++) { + mcf_uart_push_byte(s, buf[i]); + } } static const MemoryRegionOps mcf_uart_ops = { diff --git a/hw/char/pl011.c b/hw/char/pl011.c index 06ce851044..23a9db8c57 100644 --- a/hw/char/pl011.c +++ b/hw/char/pl011.c @@ -85,6 +85,7 @@ DeviceState *pl011_create(hwaddr addr, qemu_irq irq, Chardev *chr) #define CR_OUT1 (1 << 12) #define CR_RTS (1 << 11) #define CR_DTR (1 << 10) +#define CR_RXE (1 << 9) #define CR_TXE (1 << 8) #define CR_LBE (1 << 7) #define CR_UARTEN (1 << 0) @@ -184,7 +185,7 @@ static void pl011_fifo_rx_put(void *opaque, uint32_t value) s->read_fifo[slot] = value; s->read_count++; s->flags &= ~PL011_FLAG_RXFE; - trace_pl011_fifo_rx_put(value, s->read_count); + trace_pl011_fifo_rx_put(value, s->read_count, pipe_depth); if (s->read_count == pipe_depth) { trace_pl011_fifo_rx_full(); s->flags |= PL011_FLAG_RXFF; @@ -247,12 +248,13 @@ static void pl011_write_txdata(PL011State *s, uint8_t data) static uint32_t pl011_read_rxdata(PL011State *s) { uint32_t c; + unsigned fifo_depth = pl011_get_fifo_depth(s); s->flags &= ~PL011_FLAG_RXFF; c = s->read_fifo[s->read_pos]; if (s->read_count > 0) { s->read_count--; - s->read_pos = (s->read_pos + 1) & (pl011_get_fifo_depth(s) - 1); + s->read_pos = (s->read_pos + 1) & (fifo_depth - 1); } if (s->read_count == 0) { s->flags |= PL011_FLAG_RXFE; @@ -260,7 +262,7 @@ static uint32_t pl011_read_rxdata(PL011State *s) if (s->read_count == s->read_trigger - 1) { s->int_level &= ~INT_RX; } - trace_pl011_read_fifo(s->read_count); + trace_pl011_read_fifo(s->read_count, fifo_depth); s->rsr = c >> 8; pl011_update(s); qemu_chr_fe_accept_input(&s->chr); @@ -485,15 +487,25 @@ static void pl011_write(void *opaque, hwaddr offset, static int pl011_can_receive(void *opaque) { PL011State *s = (PL011State *)opaque; - int r; + unsigned fifo_depth = pl011_get_fifo_depth(s); + unsigned fifo_available = fifo_depth - s->read_count; - r = s->read_count < pl011_get_fifo_depth(s); - trace_pl011_can_receive(s->lcr, s->read_count, r); - return r; + if (!(s->cr & CR_UARTEN)) { + qemu_log_mask(LOG_GUEST_ERROR, + "PL011 receiving data on disabled UART\n"); + } + if (!(s->cr & CR_RXE)) { + qemu_log_mask(LOG_GUEST_ERROR, + "PL011 receiving data on disabled RX UART\n"); + } + trace_pl011_can_receive(s->lcr, s->read_count, fifo_depth, fifo_available); + + return fifo_available; } static void pl011_receive(void *opaque, const uint8_t *buf, int size) { + trace_pl011_receive(size); /* * In loopback mode, the RX input signal is internally disconnected * from the entire receiving logics; thus, all inputs are ignored, @@ -503,7 +515,9 @@ static void pl011_receive(void *opaque, const uint8_t *buf, int size) return; } - pl011_fifo_rx_put(opaque, *buf); + for (int i = 0; i < size; i++) { + pl011_fifo_rx_put(opaque, buf[i]); + } } static void pl011_event(void *opaque, QEMUChrEvent event) diff --git a/hw/char/sh_serial.c b/hw/char/sh_serial.c index 247aeb071a..41c8175a63 100644 --- a/hw/char/sh_serial.c +++ b/hw/char/sh_serial.c @@ -320,7 +320,7 @@ static uint64_t sh_serial_read(void *opaque, hwaddr offs, static int sh_serial_can_receive(SHSerialState *s) { - return s->scr & (1 << 4); + return s->scr & (1 << 4) ? SH_RX_FIFO_LENGTH - s->rx_head : 0; } static void sh_serial_receive_break(SHSerialState *s) @@ -353,22 +353,20 @@ static void sh_serial_receive1(void *opaque, const uint8_t *buf, int size) if (s->feat & SH_SERIAL_FEAT_SCIF) { int i; for (i = 0; i < size; i++) { - if (s->rx_cnt < SH_RX_FIFO_LENGTH) { - s->rx_fifo[s->rx_head++] = buf[i]; - if (s->rx_head == SH_RX_FIFO_LENGTH) { - s->rx_head = 0; - } - s->rx_cnt++; - if (s->rx_cnt >= s->rtrg) { - s->flags |= SH_SERIAL_FLAG_RDF; - if (s->scr & (1 << 6) && s->rxi) { - timer_del(&s->fifo_timeout_timer); - qemu_set_irq(s->rxi, 1); - } - } else { - timer_mod(&s->fifo_timeout_timer, - qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 15 * s->etu); + s->rx_fifo[s->rx_head++] = buf[i]; + if (s->rx_head == SH_RX_FIFO_LENGTH) { + s->rx_head = 0; + } + s->rx_cnt++; + if (s->rx_cnt >= s->rtrg) { + s->flags |= SH_SERIAL_FLAG_RDF; + if (s->scr & (1 << 6) && s->rxi) { + timer_del(&s->fifo_timeout_timer); + qemu_set_irq(s->rxi, 1); } + } else { + timer_mod(&s->fifo_timeout_timer, + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 15 * s->etu); } } } else { diff --git a/hw/char/sifive_uart.c b/hw/char/sifive_uart.c index 4bc5767284..b45e6c098c 100644 --- a/hw/char/sifive_uart.c +++ b/hw/char/sifive_uart.c @@ -251,6 +251,23 @@ static int sifive_uart_be_change(void *opaque) return 0; } +static void sifive_uart_reset_enter(Object *obj, ResetType type) +{ + SiFiveUARTState *s = SIFIVE_UART(obj); + + s->txfifo = 0; + s->ie = 0; + s->ip = 0; + s->txctrl = 0; + s->rxctrl = 0; + s->div = 0; + + s->rx_fifo_len = 0; + + memset(s->rx_fifo, 0, SIFIVE_UART_RX_FIFO_SIZE); + fifo8_reset(&s->tx_fifo); +} + static const Property sifive_uart_properties[] = { DEFINE_PROP_CHR("chardev", SiFiveUARTState, chr), }; @@ -270,30 +287,24 @@ static void sifive_uart_realize(DeviceState *dev, Error **errp) { SiFiveUARTState *s = SIFIVE_UART(dev); + fifo8_create(&s->tx_fifo, SIFIVE_UART_TX_FIFO_SIZE); + s->fifo_trigger_handle = timer_new_ns(QEMU_CLOCK_VIRTUAL, fifo_trigger_update, s); - qemu_chr_fe_set_handlers(&s->chr, sifive_uart_can_rx, sifive_uart_rx, - sifive_uart_event, sifive_uart_be_change, s, - NULL, true); + if (qemu_chr_fe_backend_connected(&s->chr)) { + qemu_chr_fe_set_handlers(&s->chr, sifive_uart_can_rx, sifive_uart_rx, + sifive_uart_event, sifive_uart_be_change, s, + NULL, true); + } } -static void sifive_uart_reset_enter(Object *obj, ResetType type) +static void sifive_uart_unrealize(DeviceState *dev) { - SiFiveUARTState *s = SIFIVE_UART(obj); - - s->txfifo = 0; - s->ie = 0; - s->ip = 0; - s->txctrl = 0; - s->rxctrl = 0; - s->div = 0; - - s->rx_fifo_len = 0; + SiFiveUARTState *s = SIFIVE_UART(dev); - memset(s->rx_fifo, 0, SIFIVE_UART_RX_FIFO_SIZE); - fifo8_create(&s->tx_fifo, SIFIVE_UART_TX_FIFO_SIZE); + fifo8_destroy(&s->tx_fifo); } static void sifive_uart_reset_hold(Object *obj, ResetType type) @@ -329,6 +340,7 @@ static void sifive_uart_class_init(ObjectClass *oc, void *data) ResettableClass *rc = RESETTABLE_CLASS(oc); dc->realize = sifive_uart_realize; + dc->unrealize = sifive_uart_unrealize; dc->vmsd = &vmstate_sifive_uart; rc->phases.enter = sifive_uart_reset_enter; rc->phases.hold = sifive_uart_reset_hold; diff --git a/hw/char/trace-events b/hw/char/trace-events index b2e3d25ae3..05a33036c1 100644 --- a/hw/char/trace-events +++ b/hw/char/trace-events @@ -60,12 +60,13 @@ imx_serial_put_data(const char *chrname, uint32_t value) "%s: 0x%" PRIx32 # pl011.c pl011_irq_state(int level) "irq state %d" pl011_read(uint32_t addr, uint32_t value, const char *regname) "addr 0x%03x value 0x%08x reg %s" -pl011_read_fifo(int read_count) "FIFO read, read_count now %d" +pl011_read_fifo(unsigned rx_fifo_used, size_t rx_fifo_depth) "RX FIFO read, used %u/%zu" pl011_write(uint32_t addr, uint32_t value, const char *regname) "addr 0x%03x value 0x%08x reg %s" -pl011_can_receive(uint32_t lcr, int read_count, int r) "LCR 0x%08x read_count %d returning %d" -pl011_fifo_rx_put(uint32_t c, int read_count) "new char 0x%02x read_count now %d" +pl011_can_receive(uint32_t lcr, unsigned rx_fifo_used, size_t rx_fifo_depth, unsigned rx_fifo_available) "LCR 0x%02x, RX FIFO used %u/%zu, can_receive %u chars" +pl011_fifo_rx_put(uint32_t c, unsigned read_count, size_t rx_fifo_depth) "RX FIFO push char [0x%02x] %d/%zu depth used" pl011_fifo_rx_full(void) "RX FIFO now full, RXFF set" pl011_baudrate_change(unsigned int baudrate, uint64_t clock, uint32_t ibrd, uint32_t fbrd) "new baudrate %u (clk: %" PRIu64 "hz, ibrd: %" PRIu32 ", fbrd: %" PRIu32 ")" +pl011_receive(int size) "recv %d chars" # cmsdk-apb-uart.c cmsdk_apb_uart_read(uint64_t offset, uint64_t data, unsigned size) "CMSDK APB UART read: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u" diff --git a/hw/core/qdev-properties-system.c b/hw/core/qdev-properties-system.c index 89f954f569..a91551a5ee 100644 --- a/hw/core/qdev-properties-system.c +++ b/hw/core/qdev-properties-system.c @@ -1294,3 +1294,12 @@ const PropertyInfo qdev_prop_endian_mode = { .set = qdev_propinfo_set_enum, .set_default_value = qdev_propinfo_set_default_value_enum, }; + +const PropertyInfo qdev_prop_vmapple_virtio_blk_variant = { + .name = "VMAppleVirtioBlkVariant", + .description = "unspecified/root/aux", + .enum_table = &VMAppleVirtioBlkVariant_lookup, + .get = qdev_propinfo_get_enum, + .set = qdev_propinfo_set_enum, + .set_default_value = qdev_propinfo_set_default_value_enum, +}; diff --git a/hw/core/sysbus-fdt.c b/hw/core/sysbus-fdt.c index 774c0aed41..e85066b905 100644 --- a/hw/core/sysbus-fdt.c +++ b/hw/core/sysbus-fdt.c @@ -36,6 +36,7 @@ #include "hw/vfio/vfio-calxeda-xgmac.h" #include "hw/vfio/vfio-amd-xgbe.h" #include "hw/display/ramfb.h" +#include "hw/uefi/var-service-api.h" #include "hw/arm/fdt.h" /* @@ -471,6 +472,28 @@ static int add_tpm_tis_fdt_node(SysBusDevice *sbdev, void *opaque) } #endif +static int add_uefi_vars_node(SysBusDevice *sbdev, void *opaque) +{ + PlatformBusFDTData *data = opaque; + PlatformBusDevice *pbus = data->pbus; + const char *parent_node = data->pbus_node_name; + void *fdt = data->fdt; + uint64_t mmio_base; + char *nodename; + + mmio_base = platform_bus_get_mmio_addr(pbus, sbdev, 0); + nodename = g_strdup_printf("%s/%s@%" PRIx64, parent_node, + UEFI_VARS_FDT_NODE, mmio_base); + qemu_fdt_add_subnode(fdt, nodename); + qemu_fdt_setprop_string(fdt, nodename, + "compatible", UEFI_VARS_FDT_COMPAT); + qemu_fdt_setprop_sized_cells(fdt, nodename, "reg", + 1, mmio_base, + 1, UEFI_VARS_REGS_SIZE); + g_free(nodename); + return 0; +} + static int no_fdt_node(SysBusDevice *sbdev, void *opaque) { return 0; @@ -495,6 +518,7 @@ static const BindingEntry bindings[] = { TYPE_BINDING(TYPE_TPM_TIS_SYSBUS, add_tpm_tis_fdt_node), #endif TYPE_BINDING(TYPE_RAMFB_DEVICE, no_fdt_node), + TYPE_BINDING(TYPE_UEFI_VARS_SYSBUS, add_uefi_vars_node), TYPE_BINDING("", NULL), /* last element */ }; diff --git a/hw/i386/pc_piix.c b/hw/i386/pc_piix.c index 04d2957adc..6c91e2d292 100644 --- a/hw/i386/pc_piix.c +++ b/hw/i386/pc_piix.c @@ -65,6 +65,7 @@ #include "system/numa.h" #include "hw/hyperv/vmbus-bridge.h" #include "hw/mem/nvdimm.h" +#include "hw/uefi/var-service-api.h" #include "hw/i386/acpi-build.h" #include "target/i386/cpu.h" @@ -468,6 +469,7 @@ static void pc_i440fx_machine_options(MachineClass *m) m->no_parallel = !module_object_class_by_name(TYPE_ISA_PARALLEL); machine_class_allow_dynamic_sysbus_dev(m, TYPE_RAMFB_DEVICE); machine_class_allow_dynamic_sysbus_dev(m, TYPE_VMBUS_BRIDGE); + machine_class_allow_dynamic_sysbus_dev(m, TYPE_UEFI_VARS_X64); object_class_property_add_enum(oc, "x-south-bridge", "PCSouthBridgeOption", &PCSouthBridgeOption_lookup, diff --git a/hw/i386/pc_q35.c b/hw/i386/pc_q35.c index 77536dd697..fd96d0345c 100644 --- a/hw/i386/pc_q35.c +++ b/hw/i386/pc_q35.c @@ -58,6 +58,7 @@ #include "system/numa.h" #include "hw/hyperv/vmbus-bridge.h" #include "hw/mem/nvdimm.h" +#include "hw/uefi/var-service-api.h" #include "hw/i386/acpi-build.h" #include "target/i386/cpu.h" @@ -355,6 +356,7 @@ static void pc_q35_machine_options(MachineClass *m) machine_class_allow_dynamic_sysbus_dev(m, TYPE_INTEL_IOMMU_DEVICE); machine_class_allow_dynamic_sysbus_dev(m, TYPE_RAMFB_DEVICE); machine_class_allow_dynamic_sysbus_dev(m, TYPE_VMBUS_BRIDGE); + machine_class_allow_dynamic_sysbus_dev(m, TYPE_UEFI_VARS_X64); compat_props_add(m->compat_props, pc_q35_compat_defaults, pc_q35_compat_defaults_len); } diff --git a/hw/i386/xen/xen-hvm.c b/hw/i386/xen/xen-hvm.c index d3df488c48..d4516acec6 100644 --- a/hw/i386/xen/xen-hvm.c +++ b/hw/i386/xen/xen-hvm.c @@ -10,10 +10,12 @@ #include "qemu/osdep.h" #include "qemu/units.h" +#include "qemu/error-report.h" #include "qapi/error.h" #include "qapi/qapi-commands-migration.h" #include "trace.h" +#include "hw/hw.h" #include "hw/i386/pc.h" #include "hw/irq.h" #include "hw/i386/apic-msidef.h" @@ -24,6 +26,10 @@ #include "hw/xen/arch_hvm.h" #include <xen/hvm/e820.h> #include "exec/target_page.h" +#include "target/i386/cpu.h" +#include "system/runstate.h" +#include "system/xen-mapcache.h" +#include "system/xen.h" static MemoryRegion ram_640k, ram_lo, ram_hi; static MemoryRegion *framebuffer; diff --git a/hw/i386/xen/xen-pvh.c b/hw/i386/xen/xen-pvh.c index 33c1027976..f6356f2a7e 100644 --- a/hw/i386/xen/xen-pvh.c +++ b/hw/i386/xen/xen-pvh.c @@ -14,6 +14,7 @@ #include "hw/xen/arch_hvm.h" #include <xen/hvm/hvm_info_table.h> #include "hw/xen/xen-pvh-common.h" +#include "target/i386/cpu.h" #define TYPE_XEN_PVH_X86 MACHINE_TYPE_NAME("xenpvh") OBJECT_DECLARE_SIMPLE_TYPE(XenPVHx86State, XEN_PVH_X86) diff --git a/hw/intc/Kconfig b/hw/intc/Kconfig index dd405bdb5d..7547528f2c 100644 --- a/hw/intc/Kconfig +++ b/hw/intc/Kconfig @@ -23,13 +23,13 @@ config APIC config ARM_GIC bool - select ARM_GICV3_TCG if TCG + select ARM_GICV3 if TCG select ARM_GIC_KVM if KVM select MSI_NONBROKEN -config ARM_GICV3_TCG +config ARM_GICV3 bool - depends on ARM_GIC && TCG + depends on ARM_GIC config ARM_GIC_KVM bool diff --git a/hw/intc/meson.build b/hw/intc/meson.build index 510fdfb688..602da304b0 100644 --- a/hw/intc/meson.build +++ b/hw/intc/meson.build @@ -6,7 +6,7 @@ system_ss.add(when: 'CONFIG_ARM_GIC', if_true: files( 'arm_gicv3_common.c', 'arm_gicv3_its_common.c', )) -system_ss.add(when: 'CONFIG_ARM_GICV3_TCG', if_true: files( +system_ss.add(when: 'CONFIG_ARM_GICV3', if_true: files( 'arm_gicv3.c', 'arm_gicv3_dist.c', 'arm_gicv3_its.c', @@ -39,7 +39,7 @@ endif specific_ss.add(when: 'CONFIG_APIC', if_true: files('apic.c', 'apic_common.c')) specific_ss.add(when: 'CONFIG_ARM_GIC', if_true: files('arm_gicv3_cpuif_common.c')) -specific_ss.add(when: 'CONFIG_ARM_GICV3_TCG', if_true: files('arm_gicv3_cpuif.c')) +specific_ss.add(when: 'CONFIG_ARM_GICV3', if_true: files('arm_gicv3_cpuif.c')) specific_ss.add(when: 'CONFIG_ARM_GIC_KVM', if_true: files('arm_gic_kvm.c')) specific_ss.add(when: ['CONFIG_ARM_GIC_KVM', 'TARGET_AARCH64'], if_true: files('arm_gicv3_kvm.c', 'arm_gicv3_its_kvm.c')) specific_ss.add(when: 'CONFIG_ARM_V7M', if_true: files('armv7m_nvic.c')) diff --git a/hw/meson.build b/hw/meson.build index b827c82c5d..b91f761fe0 100644 --- a/hw/meson.build +++ b/hw/meson.build @@ -35,10 +35,12 @@ subdir('smbios') subdir('ssi') subdir('timer') subdir('tpm') +subdir('uefi') subdir('ufs') subdir('usb') subdir('vfio') subdir('virtio') +subdir('vmapple') subdir('watchdog') subdir('xen') subdir('xenpv') diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig index 82bd68b4bb..ec0fa5aa9f 100644 --- a/hw/misc/Kconfig +++ b/hw/misc/Kconfig @@ -148,6 +148,10 @@ config PVPANIC_ISA depends on ISA_BUS select PVPANIC_COMMON +config PVPANIC_MMIO + bool + select PVPANIC_COMMON + config AUX bool select I2C diff --git a/hw/misc/macio/gpio.c b/hw/misc/macio/gpio.c index 7cad62819a..e87bfca1f5 100644 --- a/hw/misc/macio/gpio.c +++ b/hw/misc/macio/gpio.c @@ -34,6 +34,11 @@ #include "qemu/module.h" #include "trace.h" +enum MacioGPIORegisterBits { + OUT_DATA = 1, + IN_DATA = 2, + OUT_ENABLE = 4, +}; void macio_set_gpio(MacIOGPIOState *s, uint32_t gpio, bool state) { @@ -41,14 +46,14 @@ void macio_set_gpio(MacIOGPIOState *s, uint32_t gpio, bool state) trace_macio_set_gpio(gpio, state); - if (s->gpio_regs[gpio] & 4) { + if (s->gpio_regs[gpio] & OUT_ENABLE) { qemu_log_mask(LOG_GUEST_ERROR, "GPIO: Setting GPIO %d while it's an output\n", gpio); } - new_reg = s->gpio_regs[gpio] & ~2; + new_reg = s->gpio_regs[gpio] & ~IN_DATA; if (state) { - new_reg |= 2; + new_reg |= IN_DATA; } if (new_reg == s->gpio_regs[gpio]) { @@ -107,12 +112,12 @@ static void macio_gpio_write(void *opaque, hwaddr addr, uint64_t value, addr -= 8; if (addr < 36) { - value &= ~2; + value &= ~IN_DATA; - if (value & 4) { - ibit = (value & 1) << 1; + if (value & OUT_ENABLE) { + ibit = (value & OUT_DATA) << 1; } else { - ibit = s->gpio_regs[addr] & 2; + ibit = s->gpio_regs[addr] & IN_DATA; } s->gpio_regs[addr] = value | ibit; @@ -135,7 +140,7 @@ static uint64_t macio_gpio_read(void *opaque, hwaddr addr, unsigned size) } } - trace_macio_gpio_write(addr, val); + trace_macio_gpio_read(addr, val); return val; } diff --git a/hw/misc/macio/trace-events b/hw/misc/macio/trace-events index ad4b9d1c08..055a407aeb 100644 --- a/hw/misc/macio/trace-events +++ b/hw/misc/macio/trace-events @@ -18,7 +18,8 @@ macio_timer_read(uint64_t addr, unsigned len, uint32_t val) "read addr 0x%"PRIx6 macio_set_gpio(int gpio, bool state) "setting GPIO %d to %d" macio_gpio_irq_assert(int gpio) "asserting GPIO %d" macio_gpio_irq_deassert(int gpio) "deasserting GPIO %d" -macio_gpio_write(uint64_t addr, uint64_t val) "addr: 0x%"PRIx64" value: 0x%"PRIx64 +macio_gpio_write(uint64_t addr, uint64_t val) "addr 0x%"PRIx64" val 0x%"PRIx64 +macio_gpio_read(uint64_t addr, uint64_t val) "addr 0x%"PRIx64" val 0x%"PRIx64 # pmu.c pmu_adb_poll(int olen) "ADB autopoll, olen=%d" diff --git a/hw/misc/meson.build b/hw/misc/meson.build index 0b5187a2f7..6d47de482c 100644 --- a/hw/misc/meson.build +++ b/hw/misc/meson.build @@ -126,6 +126,7 @@ system_ss.add(when: 'CONFIG_ARMSSE_MHU', if_true: files('armsse-mhu.c')) system_ss.add(when: 'CONFIG_PVPANIC_ISA', if_true: files('pvpanic-isa.c')) system_ss.add(when: 'CONFIG_PVPANIC_PCI', if_true: files('pvpanic-pci.c')) +system_ss.add(when: 'CONFIG_PVPANIC_MMIO', if_true: files('pvpanic-mmio.c')) system_ss.add(when: 'CONFIG_AUX', if_true: files('auxbus.c')) system_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files( 'aspeed_hace.c', diff --git a/hw/misc/pvpanic-mmio.c b/hw/misc/pvpanic-mmio.c new file mode 100644 index 0000000000..70097cecc7 --- /dev/null +++ b/hw/misc/pvpanic-mmio.c @@ -0,0 +1,60 @@ +/* + * QEMU simulated pvpanic device (MMIO frontend) + * + * Copyright © 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" + +#include "hw/qdev-properties.h" +#include "hw/misc/pvpanic.h" +#include "hw/sysbus.h" +#include "standard-headers/misc/pvpanic.h" + +OBJECT_DECLARE_SIMPLE_TYPE(PVPanicMMIOState, PVPANIC_MMIO_DEVICE) + +#define PVPANIC_MMIO_SIZE 0x2 + +struct PVPanicMMIOState { + SysBusDevice parent_obj; + + PVPanicState pvpanic; +}; + +static void pvpanic_mmio_initfn(Object *obj) +{ + PVPanicMMIOState *s = PVPANIC_MMIO_DEVICE(obj); + + pvpanic_setup_io(&s->pvpanic, DEVICE(s), PVPANIC_MMIO_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->pvpanic.mr); +} + +static const Property pvpanic_mmio_properties[] = { + DEFINE_PROP_UINT8("events", PVPanicMMIOState, pvpanic.events, + PVPANIC_PANICKED | PVPANIC_CRASH_LOADED), +}; + +static void pvpanic_mmio_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + device_class_set_props(dc, pvpanic_mmio_properties); + set_bit(DEVICE_CATEGORY_MISC, dc->categories); +} + +static const TypeInfo pvpanic_mmio_info = { + .name = TYPE_PVPANIC_MMIO_DEVICE, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(PVPanicMMIOState), + .instance_init = pvpanic_mmio_initfn, + .class_init = pvpanic_mmio_class_init, +}; + +static void pvpanic_register_types(void) +{ + type_register_static(&pvpanic_mmio_info); +} + +type_init(pvpanic_register_types) diff --git a/hw/net/fsl_etsec/etsec.c b/hw/net/fsl_etsec/etsec.c index 3ce4fa2662..adde644892 100644 --- a/hw/net/fsl_etsec/etsec.c +++ b/hw/net/fsl_etsec/etsec.c @@ -423,8 +423,10 @@ static void etsec_class_init(ObjectClass *klass, void *data) DeviceClass *dc = DEVICE_CLASS(klass); dc->realize = etsec_realize; + dc->desc = "Freescale Enhanced Three-Speed Ethernet Controller"; device_class_set_legacy_reset(dc, etsec_reset); device_class_set_props(dc, etsec_properties); + set_bit(DEVICE_CATEGORY_NETWORK, dc->categories); } static const TypeInfo etsec_types[] = { diff --git a/hw/nvram/eeprom_at24c.c b/hw/nvram/eeprom_at24c.c index a40cc5dd15..ff7a21eee7 100644 --- a/hw/nvram/eeprom_at24c.c +++ b/hw/nvram/eeprom_at24c.c @@ -10,6 +10,7 @@ #include "qemu/osdep.h" #include "qapi/error.h" +#include "qemu/error-report.h" #include "qemu/module.h" #include "hw/i2c/i2c.h" #include "hw/nvram/eeprom_at24c.h" @@ -26,13 +27,8 @@ #define DPRINTK(FMT, ...) do {} while (0) #endif -#define ERR(FMT, ...) fprintf(stderr, TYPE_AT24C_EE " : " FMT, \ - ## __VA_ARGS__) - #define TYPE_AT24C_EE "at24c-eeprom" -typedef struct EEPROMState EEPROMState; -DECLARE_INSTANCE_CHECKER(EEPROMState, AT24C_EE, - TYPE_AT24C_EE) +OBJECT_DECLARE_SIMPLE_TYPE(EEPROMState, AT24C_EE) struct EEPROMState { I2CSlave parent_obj; @@ -77,8 +73,7 @@ int at24c_eeprom_event(I2CSlave *s, enum i2c_event event) if (ee->blk && ee->changed) { int ret = blk_pwrite(ee->blk, 0, ee->rsize, ee->mem, 0); if (ret < 0) { - ERR(TYPE_AT24C_EE - " : failed to write backing file\n"); + error_report("%s: failed to write backing file", __func__); } DPRINTK("Wrote to backing file\n"); } @@ -195,20 +190,18 @@ static void at24c_eeprom_realize(DeviceState *dev, Error **errp) } ee->mem = g_malloc0(ee->rsize); - memset(ee->mem, 0, ee->rsize); - - if (ee->init_rom) { - memcpy(ee->mem, ee->init_rom, MIN(ee->init_rom_size, ee->rsize)); - } if (ee->blk) { int ret = blk_pread(ee->blk, 0, ee->rsize, ee->mem, 0); if (ret < 0) { - ERR(TYPE_AT24C_EE - " : Failed initial sync with backing file\n"); + error_setg(errp, "%s: Failed initial sync with backing file", + TYPE_AT24C_EE); + return; } DPRINTK("Reset read backing file\n"); + } else if (ee->init_rom) { + memcpy(ee->mem, ee->init_rom, MIN(ee->init_rom_size, ee->rsize)); } /* diff --git a/hw/ppc/spapr_hcall.c b/hw/ppc/spapr_hcall.c index f8ab767063..f987ff323f 100644 --- a/hw/ppc/spapr_hcall.c +++ b/hw/ppc/spapr_hcall.c @@ -299,8 +299,10 @@ static target_ulong h_page_init(PowerPCCPU *cpu, SpaprMachineState *spapr, if (flags & (H_ICACHE_SYNCHRONIZE | H_ICACHE_INVALIDATE)) { if (kvm_enabled()) { kvmppc_icbi_range(cpu, pdst, len); - } else { + } else if (tcg_enabled()) { tb_flush(CPU(cpu)); + } else { + g_assert_not_reached(); } } diff --git a/hw/uefi/Kconfig b/hw/uefi/Kconfig new file mode 100644 index 0000000000..ca6c2bc46a --- /dev/null +++ b/hw/uefi/Kconfig @@ -0,0 +1,3 @@ +config UEFI_VARS + bool + default y if X86_64 || AARCH64 diff --git a/hw/uefi/LIMITATIONS.md b/hw/uefi/LIMITATIONS.md new file mode 100644 index 0000000000..29308bd587 --- /dev/null +++ b/hw/uefi/LIMITATIONS.md @@ -0,0 +1,7 @@ +known issues and limitations +---------------------------- + +* works only on little endian hosts + - accessing structs in guest ram is done without endian conversion. +* works only for 64-bit guests + - UINTN is mapped to uint64_t, for 32-bit guests that would be uint32_t diff --git a/hw/uefi/hardware-info.c b/hw/uefi/hardware-info.c new file mode 100644 index 0000000000..930502a4df --- /dev/null +++ b/hw/uefi/hardware-info.c @@ -0,0 +1,31 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * pass hardware information to uefi + * + * see OvmfPkg/Library/HardwareInfoLib/ in edk2 + */ + +#include "qemu/osdep.h" + +#include "hw/nvram/fw_cfg.h" +#include "hw/uefi/hardware-info.h" + +static void *blob; +static uint64_t blobsize; + +void hardware_info_register(HARDWARE_INFO_TYPE type, void *info, uint64_t infosize) +{ + HARDWARE_INFO_HEADER hdr = { + .type.value = cpu_to_le64(type), + .size = cpu_to_le64(infosize), + }; + + blob = g_realloc(blob, blobsize + sizeof(hdr) + infosize); + memcpy(blob + blobsize, &hdr, sizeof(hdr)); + blobsize += sizeof(hdr); + memcpy(blob + blobsize, info, infosize); + blobsize += infosize; + + fw_cfg_modify_file(fw_cfg_find(), "etc/hardware-info", blob, blobsize); +} diff --git a/hw/uefi/meson.build b/hw/uefi/meson.build new file mode 100644 index 0000000000..91eb95f89e --- /dev/null +++ b/hw/uefi/meson.build @@ -0,0 +1,21 @@ +system_ss.add(files('hardware-info.c')) + +uefi_vars_ss = ss.source_set() +if (config_all_devices.has_key('CONFIG_UEFI_VARS')) + uefi_vars_ss.add(files('var-service-core.c', + 'var-service-json.c', + 'var-service-vars.c', + 'var-service-auth.c', + 'var-service-guid.c', + 'var-service-utils.c', + 'var-service-policy.c', + 'var-service-sysbus.c')) + uefi_vars_ss.add(when: gnutls, + if_true: files('var-service-pkcs7.c'), + if_false: files('var-service-pkcs7-stub.c')) + uefi_vars_ss.add(files('var-service-siglist.c')) +endif + +modules += { 'hw-uefi' : { + 'vars' : uefi_vars_ss, +}} diff --git a/hw/uefi/trace-events b/hw/uefi/trace-events new file mode 100644 index 0000000000..3694712a94 --- /dev/null +++ b/hw/uefi/trace-events @@ -0,0 +1,17 @@ +# device +uefi_reg_read(uint64_t addr, unsigned size) "addr 0x%" PRIx64 ", size %u" +uefi_reg_write(uint64_t addr, uint64_t val, unsigned size) "addr 0x%" PRIx64 ", val 0x%" PRIx64 ", size %d" +uefi_hard_reset(void) "" + +# generic uefi +uefi_variable(const char *context, const char *name, uint64_t size, const char *uuid) "context %s, name %s, size %" PRIu64 ", uuid %s" +uefi_status(const char *context, const char *name) "context %s, status %s" +uefi_event(const char *name) "event %s" + +# variable protocol +uefi_vars_proto_cmd(const char *cmd) "cmd %s" +uefi_vars_security_violation(const char *reason) "reason %s" + +# variable policy protocol +uefi_vars_policy_cmd(const char *cmd) "cmd %s" +uefi_vars_policy_deny(const char *reason) "reason %s" diff --git a/hw/uefi/var-service-auth.c b/hw/uefi/var-service-auth.c new file mode 100644 index 0000000000..fba5a0956a --- /dev/null +++ b/hw/uefi/var-service-auth.c @@ -0,0 +1,361 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * uefi vars device - AuthVariableLib + */ + +#include "qemu/osdep.h" +#include "qemu/error-report.h" +#include "system/dma.h" + +#include "hw/uefi/var-service.h" + +static const uint16_t name_pk[] = u"PK"; +static const uint16_t name_kek[] = u"KEK"; +static const uint16_t name_db[] = u"db"; +static const uint16_t name_dbx[] = u"dbx"; +static const uint16_t name_setup_mode[] = u"SetupMode"; +static const uint16_t name_sigs_support[] = u"SignatureSupport"; +static const uint16_t name_sb[] = u"SecureBoot"; +static const uint16_t name_sb_enable[] = u"SecureBootEnable"; +static const uint16_t name_custom_mode[] = u"CustomMode"; +static const uint16_t name_vk[] = u"VendorKeys"; +static const uint16_t name_vk_nv[] = u"VendorKeysNv"; + +static const uint32_t sigdb_attrs = + EFI_VARIABLE_NON_VOLATILE | + EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS | + EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS; + +static void set_secure_boot(uefi_vars_state *uv, uint8_t sb) +{ + uefi_vars_set_variable(uv, EfiGlobalVariable, + name_sb, sizeof(name_sb), + EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS, + &sb, sizeof(sb)); +} + +static void set_secure_boot_enable(uefi_vars_state *uv, uint8_t sbe) +{ + uefi_vars_set_variable(uv, EfiSecureBootEnableDisable, + name_sb_enable, sizeof(name_sb_enable), + EFI_VARIABLE_NON_VOLATILE | + EFI_VARIABLE_BOOTSERVICE_ACCESS, + &sbe, sizeof(sbe)); +} + +static void set_setup_mode(uefi_vars_state *uv, uint8_t sm) +{ + uefi_vars_set_variable(uv, EfiGlobalVariable, + name_setup_mode, sizeof(name_setup_mode), + EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS, + &sm, sizeof(sm)); +} + +static void set_custom_mode(uefi_vars_state *uv, uint8_t cm) +{ + uefi_vars_set_variable(uv, EfiCustomModeEnable, + name_custom_mode, sizeof(name_custom_mode), + EFI_VARIABLE_NON_VOLATILE | + EFI_VARIABLE_BOOTSERVICE_ACCESS, + &cm, sizeof(cm)); +} + +static void set_signature_support(uefi_vars_state *uv) +{ + QemuUUID sigs_support[5]; + + sigs_support[0] = EfiCertSha256Guid; + sigs_support[1] = EfiCertSha384Guid; + sigs_support[2] = EfiCertSha512Guid; + sigs_support[3] = EfiCertRsa2048Guid; + sigs_support[4] = EfiCertX509Guid; + + uefi_vars_set_variable(uv, EfiGlobalVariable, + name_sigs_support, sizeof(name_sigs_support), + EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS, + sigs_support, sizeof(sigs_support)); +} + +static bool setup_mode_is_active(uefi_vars_state *uv) +{ + uefi_variable *var; + uint8_t *value; + + var = uefi_vars_find_variable(uv, EfiGlobalVariable, + name_setup_mode, sizeof(name_setup_mode)); + if (var) { + value = var->data; + if (value[0] == SETUP_MODE) { + return true; + } + } + return false; +} + +static bool custom_mode_is_active(uefi_vars_state *uv) +{ + uefi_variable *var; + uint8_t *value; + + var = uefi_vars_find_variable(uv, EfiCustomModeEnable, + name_custom_mode, sizeof(name_custom_mode)); + if (var) { + value = var->data; + if (value[0] == CUSTOM_SECURE_BOOT_MODE) { + return true; + } + } + return false; +} + +bool uefi_vars_is_sb_pk(uefi_variable *var) +{ + if (qemu_uuid_is_equal(&var->guid, &EfiGlobalVariable) && + uefi_str_equal(var->name, var->name_size, name_pk, sizeof(name_pk))) { + return true; + } + return false; +} + +static bool uefi_vars_is_sb_kek(uefi_variable *var) +{ + if (qemu_uuid_is_equal(&var->guid, &EfiGlobalVariable) && + uefi_str_equal(var->name, var->name_size, name_kek, sizeof(name_kek))) { + return true; + } + return false; +} + +static bool uefi_vars_is_sb_db(uefi_variable *var) +{ + if (!qemu_uuid_is_equal(&var->guid, &EfiImageSecurityDatabase)) { + return false; + } + if (uefi_str_equal(var->name, var->name_size, name_db, sizeof(name_db))) { + return true; + } + if (uefi_str_equal(var->name, var->name_size, name_dbx, sizeof(name_dbx))) { + return true; + } + return false; +} + +bool uefi_vars_is_sb_any(uefi_variable *var) +{ + if (uefi_vars_is_sb_pk(var) || + uefi_vars_is_sb_kek(var) || + uefi_vars_is_sb_db(var)) { + return true; + } + return false; +} + +static uefi_variable *uefi_vars_find_siglist(uefi_vars_state *uv, + uefi_variable *var) +{ + if (uefi_vars_is_sb_pk(var)) { + return uefi_vars_find_variable(uv, EfiGlobalVariable, + name_pk, sizeof(name_pk)); + } + if (uefi_vars_is_sb_kek(var)) { + return uefi_vars_find_variable(uv, EfiGlobalVariable, + name_pk, sizeof(name_pk)); + } + if (uefi_vars_is_sb_db(var)) { + return uefi_vars_find_variable(uv, EfiGlobalVariable, + name_kek, sizeof(name_kek)); + } + + return NULL; +} + +static efi_status uefi_vars_check_auth_2_sb(uefi_vars_state *uv, + uefi_variable *var, + mm_variable_access *va, + void *data, + uint64_t data_offset) +{ + variable_auth_2 *auth = data; + uefi_variable *siglist; + + if (custom_mode_is_active(uv)) { + /* no authentication in custom mode */ + return EFI_SUCCESS; + } + + if (setup_mode_is_active(uv) && !uefi_vars_is_sb_pk(var)) { + /* no authentication in setup mode (except PK) */ + return EFI_SUCCESS; + } + + if (auth->hdr_length == 24) { + /* no signature (auth->cert_data is empty) */ + return EFI_SECURITY_VIOLATION; + } + + siglist = uefi_vars_find_siglist(uv, var); + if (!siglist && setup_mode_is_active(uv) && uefi_vars_is_sb_pk(var)) { + /* check PK is self-signed */ + uefi_variable tmp = { + .guid = EfiGlobalVariable, + .name = (uint16_t *)name_pk, + .name_size = sizeof(name_pk), + .attributes = sigdb_attrs, + .data = data + data_offset, + .data_size = va->data_size - data_offset, + }; + return uefi_vars_check_pkcs7_2(&tmp, NULL, NULL, va, data); + } + + return uefi_vars_check_pkcs7_2(siglist, NULL, NULL, va, data); +} + +efi_status uefi_vars_check_auth_2(uefi_vars_state *uv, uefi_variable *var, + mm_variable_access *va, void *data) +{ + variable_auth_2 *auth = data; + uint64_t data_offset; + efi_status status; + + if (va->data_size < sizeof(*auth)) { + return EFI_SECURITY_VIOLATION; + } + if (uadd64_overflow(sizeof(efi_time), auth->hdr_length, &data_offset)) { + return EFI_SECURITY_VIOLATION; + } + if (va->data_size < data_offset) { + return EFI_SECURITY_VIOLATION; + } + + if (auth->hdr_revision != 0x0200 || + auth->hdr_cert_type != WIN_CERT_TYPE_EFI_GUID || + !qemu_uuid_is_equal(&auth->guid_cert_type, &EfiCertTypePkcs7Guid)) { + return EFI_UNSUPPORTED; + } + + if (uefi_vars_is_sb_any(var)) { + /* secure boot variables */ + status = uefi_vars_check_auth_2_sb(uv, var, va, data, data_offset); + if (status != EFI_SUCCESS) { + return status; + } + } else { + /* other authenticated variables */ + status = uefi_vars_check_pkcs7_2(NULL, + &var->digest, &var->digest_size, + va, data); + if (status != EFI_SUCCESS) { + return status; + } + } + + /* checks passed, set variable data */ + var->time = auth->timestamp; + if (va->data_size - data_offset > 0) { + var->data = g_malloc(va->data_size - data_offset); + memcpy(var->data, data + data_offset, va->data_size - data_offset); + var->data_size = va->data_size - data_offset; + } + + return EFI_SUCCESS; +} + +efi_status uefi_vars_check_secure_boot(uefi_vars_state *uv, uefi_variable *var) +{ + uint8_t *value = var->data; + + if (uefi_vars_is_sb_any(var)) { + if (var->attributes != sigdb_attrs) { + return EFI_INVALID_PARAMETER; + } + } + + /* reject SecureBootEnable updates if force_secure_boot is set */ + if (qemu_uuid_is_equal(&var->guid, &EfiSecureBootEnableDisable) && + uefi_str_equal(var->name, var->name_size, + name_sb_enable, sizeof(name_sb_enable)) && + uv->force_secure_boot && + value[0] != SECURE_BOOT_ENABLE) { + return EFI_WRITE_PROTECTED; + } + + /* reject CustomMode updates if disable_custom_mode is set */ + if (qemu_uuid_is_equal(&var->guid, &EfiCustomModeEnable) && + uefi_str_equal(var->name, var->name_size, + name_custom_mode, sizeof(name_custom_mode)) && + uv->disable_custom_mode) { + return EFI_WRITE_PROTECTED; + } + + return EFI_SUCCESS; +} + +/* AuthVariableLibInitialize */ +void uefi_vars_auth_init(uefi_vars_state *uv) +{ + uefi_variable *pk_var, *sbe_var; + uint8_t platform_mode, sb, sbe, vk; + + /* SetupMode */ + pk_var = uefi_vars_find_variable(uv, EfiGlobalVariable, + name_pk, sizeof(name_pk)); + if (!pk_var) { + platform_mode = SETUP_MODE; + } else { + platform_mode = USER_MODE; + } + set_setup_mode(uv, platform_mode); + + /* SignatureSupport */ + set_signature_support(uv); + + /* SecureBootEnable */ + sbe = SECURE_BOOT_DISABLE; + sbe_var = uefi_vars_find_variable(uv, EfiSecureBootEnableDisable, + name_sb_enable, sizeof(name_sb_enable)); + if (sbe_var) { + if (platform_mode == USER_MODE) { + sbe = ((uint8_t *)sbe_var->data)[0]; + } + } else if (platform_mode == USER_MODE) { + sbe = SECURE_BOOT_ENABLE; + set_secure_boot_enable(uv, sbe); + } + + if (uv->force_secure_boot && sbe != SECURE_BOOT_ENABLE) { + sbe = SECURE_BOOT_ENABLE; + set_secure_boot_enable(uv, sbe); + } + + /* SecureBoot */ + if ((sbe == SECURE_BOOT_ENABLE) && (platform_mode == USER_MODE)) { + sb = SECURE_BOOT_MODE_ENABLE; + } else { + sb = SECURE_BOOT_MODE_DISABLE; + } + set_secure_boot(uv, sb); + + /* CustomMode */ + set_custom_mode(uv, STANDARD_SECURE_BOOT_MODE); + + vk = 0; + uefi_vars_set_variable(uv, EfiGlobalVariable, + name_vk_nv, sizeof(name_vk_nv), + EFI_VARIABLE_NON_VOLATILE | + EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS, + &vk, sizeof(vk)); + uefi_vars_set_variable(uv, EfiGlobalVariable, + name_vk, sizeof(name_vk), + EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS, + &vk, sizeof(vk)); + + /* flush to disk */ + uefi_vars_json_save(uv); +} diff --git a/hw/uefi/var-service-core.c b/hw/uefi/var-service-core.c new file mode 100644 index 0000000000..8ed8378ab9 --- /dev/null +++ b/hw/uefi/var-service-core.c @@ -0,0 +1,321 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * uefi vars device + */ +#include "qemu/osdep.h" +#include "qemu/crc32c.h" +#include "system/dma.h" +#include "migration/vmstate.h" + +#include "hw/uefi/var-service.h" +#include "hw/uefi/var-service-api.h" +#include "hw/uefi/var-service-edk2.h" + +#include "trace/trace-hw_uefi.h" + +static int uefi_vars_pre_load(void *opaque) +{ + uefi_vars_state *uv = opaque; + + uefi_vars_clear_all(uv); + uefi_vars_policies_clear(uv); + g_free(uv->buffer); + return 0; +} + +static int uefi_vars_post_load(void *opaque, int version_id) +{ + uefi_vars_state *uv = opaque; + + uefi_vars_update_storage(uv); + uv->buffer = g_malloc(uv->buf_size); + return 0; +} + +const VMStateDescription vmstate_uefi_vars = { + .name = "uefi-vars", + .pre_load = uefi_vars_pre_load, + .post_load = uefi_vars_post_load, + .fields = (VMStateField[]) { + VMSTATE_UINT16(sts, uefi_vars_state), + VMSTATE_UINT32(buf_size, uefi_vars_state), + VMSTATE_UINT32(buf_addr_lo, uefi_vars_state), + VMSTATE_UINT32(buf_addr_hi, uefi_vars_state), + VMSTATE_UINT32(pio_xfer_offset, uefi_vars_state), + VMSTATE_VBUFFER_ALLOC_UINT32(pio_xfer_buffer, uefi_vars_state, + 0, NULL, buf_size), + VMSTATE_BOOL(end_of_dxe, uefi_vars_state), + VMSTATE_BOOL(ready_to_boot, uefi_vars_state), + VMSTATE_BOOL(exit_boot_service, uefi_vars_state), + VMSTATE_BOOL(policy_locked, uefi_vars_state), + VMSTATE_UINT64(used_storage, uefi_vars_state), + VMSTATE_QTAILQ_V(variables, uefi_vars_state, 0, + vmstate_uefi_variable, uefi_variable, next), + VMSTATE_QTAILQ_V(var_policies, uefi_vars_state, 0, + vmstate_uefi_var_policy, uefi_var_policy, next), + VMSTATE_END_OF_LIST() + }, +}; + +static uint32_t uefi_vars_cmd_mm(uefi_vars_state *uv, bool dma_mode) +{ + hwaddr dma; + mm_header *mhdr; + uint64_t size; + uint32_t retval; + + dma = uv->buf_addr_lo | ((hwaddr)uv->buf_addr_hi << 32); + mhdr = (mm_header *) uv->buffer; + + if (!uv->buffer || uv->buf_size < sizeof(*mhdr)) { + return UEFI_VARS_STS_ERR_BAD_BUFFER_SIZE; + } + + /* read header */ + if (dma_mode) { + dma_memory_read(&address_space_memory, dma, + uv->buffer, sizeof(*mhdr), + MEMTXATTRS_UNSPECIFIED); + } else { + memcpy(uv->buffer, uv->pio_xfer_buffer, sizeof(*mhdr)); + } + + if (uadd64_overflow(sizeof(*mhdr), mhdr->length, &size)) { + return UEFI_VARS_STS_ERR_BAD_BUFFER_SIZE; + } + if (uv->buf_size < size) { + return UEFI_VARS_STS_ERR_BAD_BUFFER_SIZE; + } + + /* read buffer (excl header) */ + if (dma_mode) { + dma_memory_read(&address_space_memory, dma + sizeof(*mhdr), + uv->buffer + sizeof(*mhdr), mhdr->length, + MEMTXATTRS_UNSPECIFIED); + } else { + memcpy(uv->buffer + sizeof(*mhdr), + uv->pio_xfer_buffer + sizeof(*mhdr), + mhdr->length); + } + memset(uv->buffer + size, 0, uv->buf_size - size); + + /* dispatch */ + if (qemu_uuid_is_equal(&mhdr->guid, &EfiSmmVariableProtocolGuid)) { + retval = uefi_vars_mm_vars_proto(uv); + + } else if (qemu_uuid_is_equal(&mhdr->guid, &VarCheckPolicyLibMmiHandlerGuid)) { + retval = uefi_vars_mm_check_policy_proto(uv); + + } else if (qemu_uuid_is_equal(&mhdr->guid, &EfiEndOfDxeEventGroupGuid)) { + trace_uefi_event("end-of-dxe"); + uv->end_of_dxe = true; + retval = UEFI_VARS_STS_SUCCESS; + + } else if (qemu_uuid_is_equal(&mhdr->guid, &EfiEventReadyToBootGuid)) { + trace_uefi_event("ready-to-boot"); + uv->ready_to_boot = true; + retval = UEFI_VARS_STS_SUCCESS; + + } else if (qemu_uuid_is_equal(&mhdr->guid, &EfiEventExitBootServicesGuid)) { + trace_uefi_event("exit-boot-service"); + uv->exit_boot_service = true; + retval = UEFI_VARS_STS_SUCCESS; + + } else { + retval = UEFI_VARS_STS_ERR_NOT_SUPPORTED; + } + + /* write buffer */ + if (dma_mode) { + dma_memory_write(&address_space_memory, dma, + uv->buffer, sizeof(*mhdr) + mhdr->length, + MEMTXATTRS_UNSPECIFIED); + } else { + memcpy(uv->pio_xfer_buffer + sizeof(*mhdr), + uv->buffer + sizeof(*mhdr), + sizeof(*mhdr) + mhdr->length); + } + + return retval; +} + +static void uefi_vars_soft_reset(uefi_vars_state *uv) +{ + g_free(uv->buffer); + uv->buffer = NULL; + uv->buf_size = 0; + uv->buf_addr_lo = 0; + uv->buf_addr_hi = 0; +} + +void uefi_vars_hard_reset(uefi_vars_state *uv) +{ + trace_uefi_hard_reset(); + uefi_vars_soft_reset(uv); + + uv->end_of_dxe = false; + uv->ready_to_boot = false; + uv->exit_boot_service = false; + uv->policy_locked = false; + + uefi_vars_clear_volatile(uv); + uefi_vars_policies_clear(uv); + uefi_vars_auth_init(uv); +} + +static uint32_t uefi_vars_cmd(uefi_vars_state *uv, uint32_t cmd) +{ + switch (cmd) { + case UEFI_VARS_CMD_RESET: + uefi_vars_soft_reset(uv); + return UEFI_VARS_STS_SUCCESS; + case UEFI_VARS_CMD_DMA_MM: + return uefi_vars_cmd_mm(uv, true); + case UEFI_VARS_CMD_PIO_MM: + return uefi_vars_cmd_mm(uv, false); + case UEFI_VARS_CMD_PIO_ZERO_OFFSET: + uv->pio_xfer_offset = 0; + return UEFI_VARS_STS_SUCCESS; + default: + return UEFI_VARS_STS_ERR_NOT_SUPPORTED; + } +} + +static uint64_t uefi_vars_read(void *opaque, hwaddr addr, unsigned size) +{ + uefi_vars_state *uv = opaque; + uint64_t retval = -1; + void *xfer_ptr; + + trace_uefi_reg_read(addr, size); + + switch (addr) { + case UEFI_VARS_REG_MAGIC: + retval = UEFI_VARS_MAGIC_VALUE; + break; + case UEFI_VARS_REG_CMD_STS: + retval = uv->sts; + break; + case UEFI_VARS_REG_BUFFER_SIZE: + retval = uv->buf_size; + break; + case UEFI_VARS_REG_DMA_BUFFER_ADDR_LO: + retval = uv->buf_addr_lo; + break; + case UEFI_VARS_REG_DMA_BUFFER_ADDR_HI: + retval = uv->buf_addr_hi; + break; + case UEFI_VARS_REG_PIO_BUFFER_TRANSFER: + if (uv->pio_xfer_offset + size > uv->buf_size) { + retval = 0; + break; + } + xfer_ptr = uv->pio_xfer_buffer + uv->pio_xfer_offset; + switch (size) { + case 1: + retval = *(uint8_t *)xfer_ptr; + break; + case 2: + retval = *(uint16_t *)xfer_ptr; + break; + case 4: + retval = *(uint32_t *)xfer_ptr; + break; + case 8: + retval = *(uint64_t *)xfer_ptr; + break; + } + uv->pio_xfer_offset += size; + break; + case UEFI_VARS_REG_PIO_BUFFER_CRC32C: + retval = crc32c(0xffffffff, uv->pio_xfer_buffer, uv->pio_xfer_offset); + break; + case UEFI_VARS_REG_FLAGS: + retval = 0; + if (uv->use_pio) { + retval |= UEFI_VARS_FLAG_USE_PIO; + } + } + return retval; +} + +static void uefi_vars_write(void *opaque, hwaddr addr, uint64_t val, unsigned size) +{ + uefi_vars_state *uv = opaque; + void *xfer_ptr; + + trace_uefi_reg_write(addr, val, size); + + switch (addr) { + case UEFI_VARS_REG_CMD_STS: + uv->sts = uefi_vars_cmd(uv, val); + break; + case UEFI_VARS_REG_BUFFER_SIZE: + if (val > MAX_BUFFER_SIZE) { + val = MAX_BUFFER_SIZE; + } + uv->buf_size = val; + g_free(uv->buffer); + g_free(uv->pio_xfer_buffer); + uv->buffer = g_malloc(uv->buf_size); + uv->pio_xfer_buffer = g_malloc(uv->buf_size); + break; + case UEFI_VARS_REG_DMA_BUFFER_ADDR_LO: + uv->buf_addr_lo = val; + break; + case UEFI_VARS_REG_DMA_BUFFER_ADDR_HI: + uv->buf_addr_hi = val; + break; + case UEFI_VARS_REG_PIO_BUFFER_TRANSFER: + if (uv->pio_xfer_offset + size > uv->buf_size) { + break; + } + xfer_ptr = uv->pio_xfer_buffer + uv->pio_xfer_offset; + switch (size) { + case 1: + *(uint8_t *)xfer_ptr = val; + break; + case 2: + *(uint16_t *)xfer_ptr = val; + break; + case 4: + *(uint32_t *)xfer_ptr = val; + break; + case 8: + *(uint64_t *)xfer_ptr = val; + break; + } + uv->pio_xfer_offset += size; + break; + case UEFI_VARS_REG_PIO_BUFFER_CRC32C: + case UEFI_VARS_REG_FLAGS: + default: + break; + } +} + +static const MemoryRegionOps uefi_vars_ops = { + .read = uefi_vars_read, + .write = uefi_vars_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 2, + .max_access_size = 4, + }, +}; + +void uefi_vars_init(Object *obj, uefi_vars_state *uv) +{ + QTAILQ_INIT(&uv->variables); + QTAILQ_INIT(&uv->var_policies); + uv->jsonfd = -1; + memory_region_init_io(&uv->mr, obj, &uefi_vars_ops, uv, + "uefi-vars", UEFI_VARS_REGS_SIZE); +} + +void uefi_vars_realize(uefi_vars_state *uv, Error **errp) +{ + uefi_vars_json_init(uv, errp); + uefi_vars_json_load(uv, errp); +} diff --git a/hw/uefi/var-service-guid.c b/hw/uefi/var-service-guid.c new file mode 100644 index 0000000000..eba3655c8d --- /dev/null +++ b/hw/uefi/var-service-guid.c @@ -0,0 +1,99 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * uefi vars device - GUIDs + */ + +#include "qemu/osdep.h" +#include "system/dma.h" + +#include "hw/uefi/var-service.h" + +/* variable namespaces */ + +const QemuUUID EfiGlobalVariable = { + .data = UUID_LE(0x8be4df61, 0x93ca, 0x11d2, 0xaa, 0x0d, + 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c) +}; + +const QemuUUID EfiImageSecurityDatabase = { + .data = UUID_LE(0xd719b2cb, 0x3d3a, 0x4596, 0xa3, 0xbc, + 0xda, 0xd0, 0x0e, 0x67, 0x65, 0x6f) +}; + +const QemuUUID EfiCustomModeEnable = { + .data = UUID_LE(0xc076ec0c, 0x7028, 0x4399, 0xa0, 0x72, + 0x71, 0xee, 0x5c, 0x44, 0x8b, 0x9f) +}; + +const QemuUUID EfiSecureBootEnableDisable = { + .data = UUID_LE(0xf0a30bc7, 0xaf08, 0x4556, 0x99, 0xc4, + 0x0, 0x10, 0x9, 0xc9, 0x3a, 0x44) +}; + +/* signatures */ + +const QemuUUID EfiCertSha256Guid = { + .data = UUID_LE(0xc1c41626, 0x504c, 0x4092, 0xac, 0xa9, + 0x41, 0xf9, 0x36, 0x93, 0x43, 0x28) +}; + +const QemuUUID EfiCertSha384Guid = { + .data = UUID_LE(0xff3e5307, 0x9fd0, 0x48c9, 0x85, 0xf1, + 0x8a, 0xd5, 0x6c, 0x70, 0x1e, 0x1) +}; + +const QemuUUID EfiCertSha512Guid = { + .data = UUID_LE(0x93e0fae, 0xa6c4, 0x4f50, 0x9f, 0x1b, + 0xd4, 0x1e, 0x2b, 0x89, 0xc1, 0x9a) +}; + +const QemuUUID EfiCertRsa2048Guid = { + .data = UUID_LE(0x3c5766e8, 0x269c, 0x4e34, 0xaa, 0x14, + 0xed, 0x77, 0x6e, 0x85, 0xb3, 0xb6) +}; + +const QemuUUID EfiCertX509Guid = { + .data = UUID_LE(0xa5c059a1, 0x94e4, 0x4aa7, 0x87, 0xb5, + 0xab, 0x15, 0x5c, 0x2b, 0xf0, 0x72) +}; + +const QemuUUID EfiCertTypePkcs7Guid = { + .data = UUID_LE(0x4aafd29d, 0x68df, 0x49ee, 0x8a, 0xa9, + 0x34, 0x7d, 0x37, 0x56, 0x65, 0xa7) +}; + +/* + * mm_header.guid values that the guest DXE/BDS phases use for + * sending requests to management mode + */ + +const QemuUUID EfiSmmVariableProtocolGuid = { + .data = UUID_LE(0xed32d533, 0x99e6, 0x4209, 0x9c, 0xc0, + 0x2d, 0x72, 0xcd, 0xd9, 0x98, 0xa7) +}; + +const QemuUUID VarCheckPolicyLibMmiHandlerGuid = { + .data = UUID_LE(0xda1b0d11, 0xd1a7, 0x46c4, 0x9d, 0xc9, + 0xf3, 0x71, 0x48, 0x75, 0xc6, 0xeb) +}; + +/* + * mm_header.guid values that the guest DXE/BDS phases use for + * reporting event groups being signaled to management mode + */ + +const QemuUUID EfiEndOfDxeEventGroupGuid = { + .data = UUID_LE(0x02ce967a, 0xdd7e, 0x4FFc, 0x9e, 0xe7, + 0x81, 0x0c, 0xF0, 0x47, 0x08, 0x80) +}; + +const QemuUUID EfiEventReadyToBootGuid = { + .data = UUID_LE(0x7ce88Fb3, 0x4bd7, 0x4679, 0x87, 0xa8, + 0xa8, 0xd8, 0xde, 0xe5, 0x0d, 0x2b) +}; + +const QemuUUID EfiEventExitBootServicesGuid = { + .data = UUID_LE(0x27abF055, 0xb1b8, 0x4c26, 0x80, 0x48, + 0x74, 0x8F, 0x37, 0xba, 0xa2, 0xdF) +}; diff --git a/hw/uefi/var-service-json.c b/hw/uefi/var-service-json.c new file mode 100644 index 0000000000..761082c11f --- /dev/null +++ b/hw/uefi/var-service-json.c @@ -0,0 +1,243 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * uefi vars device - serialize non-volatile varstore from/to json, + * using qapi + * + * tools which can read/write these json files: + * - https://gitlab.com/kraxel/virt-firmware + * - https://github.com/awslabs/python-uefivars + */ +#include "qemu/osdep.h" +#include "qemu/cutils.h" +#include "qemu/error-report.h" +#include "system/dma.h" + +#include "hw/uefi/var-service.h" + +#include "qobject/qobject.h" +#include "qobject/qjson.h" + +#include "qapi/dealloc-visitor.h" +#include "qapi/qobject-input-visitor.h" +#include "qapi/qobject-output-visitor.h" +#include "qapi/qapi-types-uefi.h" +#include "qapi/qapi-visit-uefi.h" + +static char *generate_hexstr(void *data, size_t len) +{ + static const char hex[] = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', + }; + uint8_t *src = data; + char *dest; + size_t i; + + dest = g_malloc(len * 2 + 1); + for (i = 0; i < len * 2;) { + dest[i++] = hex[*src >> 4]; + dest[i++] = hex[*src & 15]; + src++; + } + dest[i++] = 0; + + return dest; +} + +static UefiVarStore *uefi_vars_to_qapi(uefi_vars_state *uv) +{ + UefiVarStore *vs; + UefiVariableList **tail; + UefiVariable *v; + QemuUUID be; + uefi_variable *var; + + vs = g_new0(UefiVarStore, 1); + vs->version = 2; + tail = &vs->variables; + + QTAILQ_FOREACH(var, &uv->variables, next) { + if (!(var->attributes & EFI_VARIABLE_NON_VOLATILE)) { + continue; + } + + v = g_new0(UefiVariable, 1); + be = qemu_uuid_bswap(var->guid); + v->guid = qemu_uuid_unparse_strdup(&be); + v->name = uefi_ucs2_to_ascii(var->name, var->name_size); + v->attr = var->attributes; + + v->data = generate_hexstr(var->data, var->data_size); + + if (var->attributes & + EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS) { + v->time = generate_hexstr(&var->time, sizeof(var->time)); + if (var->digest && var->digest_size) { + v->digest = generate_hexstr(var->digest, var->digest_size); + } + } + + QAPI_LIST_APPEND(tail, v); + } + return vs; +} + +static unsigned parse_hexchar(char c) +{ + switch (c) { + case '0' ... '9': return c - '0'; + case 'a' ... 'f': return c - 'a' + 0xa; + case 'A' ... 'F': return c - 'A' + 0xA; + default: return 0; + } +} + +static void parse_hexstr(void *dest, char *src, int len) +{ + uint8_t *data = dest; + size_t i; + + for (i = 0; i < len; i += 2) { + *(data++) = + parse_hexchar(src[i]) << 4 | + parse_hexchar(src[i + 1]); + } +} + +static void uefi_vars_from_qapi(uefi_vars_state *uv, UefiVarStore *vs) +{ + UefiVariableList *item; + UefiVariable *v; + QemuUUID be; + uefi_variable *var; + uint8_t *data; + size_t i, len; + + for (item = vs->variables; item != NULL; item = item->next) { + v = item->value; + + var = g_new0(uefi_variable, 1); + var->attributes = v->attr; + qemu_uuid_parse(v->guid, &be); + var->guid = qemu_uuid_bswap(be); + + len = strlen(v->name); + var->name_size = len * 2 + 2; + var->name = g_malloc(var->name_size); + for (i = 0; i <= len; i++) { + var->name[i] = v->name[i]; + } + + len = strlen(v->data); + var->data_size = len / 2; + var->data = data = g_malloc(var->data_size); + parse_hexstr(var->data, v->data, len); + + if (v->time && strlen(v->time) == 32) { + parse_hexstr(&var->time, v->time, 32); + } + + if (v->digest) { + len = strlen(v->digest); + var->digest_size = len / 2; + var->digest = g_malloc(var->digest_size); + parse_hexstr(var->digest, v->digest, len); + } + + QTAILQ_INSERT_TAIL(&uv->variables, var, next); + } +} + +static GString *uefi_vars_to_json(uefi_vars_state *uv) +{ + UefiVarStore *vs = uefi_vars_to_qapi(uv); + QObject *qobj = NULL; + Visitor *v; + GString *gstr; + + v = qobject_output_visitor_new(&qobj); + if (visit_type_UefiVarStore(v, NULL, &vs, NULL)) { + visit_complete(v, &qobj); + } + visit_free(v); + qapi_free_UefiVarStore(vs); + + gstr = qobject_to_json_pretty(qobj, true); + qobject_unref(qobj); + + return gstr; +} + +void uefi_vars_json_init(uefi_vars_state *uv, Error **errp) +{ + if (uv->jsonfile) { + uv->jsonfd = qemu_create(uv->jsonfile, O_RDWR, 0666, errp); + } +} + +void uefi_vars_json_save(uefi_vars_state *uv) +{ + GString *gstr; + int rc; + + if (uv->jsonfd == -1) { + return; + } + + gstr = uefi_vars_to_json(uv); + + lseek(uv->jsonfd, 0, SEEK_SET); + rc = ftruncate(uv->jsonfd, 0); + if (rc != 0) { + warn_report("%s: ftruncate error", __func__); + } + rc = write(uv->jsonfd, gstr->str, gstr->len); + if (rc != gstr->len) { + warn_report("%s: write error", __func__); + } + fsync(uv->jsonfd); + + g_string_free(gstr, true); +} + +void uefi_vars_json_load(uefi_vars_state *uv, Error **errp) +{ + UefiVarStore *vs; + QObject *qobj; + Visitor *v; + char *str; + size_t len; + int rc; + + if (uv->jsonfd == -1) { + return; + } + + len = lseek(uv->jsonfd, 0, SEEK_END); + if (len == 0) { + return; + } + + str = g_malloc(len + 1); + lseek(uv->jsonfd, 0, SEEK_SET); + rc = read(uv->jsonfd, str, len); + if (rc != len) { + warn_report("%s: read error", __func__); + } + str[len] = 0; + + qobj = qobject_from_json(str, errp); + v = qobject_input_visitor_new(qobj); + visit_type_UefiVarStore(v, NULL, &vs, errp); + visit_free(v); + + if (!(*errp)) { + uefi_vars_from_qapi(uv, vs); + uefi_vars_update_storage(uv); + } + + qapi_free_UefiVarStore(vs); + qobject_unref(qobj); + g_free(str); +} diff --git a/hw/uefi/var-service-pkcs7-stub.c b/hw/uefi/var-service-pkcs7-stub.c new file mode 100644 index 0000000000..118cba446d --- /dev/null +++ b/hw/uefi/var-service-pkcs7-stub.c @@ -0,0 +1,16 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * uefi vars device - pkcs7 stubs + */ +#include "qemu/osdep.h" +#include "system/dma.h" + +#include "hw/uefi/var-service.h" + +efi_status uefi_vars_check_pkcs7_2(uefi_variable *siglist, + void **digest, uint32_t *digest_size, + mm_variable_access *va, void *data) +{ + return EFI_WRITE_PROTECTED; +} diff --git a/hw/uefi/var-service-pkcs7.c b/hw/uefi/var-service-pkcs7.c new file mode 100644 index 0000000000..32accf4e44 --- /dev/null +++ b/hw/uefi/var-service-pkcs7.c @@ -0,0 +1,436 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * uefi vars device - pkcs7 verification + */ +#include "qemu/osdep.h" +#include "qemu/error-report.h" +#include "system/dma.h" + +#include <gnutls/gnutls.h> +#include <gnutls/pkcs7.h> +#include <gnutls/crypto.h> + +#include "hw/uefi/var-service.h" + +#define AUTHVAR_DIGEST_ALGO GNUTLS_DIG_SHA256 +#define AUTHVAR_DIGEST_SIZE 32 + +/* + * Replicate the signed data for signature verification. + */ +static gnutls_datum_t *build_signed_data(mm_variable_access *va, void *data) +{ + variable_auth_2 *auth = data; + uint64_t data_offset = sizeof(efi_time) + auth->hdr_length; + uint16_t *name = (void *)va + sizeof(mm_variable_access); + gnutls_datum_t *sdata; + uint64_t pos = 0; + + sdata = g_new(gnutls_datum_t, 1); + sdata->size = (va->name_size - 2 + + sizeof(QemuUUID) + + sizeof(va->attributes) + + sizeof(auth->timestamp) + + va->data_size - data_offset); + sdata->data = g_malloc(sdata->size); + + /* Variable Name (without terminating \0) */ + memcpy(sdata->data + pos, name, va->name_size - 2); + pos += va->name_size - 2; + + /* Variable Namespace Guid */ + memcpy(sdata->data + pos, &va->guid, sizeof(va->guid)); + pos += sizeof(va->guid); + + /* Attributes */ + memcpy(sdata->data + pos, &va->attributes, sizeof(va->attributes)); + pos += sizeof(va->attributes); + + /* TimeStamp */ + memcpy(sdata->data + pos, &auth->timestamp, sizeof(auth->timestamp)); + pos += sizeof(auth->timestamp); + + /* Variable Content */ + memcpy(sdata->data + pos, data + data_offset, va->data_size - data_offset); + pos += va->data_size - data_offset; + + assert(pos == sdata->size); + return sdata; +} + +/* + * See WrapPkcs7Data() in edk2. + * + * UEFI spec allows pkcs7 signatures being used without the envelope which + * identifies them as pkcs7 signatures. openssl and gnutls will not parse them + * without the envelope though. So add it if needed. + */ +static void wrap_pkcs7(gnutls_datum_t *pkcs7) +{ + static uint8_t signed_data_oid[9] = { + 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x02 + }; + gnutls_datum_t wrap; + + if (pkcs7->data[4] == 0x06 && + pkcs7->data[5] == 0x09 && + memcmp(pkcs7->data + 6, signed_data_oid, sizeof(signed_data_oid)) == 0 && + pkcs7->data[15] == 0x0a && + pkcs7->data[16] == 0x82) { + return; + } + + wrap.size = pkcs7->size + 19; + wrap.data = g_malloc(wrap.size); + + wrap.data[0] = 0x30; + wrap.data[1] = 0x82; + wrap.data[2] = (wrap.size - 4) >> 8; + wrap.data[3] = (wrap.size - 4) & 0xff; + wrap.data[4] = 0x06; + wrap.data[5] = 0x09; + memcpy(wrap.data + 6, signed_data_oid, sizeof(signed_data_oid)); + + wrap.data[15] = 0xa0; + wrap.data[16] = 0x82; + wrap.data[17] = pkcs7->size >> 8; + wrap.data[18] = pkcs7->size & 0xff; + memcpy(wrap.data + 19, pkcs7->data, pkcs7->size); + + g_free(pkcs7->data); + *pkcs7 = wrap; +} + +static gnutls_datum_t *build_pkcs7(void *data) +{ + variable_auth_2 *auth = data; + gnutls_datum_t *pkcs7; + + pkcs7 = g_new(gnutls_datum_t, 1); + pkcs7->size = auth->hdr_length - 24; + pkcs7->data = g_malloc(pkcs7->size); + memcpy(pkcs7->data, data + 16 + 24, pkcs7->size); + + wrap_pkcs7(pkcs7); + + return pkcs7; +} + +/* + * Read UEFI signature database, store x509 all certificates found in + * gnutls_x509_trust_list_t. + */ +static gnutls_x509_trust_list_t build_trust_list_sb(uefi_variable *var) +{ + gnutls_x509_trust_list_t tlist; + gnutls_datum_t cert_data; + gnutls_x509_crt_t cert; + uefi_vars_siglist siglist; + uefi_vars_cert *c; + int rc; + + rc = gnutls_x509_trust_list_init(&tlist, 0); + if (rc < 0) { + warn_report("gnutls_x509_trust_list_init error: %s", + gnutls_strerror(rc)); + return NULL; + } + + uefi_vars_siglist_init(&siglist); + uefi_vars_siglist_parse(&siglist, var->data, var->data_size); + + QTAILQ_FOREACH(c, &siglist.x509, next) { + cert_data.size = c->size; + cert_data.data = c->data; + + rc = gnutls_x509_crt_init(&cert); + if (rc < 0) { + warn_report("gnutls_x509_crt_init error: %s", gnutls_strerror(rc)); + break; + } + rc = gnutls_x509_crt_import(cert, &cert_data, GNUTLS_X509_FMT_DER); + if (rc < 0) { + warn_report("gnutls_x509_crt_import error: %s", + gnutls_strerror(rc)); + gnutls_x509_crt_deinit(cert); + break; + } + rc = gnutls_x509_trust_list_add_cas(tlist, &cert, 1, 0); + if (rc < 0) { + warn_report("gnutls_x509_crt_import error: %s", + gnutls_strerror(rc)); + gnutls_x509_crt_deinit(cert); + break; + } + } + + uefi_vars_siglist_free(&siglist); + + return tlist; +} + +static int build_digest_authvar(gnutls_x509_crt_t signer, + gnutls_x509_crt_t root, + uint8_t *hash_digest) +{ + char *cn; + size_t cn_size = 0; + uint8_t fp[AUTHVAR_DIGEST_SIZE]; + size_t fp_size = sizeof(fp); + gnutls_hash_hd_t hash; + int rc; + + /* get signer CN */ + rc = gnutls_x509_crt_get_dn_by_oid(signer, GNUTLS_OID_X520_COMMON_NAME, + 0, 0, NULL, &cn_size); + if (rc != GNUTLS_E_SHORT_MEMORY_BUFFER) { + warn_report("gnutls_x509_crt_get_dn_by_oid error #1: %s", + gnutls_strerror(rc)); + return rc; + } + + cn = g_malloc(cn_size); + rc = gnutls_x509_crt_get_dn_by_oid(signer, GNUTLS_OID_X520_COMMON_NAME, + 0, 0, cn, &cn_size); + if (rc < 0) { + warn_report("gnutls_x509_crt_get_dn_by_oid error #2: %s", + gnutls_strerror(rc)); + goto err; + } + + /* get root certificate fingerprint */ + rc = gnutls_x509_crt_get_fingerprint(root, AUTHVAR_DIGEST_ALGO, + fp, &fp_size); + if (rc < 0) { + warn_report("gnutls_x509_crt_get_fingerprint error: %s", + gnutls_strerror(rc)); + goto err; + } + + /* digest both items */ + rc = gnutls_hash_init(&hash, AUTHVAR_DIGEST_ALGO); + if (rc < 0) { + warn_report("gnutls_hash_init error: %s", + gnutls_strerror(rc)); + goto err; + } + rc = gnutls_hash(hash, cn, cn_size); + if (rc < 0) { + warn_report("gnutls_hash error: %s", + gnutls_strerror(rc)); + goto err; + } + rc = gnutls_hash(hash, fp, fp_size); + if (rc < 0) { + warn_report("gnutls_hash error: %s", + gnutls_strerror(rc)); + goto err; + } + gnutls_hash_deinit(hash, hash_digest); + + return 0; + +err: + g_free(cn); + return rc; +} + +/* + * uefi spec 2.9, section 8.2.2 + * + * For EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS variables which are + * NOT secure boot variables we should track the root certificate of the trust + * chain, and the subject CN of the signer certificate. + * + * So we'll go store a digest of these two items so we can verify this. Also + * create a gnutls_x509_trust_list_t with the root certificate, so + * gnutls_pkcs7_verify() will pass (assuming the signature is otherwise + * correct). + */ +static gnutls_x509_trust_list_t build_trust_list_authvar(gnutls_pkcs7_t pkcs7, + uint8_t *hash_digest) +{ + gnutls_datum_t signer_data = { 0 }; + gnutls_datum_t root_data = { 0 }; + gnutls_x509_crt_t signer = NULL; + gnutls_x509_crt_t root = NULL; + gnutls_x509_trust_list_t tlist = NULL; + int n, rc; + + n = gnutls_pkcs7_get_crt_count(pkcs7); + + /* first is signer certificate */ + rc = gnutls_pkcs7_get_crt_raw2(pkcs7, 0, &signer_data); + if (rc < 0) { + warn_report("gnutls_pkcs7_get_crt_raw2(0) error: %s", + gnutls_strerror(rc)); + goto done; + } + rc = gnutls_x509_crt_init(&signer); + if (rc < 0) { + warn_report("gnutls_x509_crt_init error: %s", gnutls_strerror(rc)); + goto done; + } + rc = gnutls_x509_crt_import(signer, &signer_data, GNUTLS_X509_FMT_DER); + if (rc < 0) { + warn_report("gnutls_x509_crt_import error: %s", + gnutls_strerror(rc)); + gnutls_x509_crt_deinit(signer); + goto done; + } + + /* last is root-of-trust certificate (can be identical to signer) */ + rc = gnutls_pkcs7_get_crt_raw2(pkcs7, n - 1, &root_data); + if (rc < 0) { + warn_report("gnutls_pkcs7_get_crt_raw2(%d) error: %s", + n - 1, gnutls_strerror(rc)); + goto done; + } + rc = gnutls_x509_crt_init(&root); + if (rc < 0) { + warn_report("gnutls_x509_crt_init error: %s", gnutls_strerror(rc)); + goto done; + } + rc = gnutls_x509_crt_import(root, &root_data, GNUTLS_X509_FMT_DER); + if (rc < 0) { + warn_report("gnutls_x509_crt_import error: %s", + gnutls_strerror(rc)); + goto done; + } + + /* calc digest for signer CN + root cert */ + rc = build_digest_authvar(signer, root, hash_digest); + if (rc < 0) { + goto done; + } + + /* add root to trust list */ + rc = gnutls_x509_trust_list_init(&tlist, 0); + if (rc < 0) { + warn_report("gnutls_x509_trust_list_init error: %s", + gnutls_strerror(rc)); + goto done; + } + rc = gnutls_x509_trust_list_add_cas(tlist, &root, 1, 0); + if (rc < 0) { + warn_report("gnutls_x509_crt_import error: %s", + gnutls_strerror(rc)); + gnutls_x509_trust_list_deinit(tlist, 1); + tlist = NULL; + goto done; + } else { + /* ownership passed to tlist */ + root = NULL; + } + +done: + if (signer_data.data) { + gnutls_free(signer_data.data); + } + if (root_data.data) { + gnutls_free(root_data.data); + } + if (signer) { + gnutls_x509_crt_deinit(signer); + } + if (root) { + gnutls_x509_crt_deinit(root); + } + return tlist; +} + +static void free_datum(gnutls_datum_t *ptr) +{ + if (!ptr) { + return; + } + g_free(ptr->data); + g_free(ptr); +} + +static void gnutls_log_stderr(int level, const char *msg) +{ + if (strncmp(msg, "ASSERT:", 7) == 0) { + return; + } + fprintf(stderr, " %d: %s", level, msg); +} + +/* + * pkcs7 signature verification (EFI_VARIABLE_AUTHENTICATION_2). + */ +efi_status uefi_vars_check_pkcs7_2(uefi_variable *siglist, + void **digest, uint32_t *digest_size, + mm_variable_access *va, void *data) +{ + gnutls_x509_trust_list_t tlist = NULL; + gnutls_datum_t *signed_data = NULL; + gnutls_datum_t *pkcs7_data = NULL; + gnutls_pkcs7_t pkcs7 = NULL; + efi_status status = EFI_SECURITY_VIOLATION; + int rc; + + if (0) { + /* gnutls debug logging */ + static bool first = true; + + if (first) { + first = false; + gnutls_global_set_log_function(gnutls_log_stderr); + gnutls_global_set_log_level(99); + } + } + + signed_data = build_signed_data(va, data); + pkcs7_data = build_pkcs7(data); + + rc = gnutls_pkcs7_init(&pkcs7); + if (rc < 0) { + warn_report("gnutls_pkcs7_init error: %s", gnutls_strerror(rc)); + goto out; + } + + rc = gnutls_pkcs7_import(pkcs7, pkcs7_data, GNUTLS_X509_FMT_DER); + if (rc < 0) { + warn_report("gnutls_pkcs7_import error: %s", gnutls_strerror(rc)); + goto out; + } + + if (siglist) { + /* secure boot variables */ + tlist = build_trust_list_sb(siglist); + } else if (digest && digest_size) { + /* other authenticated variables */ + *digest_size = AUTHVAR_DIGEST_SIZE; + *digest = g_malloc(*digest_size); + tlist = build_trust_list_authvar(pkcs7, *digest); + } else { + /* should not happen */ + goto out; + } + + rc = gnutls_pkcs7_verify(pkcs7, tlist, + NULL, 0, + 0, signed_data, + GNUTLS_VERIFY_DISABLE_TIME_CHECKS | + GNUTLS_VERIFY_DISABLE_TRUSTED_TIME_CHECKS); + if (rc < 0) { + warn_report("gnutls_pkcs7_verify error: %s", gnutls_strerror(rc)); + goto out; + } + + /* check passed */ + status = EFI_SUCCESS; + +out: + free_datum(signed_data); + free_datum(pkcs7_data); + if (tlist) { + gnutls_x509_trust_list_deinit(tlist, 1); + } + if (pkcs7) { + gnutls_pkcs7_deinit(pkcs7); + } + return status; +} diff --git a/hw/uefi/var-service-policy.c b/hw/uefi/var-service-policy.c new file mode 100644 index 0000000000..3b1155fe4e --- /dev/null +++ b/hw/uefi/var-service-policy.c @@ -0,0 +1,370 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * uefi vars device - VarCheckPolicyLibMmiHandler implementation + * + * variable policy specs: + * https://github.com/tianocore/edk2/blob/master/MdeModulePkg/Library/VariablePolicyLib/ReadMe.md + */ +#include "qemu/osdep.h" +#include "system/dma.h" +#include "migration/vmstate.h" + +#include "hw/uefi/var-service.h" +#include "hw/uefi/var-service-api.h" +#include "hw/uefi/var-service-edk2.h" + +#include "trace/trace-hw_uefi.h" + +static void calc_policy(uefi_var_policy *pol); + +static int uefi_var_policy_post_load(void *opaque, int version_id) +{ + uefi_var_policy *pol = opaque; + + calc_policy(pol); + return 0; +} + +const VMStateDescription vmstate_uefi_var_policy = { + .name = "uefi-var-policy", + .post_load = uefi_var_policy_post_load, + .fields = (VMStateField[]) { + VMSTATE_UINT32(entry_size, uefi_var_policy), + VMSTATE_VBUFFER_ALLOC_UINT32(entry, uefi_var_policy, + 0, NULL, entry_size), + VMSTATE_END_OF_LIST() + }, +}; + +static void print_policy_entry(variable_policy_entry *pe) +{ + uint16_t *name = (void *)pe + pe->offset_to_name; + + fprintf(stderr, "%s:\n", __func__); + + fprintf(stderr, " name ´"); + while (*name) { + fprintf(stderr, "%c", *name); + name++; + } + fprintf(stderr, "', version=%d.%d, size=%d\n", + pe->version >> 16, pe->version & 0xffff, pe->size); + + if (pe->min_size) { + fprintf(stderr, " size min=%d\n", pe->min_size); + } + if (pe->max_size != UINT32_MAX) { + fprintf(stderr, " size max=%u\n", pe->max_size); + } + if (pe->attributes_must_have) { + fprintf(stderr, " attr must=0x%x\n", pe->attributes_must_have); + } + if (pe->attributes_cant_have) { + fprintf(stderr, " attr cant=0x%x\n", pe->attributes_cant_have); + } + if (pe->lock_policy_type) { + fprintf(stderr, " lock policy type %d\n", pe->lock_policy_type); + } +} + +static gboolean wildcard_str_equal(uefi_var_policy *pol, + uefi_variable *var) +{ + return uefi_str_equal_ex(pol->name, pol->name_size, + var->name, var->name_size, + true); +} + +static uefi_var_policy *find_policy(uefi_vars_state *uv, QemuUUID guid, + uint16_t *name, uint64_t name_size) +{ + uefi_var_policy *pol; + + QTAILQ_FOREACH(pol, &uv->var_policies, next) { + if (!qemu_uuid_is_equal(&pol->entry->namespace, &guid)) { + continue; + } + if (!uefi_str_equal(pol->name, pol->name_size, + name, name_size)) { + continue; + } + return pol; + } + return NULL; +} + +static uefi_var_policy *wildcard_find_policy(uefi_vars_state *uv, + uefi_variable *var) +{ + uefi_var_policy *pol; + + QTAILQ_FOREACH(pol, &uv->var_policies, next) { + if (!qemu_uuid_is_equal(&pol->entry->namespace, &var->guid)) { + continue; + } + if (!wildcard_str_equal(pol, var)) { + continue; + } + return pol; + } + return NULL; +} + +static void calc_policy(uefi_var_policy *pol) +{ + variable_policy_entry *pe = pol->entry; + unsigned int i; + + pol->name = (void *)pol->entry + pe->offset_to_name; + pol->name_size = pe->size - pe->offset_to_name; + + for (i = 0; i < pol->name_size / 2; i++) { + if (pol->name[i] == '#') { + pol->hashmarks++; + } + } +} + +uefi_var_policy *uefi_vars_add_policy(uefi_vars_state *uv, + variable_policy_entry *pe) +{ + uefi_var_policy *pol, *p; + + pol = g_new0(uefi_var_policy, 1); + pol->entry = g_malloc(pe->size); + memcpy(pol->entry, pe, pe->size); + pol->entry_size = pe->size; + + calc_policy(pol); + + /* keep list sorted by priority, add to tail of priority group */ + QTAILQ_FOREACH(p, &uv->var_policies, next) { + if ((p->hashmarks > pol->hashmarks) || + (!p->name_size && pol->name_size)) { + QTAILQ_INSERT_BEFORE(p, pol, next); + return pol; + } + } + + QTAILQ_INSERT_TAIL(&uv->var_policies, pol, next); + return pol; +} + +efi_status uefi_vars_policy_check(uefi_vars_state *uv, + uefi_variable *var, + gboolean is_newvar) +{ + uefi_var_policy *pol; + variable_policy_entry *pe; + variable_lock_on_var_state *lvarstate; + uint16_t *lvarname; + size_t lvarnamesize; + uefi_variable *lvar; + + if (!uv->end_of_dxe) { + return EFI_SUCCESS; + } + + pol = wildcard_find_policy(uv, var); + if (!pol) { + return EFI_SUCCESS; + } + pe = pol->entry; + + uefi_trace_variable(__func__, var->guid, var->name, var->name_size); + print_policy_entry(pe); + + if ((var->attributes & pe->attributes_must_have) != pe->attributes_must_have) { + trace_uefi_vars_policy_deny("must-have-attr"); + return EFI_INVALID_PARAMETER; + } + if ((var->attributes & pe->attributes_cant_have) != 0) { + trace_uefi_vars_policy_deny("cant-have-attr"); + return EFI_INVALID_PARAMETER; + } + + if (var->data_size < pe->min_size) { + trace_uefi_vars_policy_deny("min-size"); + return EFI_INVALID_PARAMETER; + } + if (var->data_size > pe->max_size) { + trace_uefi_vars_policy_deny("max-size"); + return EFI_INVALID_PARAMETER; + } + + switch (pe->lock_policy_type) { + case VARIABLE_POLICY_TYPE_NO_LOCK: + break; + + case VARIABLE_POLICY_TYPE_LOCK_NOW: + trace_uefi_vars_policy_deny("lock-now"); + return EFI_WRITE_PROTECTED; + + case VARIABLE_POLICY_TYPE_LOCK_ON_CREATE: + if (!is_newvar) { + trace_uefi_vars_policy_deny("lock-on-create"); + return EFI_WRITE_PROTECTED; + } + break; + + case VARIABLE_POLICY_TYPE_LOCK_ON_VAR_STATE: + lvarstate = (void *)pol->entry + sizeof(*pe); + lvarname = (void *)pol->entry + sizeof(*pe) + sizeof(*lvarstate); + lvarnamesize = pe->offset_to_name - sizeof(*pe) - sizeof(*lvarstate); + + uefi_trace_variable(__func__, lvarstate->namespace, + lvarname, lvarnamesize); + lvar = uefi_vars_find_variable(uv, lvarstate->namespace, + lvarname, lvarnamesize); + if (lvar && lvar->data_size == 1) { + uint8_t *value = lvar->data; + if (lvarstate->value == *value) { + return EFI_WRITE_PROTECTED; + } + } + break; + } + + return EFI_SUCCESS; +} + +void uefi_vars_policies_clear(uefi_vars_state *uv) +{ + uefi_var_policy *pol; + + while (!QTAILQ_EMPTY(&uv->var_policies)) { + pol = QTAILQ_FIRST(&uv->var_policies); + QTAILQ_REMOVE(&uv->var_policies, pol, next); + g_free(pol->entry); + g_free(pol); + } +} + +static size_t uefi_vars_mm_policy_error(mm_header *mhdr, + mm_check_policy *mchk, + uint64_t status) +{ + mchk->result = status; + return sizeof(*mchk); +} + +static uint32_t uefi_vars_mm_check_policy_is_enabled(uefi_vars_state *uv, + mm_header *mhdr, + mm_check_policy *mchk, + void *func) +{ + mm_check_policy_is_enabled *mpar = func; + size_t length; + + length = sizeof(*mchk) + sizeof(*mpar); + if (mhdr->length < length) { + return uefi_vars_mm_policy_error(mhdr, mchk, EFI_BAD_BUFFER_SIZE); + } + + mpar->state = TRUE; + mchk->result = EFI_SUCCESS; + return sizeof(*mchk); +} + +static uint32_t uefi_vars_mm_check_policy_register(uefi_vars_state *uv, + mm_header *mhdr, + mm_check_policy *mchk, + void *func) +{ + variable_policy_entry *pe = func; + uefi_var_policy *pol; + uint64_t length; + + if (uadd64_overflow(sizeof(*mchk), pe->size, &length)) { + return uefi_vars_mm_policy_error(mhdr, mchk, EFI_BAD_BUFFER_SIZE); + } + if (mhdr->length < length) { + return uefi_vars_mm_policy_error(mhdr, mchk, EFI_BAD_BUFFER_SIZE); + } + if (pe->size < sizeof(*pe)) { + return uefi_vars_mm_policy_error(mhdr, mchk, EFI_BAD_BUFFER_SIZE); + } + if (pe->offset_to_name < sizeof(*pe)) { + return uefi_vars_mm_policy_error(mhdr, mchk, EFI_BAD_BUFFER_SIZE); + } + + if (pe->lock_policy_type == VARIABLE_POLICY_TYPE_LOCK_ON_VAR_STATE && + pe->offset_to_name < sizeof(*pe) + sizeof(variable_lock_on_var_state)) { + return uefi_vars_mm_policy_error(mhdr, mchk, EFI_BAD_BUFFER_SIZE); + } + + /* check space for minimum string length */ + if (pe->size < (size_t)pe->offset_to_name) { + return uefi_vars_mm_policy_error(mhdr, mchk, EFI_BAD_BUFFER_SIZE); + } + + if (!uefi_str_is_valid((void *)pe + pe->offset_to_name, + pe->size - pe->offset_to_name, + false)) { + return uefi_vars_mm_policy_error(mhdr, mchk, EFI_INVALID_PARAMETER); + } + + pol = find_policy(uv, pe->namespace, + (void *)pe + pe->offset_to_name, + pe->size - pe->offset_to_name); + if (pol) { + return uefi_vars_mm_policy_error(mhdr, mchk, EFI_ALREADY_STARTED); + } + + uefi_vars_add_policy(uv, pe); + + mchk->result = EFI_SUCCESS; + return sizeof(*mchk); +} + +uint32_t uefi_vars_mm_check_policy_proto(uefi_vars_state *uv) +{ + static const char *fnames[] = { + "zero", + "disable", + "is-enabled", + "register", + "dump", + "lock", + }; + const char *fname; + mm_header *mhdr = (mm_header *) uv->buffer; + mm_check_policy *mchk = (mm_check_policy *) (uv->buffer + sizeof(*mhdr)); + void *func = (uv->buffer + sizeof(*mhdr) + sizeof(*mchk)); + + if (mhdr->length < sizeof(*mchk)) { + return UEFI_VARS_STS_ERR_BAD_BUFFER_SIZE; + } + + fname = mchk->command < ARRAY_SIZE(fnames) + ? fnames[mchk->command] + : "unknown"; + trace_uefi_vars_policy_cmd(fname); + + switch (mchk->command) { + case VAR_CHECK_POLICY_COMMAND_DISABLE: + mchk->result = EFI_UNSUPPORTED; + break; + case VAR_CHECK_POLICY_COMMAND_IS_ENABLED: + uefi_vars_mm_check_policy_is_enabled(uv, mhdr, mchk, func); + break; + case VAR_CHECK_POLICY_COMMAND_REGISTER: + if (uv->policy_locked) { + mchk->result = EFI_WRITE_PROTECTED; + } else { + uefi_vars_mm_check_policy_register(uv, mhdr, mchk, func); + } + break; + case VAR_CHECK_POLICY_COMMAND_LOCK: + uv->policy_locked = true; + mchk->result = EFI_SUCCESS; + break; + default: + mchk->result = EFI_UNSUPPORTED; + break; + } + + uefi_trace_status(__func__, mchk->result); + return UEFI_VARS_STS_SUCCESS; +} diff --git a/hw/uefi/var-service-siglist.c b/hw/uefi/var-service-siglist.c new file mode 100644 index 0000000000..8948f1b784 --- /dev/null +++ b/hw/uefi/var-service-siglist.c @@ -0,0 +1,212 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * uefi vars device - parse and generate efi signature databases + */ + +#include "qemu/osdep.h" +#include "qemu/error-report.h" +#include "system/dma.h" + +#include "hw/uefi/var-service.h" + +/* + * Add x509 certificate to list (with duplicate check). + */ +static void uefi_vars_siglist_add_x509(uefi_vars_siglist *siglist, + QemuUUID *owner, + void *data, uint64_t size) +{ + uefi_vars_cert *c; + + QTAILQ_FOREACH(c, &siglist->x509, next) { + if (c->size != size) { + continue; + } + if (memcmp(c->data, data, size) != 0) { + continue; + } + return; + } + + c = g_malloc(sizeof(*c) + size); + c->owner = *owner; + c->size = size; + memcpy(c->data, data, size); + QTAILQ_INSERT_TAIL(&siglist->x509, c, next); +} + +/* + * Add sha256 hash to list (with duplicate check). + */ +static void uefi_vars_siglist_add_sha256(uefi_vars_siglist *siglist, + QemuUUID *owner, + void *data) +{ + uefi_vars_hash *h; + + QTAILQ_FOREACH(h, &siglist->sha256, next) { + if (memcmp(h->data, data, 32) != 0) { + continue; + } + return; + } + + h = g_malloc(sizeof(*h) + 32); + h->owner = *owner; + memcpy(h->data, data, 32); + QTAILQ_INSERT_TAIL(&siglist->sha256, h, next); +} + +void uefi_vars_siglist_init(uefi_vars_siglist *siglist) +{ + memset(siglist, 0, sizeof(*siglist)); + QTAILQ_INIT(&siglist->x509); + QTAILQ_INIT(&siglist->sha256); +} + +void uefi_vars_siglist_free(uefi_vars_siglist *siglist) +{ + uefi_vars_cert *c, *cs; + uefi_vars_hash *h, *hs; + + QTAILQ_FOREACH_SAFE(c, &siglist->x509, next, cs) { + QTAILQ_REMOVE(&siglist->x509, c, next); + g_free(c); + } + QTAILQ_FOREACH_SAFE(h, &siglist->sha256, next, hs) { + QTAILQ_REMOVE(&siglist->sha256, h, next); + g_free(h); + } +} + +/* + * Parse UEFI signature list. + */ +void uefi_vars_siglist_parse(uefi_vars_siglist *siglist, + void *data, uint64_t size) +{ + efi_siglist *efilist; + uint64_t start; + + while (size) { + if (size < sizeof(*efilist)) { + break; + } + efilist = data; + if (size < efilist->siglist_size) { + break; + } + + if (uadd64_overflow(sizeof(*efilist), efilist->header_size, &start)) { + break; + } + if (efilist->sig_size <= sizeof(QemuUUID)) { + break; + } + + if (qemu_uuid_is_equal(&efilist->guid_type, &EfiCertX509Guid)) { + if (start + efilist->sig_size != efilist->siglist_size) { + break; + } + uefi_vars_siglist_add_x509(siglist, + (QemuUUID *)(data + start), + data + start + sizeof(QemuUUID), + efilist->sig_size - sizeof(QemuUUID)); + + } else if (qemu_uuid_is_equal(&efilist->guid_type, &EfiCertSha256Guid)) { + if (efilist->sig_size != sizeof(QemuUUID) + 32) { + break; + } + if (start + efilist->sig_size > efilist->siglist_size) { + break; + } + while (start <= efilist->siglist_size - efilist->sig_size) { + uefi_vars_siglist_add_sha256(siglist, + (QemuUUID *)(data + start), + data + start + sizeof(QemuUUID)); + start += efilist->sig_size; + } + + } else { + QemuUUID be = qemu_uuid_bswap(efilist->guid_type); + char *str_uuid = qemu_uuid_unparse_strdup(&be); + warn_report("%s: unknown type (%s)", __func__, str_uuid); + g_free(str_uuid); + } + + data += efilist->siglist_size; + size -= efilist->siglist_size; + } +} + +uint64_t uefi_vars_siglist_blob_size(uefi_vars_siglist *siglist) +{ + uefi_vars_cert *c; + uefi_vars_hash *h; + uint64_t size = 0; + + QTAILQ_FOREACH(c, &siglist->x509, next) { + size += sizeof(efi_siglist) + sizeof(QemuUUID) + c->size; + } + + if (!QTAILQ_EMPTY(&siglist->sha256)) { + size += sizeof(efi_siglist); + QTAILQ_FOREACH(h, &siglist->sha256, next) { + size += sizeof(QemuUUID) + 32; + } + } + + return size; +} + +/* + * Generate UEFI signature list. + */ +void uefi_vars_siglist_blob_generate(uefi_vars_siglist *siglist, + void *data, uint64_t size) +{ + uefi_vars_cert *c; + uefi_vars_hash *h; + efi_siglist *efilist; + uint64_t pos = 0, start; + uint32_t i; + + QTAILQ_FOREACH(c, &siglist->x509, next) { + efilist = data + pos; + efilist->guid_type = EfiCertX509Guid; + efilist->sig_size = sizeof(QemuUUID) + c->size; + efilist->header_size = 0; + + start = pos + sizeof(efi_siglist); + memcpy(data + start, + &c->owner, sizeof(QemuUUID)); + memcpy(data + start + sizeof(QemuUUID), + c->data, c->size); + + efilist->siglist_size = sizeof(efi_siglist) + efilist->sig_size; + pos += efilist->siglist_size; + } + + if (!QTAILQ_EMPTY(&siglist->sha256)) { + efilist = data + pos; + efilist->guid_type = EfiCertSha256Guid; + efilist->sig_size = sizeof(QemuUUID) + 32; + efilist->header_size = 0; + + i = 0; + start = pos + sizeof(efi_siglist); + QTAILQ_FOREACH(h, &siglist->sha256, next) { + memcpy(data + start + efilist->sig_size * i, + &h->owner, sizeof(QemuUUID)); + memcpy(data + start + efilist->sig_size * i + sizeof(QemuUUID), + h->data, 32); + i++; + } + + efilist->siglist_size = sizeof(efi_siglist) + efilist->sig_size * i; + pos += efilist->siglist_size; + } + + assert(pos == size); +} diff --git a/hw/uefi/var-service-sysbus.c b/hw/uefi/var-service-sysbus.c new file mode 100644 index 0000000000..97da8672ee --- /dev/null +++ b/hw/uefi/var-service-sysbus.c @@ -0,0 +1,124 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * uefi vars device - sysbus variant. + */ +#include "qemu/osdep.h" +#include "migration/vmstate.h" + +#include "hw/qdev-properties.h" +#include "hw/sysbus.h" + +#include "hw/uefi/hardware-info.h" +#include "hw/uefi/var-service.h" +#include "hw/uefi/var-service-api.h" + +OBJECT_DECLARE_SIMPLE_TYPE(uefi_vars_sysbus_state, UEFI_VARS_SYSBUS) + +struct uefi_vars_sysbus_state { + SysBusDevice parent_obj; + struct uefi_vars_state state; +}; + +static const VMStateDescription vmstate_uefi_vars_sysbus = { + .name = TYPE_UEFI_VARS_SYSBUS, + .fields = (VMStateField[]) { + VMSTATE_STRUCT(state, uefi_vars_sysbus_state, 0, + vmstate_uefi_vars, uefi_vars_state), + VMSTATE_END_OF_LIST() + } +}; + +static const Property uefi_vars_sysbus_properties[] = { + DEFINE_PROP_SIZE("size", uefi_vars_sysbus_state, state.max_storage, + 256 * 1024), + DEFINE_PROP_STRING("jsonfile", uefi_vars_sysbus_state, state.jsonfile), + DEFINE_PROP_BOOL("force-secure-boot", uefi_vars_sysbus_state, + state.force_secure_boot, false), + DEFINE_PROP_BOOL("disable-custom-mode", uefi_vars_sysbus_state, + state.disable_custom_mode, false), + DEFINE_PROP_BOOL("use-pio", uefi_vars_sysbus_state, + state.use_pio, false), +}; + +static void uefi_vars_sysbus_init(Object *obj) +{ + uefi_vars_sysbus_state *uv = UEFI_VARS_SYSBUS(obj); + + uefi_vars_init(obj, &uv->state); +} + +static void uefi_vars_sysbus_reset(DeviceState *dev) +{ + uefi_vars_sysbus_state *uv = UEFI_VARS_SYSBUS(dev); + + uefi_vars_hard_reset(&uv->state); +} + +static void uefi_vars_sysbus_realize(DeviceState *dev, Error **errp) +{ + uefi_vars_sysbus_state *uv = UEFI_VARS_SYSBUS(dev); + SysBusDevice *sysbus = SYS_BUS_DEVICE(dev); + + sysbus_init_mmio(sysbus, &uv->state.mr); + uefi_vars_realize(&uv->state, errp); +} + +static void uefi_vars_sysbus_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = uefi_vars_sysbus_realize; + dc->vmsd = &vmstate_uefi_vars_sysbus; + dc->user_creatable = true; + device_class_set_legacy_reset(dc, uefi_vars_sysbus_reset); + device_class_set_props(dc, uefi_vars_sysbus_properties); + set_bit(DEVICE_CATEGORY_MISC, dc->categories); +} + +/* generic: hardware discovery via FDT */ +static const TypeInfo uefi_vars_sysbus_info = { + .name = TYPE_UEFI_VARS_SYSBUS, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(uefi_vars_sysbus_state), + .instance_init = uefi_vars_sysbus_init, + .class_init = uefi_vars_sysbus_class_init, +}; +module_obj(TYPE_UEFI_VARS_SYSBUS); + +static void uefi_vars_x64_realize(DeviceState *dev, Error **errp) +{ + HARDWARE_INFO_SIMPLE_DEVICE hwinfo = { + .mmio_address = cpu_to_le64(0xfef10000), + }; + SysBusDevice *sysbus = SYS_BUS_DEVICE(dev); + + uefi_vars_sysbus_realize(dev, errp); + + hardware_info_register(HardwareInfoQemuUefiVars, + &hwinfo, sizeof(hwinfo)); + sysbus_mmio_map(sysbus, 0, hwinfo.mmio_address); +} + +static void uefi_vars_x64_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = uefi_vars_x64_realize; +} + +/* x64: hardware discovery via etc/hardware-info fw_cfg */ +static const TypeInfo uefi_vars_x64_info = { + .name = TYPE_UEFI_VARS_X64, + .parent = TYPE_UEFI_VARS_SYSBUS, + .class_init = uefi_vars_x64_class_init, +}; +module_obj(TYPE_UEFI_VARS_X64); + +static void uefi_vars_sysbus_register_types(void) +{ + type_register_static(&uefi_vars_sysbus_info); + type_register_static(&uefi_vars_x64_info); +} + +type_init(uefi_vars_sysbus_register_types) diff --git a/hw/uefi/var-service-utils.c b/hw/uefi/var-service-utils.c new file mode 100644 index 0000000000..c9ef46570f --- /dev/null +++ b/hw/uefi/var-service-utils.c @@ -0,0 +1,241 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * uefi vars device - helper functions for ucs2 strings and tracing + */ +#include "qemu/osdep.h" +#include "system/dma.h" + +#include "hw/uefi/var-service.h" + +#include "trace/trace-hw_uefi.h" + +/* ------------------------------------------------------------------ */ + +/* + * string helper functions. + * + * Most of the time uefi ucs2 strings are NULL-terminated, except + * sometimes when they are not (for example in variable policies). + */ + +gboolean uefi_str_is_valid(const uint16_t *str, size_t len, + gboolean must_be_null_terminated) +{ + size_t pos = 0; + + for (;;) { + if (pos == len) { + if (must_be_null_terminated) { + return false; + } else { + return true; + } + } + switch (str[pos]) { + case 0: + /* end of string */ + return true; + case 0xd800 ... 0xdfff: + /* reject surrogates */ + return false; + default: + /* char is good, check next */ + break; + } + pos++; + } +} + +size_t uefi_strlen(const uint16_t *str, size_t len) +{ + size_t pos = 0; + + for (;;) { + if (pos == len) { + return pos; + } + if (str[pos] == 0) { + return pos; + } + pos++; + } +} + +gboolean uefi_str_equal_ex(const uint16_t *a, size_t alen, + const uint16_t *b, size_t blen, + gboolean wildcards_in_a) +{ + size_t pos = 0; + + alen = alen / 2; + blen = blen / 2; + for (;;) { + if (pos == alen && pos == blen) { + return true; + } + if (pos == alen && b[pos] == 0) { + return true; + } + if (pos == blen && a[pos] == 0) { + return true; + } + if (pos == alen || pos == blen) { + return false; + } + if (a[pos] == 0 && b[pos] == 0) { + return true; + } + + if (wildcards_in_a && a[pos] == '#') { + if (!isxdigit(b[pos])) { + return false; + } + } else { + if (a[pos] != b[pos]) { + return false; + } + } + pos++; + } +} + +gboolean uefi_str_equal(const uint16_t *a, size_t alen, + const uint16_t *b, size_t blen) +{ + return uefi_str_equal_ex(a, alen, b, blen, false); +} + +char *uefi_ucs2_to_ascii(const uint16_t *ucs2, uint64_t ucs2_size) +{ + char *str = g_malloc0(ucs2_size / 2 + 1); + int i; + + for (i = 0; i * 2 < ucs2_size; i++) { + if (ucs2[i] == 0) { + break; + } + if (ucs2[i] < 128) { + str[i] = ucs2[i]; + } else { + str[i] = '?'; + } + } + str[i] = 0; + return str; +} + +/* ------------------------------------------------------------------ */ +/* time helper functions */ + +int uefi_time_compare(efi_time *a, efi_time *b) +{ + if (a->year < b->year) { + return -1; + } + if (a->year > b->year) { + return 1; + } + + if (a->month < b->month) { + return -1; + } + if (a->month > b->month) { + return 1; + } + + if (a->day < b->day) { + return -1; + } + if (a->day > b->day) { + return 1; + } + + if (a->hour < b->hour) { + return -1; + } + if (a->hour > b->hour) { + return 1; + } + + if (a->minute < b->minute) { + return -1; + } + if (a->minute > b->minute) { + return 1; + } + + if (a->second < b->second) { + return -1; + } + if (a->second > b->second) { + return 1; + } + + if (a->nanosecond < b->nanosecond) { + return -1; + } + if (a->nanosecond > b->nanosecond) { + return 1; + } + + return 0; +} + +/* ------------------------------------------------------------------ */ +/* tracing helper functions */ + +void uefi_trace_variable(const char *action, QemuUUID guid, + const uint16_t *name, uint64_t name_size) +{ + QemuUUID be = qemu_uuid_bswap(guid); + char *str_uuid = qemu_uuid_unparse_strdup(&be); + char *str_name = uefi_ucs2_to_ascii(name, name_size); + + trace_uefi_variable(action, str_name, name_size, str_uuid); + + g_free(str_name); + g_free(str_uuid); +} + +void uefi_trace_status(const char *action, efi_status status) +{ + switch (status) { + case EFI_SUCCESS: + trace_uefi_status(action, "success"); + break; + case EFI_INVALID_PARAMETER: + trace_uefi_status(action, "invalid parameter"); + break; + case EFI_UNSUPPORTED: + trace_uefi_status(action, "unsupported"); + break; + case EFI_BAD_BUFFER_SIZE: + trace_uefi_status(action, "bad buffer size"); + break; + case EFI_BUFFER_TOO_SMALL: + trace_uefi_status(action, "buffer too small"); + break; + case EFI_WRITE_PROTECTED: + trace_uefi_status(action, "write protected"); + break; + case EFI_OUT_OF_RESOURCES: + trace_uefi_status(action, "out of resources"); + break; + case EFI_NOT_FOUND: + trace_uefi_status(action, "not found"); + break; + case EFI_ACCESS_DENIED: + trace_uefi_status(action, "access denied"); + break; + case EFI_ALREADY_STARTED: + trace_uefi_status(action, "already started"); + break; + case EFI_SECURITY_VIOLATION: + trace_uefi_status(action, "security violation"); + break; + default: + trace_uefi_status(action, "unknown error"); + break; + } +} diff --git a/hw/uefi/var-service-vars.c b/hw/uefi/var-service-vars.c new file mode 100644 index 0000000000..7f98d77a38 --- /dev/null +++ b/hw/uefi/var-service-vars.c @@ -0,0 +1,725 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * uefi vars device - EfiSmmVariableProtocol implementation + */ +#include "qemu/osdep.h" +#include "qemu/error-report.h" +#include "system/dma.h" +#include "migration/vmstate.h" + +#include "hw/uefi/var-service.h" +#include "hw/uefi/var-service-api.h" +#include "hw/uefi/var-service-edk2.h" + +#include "trace/trace-hw_uefi.h" + +#define EFI_VARIABLE_ATTRIBUTE_SUPPORTED \ + (EFI_VARIABLE_NON_VOLATILE | \ + EFI_VARIABLE_BOOTSERVICE_ACCESS | \ + EFI_VARIABLE_RUNTIME_ACCESS | \ + EFI_VARIABLE_HARDWARE_ERROR_RECORD | \ + EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS | \ + EFI_VARIABLE_APPEND_WRITE) + + +const VMStateDescription vmstate_uefi_time = { + .name = "uefi-time", + .fields = (VMStateField[]) { + VMSTATE_UINT16(year, efi_time), + VMSTATE_UINT8(month, efi_time), + VMSTATE_UINT8(day, efi_time), + VMSTATE_UINT8(hour, efi_time), + VMSTATE_UINT8(minute, efi_time), + VMSTATE_UINT8(second, efi_time), + VMSTATE_UINT32(nanosecond, efi_time), + VMSTATE_END_OF_LIST() + }, +}; + +const VMStateDescription vmstate_uefi_variable = { + .name = "uefi-variable", + .fields = (VMStateField[]) { + VMSTATE_UINT8_ARRAY_V(guid.data, uefi_variable, sizeof(QemuUUID), 0), + VMSTATE_UINT32(name_size, uefi_variable), + VMSTATE_UINT32(data_size, uefi_variable), + VMSTATE_UINT32(attributes, uefi_variable), + VMSTATE_VBUFFER_ALLOC_UINT32(name, uefi_variable, 0, NULL, name_size), + VMSTATE_VBUFFER_ALLOC_UINT32(data, uefi_variable, 0, NULL, data_size), + VMSTATE_STRUCT(time, uefi_variable, 0, vmstate_uefi_time, efi_time), + VMSTATE_END_OF_LIST() + }, +}; + +uefi_variable *uefi_vars_find_variable(uefi_vars_state *uv, QemuUUID guid, + const uint16_t *name, uint64_t name_size) +{ + uefi_variable *var; + + QTAILQ_FOREACH(var, &uv->variables, next) { + if (!uefi_str_equal(var->name, var->name_size, + name, name_size)) { + continue; + } + if (!qemu_uuid_is_equal(&var->guid, &guid)) { + continue; + } + if (!var->data_size) { + /* in process of being created/updated */ + continue; + } + return var; + } + return NULL; +} + +static uefi_variable *add_variable(uefi_vars_state *uv, QemuUUID guid, + const uint16_t *name, uint64_t name_size, + uint32_t attributes) +{ + uefi_variable *var; + + var = g_new0(uefi_variable, 1); + var->guid = guid; + var->name = g_malloc(name_size); + memcpy(var->name, name, name_size); + var->name_size = name_size; + var->attributes = attributes; + + var->attributes &= ~EFI_VARIABLE_APPEND_WRITE; + + QTAILQ_INSERT_TAIL(&uv->variables, var, next); + return var; +} + +static void del_variable(uefi_vars_state *uv, uefi_variable *var) +{ + if (!var) { + return; + } + + QTAILQ_REMOVE(&uv->variables, var, next); + g_free(var->data); + g_free(var->name); + g_free(var->digest); + g_free(var); +} + +static size_t variable_size(uefi_variable *var) +{ + size_t size; + + size = sizeof(*var); + size += var->name_size; + size += var->data_size; + size += var->digest_size; + return size; +} + +void uefi_vars_set_variable(uefi_vars_state *uv, QemuUUID guid, + const uint16_t *name, uint64_t name_size, + uint32_t attributes, + void *data, uint64_t data_size) +{ + uefi_variable *old_var, *new_var; + + uefi_trace_variable(__func__, guid, name, name_size); + + old_var = uefi_vars_find_variable(uv, guid, name, name_size); + if (old_var) { + uv->used_storage -= variable_size(old_var); + del_variable(uv, old_var); + } + + new_var = add_variable(uv, guid, name, name_size, attributes); + new_var->data = g_malloc(data_size); + new_var->data_size = data_size; + memcpy(new_var->data, data, data_size); + uv->used_storage += variable_size(new_var); +} + +void uefi_vars_clear_volatile(uefi_vars_state *uv) +{ + uefi_variable *var, *n; + + QTAILQ_FOREACH_SAFE(var, &uv->variables, next, n) { + if (var->attributes & EFI_VARIABLE_NON_VOLATILE) { + continue; + } + uv->used_storage -= variable_size(var); + del_variable(uv, var); + } +} + +void uefi_vars_clear_all(uefi_vars_state *uv) +{ + uefi_variable *var, *n; + + QTAILQ_FOREACH_SAFE(var, &uv->variables, next, n) { + del_variable(uv, var); + } + uv->used_storage = 0; +} + +void uefi_vars_update_storage(uefi_vars_state *uv) +{ + uefi_variable *var; + + uv->used_storage = 0; + QTAILQ_FOREACH(var, &uv->variables, next) { + uv->used_storage += variable_size(var); + } +} + +static gboolean check_access(uefi_vars_state *uv, uefi_variable *var) +{ + if (!uv->exit_boot_service) { + if (!(var->attributes & EFI_VARIABLE_BOOTSERVICE_ACCESS)) { + return false; + } + } else { + if (!(var->attributes & EFI_VARIABLE_RUNTIME_ACCESS)) { + return false; + } + } + return true; +} + +static efi_status check_update(uefi_vars_state *uv, uefi_variable *old_var, + uefi_variable *new_var) +{ + efi_status status; + + if (old_var) { + if (!check_access(uv, old_var)) { + return EFI_ACCESS_DENIED; + } + } + + if (new_var) { + if (new_var->attributes & ~EFI_VARIABLE_ATTRIBUTE_SUPPORTED) { + return EFI_UNSUPPORTED; + } + if (!check_access(uv, new_var)) { + return EFI_ACCESS_DENIED; + } + } + + if (old_var && new_var) { + if (old_var->attributes != new_var->attributes) { + return EFI_INVALID_PARAMETER; + } + } + + if (new_var) { + /* create + update */ + status = uefi_vars_policy_check(uv, new_var, old_var == NULL); + } else { + /* delete */ + g_assert(old_var); + status = uefi_vars_policy_check(uv, old_var, false); + } + if (status != EFI_SUCCESS) { + return status; + } + + status = uefi_vars_check_secure_boot(uv, new_var ?: old_var); + if (status != EFI_SUCCESS) { + return status; + } + + return EFI_SUCCESS; +} + +static void append_write(uefi_variable *old_var, + uefi_variable *new_var) +{ + uefi_vars_siglist siglist; + uint64_t size; + void *data; + + uefi_vars_siglist_init(&siglist); + uefi_vars_siglist_parse(&siglist, old_var->data, old_var->data_size); + uefi_vars_siglist_parse(&siglist, new_var->data, new_var->data_size); + + size = uefi_vars_siglist_blob_size(&siglist); + data = g_malloc(size); + uefi_vars_siglist_blob_generate(&siglist, data, size); + + g_free(new_var->data); + new_var->data = data; + new_var->data_size = size; + + uefi_vars_siglist_free(&siglist); +} + +static size_t uefi_vars_mm_error(mm_header *mhdr, mm_variable *mvar, + uint64_t status) +{ + mvar->status = status; + return sizeof(*mvar); +} + +static size_t uefi_vars_mm_get_variable(uefi_vars_state *uv, mm_header *mhdr, + mm_variable *mvar, void *func) +{ + mm_variable_access *va = func; + uint16_t *name; + void *data; + uefi_variable *var; + uint64_t length; + + length = sizeof(*mvar) + sizeof(*va); + if (mhdr->length < length) { + return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); + } + + if (va->name_size > uv->max_storage || + va->data_size > uv->max_storage) { + return uefi_vars_mm_error(mhdr, mvar, EFI_OUT_OF_RESOURCES); + } + + name = func + sizeof(*va); + if (uadd64_overflow(length, va->name_size, &length)) { + return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); + } + if (mhdr->length < length) { + return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); + } + + if (!uefi_str_is_valid(name, va->name_size, true)) { + return uefi_vars_mm_error(mhdr, mvar, EFI_INVALID_PARAMETER); + } + + uefi_trace_variable(__func__, va->guid, name, va->name_size); + + var = uefi_vars_find_variable(uv, va->guid, name, va->name_size); + if (!var) { + return uefi_vars_mm_error(mhdr, mvar, EFI_NOT_FOUND); + } + + /* check permissions etc. */ + if (!check_access(uv, var)) { + return uefi_vars_mm_error(mhdr, mvar, EFI_ACCESS_DENIED); + } + + data = func + sizeof(*va) + va->name_size; + if (uadd64_overflow(length, va->data_size, &length)) { + return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); + } + if (uv->buf_size < length) { + return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); + } + + va->attributes = var->attributes; + if (va->data_size < var->data_size) { + va->data_size = var->data_size; + length -= va->data_size; + mvar->status = EFI_BUFFER_TOO_SMALL; + } else { + va->data_size = var->data_size; + memcpy(data, var->data, var->data_size); + mvar->status = EFI_SUCCESS; + } + return length; +} + +static size_t +uefi_vars_mm_get_next_variable(uefi_vars_state *uv, mm_header *mhdr, + mm_variable *mvar, void *func) +{ + mm_next_variable *nv = func; + uefi_variable *var; + uint16_t *name; + uint64_t length; + + length = sizeof(*mvar) + sizeof(*nv); + if (mhdr->length < length) { + return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); + } + + if (nv->name_size > uv->max_storage) { + return uefi_vars_mm_error(mhdr, mvar, EFI_OUT_OF_RESOURCES); + } + + name = func + sizeof(*nv); + if (uadd64_overflow(length, nv->name_size, &length)) { + return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); + } + if (mhdr->length < length) { + return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); + } + + if (!uefi_str_is_valid(name, nv->name_size, true)) { + return uefi_vars_mm_error(mhdr, mvar, EFI_INVALID_PARAMETER); + } + + if (uefi_strlen(name, nv->name_size) == 0) { + /* empty string -> first */ + var = QTAILQ_FIRST(&uv->variables); + if (!var) { + return uefi_vars_mm_error(mhdr, mvar, EFI_NOT_FOUND); + } + } else { + var = uefi_vars_find_variable(uv, nv->guid, name, nv->name_size); + if (!var) { + return uefi_vars_mm_error(mhdr, mvar, EFI_INVALID_PARAMETER); + } + do { + var = QTAILQ_NEXT(var, next); + } while (var && !check_access(uv, var)); + if (!var) { + return uefi_vars_mm_error(mhdr, mvar, EFI_NOT_FOUND); + } + } + + length = sizeof(*mvar) + sizeof(*nv) + var->name_size; + if (uv->buf_size < length) { + return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); + } + + nv->guid = var->guid; + nv->name_size = var->name_size; + memcpy(name, var->name, var->name_size); + mvar->status = EFI_SUCCESS; + return length; +} + +static bool uefi_vars_mm_digest_compare(uefi_variable *old_var, + uefi_variable *new_var) +{ + if (!old_var->digest || + !new_var->digest || + !old_var->digest_size || + !new_var->digest_size) { + /* should not happen */ + trace_uefi_vars_security_violation("inconsistent authvar digest state"); + return false; + } + if (old_var->digest_size != new_var->digest_size) { + trace_uefi_vars_security_violation("authvar digest size mismatch"); + return false; + } + if (memcmp(old_var->digest, new_var->digest, + old_var->digest_size) != 0) { + trace_uefi_vars_security_violation("authvar digest data mismatch"); + return false; + } + return true; +} + +static size_t uefi_vars_mm_set_variable(uefi_vars_state *uv, mm_header *mhdr, + mm_variable *mvar, void *func) +{ + mm_variable_access *va = func; + uint32_t attributes = 0; + uint16_t *name; + void *data; + uefi_variable *old_var, *new_var; + uint64_t length; + size_t new_storage; + efi_status status; + + length = sizeof(*mvar) + sizeof(*va); + if (mhdr->length < length) { + return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); + } + + if (va->name_size > uv->max_storage || + va->data_size > uv->max_storage) { + return uefi_vars_mm_error(mhdr, mvar, EFI_OUT_OF_RESOURCES); + } + + name = func + sizeof(*va); + if (uadd64_overflow(length, va->name_size, &length)) { + return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); + } + if (mhdr->length < length) { + return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); + } + + data = func + sizeof(*va) + va->name_size; + if (uadd64_overflow(length, va->data_size, &length)) { + return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); + } + if (mhdr->length < length) { + return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); + } + + g_assert(va->name_size < G_MAXUINT32); + g_assert(va->data_size < G_MAXUINT32); + + if (!uefi_str_is_valid(name, va->name_size, true)) { + return uefi_vars_mm_error(mhdr, mvar, EFI_INVALID_PARAMETER); + } + + uefi_trace_variable(__func__, va->guid, name, va->name_size); + + old_var = uefi_vars_find_variable(uv, va->guid, name, va->name_size); + if (va->data_size) { + new_var = add_variable(uv, va->guid, name, va->name_size, + va->attributes); + if (va->attributes & EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS) { + /* not implemented (deprecated in uefi spec) */ + warn_report("%s: AUTHENTICATED_WRITE_ACCESS", __func__); + mvar->status = EFI_UNSUPPORTED; + goto rollback; + } else if (va->attributes & + EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS) { + status = uefi_vars_check_auth_2(uv, new_var, va, data); + if (status != EFI_SUCCESS) { + mvar->status = status; + goto rollback; + } + if (old_var && new_var) { + if (uefi_time_compare(&old_var->time, &new_var->time) > 0) { + trace_uefi_vars_security_violation("time check failed"); + mvar->status = EFI_SECURITY_VIOLATION; + goto rollback; + } + if (old_var->digest_size || new_var->digest_size) { + if (!uefi_vars_mm_digest_compare(old_var, new_var)) { + mvar->status = EFI_SECURITY_VIOLATION; + goto rollback; + } + } + } + } else { + new_var->data = g_malloc(va->data_size); + memcpy(new_var->data, data, va->data_size); + new_var->data_size = va->data_size; + } + if (!new_var->data) { + /* we land here when deleting authenticated variables */ + del_variable(uv, new_var); + new_var = NULL; + } + } else { + new_var = NULL; + } + + if (!old_var && !new_var) { + /* delete non-existing variable -> nothing to do */ + mvar->status = EFI_SUCCESS; + return sizeof(*mvar); + } + + /* check permissions etc. */ + status = check_update(uv, old_var, new_var); + if (status != EFI_SUCCESS) { + mvar->status = status; + goto rollback; + } + + if (va->attributes & EFI_VARIABLE_APPEND_WRITE && old_var && new_var) { + /* merge signature databases */ + if (!uefi_vars_is_sb_any(new_var)) { + mvar->status = EFI_UNSUPPORTED; + goto rollback; + } + append_write(old_var, new_var); + } + + /* check storage space */ + new_storage = uv->used_storage; + if (old_var) { + new_storage -= variable_size(old_var); + } + if (new_var) { + new_storage += variable_size(new_var); + } + if (new_storage > uv->max_storage) { + mvar->status = EFI_OUT_OF_RESOURCES; + goto rollback; + } + + attributes = new_var + ? new_var->attributes + : old_var->attributes; + + /* all good, commit */ + del_variable(uv, old_var); + uv->used_storage = new_storage; + + if (attributes & EFI_VARIABLE_NON_VOLATILE) { + uefi_vars_json_save(uv); + } + + if (new_var && uefi_vars_is_sb_pk(new_var)) { + uefi_vars_auth_init(uv); + } + + mvar->status = EFI_SUCCESS; + return sizeof(*mvar); + +rollback: + del_variable(uv, new_var); + return sizeof(*mvar); +} + +static size_t uefi_vars_mm_variable_info(uefi_vars_state *uv, mm_header *mhdr, + mm_variable *mvar, void *func) +{ + mm_variable_info *vi = func; + uint64_t length; + + length = sizeof(*mvar) + sizeof(*vi); + if (uv->buf_size < length) { + return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); + } + + vi->max_storage_size = uv->max_storage; + vi->free_storage_size = uv->max_storage - uv->used_storage; + vi->max_variable_size = uv->max_storage >> 2; + vi->attributes = 0; + + mvar->status = EFI_SUCCESS; + return length; +} + +static size_t +uefi_vars_mm_get_payload_size(uefi_vars_state *uv, mm_header *mhdr, + mm_variable *mvar, void *func) +{ + mm_get_payload_size *ps = func; + uint64_t length; + + length = sizeof(*mvar) + sizeof(*ps); + if (uv->buf_size < length) { + return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); + } + + ps->payload_size = uv->buf_size; + mvar->status = EFI_SUCCESS; + return length; +} + +static size_t +uefi_vars_mm_lock_variable(uefi_vars_state *uv, mm_header *mhdr, + mm_variable *mvar, void *func) +{ + mm_lock_variable *lv = func; + variable_policy_entry *pe; + uint16_t *name, *dest; + uint64_t length; + + length = sizeof(*mvar) + sizeof(*lv); + if (mhdr->length < length) { + return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); + } + + name = func + sizeof(*lv); + if (uadd64_overflow(length, lv->name_size, &length)) { + return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); + } + if (mhdr->length < length) { + return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); + } + + uefi_trace_variable(__func__, lv->guid, name, lv->name_size); + + pe = g_malloc0(sizeof(*pe) + lv->name_size); + pe->version = VARIABLE_POLICY_ENTRY_REVISION; + pe->size = sizeof(*pe) + lv->name_size; + pe->offset_to_name = sizeof(*pe); + pe->namespace = lv->guid; + pe->min_size = 0; + pe->max_size = UINT32_MAX; + pe->attributes_must_have = 0; + pe->attributes_cant_have = 0; + pe->lock_policy_type = VARIABLE_POLICY_TYPE_LOCK_NOW; + + dest = (void *)pe + pe->offset_to_name; + memcpy(dest, name, lv->name_size); + + uefi_vars_add_policy(uv, pe); + g_free(pe); + + mvar->status = EFI_SUCCESS; + return length; +} + +uint32_t uefi_vars_mm_vars_proto(uefi_vars_state *uv) +{ + static const char *fnames[] = { + "zero", + "get-variable", + "get-next-variable-name", + "set-variable", + "query-variable-info", + "ready-to-boot", + "exit-boot-service", + "get-statistics", + "lock-variable", + "var-check-prop-set", + "var-check-prop-get", + "get-payload-size", + "init-runtime-cache-contect", + "sync-runtime-cache", + "get-runtime-cache-info", + }; + const char *fname; + uint64_t length; + + mm_header *mhdr = (mm_header *) uv->buffer; + mm_variable *mvar = (mm_variable *) (uv->buffer + sizeof(*mhdr)); + void *func = (uv->buffer + sizeof(*mhdr) + sizeof(*mvar)); + + if (mhdr->length < sizeof(*mvar)) { + return UEFI_VARS_STS_ERR_BAD_BUFFER_SIZE; + } + + fname = mvar->function < ARRAY_SIZE(fnames) + ? fnames[mvar->function] + : "unknown"; + trace_uefi_vars_proto_cmd(fname); + + switch (mvar->function) { + case SMM_VARIABLE_FUNCTION_GET_VARIABLE: + length = uefi_vars_mm_get_variable(uv, mhdr, mvar, func); + break; + + case SMM_VARIABLE_FUNCTION_GET_NEXT_VARIABLE_NAME: + length = uefi_vars_mm_get_next_variable(uv, mhdr, mvar, func); + break; + + case SMM_VARIABLE_FUNCTION_SET_VARIABLE: + length = uefi_vars_mm_set_variable(uv, mhdr, mvar, func); + break; + + case SMM_VARIABLE_FUNCTION_QUERY_VARIABLE_INFO: + length = uefi_vars_mm_variable_info(uv, mhdr, mvar, func); + break; + + case SMM_VARIABLE_FUNCTION_LOCK_VARIABLE: + length = uefi_vars_mm_lock_variable(uv, mhdr, mvar, func); + break; + + case SMM_VARIABLE_FUNCTION_GET_PAYLOAD_SIZE: + length = uefi_vars_mm_get_payload_size(uv, mhdr, mvar, func); + break; + + case SMM_VARIABLE_FUNCTION_READY_TO_BOOT: + trace_uefi_event("ready-to-boot"); + uv->ready_to_boot = true; + length = 0; + break; + + case SMM_VARIABLE_FUNCTION_EXIT_BOOT_SERVICE: + trace_uefi_event("exit-boot-service"); + uv->exit_boot_service = true; + length = 0; + break; + + default: + length = uefi_vars_mm_error(mhdr, mvar, EFI_UNSUPPORTED); + break; + } + + if (mhdr->length < length) { + mvar->status = EFI_BUFFER_TOO_SMALL; + } + + uefi_trace_status(__func__, mvar->status); + return UEFI_VARS_STS_SUCCESS; +} diff --git a/hw/ufs/ufs.c b/hw/ufs/ufs.c index 1ccd6f88b6..857de6e9c2 100644 --- a/hw/ufs/ufs.c +++ b/hw/ufs/ufs.c @@ -34,6 +34,11 @@ #define UFS_MAX_NUTMRS 8 #define UFS_MCQ_QCFGPTR 2 +/* Each value represents the temperature in celsius as (value - 80) */ +#define UFS_TEMPERATURE 120 +#define UFS_TOO_HIGH_TEMP_BOUNDARY 160 +#define UFS_TOO_LOW_TEMP_BOUNDARY 60 + static void ufs_exec_req(UfsRequest *req); static void ufs_clear_req(UfsRequest *req); @@ -838,6 +843,42 @@ static const MemoryRegionOps ufs_mmio_ops = { }, }; +static void ufs_update_ee_status(UfsHc *u) +{ + uint16_t ee_status = be16_to_cpu(u->attributes.exception_event_status); + uint8_t high_temp_thresh = u->attributes.device_too_high_temp_boundary; + uint8_t low_temp_thresh = u->attributes.device_too_low_temp_boundary; + + if (u->temperature >= high_temp_thresh) { + ee_status |= MASK_EE_TOO_HIGH_TEMP; + } else { + ee_status &= ~MASK_EE_TOO_HIGH_TEMP; + } + + if (u->temperature <= low_temp_thresh) { + ee_status |= MASK_EE_TOO_LOW_TEMP; + } else { + ee_status &= ~MASK_EE_TOO_LOW_TEMP; + } + + u->attributes.exception_event_status = cpu_to_be16(ee_status); +} + +static bool ufs_check_exception_event_alert(UfsHc *u, uint8_t trans_type) +{ + uint16_t ee_control = be16_to_cpu(u->attributes.exception_event_control); + uint16_t ee_status; + + if (trans_type != UFS_UPIU_TRANSACTION_RESPONSE) { + return false; + } + + ufs_update_ee_status(u); + + ee_status = be16_to_cpu(u->attributes.exception_event_status); + + return ee_control & ee_status; +} void ufs_build_upiu_header(UfsRequest *req, uint8_t trans_type, uint8_t flags, uint8_t response, uint8_t scsi_status, @@ -848,6 +889,8 @@ void ufs_build_upiu_header(UfsRequest *req, uint8_t trans_type, uint8_t flags, req->rsp_upiu.header.flags = flags; req->rsp_upiu.header.response = response; req->rsp_upiu.header.scsi_status = scsi_status; + req->rsp_upiu.header.device_inf = + ufs_check_exception_event_alert(req->hc, trans_type); req->rsp_upiu.header.data_segment_length = cpu_to_be16(data_segment_length); } @@ -1042,6 +1085,25 @@ static QueryRespCode ufs_exec_query_flag(UfsRequest *req, int op) return UFS_QUERY_RESULT_SUCCESS; } +static inline uint8_t ufs_read_device_temp(UfsHc *u) +{ + uint8_t feat_sup = u->device_desc.ufs_features_support; + bool high_temp_sup, low_temp_sup, high_temp_en, low_temp_en; + uint16_t ee_control = be16_to_cpu(u->attributes.exception_event_control); + + high_temp_sup = feat_sup & UFS_DEV_HIGH_TEMP_NOTIF; + low_temp_sup = feat_sup & UFS_DEV_LOW_TEMP_NOTIF; + high_temp_en = ee_control & MASK_EE_TOO_HIGH_TEMP; + low_temp_en = ee_control & MASK_EE_TOO_LOW_TEMP; + + if ((high_temp_sup && high_temp_en) || + (low_temp_sup && low_temp_en)) { + return u->temperature; + } + + return 0; +} + static uint32_t ufs_read_attr_value(UfsHc *u, uint8_t idn) { switch (idn) { @@ -1072,6 +1134,7 @@ static uint32_t ufs_read_attr_value(UfsHc *u, uint8_t idn) case UFS_QUERY_ATTR_IDN_EE_CONTROL: return be16_to_cpu(u->attributes.exception_event_control); case UFS_QUERY_ATTR_IDN_EE_STATUS: + ufs_update_ee_status(u); return be16_to_cpu(u->attributes.exception_event_status); case UFS_QUERY_ATTR_IDN_SECONDS_PASSED: return be32_to_cpu(u->attributes.seconds_passed); @@ -1086,7 +1149,8 @@ static uint32_t ufs_read_attr_value(UfsHc *u, uint8_t idn) case UFS_QUERY_ATTR_IDN_REF_CLK_GATING_WAIT_TIME: return u->attributes.ref_clk_gating_wait_time; case UFS_QUERY_ATTR_IDN_CASE_ROUGH_TEMP: - return u->attributes.device_case_rough_temperaure; + u->attributes.device_case_rough_temperature = ufs_read_device_temp(u); + return u->attributes.device_case_rough_temperature; case UFS_QUERY_ATTR_IDN_HIGH_TEMP_BOUND: return u->attributes.device_too_high_temp_boundary; case UFS_QUERY_ATTR_IDN_LOW_TEMP_BOUND: @@ -1677,8 +1741,12 @@ static void ufs_init_hc(UfsHc *u) u->device_desc.ud_0_base_offset = 0x16; u->device_desc.ud_config_p_length = 0x1A; u->device_desc.device_rtt_cap = 0x02; + u->device_desc.ufs_features_support = UFS_DEV_HIGH_TEMP_NOTIF | + UFS_DEV_LOW_TEMP_NOTIF; u->device_desc.queue_depth = u->params.nutrs; u->device_desc.product_revision_level = 0x04; + u->device_desc.extended_ufs_features_support = + cpu_to_be32(UFS_DEV_HIGH_TEMP_NOTIF | UFS_DEV_LOW_TEMP_NOTIF); memset(&u->geometry_desc, 0, sizeof(GeometryDescriptor)); u->geometry_desc.length = sizeof(GeometryDescriptor); @@ -1702,9 +1770,17 @@ static void ufs_init_hc(UfsHc *u) /* configure descriptor is not supported */ u->attributes.config_descr_lock = 0x01; u->attributes.max_num_of_rtt = 0x02; + u->attributes.device_too_high_temp_boundary = UFS_TOO_HIGH_TEMP_BOUNDARY; + u->attributes.device_too_low_temp_boundary = UFS_TOO_LOW_TEMP_BOUNDARY; memset(&u->flags, 0, sizeof(u->flags)); u->flags.permanently_disable_fw_update = 1; + + /* + * The temperature value is fixed to UFS_TEMPERATURE and does not change + * dynamically + */ + u->temperature = UFS_TEMPERATURE; } static void ufs_realize(PCIDevice *pci_dev, Error **errp) diff --git a/hw/ufs/ufs.h b/hw/ufs/ufs.h index 4bcc41f53a..3799d97f30 100644 --- a/hw/ufs/ufs.h +++ b/hw/ufs/ufs.h @@ -146,6 +146,8 @@ typedef struct UfsHc { /* MCQ properties */ UfsSq *sq[UFS_MAX_MCQ_QNUM]; UfsCq *cq[UFS_MAX_MCQ_QNUM]; + + uint8_t temperature; } UfsHc; static inline uint32_t ufs_mcq_sq_tail(UfsHc *u, uint32_t qid) diff --git a/hw/usb/hcd-xhci-pci.c b/hw/usb/hcd-xhci-pci.c index 49642aab58..d908eb787d 100644 --- a/hw/usb/hcd-xhci-pci.c +++ b/hw/usb/hcd-xhci-pci.c @@ -82,6 +82,21 @@ static bool xhci_pci_intr_raise(XHCIState *xhci, int n, bool level) return false; } +static bool xhci_pci_intr_mapping_conditional(XHCIState *xhci) +{ + XHCIPciState *s = container_of(xhci, XHCIPciState, xhci); + PCIDevice *pci_dev = PCI_DEVICE(s); + + /* + * Implementation of the "conditional-intr-mapping" property, which only + * enables interrupter mapping if MSI or MSI-X is available and active. + * Forces all events onto interrupter/event ring 0 in pin-based IRQ mode. + * Provides compatibility with macOS guests on machine types where MSI(-X) + * is not available. + */ + return msix_enabled(pci_dev) || msi_enabled(pci_dev); +} + static void xhci_pci_reset(DeviceState *dev) { XHCIPciState *s = XHCI_PCI(dev); @@ -119,6 +134,9 @@ static void usb_xhci_pci_realize(struct PCIDevice *dev, Error **errp) object_property_set_link(OBJECT(&s->xhci), "host", OBJECT(s), NULL); s->xhci.intr_update = xhci_pci_intr_update; s->xhci.intr_raise = xhci_pci_intr_raise; + if (s->conditional_intr_mapping) { + s->xhci.intr_mapping_supported = xhci_pci_intr_mapping_conditional; + } if (!qdev_realize(DEVICE(&s->xhci), NULL, errp)) { return; } @@ -201,6 +219,8 @@ static void xhci_instance_init(Object *obj) static const Property xhci_pci_properties[] = { DEFINE_PROP_ON_OFF_AUTO("msi", XHCIPciState, msi, ON_OFF_AUTO_AUTO), DEFINE_PROP_ON_OFF_AUTO("msix", XHCIPciState, msix, ON_OFF_AUTO_AUTO), + DEFINE_PROP_BOOL("conditional-intr-mapping", XHCIPciState, + conditional_intr_mapping, false), }; static void xhci_class_init(ObjectClass *klass, void *data) @@ -215,6 +235,10 @@ static void xhci_class_init(ObjectClass *klass, void *data) k->exit = usb_xhci_pci_exit; k->class_id = PCI_CLASS_SERIAL_USB; device_class_set_props(dc, xhci_pci_properties); + object_class_property_set_description(klass, "conditional-intr-mapping", + "When true, disables interrupter mapping for pin-based IRQ mode. " + "Intended to be used with guest drivers with questionable behaviour, " + "such as macOS's."); } static const TypeInfo xhci_pci_info = { diff --git a/hw/usb/hcd-xhci-pci.h b/hw/usb/hcd-xhci-pci.h index 08f70ce97c..5b61ae8455 100644 --- a/hw/usb/hcd-xhci-pci.h +++ b/hw/usb/hcd-xhci-pci.h @@ -40,6 +40,7 @@ typedef struct XHCIPciState { XHCIState xhci; OnOffAuto msi; OnOffAuto msix; + bool conditional_intr_mapping; } XHCIPciState; #endif diff --git a/hw/usb/hcd-xhci.c b/hw/usb/hcd-xhci.c index 00d5bc3779..64c3a23b9b 100644 --- a/hw/usb/hcd-xhci.c +++ b/hw/usb/hcd-xhci.c @@ -644,7 +644,8 @@ static void xhci_event(XHCIState *xhci, XHCIEvent *event, int v) dma_addr_t erdp; unsigned int dp_idx; - if (xhci->numintrs == 1) { + if (xhci->numintrs == 1 || + (xhci->intr_mapping_supported && !xhci->intr_mapping_supported(xhci))) { v = 0; } diff --git a/hw/usb/hcd-xhci.h b/hw/usb/hcd-xhci.h index 9609b83514..9c3974f148 100644 --- a/hw/usb/hcd-xhci.h +++ b/hw/usb/hcd-xhci.h @@ -193,6 +193,11 @@ typedef struct XHCIState { uint32_t max_pstreams_mask; void (*intr_update)(XHCIState *s, int n, bool enable); bool (*intr_raise)(XHCIState *s, int n, bool level); + /* + * Callback for special-casing interrupter mapping support. NULL for most + * implementations, for defaulting to enabled mapping unless numintrs == 1. + */ + bool (*intr_mapping_supported)(XHCIState *s); DeviceState *hostOpaque; /* Operational Registers */ diff --git a/hw/vmapple/Kconfig b/hw/vmapple/Kconfig new file mode 100644 index 0000000000..2382b29767 --- /dev/null +++ b/hw/vmapple/Kconfig @@ -0,0 +1,34 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +config VMAPPLE_AES + bool + +config VMAPPLE_BDIF + bool + +config VMAPPLE_CFG + bool + +config VMAPPLE_VIRTIO_BLK + bool + +config VMAPPLE + bool + depends on ARM + depends on HVF + default y if ARM + imply PCI_DEVICES + select ARM_GICV3 + select PLATFORM_BUS + select PCI_EXPRESS + select PCI_EXPRESS_GENERIC_BRIDGE + select PL011 # UART + select PL031 # RTC + select PL061 # GPIO + select GPIO_PWR + select PVPANIC_MMIO + select VMAPPLE_AES + select VMAPPLE_BDIF + select VMAPPLE_CFG + select MAC_PVG_MMIO + select VMAPPLE_VIRTIO_BLK diff --git a/hw/vmapple/aes.c b/hw/vmapple/aes.c new file mode 100644 index 0000000000..3a7641ab4b --- /dev/null +++ b/hw/vmapple/aes.c @@ -0,0 +1,581 @@ +/* + * QEMU Apple AES device emulation + * + * Copyright © 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "trace.h" +#include "crypto/hash.h" +#include "crypto/aes.h" +#include "crypto/cipher.h" +#include "hw/irq.h" +#include "hw/sysbus.h" +#include "hw/vmapple/vmapple.h" +#include "migration/vmstate.h" +#include "qemu/cutils.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "system/dma.h" + +OBJECT_DECLARE_SIMPLE_TYPE(AESState, APPLE_AES) + +#define MAX_FIFO_SIZE 9 + +#define CMD_KEY 0x1 +#define CMD_KEY_CONTEXT_SHIFT 27 +#define CMD_KEY_CONTEXT_MASK (0x1 << CMD_KEY_CONTEXT_SHIFT) +#define CMD_KEY_SELECT_MAX_IDX 0x7 +#define CMD_KEY_SELECT_SHIFT 24 +#define CMD_KEY_SELECT_MASK (CMD_KEY_SELECT_MAX_IDX << CMD_KEY_SELECT_SHIFT) +#define CMD_KEY_KEY_LEN_NUM 4u +#define CMD_KEY_KEY_LEN_SHIFT 22 +#define CMD_KEY_KEY_LEN_MASK ((CMD_KEY_KEY_LEN_NUM - 1u) << CMD_KEY_KEY_LEN_SHIFT) +#define CMD_KEY_ENCRYPT_SHIFT 20 +#define CMD_KEY_ENCRYPT_MASK (0x1 << CMD_KEY_ENCRYPT_SHIFT) +#define CMD_KEY_BLOCK_MODE_SHIFT 16 +#define CMD_KEY_BLOCK_MODE_MASK (0x3 << CMD_KEY_BLOCK_MODE_SHIFT) +#define CMD_IV 0x2 +#define CMD_IV_CONTEXT_SHIFT 26 +#define CMD_IV_CONTEXT_MASK (0x3 << CMD_KEY_CONTEXT_SHIFT) +#define CMD_DSB 0x3 +#define CMD_SKG 0x4 +#define CMD_DATA 0x5 +#define CMD_DATA_KEY_CTX_SHIFT 27 +#define CMD_DATA_KEY_CTX_MASK (0x1 << CMD_DATA_KEY_CTX_SHIFT) +#define CMD_DATA_IV_CTX_SHIFT 25 +#define CMD_DATA_IV_CTX_MASK (0x3 << CMD_DATA_IV_CTX_SHIFT) +#define CMD_DATA_LEN_MASK 0xffffff +#define CMD_STORE_IV 0x6 +#define CMD_STORE_IV_ADDR_MASK 0xffffff +#define CMD_WRITE_REG 0x7 +#define CMD_FLAG 0x8 +#define CMD_FLAG_STOP_MASK BIT(26) +#define CMD_FLAG_RAISE_IRQ_MASK BIT(27) +#define CMD_FLAG_INFO_MASK 0xff +#define CMD_MAX 0x10 + +#define CMD_SHIFT 28 + +#define REG_STATUS 0xc +#define REG_STATUS_DMA_READ_RUNNING BIT(0) +#define REG_STATUS_DMA_READ_PENDING BIT(1) +#define REG_STATUS_DMA_WRITE_RUNNING BIT(2) +#define REG_STATUS_DMA_WRITE_PENDING BIT(3) +#define REG_STATUS_BUSY BIT(4) +#define REG_STATUS_EXECUTING BIT(5) +#define REG_STATUS_READY BIT(6) +#define REG_STATUS_TEXT_DPA_SEEDED BIT(7) +#define REG_STATUS_UNWRAP_DPA_SEEDED BIT(8) + +#define REG_IRQ_STATUS 0x18 +#define REG_IRQ_STATUS_INVALID_CMD BIT(2) +#define REG_IRQ_STATUS_FLAG BIT(5) +#define REG_IRQ_ENABLE 0x1c +#define REG_WATERMARK 0x20 +#define REG_Q_STATUS 0x24 +#define REG_FLAG_INFO 0x30 +#define REG_FIFO 0x200 + +static const uint32_t key_lens[CMD_KEY_KEY_LEN_NUM] = { + [0] = 16, + [1] = 24, + [2] = 32, + [3] = 64, +}; + +typedef struct Key { + uint32_t key_len; + uint8_t key[32]; +} Key; + +typedef struct IV { + uint32_t iv[4]; +} IV; + +static Key builtin_keys[CMD_KEY_SELECT_MAX_IDX + 1] = { + [1] = { + .key_len = 32, + .key = { 0x1 }, + }, + [2] = { + .key_len = 32, + .key = { 0x2 }, + }, + [3] = { + .key_len = 32, + .key = { 0x3 }, + } +}; + +struct AESState { + SysBusDevice parent_obj; + + qemu_irq irq; + MemoryRegion iomem1; + MemoryRegion iomem2; + AddressSpace *as; + + uint32_t status; + uint32_t q_status; + uint32_t irq_status; + uint32_t irq_enable; + uint32_t watermark; + uint32_t flag_info; + uint32_t fifo[MAX_FIFO_SIZE]; + uint32_t fifo_idx; + Key key[2]; + IV iv[4]; + bool is_encrypt; + QCryptoCipherMode block_mode; +}; + +static void aes_update_irq(AESState *s) +{ + qemu_set_irq(s->irq, !!(s->irq_status & s->irq_enable)); +} + +static uint64_t aes1_read(void *opaque, hwaddr offset, unsigned size) +{ + AESState *s = opaque; + uint64_t res = 0; + + switch (offset) { + case REG_STATUS: + res = s->status; + break; + case REG_IRQ_STATUS: + res = s->irq_status; + break; + case REG_IRQ_ENABLE: + res = s->irq_enable; + break; + case REG_WATERMARK: + res = s->watermark; + break; + case REG_Q_STATUS: + res = s->q_status; + break; + case REG_FLAG_INFO: + res = s->flag_info; + break; + + default: + qemu_log_mask(LOG_UNIMP, "%s: Unknown AES MMIO offset %" PRIx64 "\n", + __func__, offset); + break; + } + + trace_aes_read(offset, res); + + return res; +} + +static void fifo_append(AESState *s, uint64_t val) +{ + if (s->fifo_idx == MAX_FIFO_SIZE) { + /* Exceeded the FIFO. Bail out */ + return; + } + + s->fifo[s->fifo_idx++] = val; +} + +static bool has_payload(AESState *s, uint32_t elems) +{ + return s->fifo_idx >= elems + 1; +} + +static bool cmd_key(AESState *s) +{ + uint32_t cmd = s->fifo[0]; + uint32_t key_select = (cmd & CMD_KEY_SELECT_MASK) >> CMD_KEY_SELECT_SHIFT; + uint32_t ctxt = (cmd & CMD_KEY_CONTEXT_MASK) >> CMD_KEY_CONTEXT_SHIFT; + uint32_t key_len; + + switch ((cmd & CMD_KEY_BLOCK_MODE_MASK) >> CMD_KEY_BLOCK_MODE_SHIFT) { + case 0: + s->block_mode = QCRYPTO_CIPHER_MODE_ECB; + break; + case 1: + s->block_mode = QCRYPTO_CIPHER_MODE_CBC; + break; + default: + return false; + } + + s->is_encrypt = cmd & CMD_KEY_ENCRYPT_MASK; + key_len = key_lens[(cmd & CMD_KEY_KEY_LEN_MASK) >> CMD_KEY_KEY_LEN_SHIFT]; + + if (key_select) { + trace_aes_cmd_key_select_builtin(ctxt, key_select, + s->is_encrypt ? "en" : "de", + QCryptoCipherMode_str(s->block_mode)); + s->key[ctxt] = builtin_keys[key_select]; + } else { + trace_aes_cmd_key_select_new(ctxt, key_len, + s->is_encrypt ? "en" : "de", + QCryptoCipherMode_str(s->block_mode)); + if (key_len > sizeof(s->key[ctxt].key)) { + return false; + } + if (!has_payload(s, key_len / sizeof(uint32_t))) { + /* wait for payload */ + qemu_log_mask(LOG_GUEST_ERROR, "%s: No payload\n", __func__); + return false; + } + memcpy(&s->key[ctxt].key, &s->fifo[1], key_len); + s->key[ctxt].key_len = key_len; + } + + return true; +} + +static bool cmd_iv(AESState *s) +{ + uint32_t cmd = s->fifo[0]; + uint32_t ctxt = (cmd & CMD_IV_CONTEXT_MASK) >> CMD_IV_CONTEXT_SHIFT; + + if (!has_payload(s, 4)) { + /* wait for payload */ + return false; + } + memcpy(&s->iv[ctxt].iv, &s->fifo[1], sizeof(s->iv[ctxt].iv)); + trace_aes_cmd_iv(ctxt, s->fifo[1], s->fifo[2], s->fifo[3], s->fifo[4]); + + return true; +} + +static void dump_data(const char *desc, const void *p, size_t len) +{ + static const size_t MAX_LEN = 0x1000; + char hex[MAX_LEN * 2 + 1] = ""; + + if (len > MAX_LEN) { + return; + } + + qemu_hexdump_to_buffer(hex, sizeof(hex), p, len); + trace_aes_dump_data(desc, hex); +} + +static bool cmd_data(AESState *s) +{ + uint32_t cmd = s->fifo[0]; + uint32_t ctxt_iv = 0; + uint32_t ctxt_key = (cmd & CMD_DATA_KEY_CTX_MASK) >> CMD_DATA_KEY_CTX_SHIFT; + uint32_t len = cmd & CMD_DATA_LEN_MASK; + uint64_t src_addr = s->fifo[2]; + uint64_t dst_addr = s->fifo[3]; + QCryptoCipherAlgo alg; + g_autoptr(QCryptoCipher) cipher = NULL; + g_autoptr(GByteArray) src = NULL; + g_autoptr(GByteArray) dst = NULL; + MemTxResult r; + + src_addr |= ((uint64_t)s->fifo[1] << 16) & 0xffff00000000ULL; + dst_addr |= ((uint64_t)s->fifo[1] << 32) & 0xffff00000000ULL; + + trace_aes_cmd_data(ctxt_key, ctxt_iv, src_addr, dst_addr, len); + + if (!has_payload(s, 3)) { + /* wait for payload */ + qemu_log_mask(LOG_GUEST_ERROR, "%s: No payload\n", __func__); + return false; + } + + if (ctxt_key >= ARRAY_SIZE(s->key) || + ctxt_iv >= ARRAY_SIZE(s->iv)) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Invalid key or iv\n", __func__); + return false; + } + + src = g_byte_array_sized_new(len); + g_byte_array_set_size(src, len); + dst = g_byte_array_sized_new(len); + g_byte_array_set_size(dst, len); + + r = dma_memory_read(s->as, src_addr, src->data, len, MEMTXATTRS_UNSPECIFIED); + if (r != MEMTX_OK) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: DMA read of %"PRIu32" bytes " + "from 0x%"PRIx64" failed. (r=%d)\n", + __func__, len, src_addr, r); + return false; + } + + dump_data("cmd_data(): src_data=", src->data, len); + + switch (s->key[ctxt_key].key_len) { + case 128 / 8: + alg = QCRYPTO_CIPHER_ALGO_AES_128; + break; + case 192 / 8: + alg = QCRYPTO_CIPHER_ALGO_AES_192; + break; + case 256 / 8: + alg = QCRYPTO_CIPHER_ALGO_AES_256; + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, "%s: Invalid key length\n", __func__); + return false; + } + cipher = qcrypto_cipher_new(alg, s->block_mode, + s->key[ctxt_key].key, + s->key[ctxt_key].key_len, NULL); + if (!cipher) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Failed to create cipher object\n", + __func__); + return false; + } + if (s->block_mode != QCRYPTO_CIPHER_MODE_ECB) { + if (qcrypto_cipher_setiv(cipher, (void *)s->iv[ctxt_iv].iv, + sizeof(s->iv[ctxt_iv].iv), NULL) != 0) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Failed to set IV\n", __func__); + return false; + } + } + if (s->is_encrypt) { + if (qcrypto_cipher_encrypt(cipher, src->data, dst->data, len, NULL) != 0) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Encryption failed\n", __func__); + return false; + } + } else { + if (qcrypto_cipher_decrypt(cipher, src->data, dst->data, len, NULL) != 0) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Decryption failed\n", __func__); + return false; + } + } + + dump_data("cmd_data(): dst_data=", dst->data, len); + r = dma_memory_write(s->as, dst_addr, dst->data, len, MEMTXATTRS_UNSPECIFIED); + if (r != MEMTX_OK) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: DMA write of %"PRIu32" bytes " + "to 0x%"PRIx64" failed. (r=%d)\n", + __func__, len, src_addr, r); + return false; + } + + return true; +} + +static bool cmd_store_iv(AESState *s) +{ + uint32_t cmd = s->fifo[0]; + uint32_t ctxt = (cmd & CMD_IV_CONTEXT_MASK) >> CMD_IV_CONTEXT_SHIFT; + uint64_t addr = s->fifo[1]; + MemTxResult dma_result; + + if (!has_payload(s, 1)) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: No payload\n", __func__); + return false; + } + + if (ctxt >= ARRAY_SIZE(s->iv)) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Invalid context. ctxt = %u, allowed: 0..%zu\n", + __func__, ctxt, ARRAY_SIZE(s->iv) - 1); + return false; + } + + addr |= ((uint64_t)cmd << 32) & 0xff00000000ULL; + dma_result = dma_memory_write(&address_space_memory, addr, + &s->iv[ctxt].iv, sizeof(s->iv[ctxt].iv), + MEMTXATTRS_UNSPECIFIED); + + trace_aes_cmd_store_iv(ctxt, addr, s->iv[ctxt].iv[0], s->iv[ctxt].iv[1], + s->iv[ctxt].iv[2], s->iv[ctxt].iv[3]); + + return dma_result == MEMTX_OK; +} + +static bool cmd_flag(AESState *s) +{ + uint32_t cmd = s->fifo[0]; + uint32_t raise_irq = cmd & CMD_FLAG_RAISE_IRQ_MASK; + + /* We always process data when it's coming in, so fire an IRQ immediately */ + if (raise_irq) { + s->irq_status |= REG_IRQ_STATUS_FLAG; + } + + s->flag_info = cmd & CMD_FLAG_INFO_MASK; + + trace_aes_cmd_flag(!!raise_irq, s->flag_info); + + return true; +} + +static void fifo_process(AESState *s) +{ + uint32_t cmd = s->fifo[0] >> CMD_SHIFT; + bool success = false; + + if (!s->fifo_idx) { + return; + } + + switch (cmd) { + case CMD_KEY: + success = cmd_key(s); + break; + case CMD_IV: + success = cmd_iv(s); + break; + case CMD_DATA: + success = cmd_data(s); + break; + case CMD_STORE_IV: + success = cmd_store_iv(s); + break; + case CMD_FLAG: + success = cmd_flag(s); + break; + default: + s->irq_status |= REG_IRQ_STATUS_INVALID_CMD; + break; + } + + if (success) { + s->fifo_idx = 0; + } + + trace_aes_fifo_process(cmd, success); +} + +static void aes1_write(void *opaque, hwaddr offset, uint64_t val, unsigned size) +{ + AESState *s = opaque; + + trace_aes_write(offset, val); + + switch (offset) { + case REG_IRQ_STATUS: + s->irq_status &= ~val; + break; + case REG_IRQ_ENABLE: + s->irq_enable = val; + break; + case REG_FIFO: + fifo_append(s, val); + fifo_process(s); + break; + default: + qemu_log_mask(LOG_UNIMP, + "%s: Unknown AES MMIO offset %"PRIx64", data %"PRIx64"\n", + __func__, offset, val); + return; + } + + aes_update_irq(s); +} + +static const MemoryRegionOps aes1_ops = { + .read = aes1_read, + .write = aes1_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 8, + }, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static uint64_t aes2_read(void *opaque, hwaddr offset, unsigned size) +{ + uint64_t res = 0; + + switch (offset) { + case 0: + res = 0; + break; + default: + qemu_log_mask(LOG_UNIMP, + "%s: Unknown AES MMIO 2 offset %"PRIx64"\n", + __func__, offset); + break; + } + + trace_aes_2_read(offset, res); + + return res; +} + +static void aes2_write(void *opaque, hwaddr offset, uint64_t val, unsigned size) +{ + trace_aes_2_write(offset, val); + + switch (offset) { + default: + qemu_log_mask(LOG_UNIMP, + "%s: Unknown AES MMIO 2 offset %"PRIx64", data %"PRIx64"\n", + __func__, offset, val); + return; + } +} + +static const MemoryRegionOps aes2_ops = { + .read = aes2_read, + .write = aes2_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 8, + }, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static void aes_reset(Object *obj, ResetType type) +{ + AESState *s = APPLE_AES(obj); + + s->status = 0x3f80; + s->q_status = 2; + s->irq_status = 0; + s->irq_enable = 0; + s->watermark = 0; +} + +static void aes_init(Object *obj) +{ + AESState *s = APPLE_AES(obj); + + memory_region_init_io(&s->iomem1, obj, &aes1_ops, s, TYPE_APPLE_AES, 0x4000); + memory_region_init_io(&s->iomem2, obj, &aes2_ops, s, TYPE_APPLE_AES, 0x4000); + sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem1); + sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem2); + sysbus_init_irq(SYS_BUS_DEVICE(s), &s->irq); + s->as = &address_space_memory; +} + +static void aes_class_init(ObjectClass *klass, void *data) +{ + ResettableClass *rc = RESETTABLE_CLASS(klass); + + rc->phases.hold = aes_reset; +} + +static const TypeInfo aes_info = { + .name = TYPE_APPLE_AES, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(AESState), + .class_init = aes_class_init, + .instance_init = aes_init, +}; + +static void aes_register_types(void) +{ + type_register_static(&aes_info); +} + +type_init(aes_register_types) diff --git a/hw/vmapple/bdif.c b/hw/vmapple/bdif.c new file mode 100644 index 0000000000..5827dd2aab --- /dev/null +++ b/hw/vmapple/bdif.c @@ -0,0 +1,274 @@ +/* + * VMApple Backdoor Interface + * + * Copyright © 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/units.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "trace.h" +#include "hw/vmapple/vmapple.h" +#include "hw/sysbus.h" +#include "hw/block/block.h" +#include "qapi/error.h" +#include "system/block-backend.h" +#include "system/dma.h" + +OBJECT_DECLARE_SIMPLE_TYPE(VMAppleBdifState, VMAPPLE_BDIF) + +struct VMAppleBdifState { + SysBusDevice parent_obj; + + BlockBackend *aux; + BlockBackend *root; + MemoryRegion mmio; +}; + +#define VMAPPLE_BDIF_SIZE 0x00200000 + +#define REG_DEVID_MASK 0xffff0000 +#define DEVID_ROOT 0x00000000 +#define DEVID_AUX 0x00010000 +#define DEVID_USB 0x00100000 + +#define REG_STATUS 0x0 +#define REG_STATUS_ACTIVE BIT(0) +#define REG_CFG 0x4 +#define REG_CFG_ACTIVE BIT(1) +#define REG_UNK1 0x8 +#define REG_BUSY 0x10 +#define REG_BUSY_READY BIT(0) +#define REG_UNK2 0x400 +#define REG_CMD 0x408 +#define REG_NEXT_DEVICE 0x420 +#define REG_UNK3 0x434 + +typedef struct VblkSector { + uint32_t pad; + uint32_t pad2; + uint32_t sector; + uint32_t pad3; +} VblkSector; + +typedef struct VblkReqCmd { + uint64_t addr; + uint32_t len; + uint32_t flags; +} VblkReqCmd; + +typedef struct VblkReq { + VblkReqCmd sector; + VblkReqCmd data; + VblkReqCmd retval; +} VblkReq; + +#define VBLK_DATA_FLAGS_READ 0x00030001 +#define VBLK_DATA_FLAGS_WRITE 0x00010001 + +#define VBLK_RET_SUCCESS 0 +#define VBLK_RET_FAILED 1 + +static uint64_t bdif_read(void *opaque, hwaddr offset, unsigned size) +{ + uint64_t ret = -1; + uint64_t devid = offset & REG_DEVID_MASK; + + switch (offset & ~REG_DEVID_MASK) { + case REG_STATUS: + ret = REG_STATUS_ACTIVE; + break; + case REG_CFG: + ret = REG_CFG_ACTIVE; + break; + case REG_UNK1: + ret = 0x420; + break; + case REG_BUSY: + ret = REG_BUSY_READY; + break; + case REG_UNK2: + ret = 0x1; + break; + case REG_UNK3: + ret = 0x0; + break; + case REG_NEXT_DEVICE: + switch (devid) { + case DEVID_ROOT: + ret = 0x8000000; + break; + case DEVID_AUX: + ret = 0x10000; + break; + } + break; + } + + trace_bdif_read(offset, size, ret); + return ret; +} + +static void le2cpu_sector(VblkSector *sector) +{ + sector->sector = le32_to_cpu(sector->sector); +} + +static void le2cpu_reqcmd(VblkReqCmd *cmd) +{ + cmd->addr = le64_to_cpu(cmd->addr); + cmd->len = le32_to_cpu(cmd->len); + cmd->flags = le32_to_cpu(cmd->flags); +} + +static void le2cpu_req(VblkReq *req) +{ + le2cpu_reqcmd(&req->sector); + le2cpu_reqcmd(&req->data); + le2cpu_reqcmd(&req->retval); +} + +static void vblk_cmd(uint64_t devid, BlockBackend *blk, uint64_t gp_addr, + uint64_t static_off) +{ + VblkReq req; + VblkSector sector; + uint64_t off = 0; + g_autofree char *buf = NULL; + uint8_t ret = VBLK_RET_FAILED; + int r; + MemTxResult dma_result; + + dma_result = dma_memory_read(&address_space_memory, gp_addr, + &req, sizeof(req), MEMTXATTRS_UNSPECIFIED); + if (dma_result != MEMTX_OK) { + goto out; + } + + le2cpu_req(&req); + + if (req.sector.len != sizeof(sector)) { + goto out; + } + + /* Read the vblk command */ + dma_result = dma_memory_read(&address_space_memory, req.sector.addr, + §or, sizeof(sector), + MEMTXATTRS_UNSPECIFIED); + if (dma_result != MEMTX_OK) { + goto out; + } + le2cpu_sector(§or); + + off = sector.sector * 512ULL + static_off; + + /* Sanity check that we're not allocating bogus sizes */ + if (req.data.len > 128 * MiB) { + goto out; + } + + buf = g_malloc0(req.data.len); + switch (req.data.flags) { + case VBLK_DATA_FLAGS_READ: + r = blk_pread(blk, off, req.data.len, buf, 0); + trace_bdif_vblk_read(devid == DEVID_AUX ? "aux" : "root", + req.data.addr, off, req.data.len, r); + if (r < 0) { + goto out; + } + dma_result = dma_memory_write(&address_space_memory, req.data.addr, buf, + req.data.len, MEMTXATTRS_UNSPECIFIED); + if (dma_result == MEMTX_OK) { + ret = VBLK_RET_SUCCESS; + } + break; + case VBLK_DATA_FLAGS_WRITE: + /* Not needed, iBoot only reads */ + break; + default: + break; + } + +out: + dma_memory_write(&address_space_memory, req.retval.addr, &ret, 1, + MEMTXATTRS_UNSPECIFIED); +} + +static void bdif_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + VMAppleBdifState *s = opaque; + uint64_t devid = (offset & REG_DEVID_MASK); + + trace_bdif_write(offset, size, value); + + switch (offset & ~REG_DEVID_MASK) { + case REG_CMD: + switch (devid) { + case DEVID_ROOT: + vblk_cmd(devid, s->root, value, 0x0); + break; + case DEVID_AUX: + vblk_cmd(devid, s->aux, value, 0x0); + break; + } + break; + } +} + +static const MemoryRegionOps bdif_ops = { + .read = bdif_read, + .write = bdif_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 1, + .max_access_size = 8, + }, + .impl = { + .min_access_size = 1, + .max_access_size = 8, + }, +}; + +static void bdif_init(Object *obj) +{ + VMAppleBdifState *s = VMAPPLE_BDIF(obj); + + memory_region_init_io(&s->mmio, obj, &bdif_ops, obj, + "VMApple Backdoor Interface", VMAPPLE_BDIF_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio); +} + +static const Property bdif_properties[] = { + DEFINE_PROP_DRIVE("aux", VMAppleBdifState, aux), + DEFINE_PROP_DRIVE("root", VMAppleBdifState, root), +}; + +static void bdif_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->desc = "VMApple Backdoor Interface"; + device_class_set_props(dc, bdif_properties); +} + +static const TypeInfo bdif_info = { + .name = TYPE_VMAPPLE_BDIF, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(VMAppleBdifState), + .instance_init = bdif_init, + .class_init = bdif_class_init, +}; + +static void bdif_register_types(void) +{ + type_register_static(&bdif_info); +} + +type_init(bdif_register_types) diff --git a/hw/vmapple/cfg.c b/hw/vmapple/cfg.c new file mode 100644 index 0000000000..63414d801f --- /dev/null +++ b/hw/vmapple/cfg.c @@ -0,0 +1,195 @@ +/* + * VMApple Configuration Region + * + * Copyright © 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#include "qemu/osdep.h" +#include "hw/vmapple/vmapple.h" +#include "hw/sysbus.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "qapi/error.h" +#include "net/net.h" + +OBJECT_DECLARE_SIMPLE_TYPE(VMAppleCfgState, VMAPPLE_CFG) + +#define VMAPPLE_CFG_SIZE 0x00010000 + +typedef struct VMAppleCfg { + uint32_t version; /* 0x000 */ + uint32_t nr_cpus; /* 0x004 */ + uint32_t unk1; /* 0x008 */ + uint32_t unk2; /* 0x00c */ + uint32_t unk3; /* 0x010 */ + uint32_t unk4; /* 0x014 */ + uint64_t ecid; /* 0x018 */ + uint64_t ram_size; /* 0x020 */ + uint32_t run_installer1; /* 0x028 */ + uint32_t unk5; /* 0x02c */ + uint32_t unk6; /* 0x030 */ + uint32_t run_installer2; /* 0x034 */ + uint32_t rnd; /* 0x038 */ + uint32_t unk7; /* 0x03c */ + MACAddr mac_en0; /* 0x040 */ + uint8_t pad1[2]; + MACAddr mac_en1; /* 0x048 */ + uint8_t pad2[2]; + MACAddr mac_wifi0; /* 0x050 */ + uint8_t pad3[2]; + MACAddr mac_bt0; /* 0x058 */ + uint8_t pad4[2]; + uint8_t reserved[0xa0]; /* 0x060 */ + uint32_t cpu_ids[0x80]; /* 0x100 */ + uint8_t scratch[0x200]; /* 0x180 */ + char serial[32]; /* 0x380 */ + char unk8[32]; /* 0x3a0 */ + char model[32]; /* 0x3c0 */ + uint8_t unk9[32]; /* 0x3e0 */ + uint32_t unk10; /* 0x400 */ + char soc_name[32]; /* 0x404 */ +} VMAppleCfg; + +struct VMAppleCfgState { + SysBusDevice parent_obj; + VMAppleCfg cfg; + + MemoryRegion mem; + char *serial; + char *model; + char *soc_name; +}; + +static void vmapple_cfg_reset(Object *obj, ResetType type) +{ + VMAppleCfgState *s = VMAPPLE_CFG(obj); + VMAppleCfg *cfg; + + cfg = memory_region_get_ram_ptr(&s->mem); + memset(cfg, 0, VMAPPLE_CFG_SIZE); + *cfg = s->cfg; +} + +static bool set_fixlen_property_or_error(char *restrict dst, + const char *restrict src, + size_t dst_size, Error **errp, + const char *property_name) +{ + ERRP_GUARD(); + size_t len; + + len = g_strlcpy(dst, src, dst_size); + if (len < dst_size) { /* len does not count nul terminator */ + return true; + } + + error_setg(errp, "Provided value too long for property '%s'", property_name); + error_append_hint(errp, "length (%zu) exceeds maximum of %zu\n", + len, dst_size - 1); + return false; +} + +#define set_fixlen_property_or_return(dst_array, src, errp, property_name) \ + do { \ + if (!set_fixlen_property_or_error((dst_array), (src), \ + ARRAY_SIZE(dst_array), \ + (errp), (property_name))) { \ + return; \ + } \ + } while (0) + +static void vmapple_cfg_realize(DeviceState *dev, Error **errp) +{ + VMAppleCfgState *s = VMAPPLE_CFG(dev); + uint32_t i; + + if (!s->serial) { + s->serial = g_strdup("1234"); + } + if (!s->model) { + s->model = g_strdup("VM0001"); + } + if (!s->soc_name) { + s->soc_name = g_strdup("Apple M1 (Virtual)"); + } + + set_fixlen_property_or_return(s->cfg.serial, s->serial, errp, "serial"); + set_fixlen_property_or_return(s->cfg.model, s->model, errp, "model"); + set_fixlen_property_or_return(s->cfg.soc_name, s->soc_name, errp, "soc_name"); + set_fixlen_property_or_return(s->cfg.unk8, "D/A", errp, "unk8"); + s->cfg.version = 2; + s->cfg.unk1 = 1; + s->cfg.unk2 = 1; + s->cfg.unk3 = 0x20; + s->cfg.unk4 = 0; + s->cfg.unk5 = 1; + s->cfg.unk6 = 1; + s->cfg.unk7 = 0; + s->cfg.unk10 = 1; + + if (s->cfg.nr_cpus > ARRAY_SIZE(s->cfg.cpu_ids)) { + error_setg(errp, + "Failed to create %u CPUs, vmapple machine supports %zu max", + s->cfg.nr_cpus, ARRAY_SIZE(s->cfg.cpu_ids)); + return; + } + for (i = 0; i < s->cfg.nr_cpus; i++) { + s->cfg.cpu_ids[i] = i; + } +} + +static void vmapple_cfg_init(Object *obj) +{ + VMAppleCfgState *s = VMAPPLE_CFG(obj); + + memory_region_init_ram(&s->mem, obj, "VMApple Config", VMAPPLE_CFG_SIZE, + &error_fatal); + sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mem); +} + +static const Property vmapple_cfg_properties[] = { + DEFINE_PROP_UINT32("nr-cpus", VMAppleCfgState, cfg.nr_cpus, 1), + DEFINE_PROP_UINT64("ecid", VMAppleCfgState, cfg.ecid, 0), + DEFINE_PROP_UINT64("ram-size", VMAppleCfgState, cfg.ram_size, 0), + DEFINE_PROP_UINT32("run_installer1", VMAppleCfgState, cfg.run_installer1, 0), + DEFINE_PROP_UINT32("run_installer2", VMAppleCfgState, cfg.run_installer2, 0), + DEFINE_PROP_UINT32("rnd", VMAppleCfgState, cfg.rnd, 0), + DEFINE_PROP_MACADDR("mac-en0", VMAppleCfgState, cfg.mac_en0), + DEFINE_PROP_MACADDR("mac-en1", VMAppleCfgState, cfg.mac_en1), + DEFINE_PROP_MACADDR("mac-wifi0", VMAppleCfgState, cfg.mac_wifi0), + DEFINE_PROP_MACADDR("mac-bt0", VMAppleCfgState, cfg.mac_bt0), + DEFINE_PROP_STRING("serial", VMAppleCfgState, serial), + DEFINE_PROP_STRING("model", VMAppleCfgState, model), + DEFINE_PROP_STRING("soc_name", VMAppleCfgState, soc_name), +}; + +static void vmapple_cfg_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + ResettableClass *rc = RESETTABLE_CLASS(klass); + + dc->realize = vmapple_cfg_realize; + dc->desc = "VMApple Configuration Region"; + device_class_set_props(dc, vmapple_cfg_properties); + rc->phases.hold = vmapple_cfg_reset; +} + +static const TypeInfo vmapple_cfg_info = { + .name = TYPE_VMAPPLE_CFG, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(VMAppleCfgState), + .instance_init = vmapple_cfg_init, + .class_init = vmapple_cfg_class_init, +}; + +static void vmapple_cfg_register_types(void) +{ + type_register_static(&vmapple_cfg_info); +} + +type_init(vmapple_cfg_register_types) diff --git a/hw/vmapple/meson.build b/hw/vmapple/meson.build new file mode 100644 index 0000000000..23bc4c999e --- /dev/null +++ b/hw/vmapple/meson.build @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +system_ss.add(when: 'CONFIG_VMAPPLE_AES', if_true: files('aes.c')) +system_ss.add(when: 'CONFIG_VMAPPLE_BDIF', if_true: files('bdif.c')) +system_ss.add(when: 'CONFIG_VMAPPLE_CFG', if_true: files('cfg.c')) +system_ss.add(when: 'CONFIG_VMAPPLE_VIRTIO_BLK', if_true: files('virtio-blk.c')) +specific_ss.add(when: 'CONFIG_VMAPPLE', if_true: files('vmapple.c')) diff --git a/hw/vmapple/trace-events b/hw/vmapple/trace-events new file mode 100644 index 0000000000..93380ede14 --- /dev/null +++ b/hw/vmapple/trace-events @@ -0,0 +1,21 @@ +# See docs/devel/tracing.rst for syntax documentation. +# SPDX-License-Identifier: GPL-2.0-or-later + +# aes.c +aes_read(uint64_t offset, uint64_t res) "offset=0x%"PRIx64" res=0x%"PRIx64 +aes_cmd_key_select_builtin(uint32_t ctx, uint32_t key_id, const char *direction, const char *cipher) "[%d] Selecting builtin key %d to %scrypt with %s" +aes_cmd_key_select_new(uint32_t ctx, uint32_t key_len, const char *direction, const char *cipher) "[%d] Selecting new key size=%d to %scrypt with %s" +aes_cmd_iv(uint32_t ctx, uint32_t iv0, uint32_t iv1, uint32_t iv2, uint32_t iv3) "[%d] 0x%08x 0x%08x 0x%08x 0x%08x" +aes_cmd_data(uint32_t key, uint32_t iv, uint64_t src, uint64_t dst, uint32_t len) "[key=%d iv=%d] src=0x%"PRIx64" dst=0x%"PRIx64" len=0x%x" +aes_cmd_store_iv(uint32_t ctx, uint64_t addr, uint32_t iv0, uint32_t iv1, uint32_t iv2, uint32_t iv3) "[%d] addr=0x%"PRIx64"x -> 0x%08x 0x%08x 0x%08x 0x%08x" +aes_cmd_flag(uint32_t raise, uint32_t flag_info) "raise=%d flag_info=0x%x" +aes_fifo_process(uint32_t cmd, bool success) "cmd=%d success=%d" +aes_write(uint64_t offset, uint64_t val) "offset=0x%"PRIx64" val=0x%"PRIx64 +aes_2_read(uint64_t offset, uint64_t res) "offset=0x%"PRIx64" res=0x%"PRIx64 +aes_2_write(uint64_t offset, uint64_t val) "offset=0x%"PRIx64" val=0x%"PRIx64 +aes_dump_data(const char *desc, const char *hex) "%s%s" + +# bdif.c +bdif_read(uint64_t offset, uint32_t size, uint64_t value) "offset=0x%"PRIx64" size=0x%x value=0x%"PRIx64 +bdif_write(uint64_t offset, uint32_t size, uint64_t value) "offset=0x%"PRIx64" size=0x%x value=0x%"PRIx64 +bdif_vblk_read(const char *dev, uint64_t addr, uint64_t offset, uint32_t len, int r) "dev=%s addr=0x%"PRIx64" off=0x%"PRIx64" size=0x%x r=%d" diff --git a/hw/vmapple/trace.h b/hw/vmapple/trace.h new file mode 100644 index 0000000000..d099d5ecd9 --- /dev/null +++ b/hw/vmapple/trace.h @@ -0,0 +1,2 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#include "trace/trace-hw_vmapple.h" diff --git a/hw/vmapple/virtio-blk.c b/hw/vmapple/virtio-blk.c new file mode 100644 index 0000000000..aa3f18c47d --- /dev/null +++ b/hw/vmapple/virtio-blk.c @@ -0,0 +1,204 @@ +/* + * VMApple specific VirtIO Block implementation + * + * Copyright © 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * VMApple uses almost standard VirtIO Block, but with a few key differences: + * + * - Different PCI device/vendor ID + * - An additional "type" identifier to differentiate AUX and Root volumes + * - An additional BARRIER command + */ + +#include "qemu/osdep.h" +#include "hw/vmapple/vmapple.h" +#include "hw/virtio/virtio-blk.h" +#include "hw/virtio/virtio-pci.h" +#include "qemu/bswap.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "qapi/error.h" + +#define TYPE_VMAPPLE_VIRTIO_BLK "vmapple-virtio-blk" +OBJECT_DECLARE_TYPE(VMAppleVirtIOBlk, VMAppleVirtIOBlkClass, VMAPPLE_VIRTIO_BLK) + +typedef struct VMAppleVirtIOBlkClass { + VirtIOBlkClass parent; + + void (*get_config)(VirtIODevice *vdev, uint8_t *config); +} VMAppleVirtIOBlkClass; + +typedef struct VMAppleVirtIOBlk { + VirtIOBlock parent_obj; + + uint32_t apple_type; +} VMAppleVirtIOBlk; + +/* + * vmapple-virtio-blk-pci: This extends VirtioPCIProxy. + */ +OBJECT_DECLARE_SIMPLE_TYPE(VMAppleVirtIOBlkPCI, VMAPPLE_VIRTIO_BLK_PCI) + +#define VIRTIO_BLK_T_APPLE_BARRIER 0x10000 + +static bool vmapple_virtio_blk_handle_unknown_request(VirtIOBlockReq *req, + MultiReqBuffer *mrb, + uint32_t type) +{ + switch (type) { + case VIRTIO_BLK_T_APPLE_BARRIER: + qemu_log_mask(LOG_UNIMP, "%s: Barrier requests are currently no-ops\n", + __func__); + virtio_blk_req_complete(req, VIRTIO_BLK_S_OK); + g_free(req); + return true; + default: + return false; + } +} + +/* + * VMApple virtio-blk uses the same config format as normal virtio, with one + * exception: It adds an "apple type" specififer at the same location that + * the spec reserves for max_secure_erase_sectors. Let's hook into the + * get_config code path here, run it as usual and then patch in the apple type. + */ +static void vmapple_virtio_blk_get_config(VirtIODevice *vdev, uint8_t *config) +{ + VMAppleVirtIOBlk *dev = VMAPPLE_VIRTIO_BLK(vdev); + VMAppleVirtIOBlkClass *vvbk = VMAPPLE_VIRTIO_BLK_GET_CLASS(dev); + struct virtio_blk_config *blkcfg = (struct virtio_blk_config *)config; + + vvbk->get_config(vdev, config); + + g_assert(dev->parent_obj.config_size >= endof(struct virtio_blk_config, zoned)); + + /* Apple abuses the field for max_secure_erase_sectors as type id */ + stl_he_p(&blkcfg->max_secure_erase_sectors, dev->apple_type); +} + +static void vmapple_virtio_blk_class_init(ObjectClass *klass, void *data) +{ + VirtIOBlkClass *vbk = VIRTIO_BLK_CLASS(klass); + VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass); + VMAppleVirtIOBlkClass *vvbk = VMAPPLE_VIRTIO_BLK_CLASS(klass); + + vbk->handle_unknown_request = vmapple_virtio_blk_handle_unknown_request; + vvbk->get_config = vdc->get_config; + vdc->get_config = vmapple_virtio_blk_get_config; +} + +static const TypeInfo vmapple_virtio_blk_info = { + .name = TYPE_VMAPPLE_VIRTIO_BLK, + .parent = TYPE_VIRTIO_BLK, + .instance_size = sizeof(VMAppleVirtIOBlk), + .class_size = sizeof(VMAppleVirtIOBlkClass), + .class_init = vmapple_virtio_blk_class_init, +}; + +/* PCI Devices */ + +struct VMAppleVirtIOBlkPCI { + VirtIOPCIProxy parent_obj; + + VMAppleVirtIOBlk vdev; + VMAppleVirtioBlkVariant variant; +}; + +static const Property vmapple_virtio_blk_pci_properties[] = { + DEFINE_PROP_UINT32("class", VirtIOPCIProxy, class_code, 0), + DEFINE_PROP_BIT("ioeventfd", VirtIOPCIProxy, flags, + VIRTIO_PCI_FLAG_USE_IOEVENTFD_BIT, true), + DEFINE_PROP_UINT32("vectors", VirtIOPCIProxy, nvectors, + DEV_NVECTORS_UNSPECIFIED), + DEFINE_PROP_VMAPPLE_VIRTIO_BLK_VARIANT("variant", VMAppleVirtIOBlkPCI, variant, + VM_APPLE_VIRTIO_BLK_VARIANT_UNSPECIFIED), +}; + +static void vmapple_virtio_blk_pci_realize(VirtIOPCIProxy *vpci_dev, Error **errp) +{ + ERRP_GUARD(); + VMAppleVirtIOBlkPCI *dev = VMAPPLE_VIRTIO_BLK_PCI(vpci_dev); + DeviceState *vdev = DEVICE(&dev->vdev); + VirtIOBlkConf *conf = &dev->vdev.parent_obj.conf; + + if (dev->variant == VM_APPLE_VIRTIO_BLK_VARIANT_UNSPECIFIED) { + error_setg(errp, "vmapple virtio block device variant unspecified"); + error_append_hint(errp, + "Variant property must be set to 'aux' or 'root'.\n" + "Use a regular virtio-blk-pci device instead when " + "neither is applicaple.\n"); + return; + } + + if (conf->num_queues == VIRTIO_BLK_AUTO_NUM_QUEUES) { + conf->num_queues = virtio_pci_optimal_num_queues(0); + } + + if (vpci_dev->nvectors == DEV_NVECTORS_UNSPECIFIED) { + vpci_dev->nvectors = conf->num_queues + 1; + } + + /* + * We don't support zones, but we need the additional config space size. + * Let's just expose the feature so the rest of the virtio-blk logic + * allocates enough space for us. The guest will ignore zones anyway. + */ + virtio_add_feature(&dev->vdev.parent_obj.host_features, VIRTIO_BLK_F_ZONED); + /* Propagate the apple type down to the virtio-blk device */ + dev->vdev.apple_type = dev->variant; + /* and spawn the virtio-blk device */ + qdev_realize(vdev, BUS(&vpci_dev->bus), errp); + + /* + * The virtio-pci machinery adjusts its vendor/device ID based on whether + * we support modern or legacy virtio. Let's patch it back to the Apple + * identifiers here. + */ + pci_config_set_vendor_id(vpci_dev->pci_dev.config, PCI_VENDOR_ID_APPLE); + pci_config_set_device_id(vpci_dev->pci_dev.config, + PCI_DEVICE_ID_APPLE_VIRTIO_BLK); +} + +static void vmapple_virtio_blk_pci_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + VirtioPCIClass *k = VIRTIO_PCI_CLASS(klass); + PCIDeviceClass *pcidev_k = PCI_DEVICE_CLASS(klass); + + set_bit(DEVICE_CATEGORY_STORAGE, dc->categories); + device_class_set_props(dc, vmapple_virtio_blk_pci_properties); + k->realize = vmapple_virtio_blk_pci_realize; + pcidev_k->vendor_id = PCI_VENDOR_ID_APPLE; + pcidev_k->device_id = PCI_DEVICE_ID_APPLE_VIRTIO_BLK; + pcidev_k->revision = VIRTIO_PCI_ABI_VERSION; + pcidev_k->class_id = PCI_CLASS_STORAGE_SCSI; +} + +static void vmapple_virtio_blk_pci_instance_init(Object *obj) +{ + VMAppleVirtIOBlkPCI *dev = VMAPPLE_VIRTIO_BLK_PCI(obj); + + virtio_instance_init_common(obj, &dev->vdev, sizeof(dev->vdev), + TYPE_VMAPPLE_VIRTIO_BLK); +} + +static const VirtioPCIDeviceTypeInfo vmapple_virtio_blk_pci_info = { + .generic_name = TYPE_VMAPPLE_VIRTIO_BLK_PCI, + .instance_size = sizeof(VMAppleVirtIOBlkPCI), + .instance_init = vmapple_virtio_blk_pci_instance_init, + .class_init = vmapple_virtio_blk_pci_class_init, +}; + +static void vmapple_virtio_blk_register_types(void) +{ + type_register_static(&vmapple_virtio_blk_info); + virtio_pci_types_register(&vmapple_virtio_blk_pci_info); +} + +type_init(vmapple_virtio_blk_register_types) diff --git a/hw/vmapple/vmapple.c b/hw/vmapple/vmapple.c new file mode 100644 index 0000000000..fa117bf151 --- /dev/null +++ b/hw/vmapple/vmapple.c @@ -0,0 +1,618 @@ +/* + * VMApple machine emulation + * + * Copyright © 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * VMApple is the device model that the macOS built-in hypervisor called + * "Virtualization.framework" exposes to Apple Silicon macOS guests. The + * machine model in this file implements the same device model in QEMU, but + * does not use any code from Virtualization.Framework. + */ + +#include "qemu/osdep.h" +#include "qemu/bitops.h" +#include "qemu/datadir.h" +#include "qemu/error-report.h" +#include "qemu/guest-random.h" +#include "qemu/help-texts.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "qemu/option.h" +#include "qemu/units.h" +#include "monitor/qdev.h" +#include "hw/boards.h" +#include "hw/irq.h" +#include "hw/loader.h" +#include "hw/qdev-properties.h" +#include "hw/sysbus.h" +#include "hw/usb.h" +#include "hw/arm/boot.h" +#include "hw/arm/primecell.h" +#include "hw/char/pl011.h" +#include "hw/intc/arm_gic.h" +#include "hw/intc/arm_gicv3_common.h" +#include "hw/misc/pvpanic.h" +#include "hw/pci-host/gpex.h" +#include "hw/usb/hcd-xhci-pci.h" +#include "hw/virtio/virtio-pci.h" +#include "hw/vmapple/vmapple.h" +#include "net/net.h" +#include "qapi/error.h" +#include "qapi/visitor.h" +#include "qapi/qapi-visit-common.h" +#include "qobject/qlist.h" +#include "standard-headers/linux/input.h" +#include "system/hvf.h" +#include "system/reset.h" +#include "system/runstate.h" +#include "system/system.h" + +struct VMAppleMachineState { + MachineState parent; + + Notifier machine_done; + struct arm_boot_info bootinfo; + const MemMapEntry *memmap; + const int *irqmap; + DeviceState *gic; + DeviceState *cfg; + DeviceState *pvpanic; + Notifier powerdown_notifier; + PCIBus *bus; + MemoryRegion fw_mr; + MemoryRegion ecam_alias; + uint64_t uuid; +}; + +#define TYPE_VMAPPLE_MACHINE MACHINE_TYPE_NAME("vmapple") +OBJECT_DECLARE_SIMPLE_TYPE(VMAppleMachineState, VMAPPLE_MACHINE) + +/* Number of external interrupt lines to configure the GIC with */ +#define NUM_IRQS 256 + +enum { + VMAPPLE_FIRMWARE, + VMAPPLE_CONFIG, + VMAPPLE_MEM, + VMAPPLE_GIC_DIST, + VMAPPLE_GIC_REDIST, + VMAPPLE_UART, + VMAPPLE_RTC, + VMAPPLE_PCIE, + VMAPPLE_PCIE_MMIO, + VMAPPLE_PCIE_ECAM, + VMAPPLE_GPIO, + VMAPPLE_PVPANIC, + VMAPPLE_APV_GFX, + VMAPPLE_APV_IOSFC, + VMAPPLE_AES_1, + VMAPPLE_AES_2, + VMAPPLE_BDOOR, + VMAPPLE_MEMMAP_LAST, +}; + +static const MemMapEntry memmap[] = { + [VMAPPLE_FIRMWARE] = { 0x00100000, 0x00100000 }, + [VMAPPLE_CONFIG] = { 0x00400000, 0x00010000 }, + + [VMAPPLE_GIC_DIST] = { 0x10000000, 0x00010000 }, + [VMAPPLE_GIC_REDIST] = { 0x10010000, 0x00400000 }, + + [VMAPPLE_UART] = { 0x20010000, 0x00010000 }, + [VMAPPLE_RTC] = { 0x20050000, 0x00001000 }, + [VMAPPLE_GPIO] = { 0x20060000, 0x00001000 }, + [VMAPPLE_PVPANIC] = { 0x20070000, 0x00000002 }, + [VMAPPLE_BDOOR] = { 0x30000000, 0x00200000 }, + [VMAPPLE_APV_GFX] = { 0x30200000, 0x00010000 }, + [VMAPPLE_APV_IOSFC] = { 0x30210000, 0x00010000 }, + [VMAPPLE_AES_1] = { 0x30220000, 0x00004000 }, + [VMAPPLE_AES_2] = { 0x30230000, 0x00004000 }, + [VMAPPLE_PCIE_ECAM] = { 0x40000000, 0x10000000 }, + [VMAPPLE_PCIE_MMIO] = { 0x50000000, 0x1fff0000 }, + + /* Actual RAM size depends on configuration */ + [VMAPPLE_MEM] = { 0x70000000ULL, GiB}, +}; + +static const int irqmap[] = { + [VMAPPLE_UART] = 1, + [VMAPPLE_RTC] = 2, + [VMAPPLE_GPIO] = 0x5, + [VMAPPLE_APV_IOSFC] = 0x10, + [VMAPPLE_APV_GFX] = 0x11, + [VMAPPLE_AES_1] = 0x12, + [VMAPPLE_PCIE] = 0x20, +}; + +#define GPEX_NUM_IRQS 16 + +static void create_bdif(VMAppleMachineState *vms, MemoryRegion *mem) +{ + DeviceState *bdif; + SysBusDevice *bdif_sb; + DriveInfo *di_aux = drive_get(IF_PFLASH, 0, 0); + DriveInfo *di_root = drive_get(IF_PFLASH, 0, 1); + + if (!di_aux) { + error_report("No AUX device. Please specify one as pflash drive."); + exit(1); + } + + if (!di_root) { + /* Fall back to the first IF_VIRTIO device as root device */ + di_root = drive_get(IF_VIRTIO, 0, 0); + } + + if (!di_root) { + error_report("No root device. Please specify one as virtio drive."); + exit(1); + } + + /* PV backdoor device */ + bdif = qdev_new(TYPE_VMAPPLE_BDIF); + bdif_sb = SYS_BUS_DEVICE(bdif); + sysbus_mmio_map(bdif_sb, 0, vms->memmap[VMAPPLE_BDOOR].base); + + qdev_prop_set_drive(DEVICE(bdif), "aux", blk_by_legacy_dinfo(di_aux)); + qdev_prop_set_drive(DEVICE(bdif), "root", blk_by_legacy_dinfo(di_root)); + + sysbus_realize_and_unref(bdif_sb, &error_fatal); +} + +static void create_pvpanic(VMAppleMachineState *vms, MemoryRegion *mem) +{ + SysBusDevice *pvpanic; + + vms->pvpanic = qdev_new(TYPE_PVPANIC_MMIO_DEVICE); + pvpanic = SYS_BUS_DEVICE(vms->pvpanic); + sysbus_mmio_map(pvpanic, 0, vms->memmap[VMAPPLE_PVPANIC].base); + + sysbus_realize_and_unref(pvpanic, &error_fatal); +} + +static bool create_cfg(VMAppleMachineState *vms, MemoryRegion *mem, + Error **errp) +{ + ERRP_GUARD(); + SysBusDevice *cfg; + MachineState *machine = MACHINE(vms); + uint32_t rnd = 1; + + vms->cfg = qdev_new(TYPE_VMAPPLE_CFG); + cfg = SYS_BUS_DEVICE(vms->cfg); + sysbus_mmio_map(cfg, 0, vms->memmap[VMAPPLE_CONFIG].base); + + qemu_guest_getrandom_nofail(&rnd, sizeof(rnd)); + + qdev_prop_set_uint32(vms->cfg, "nr-cpus", machine->smp.cpus); + qdev_prop_set_uint64(vms->cfg, "ecid", vms->uuid); + qdev_prop_set_uint64(vms->cfg, "ram-size", machine->ram_size); + qdev_prop_set_uint32(vms->cfg, "rnd", rnd); + + if (!sysbus_realize_and_unref(cfg, errp)) { + error_prepend(errp, "Error creating vmapple cfg device: "); + return false; + } + + return true; +} + +static void create_gfx(VMAppleMachineState *vms, MemoryRegion *mem) +{ + int irq_gfx = vms->irqmap[VMAPPLE_APV_GFX]; + int irq_iosfc = vms->irqmap[VMAPPLE_APV_IOSFC]; + SysBusDevice *gfx; + + gfx = SYS_BUS_DEVICE(qdev_new("apple-gfx-mmio")); + sysbus_mmio_map(gfx, 0, vms->memmap[VMAPPLE_APV_GFX].base); + sysbus_mmio_map(gfx, 1, vms->memmap[VMAPPLE_APV_IOSFC].base); + sysbus_connect_irq(gfx, 0, qdev_get_gpio_in(vms->gic, irq_gfx)); + sysbus_connect_irq(gfx, 1, qdev_get_gpio_in(vms->gic, irq_iosfc)); + sysbus_realize_and_unref(gfx, &error_fatal); +} + +static void create_aes(VMAppleMachineState *vms, MemoryRegion *mem) +{ + int irq = vms->irqmap[VMAPPLE_AES_1]; + SysBusDevice *aes; + + aes = SYS_BUS_DEVICE(qdev_new(TYPE_APPLE_AES)); + sysbus_mmio_map(aes, 0, vms->memmap[VMAPPLE_AES_1].base); + sysbus_mmio_map(aes, 1, vms->memmap[VMAPPLE_AES_2].base); + sysbus_connect_irq(aes, 0, qdev_get_gpio_in(vms->gic, irq)); + sysbus_realize_and_unref(aes, &error_fatal); +} + +static int arm_gic_ppi_index(int cpu_nr, int ppi_index) +{ + return NUM_IRQS + cpu_nr * GIC_INTERNAL + ppi_index; +} + +static void create_gic(VMAppleMachineState *vms, MemoryRegion *mem) +{ + MachineState *ms = MACHINE(vms); + /* We create a standalone GIC */ + SysBusDevice *gicbusdev; + QList *redist_region_count; + int i; + unsigned int smp_cpus = ms->smp.cpus; + + vms->gic = qdev_new(gicv3_class_name()); + qdev_prop_set_uint32(vms->gic, "revision", 3); + qdev_prop_set_uint32(vms->gic, "num-cpu", smp_cpus); + /* + * Note that the num-irq property counts both internal and external + * interrupts; there are always 32 of the former (mandated by GIC spec). + */ + qdev_prop_set_uint32(vms->gic, "num-irq", NUM_IRQS + 32); + + uint32_t redist0_capacity = + vms->memmap[VMAPPLE_GIC_REDIST].size / GICV3_REDIST_SIZE; + uint32_t redist0_count = MIN(smp_cpus, redist0_capacity); + + redist_region_count = qlist_new(); + qlist_append_int(redist_region_count, redist0_count); + qdev_prop_set_array(vms->gic, "redist-region-count", redist_region_count); + + gicbusdev = SYS_BUS_DEVICE(vms->gic); + sysbus_realize_and_unref(gicbusdev, &error_fatal); + sysbus_mmio_map(gicbusdev, 0, vms->memmap[VMAPPLE_GIC_DIST].base); + sysbus_mmio_map(gicbusdev, 1, vms->memmap[VMAPPLE_GIC_REDIST].base); + + /* + * Wire the outputs from each CPU's generic timer and the GICv3 + * maintenance interrupt signal to the appropriate GIC PPI inputs, + * and the GIC's IRQ/FIQ/VIRQ/VFIQ interrupt outputs to the CPU's inputs. + */ + for (i = 0; i < smp_cpus; i++) { + DeviceState *cpudev = DEVICE(qemu_get_cpu(i)); + + /* Map the virt timer to PPI 27 */ + qdev_connect_gpio_out(cpudev, GTIMER_VIRT, + qdev_get_gpio_in(vms->gic, + arm_gic_ppi_index(i, 27))); + + /* Map the GIC IRQ and FIQ lines to CPU */ + sysbus_connect_irq(gicbusdev, i, qdev_get_gpio_in(cpudev, ARM_CPU_IRQ)); + sysbus_connect_irq(gicbusdev, i + smp_cpus, + qdev_get_gpio_in(cpudev, ARM_CPU_FIQ)); + } +} + +static void create_uart(const VMAppleMachineState *vms, int uart, + MemoryRegion *mem, Chardev *chr) +{ + hwaddr base = vms->memmap[uart].base; + int irq = vms->irqmap[uart]; + DeviceState *dev = qdev_new(TYPE_PL011); + SysBusDevice *s = SYS_BUS_DEVICE(dev); + + qdev_prop_set_chr(dev, "chardev", chr); + sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); + memory_region_add_subregion(mem, base, + sysbus_mmio_get_region(s, 0)); + sysbus_connect_irq(s, 0, qdev_get_gpio_in(vms->gic, irq)); +} + +static void create_rtc(const VMAppleMachineState *vms) +{ + hwaddr base = vms->memmap[VMAPPLE_RTC].base; + int irq = vms->irqmap[VMAPPLE_RTC]; + + sysbus_create_simple("pl031", base, qdev_get_gpio_in(vms->gic, irq)); +} + +static DeviceState *gpio_key_dev; +static void vmapple_powerdown_req(Notifier *n, void *opaque) +{ + /* use gpio Pin 3 for power button event */ + qemu_set_irq(qdev_get_gpio_in(gpio_key_dev, 0), 1); +} + +static void create_gpio_devices(const VMAppleMachineState *vms, int gpio, + MemoryRegion *mem) +{ + DeviceState *pl061_dev; + hwaddr base = vms->memmap[gpio].base; + int irq = vms->irqmap[gpio]; + SysBusDevice *s; + + pl061_dev = qdev_new("pl061"); + /* Pull lines down to 0 if not driven by the PL061 */ + qdev_prop_set_uint32(pl061_dev, "pullups", 0); + qdev_prop_set_uint32(pl061_dev, "pulldowns", 0xff); + s = SYS_BUS_DEVICE(pl061_dev); + sysbus_realize_and_unref(s, &error_fatal); + memory_region_add_subregion(mem, base, sysbus_mmio_get_region(s, 0)); + sysbus_connect_irq(s, 0, qdev_get_gpio_in(vms->gic, irq)); + gpio_key_dev = sysbus_create_simple("gpio-key", -1, + qdev_get_gpio_in(pl061_dev, 3)); +} + +static void vmapple_firmware_init(VMAppleMachineState *vms, + MemoryRegion *sysmem) +{ + hwaddr size = vms->memmap[VMAPPLE_FIRMWARE].size; + hwaddr base = vms->memmap[VMAPPLE_FIRMWARE].base; + const char *bios_name; + int image_size; + char *fname; + + bios_name = MACHINE(vms)->firmware; + if (!bios_name) { + error_report("No firmware specified"); + exit(1); + } + + fname = qemu_find_file(QEMU_FILE_TYPE_BIOS, bios_name); + if (!fname) { + error_report("Could not find ROM image '%s'", bios_name); + exit(1); + } + + memory_region_init_ram(&vms->fw_mr, NULL, "firmware", size, &error_fatal); + image_size = load_image_mr(fname, &vms->fw_mr); + + g_free(fname); + if (image_size < 0) { + error_report("Could not load ROM image '%s'", bios_name); + exit(1); + } + + memory_region_add_subregion(get_system_memory(), base, &vms->fw_mr); +} + +static void create_pcie(VMAppleMachineState *vms) +{ + hwaddr base_mmio = vms->memmap[VMAPPLE_PCIE_MMIO].base; + hwaddr size_mmio = vms->memmap[VMAPPLE_PCIE_MMIO].size; + hwaddr base_ecam = vms->memmap[VMAPPLE_PCIE_ECAM].base; + hwaddr size_ecam = vms->memmap[VMAPPLE_PCIE_ECAM].size; + int irq = vms->irqmap[VMAPPLE_PCIE]; + MemoryRegion *mmio_alias; + MemoryRegion *mmio_reg; + MemoryRegion *ecam_reg; + DeviceState *dev; + int i; + PCIHostState *pci; + DeviceState *usb_controller; + USBBus *usb_bus; + + dev = qdev_new(TYPE_GPEX_HOST); + qdev_prop_set_uint32(dev, "num-irqs", GPEX_NUM_IRQS); + sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); + + /* Map only the first size_ecam bytes of ECAM space */ + ecam_reg = sysbus_mmio_get_region(SYS_BUS_DEVICE(dev), 0); + memory_region_init_alias(&vms->ecam_alias, OBJECT(dev), "pcie-ecam", + ecam_reg, 0, size_ecam); + memory_region_add_subregion(get_system_memory(), base_ecam, + &vms->ecam_alias); + + /* + * Map the MMIO window from [0x50000000-0x7fff0000] in PCI space into + * system address space at [0x50000000-0x7fff0000]. + */ + mmio_alias = g_new0(MemoryRegion, 1); + mmio_reg = sysbus_mmio_get_region(SYS_BUS_DEVICE(dev), 1); + memory_region_init_alias(mmio_alias, OBJECT(dev), "pcie-mmio", + mmio_reg, base_mmio, size_mmio); + memory_region_add_subregion(get_system_memory(), base_mmio, mmio_alias); + + for (i = 0; i < GPEX_NUM_IRQS; i++) { + sysbus_connect_irq(SYS_BUS_DEVICE(dev), i, + qdev_get_gpio_in(vms->gic, irq + i)); + gpex_set_irq_num(GPEX_HOST(dev), i, irq + i); + } + + pci = PCI_HOST_BRIDGE(dev); + vms->bus = pci->bus; + g_assert(vms->bus); + + while ((dev = qemu_create_nic_device("virtio-net-pci", true, NULL))) { + qdev_realize_and_unref(dev, BUS(vms->bus), &error_fatal); + } + + if (defaults_enabled()) { + usb_controller = qdev_new(TYPE_QEMU_XHCI); + qdev_realize_and_unref(usb_controller, BUS(pci->bus), &error_fatal); + + usb_bus = USB_BUS(object_resolve_type_unambiguous(TYPE_USB_BUS, + &error_fatal)); + usb_create_simple(usb_bus, "usb-kbd"); + usb_create_simple(usb_bus, "usb-tablet"); + } +} + +static void vmapple_reset(void *opaque) +{ + VMAppleMachineState *vms = opaque; + hwaddr base = vms->memmap[VMAPPLE_FIRMWARE].base; + + cpu_set_pc(first_cpu, base); +} + +static void mach_vmapple_init(MachineState *machine) +{ + VMAppleMachineState *vms = VMAPPLE_MACHINE(machine); + MachineClass *mc = MACHINE_GET_CLASS(machine); + const CPUArchIdList *possible_cpus; + MemoryRegion *sysmem = get_system_memory(); + int n; + unsigned int smp_cpus = machine->smp.cpus; + unsigned int max_cpus = machine->smp.max_cpus; + + vms->memmap = memmap; + machine->usb = true; + + possible_cpus = mc->possible_cpu_arch_ids(machine); + assert(possible_cpus->len == max_cpus); + for (n = 0; n < possible_cpus->len; n++) { + Object *cpu; + CPUState *cs; + + if (n >= smp_cpus) { + break; + } + + cpu = object_new(possible_cpus->cpus[n].type); + object_property_set_int(cpu, "mp-affinity", + possible_cpus->cpus[n].arch_id, &error_fatal); + + cs = CPU(cpu); + cs->cpu_index = n; + + numa_cpu_pre_plug(&possible_cpus->cpus[cs->cpu_index], DEVICE(cpu), + &error_fatal); + + if (object_property_find(cpu, "has_el3")) { + object_property_set_bool(cpu, "has_el3", false, &error_fatal); + } + if (object_property_find(cpu, "has_el2")) { + object_property_set_bool(cpu, "has_el2", false, &error_fatal); + } + object_property_set_int(cpu, "psci-conduit", QEMU_PSCI_CONDUIT_HVC, + &error_fatal); + + /* Secondary CPUs start in PSCI powered-down state */ + if (n > 0) { + object_property_set_bool(cpu, "start-powered-off", true, + &error_fatal); + } + + object_property_set_link(cpu, "memory", OBJECT(sysmem), &error_abort); + qdev_realize(DEVICE(cpu), NULL, &error_fatal); + object_unref(cpu); + } + + memory_region_add_subregion(sysmem, vms->memmap[VMAPPLE_MEM].base, + machine->ram); + + create_gic(vms, sysmem); + create_bdif(vms, sysmem); + create_pvpanic(vms, sysmem); + create_aes(vms, sysmem); + create_gfx(vms, sysmem); + create_uart(vms, VMAPPLE_UART, sysmem, serial_hd(0)); + create_rtc(vms); + create_pcie(vms); + + create_gpio_devices(vms, VMAPPLE_GPIO, sysmem); + + vmapple_firmware_init(vms, sysmem); + create_cfg(vms, sysmem, &error_fatal); + + /* connect powerdown request */ + vms->powerdown_notifier.notify = vmapple_powerdown_req; + qemu_register_powerdown_notifier(&vms->powerdown_notifier); + + vms->bootinfo.ram_size = machine->ram_size; + vms->bootinfo.board_id = -1; + vms->bootinfo.loader_start = vms->memmap[VMAPPLE_MEM].base; + vms->bootinfo.skip_dtb_autoload = true; + vms->bootinfo.firmware_loaded = true; + arm_load_kernel(ARM_CPU(first_cpu), machine, &vms->bootinfo); + + qemu_register_reset(vmapple_reset, vms); +} + +static CpuInstanceProperties +vmapple_cpu_index_to_props(MachineState *ms, unsigned cpu_index) +{ + MachineClass *mc = MACHINE_GET_CLASS(ms); + const CPUArchIdList *possible_cpus = mc->possible_cpu_arch_ids(ms); + + assert(cpu_index < possible_cpus->len); + return possible_cpus->cpus[cpu_index].props; +} + + +static int64_t vmapple_get_default_cpu_node_id(const MachineState *ms, int idx) +{ + return idx % ms->numa_state->num_nodes; +} + +static const CPUArchIdList *vmapple_possible_cpu_arch_ids(MachineState *ms) +{ + int n; + unsigned int max_cpus = ms->smp.max_cpus; + + if (ms->possible_cpus) { + assert(ms->possible_cpus->len == max_cpus); + return ms->possible_cpus; + } + + ms->possible_cpus = g_malloc0(sizeof(CPUArchIdList) + + sizeof(CPUArchId) * max_cpus); + ms->possible_cpus->len = max_cpus; + for (n = 0; n < ms->possible_cpus->len; n++) { + ms->possible_cpus->cpus[n].type = ms->cpu_type; + ms->possible_cpus->cpus[n].arch_id = + arm_build_mp_affinity(n, GICV3_TARGETLIST_BITS); + ms->possible_cpus->cpus[n].props.has_thread_id = true; + ms->possible_cpus->cpus[n].props.thread_id = n; + } + return ms->possible_cpus; +} + +static GlobalProperty vmapple_compat_defaults[] = { + { TYPE_VIRTIO_PCI, "disable-legacy", "on" }, + /* + * macOS XHCI driver attempts to schedule events onto even rings 1 & 2 + * even when (as here) there is no MSI(-X) support. Disabling interrupter + * mapping in the XHCI controller works around the problem. + */ + { TYPE_XHCI_PCI, "conditional-intr-mapping", "on" }, +}; + +static void vmapple_machine_class_init(ObjectClass *oc, void *data) +{ + MachineClass *mc = MACHINE_CLASS(oc); + + mc->init = mach_vmapple_init; + mc->max_cpus = 32; + mc->block_default_type = IF_VIRTIO; + mc->no_cdrom = 1; + mc->pci_allow_0_address = true; + mc->minimum_page_bits = 12; + mc->possible_cpu_arch_ids = vmapple_possible_cpu_arch_ids; + mc->cpu_index_to_instance_props = vmapple_cpu_index_to_props; + mc->default_cpu_type = ARM_CPU_TYPE_NAME("host"); + mc->get_default_cpu_node_id = vmapple_get_default_cpu_node_id; + mc->default_ram_id = "mach-vmapple.ram"; + mc->desc = "Apple aarch64 Virtual Machine"; + + compat_props_add(mc->compat_props, vmapple_compat_defaults, + G_N_ELEMENTS(vmapple_compat_defaults)); +} + +static void vmapple_instance_init(Object *obj) +{ + VMAppleMachineState *vms = VMAPPLE_MACHINE(obj); + + vms->irqmap = irqmap; + + object_property_add_uint64_ptr(obj, "uuid", &vms->uuid, + OBJ_PROP_FLAG_READWRITE); + object_property_set_description(obj, "uuid", "Machine UUID (SDOM)"); +} + +static const TypeInfo vmapple_machine_info = { + .name = TYPE_VMAPPLE_MACHINE, + .parent = TYPE_MACHINE, + .instance_size = sizeof(VMAppleMachineState), + .class_init = vmapple_machine_class_init, + .instance_init = vmapple_instance_init, +}; + +static void machvmapple_machine_init(void) +{ + type_register_static(&vmapple_machine_info); +} +type_init(machvmapple_machine_init); + diff --git a/hw/xen/xen-hvm-common.c b/hw/xen/xen-hvm-common.c index 7ffbbfea23..9a677e8ed7 100644 --- a/hw/xen/xen-hvm-common.c +++ b/hw/xen/xen-hvm-common.c @@ -1,14 +1,21 @@ #include "qemu/osdep.h" #include "qemu/units.h" +#include "qemu/error-report.h" #include "qapi/error.h" +#include "exec/target_long.h" #include "exec/target_page.h" #include "trace.h" +#include "hw/hw.h" #include "hw/pci/pci_host.h" #include "hw/xen/xen-hvm-common.h" #include "hw/xen/xen-bus.h" #include "hw/boards.h" #include "hw/xen/arch_hvm.h" +#include "system/runstate.h" +#include "system/system.h" +#include "system/xen.h" +#include "system/xen-mapcache.h" MemoryRegion xen_memory, xen_grants; diff --git a/hw/xen/xen-pvh-common.c b/hw/xen/xen-pvh-common.c index 9c21fa858d..d675f7a8ae 100644 --- a/hw/xen/xen-pvh-common.c +++ b/hw/xen/xen-pvh-common.c @@ -8,14 +8,13 @@ #include "qemu/osdep.h" #include "qemu/error-report.h" -#include "qapi/error.h" +#include "qemu/units.h" #include "qapi/visitor.h" #include "hw/boards.h" #include "hw/irq.h" -#include "hw/sysbus.h" -#include "system/system.h" #include "system/tpm.h" #include "system/tpm_backend.h" +#include "system/runstate.h" #include "hw/xen/xen-pvh-common.h" #include "trace.h" diff --git a/include/block/ufs.h b/include/block/ufs.h index 57f5ea3500..a3ee62b027 100644 --- a/include/block/ufs.h +++ b/include/block/ufs.h @@ -461,7 +461,7 @@ typedef struct Attributes { uint8_t psa_state; uint32_t psa_data_size; uint8_t ref_clk_gating_wait_time; - uint8_t device_case_rough_temperaure; + uint8_t device_case_rough_temperature; uint8_t device_too_high_temp_boundary; uint8_t device_too_low_temp_boundary; uint8_t throttling_status; @@ -1073,6 +1073,11 @@ enum health_desc_param { UFS_HEALTH_DESC_PARAM_LIFE_TIME_EST_B = 0x4, }; +enum { + UFS_DEV_HIGH_TEMP_NOTIF = BIT(4), + UFS_DEV_LOW_TEMP_NOTIF = BIT(5), +}; + /* WriteBooster buffer mode */ enum { UFS_WB_BUF_MODE_LU_DEDICATED = 0x0, @@ -1091,6 +1096,12 @@ enum ufs_lu_wp_type { UFS_LU_PERM_WP = 0x02, }; +/* Exception event mask values */ +enum { + MASK_EE_TOO_HIGH_TEMP = BIT(3), + MASK_EE_TOO_LOW_TEMP = BIT(4), +}; + /* UTP QUERY Transaction Specific Fields OpCode */ enum query_opcode { UFS_UPIU_QUERY_OPCODE_NOP = 0x0, diff --git a/include/hw/acpi/ghes.h b/include/hw/acpi/ghes.h index 39619a2457..578a582203 100644 --- a/include/hw/acpi/ghes.h +++ b/include/hw/acpi/ghes.h @@ -75,8 +75,6 @@ void acpi_build_hest(GArray *table_data, GArray *hardware_errors, void acpi_ghes_add_fw_cfg(AcpiGhesState *vms, FWCfgState *s, GArray *hardware_errors); int acpi_ghes_memory_errors(uint16_t source_id, uint64_t error_physical_addr); -void ghes_record_cper_errors(const void *cper, size_t len, - uint16_t source_id, Error **errp); /** * acpi_ghes_present: Report whether ACPI GHES table is present diff --git a/include/hw/misc/pvpanic.h b/include/hw/misc/pvpanic.h index 9a71a5ad0d..049a94c112 100644 --- a/include/hw/misc/pvpanic.h +++ b/include/hw/misc/pvpanic.h @@ -26,6 +26,7 @@ #define TYPE_PVPANIC_ISA_DEVICE "pvpanic" #define TYPE_PVPANIC_PCI_DEVICE "pvpanic-pci" +#define TYPE_PVPANIC_MMIO_DEVICE "pvpanic-mmio" #define PVPANIC_IOPORT_PROP "ioport" diff --git a/include/hw/pci/pci_ids.h b/include/hw/pci/pci_ids.h index f1a53fea8d..33e2898be9 100644 --- a/include/hw/pci/pci_ids.h +++ b/include/hw/pci/pci_ids.h @@ -191,6 +191,7 @@ #define PCI_DEVICE_ID_APPLE_UNI_N_AGP 0x0020 #define PCI_DEVICE_ID_APPLE_U3_AGP 0x004b #define PCI_DEVICE_ID_APPLE_UNI_N_GMAC 0x0021 +#define PCI_DEVICE_ID_APPLE_VIRTIO_BLK 0x1a00 #define PCI_VENDOR_ID_SUN 0x108e #define PCI_DEVICE_ID_SUN_EBUS 0x1000 diff --git a/include/hw/qdev-properties-system.h b/include/hw/qdev-properties-system.h index ead4dfc2f0..b921392c52 100644 --- a/include/hw/qdev-properties-system.h +++ b/include/hw/qdev-properties-system.h @@ -31,6 +31,7 @@ extern const PropertyInfo qdev_prop_pcie_link_width; extern const PropertyInfo qdev_prop_cpus390entitlement; extern const PropertyInfo qdev_prop_iothread_vq_mapping_list; extern const PropertyInfo qdev_prop_endian_mode; +extern const PropertyInfo qdev_prop_vmapple_virtio_blk_variant; #define DEFINE_PROP_PCI_DEVFN(_n, _s, _f, _d) \ DEFINE_PROP_SIGNED(_n, _s, _f, _d, qdev_prop_pci_devfn, int32_t) @@ -104,4 +105,9 @@ extern const PropertyInfo qdev_prop_endian_mode; #define DEFINE_PROP_ENDIAN_NODEFAULT(_name, _state, _field) \ DEFINE_PROP_ENDIAN(_name, _state, _field, ENDIAN_MODE_UNSPECIFIED) +#define DEFINE_PROP_VMAPPLE_VIRTIO_BLK_VARIANT(_name, _state, _fld, _default) \ + DEFINE_PROP_UNSIGNED(_name, _state, _fld, _default, \ + qdev_prop_vmapple_virtio_blk_variant, \ + VMAppleVirtioBlkVariant) + #endif diff --git a/include/hw/uefi/hardware-info.h b/include/hw/uefi/hardware-info.h new file mode 100644 index 0000000000..94c38cff20 --- /dev/null +++ b/include/hw/uefi/hardware-info.h @@ -0,0 +1,35 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * pass hardware information to uefi + * + * see OvmfPkg/Library/HardwareInfoLib/ in edk2 + */ +#ifndef QEMU_UEFI_HARDWARE_INFO_H +#define QEMU_UEFI_HARDWARE_INFO_H + +/* data structures */ + +typedef enum { + HardwareInfoTypeUndefined = 0, + HardwareInfoTypeHostBridge = 1, + HardwareInfoQemuUefiVars = 2, +} HARDWARE_INFO_TYPE; + +typedef struct { + union { + uint64_t uint64; + HARDWARE_INFO_TYPE value; + } type; + uint64_t size; +} HARDWARE_INFO_HEADER; + +typedef struct { + uint64_t mmio_address; +} HARDWARE_INFO_SIMPLE_DEVICE; + +/* qemu functions */ + +void hardware_info_register(HARDWARE_INFO_TYPE type, void *info, uint64_t size); + +#endif /* QEMU_UEFI_HARDWARE_INFO_H */ diff --git a/include/hw/uefi/var-service-api.h b/include/hw/uefi/var-service-api.h new file mode 100644 index 0000000000..0d71638f3e --- /dev/null +++ b/include/hw/uefi/var-service-api.h @@ -0,0 +1,48 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * uefi-vars device - API of the virtual device for guest/host communication. + */ +#ifndef QEMU_UEFI_VAR_SERVICE_API_H +#define QEMU_UEFI_VAR_SERVICE_API_H + +/* qom: device names */ +#define TYPE_UEFI_VARS_X64 "uefi-vars-x64" +#define TYPE_UEFI_VARS_SYSBUS "uefi-vars-sysbus" + +/* sysbus: fdt node path */ +#define UEFI_VARS_FDT_NODE "qemu-uefi-vars" +#define UEFI_VARS_FDT_COMPAT "qemu,uefi-vars" + +/* registers */ +#define UEFI_VARS_REG_MAGIC 0x00 /* 16 bit */ +#define UEFI_VARS_REG_CMD_STS 0x02 /* 16 bit */ +#define UEFI_VARS_REG_BUFFER_SIZE 0x04 /* 32 bit */ +#define UEFI_VARS_REG_DMA_BUFFER_ADDR_LO 0x08 /* 32 bit */ +#define UEFI_VARS_REG_DMA_BUFFER_ADDR_HI 0x0c /* 32 bit */ +#define UEFI_VARS_REG_PIO_BUFFER_TRANSFER 0x10 /* 8-64 bit */ +#define UEFI_VARS_REG_PIO_BUFFER_CRC32C 0x18 /* 32 bit (read-only) */ +#define UEFI_VARS_REG_FLAGS 0x1c /* 32 bit */ +#define UEFI_VARS_REGS_SIZE 0x20 + +/* flags register */ +#define UEFI_VARS_FLAG_USE_PIO (1 << 0) + +/* magic value */ +#define UEFI_VARS_MAGIC_VALUE 0xef1 + +/* command values */ +#define UEFI_VARS_CMD_RESET 0x01 +#define UEFI_VARS_CMD_DMA_MM 0x02 +#define UEFI_VARS_CMD_PIO_MM 0x03 +#define UEFI_VARS_CMD_PIO_ZERO_OFFSET 0x04 + +/* status values */ +#define UEFI_VARS_STS_SUCCESS 0x00 +#define UEFI_VARS_STS_BUSY 0x01 +#define UEFI_VARS_STS_ERR_UNKNOWN 0x10 +#define UEFI_VARS_STS_ERR_NOT_SUPPORTED 0x11 +#define UEFI_VARS_STS_ERR_BAD_BUFFER_SIZE 0x12 + + +#endif /* QEMU_UEFI_VAR_SERVICE_API_H */ diff --git a/include/hw/uefi/var-service-edk2.h b/include/hw/uefi/var-service-edk2.h new file mode 100644 index 0000000000..c743a8df94 --- /dev/null +++ b/include/hw/uefi/var-service-edk2.h @@ -0,0 +1,227 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * uefi-vars device - structs and defines from edk2 + * + * Note: The edk2 UINTN type has been mapped to uint64_t, + * so the structs are compatible with 64bit edk2 builds. + */ +#ifndef QEMU_UEFI_VAR_SERVICE_EDK2_H +#define QEMU_UEFI_VAR_SERVICE_EDK2_H + +#include "qemu/uuid.h" + +#define MAX_BIT 0x8000000000000000ULL +#define ENCODE_ERROR(StatusCode) (MAX_BIT | (StatusCode)) +#define EFI_SUCCESS 0 +#define EFI_INVALID_PARAMETER ENCODE_ERROR(2) +#define EFI_UNSUPPORTED ENCODE_ERROR(3) +#define EFI_BAD_BUFFER_SIZE ENCODE_ERROR(4) +#define EFI_BUFFER_TOO_SMALL ENCODE_ERROR(5) +#define EFI_WRITE_PROTECTED ENCODE_ERROR(8) +#define EFI_OUT_OF_RESOURCES ENCODE_ERROR(9) +#define EFI_NOT_FOUND ENCODE_ERROR(14) +#define EFI_ACCESS_DENIED ENCODE_ERROR(15) +#define EFI_ALREADY_STARTED ENCODE_ERROR(20) +#define EFI_SECURITY_VIOLATION ENCODE_ERROR(26) + +#define EFI_VARIABLE_NON_VOLATILE 0x01 +#define EFI_VARIABLE_BOOTSERVICE_ACCESS 0x02 +#define EFI_VARIABLE_RUNTIME_ACCESS 0x04 +#define EFI_VARIABLE_HARDWARE_ERROR_RECORD 0x08 +#define EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS 0x10 /* deprecated */ +#define EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS 0x20 +#define EFI_VARIABLE_APPEND_WRITE 0x40 + +/* SecureBootEnable */ +#define SECURE_BOOT_ENABLE 1 +#define SECURE_BOOT_DISABLE 0 + +/* SecureBoot */ +#define SECURE_BOOT_MODE_ENABLE 1 +#define SECURE_BOOT_MODE_DISABLE 0 + +/* CustomMode */ +#define CUSTOM_SECURE_BOOT_MODE 1 +#define STANDARD_SECURE_BOOT_MODE 0 + +/* SetupMode */ +#define SETUP_MODE 1 +#define USER_MODE 0 + +typedef uint64_t efi_status; +typedef struct mm_header mm_header; + +/* EFI_MM_COMMUNICATE_HEADER */ +struct mm_header { + QemuUUID guid; + uint64_t length; +}; + +/* --- EfiSmmVariableProtocol ---------------------------------------- */ + +#define SMM_VARIABLE_FUNCTION_GET_VARIABLE 1 +#define SMM_VARIABLE_FUNCTION_GET_NEXT_VARIABLE_NAME 2 +#define SMM_VARIABLE_FUNCTION_SET_VARIABLE 3 +#define SMM_VARIABLE_FUNCTION_QUERY_VARIABLE_INFO 4 +#define SMM_VARIABLE_FUNCTION_READY_TO_BOOT 5 +#define SMM_VARIABLE_FUNCTION_EXIT_BOOT_SERVICE 6 +#define SMM_VARIABLE_FUNCTION_LOCK_VARIABLE 8 +#define SMM_VARIABLE_FUNCTION_GET_PAYLOAD_SIZE 11 + +typedef struct mm_variable mm_variable; +typedef struct mm_variable_access mm_variable_access; +typedef struct mm_next_variable mm_next_variable; +typedef struct mm_next_variable mm_lock_variable; +typedef struct mm_variable_info mm_variable_info; +typedef struct mm_get_payload_size mm_get_payload_size; + +/* SMM_VARIABLE_COMMUNICATE_HEADER */ +struct mm_variable { + uint64_t function; + uint64_t status; +}; + +/* SMM_VARIABLE_COMMUNICATE_ACCESS_VARIABLE */ +struct QEMU_PACKED mm_variable_access { + QemuUUID guid; + uint64_t data_size; + uint64_t name_size; + uint32_t attributes; + /* Name */ + /* Data */ +}; + +/* SMM_VARIABLE_COMMUNICATE_GET_NEXT_VARIABLE_NAME */ +struct mm_next_variable { + QemuUUID guid; + uint64_t name_size; + /* Name */ +}; + +/* SMM_VARIABLE_COMMUNICATE_QUERY_VARIABLE_INFO */ +struct QEMU_PACKED mm_variable_info { + uint64_t max_storage_size; + uint64_t free_storage_size; + uint64_t max_variable_size; + uint32_t attributes; +}; + +/* SMM_VARIABLE_COMMUNICATE_GET_PAYLOAD_SIZE */ +struct mm_get_payload_size { + uint64_t payload_size; +}; + +/* --- VarCheckPolicyLibMmiHandler ----------------------------------- */ + +#define VAR_CHECK_POLICY_COMMAND_DISABLE 0x01 +#define VAR_CHECK_POLICY_COMMAND_IS_ENABLED 0x02 +#define VAR_CHECK_POLICY_COMMAND_REGISTER 0x03 +#define VAR_CHECK_POLICY_COMMAND_DUMP 0x04 +#define VAR_CHECK_POLICY_COMMAND_LOCK 0x05 + +typedef struct mm_check_policy mm_check_policy; +typedef struct mm_check_policy_is_enabled mm_check_policy_is_enabled; +typedef struct mm_check_policy_dump_params mm_check_policy_dump_params; + +/* VAR_CHECK_POLICY_COMM_HEADER */ +struct QEMU_PACKED mm_check_policy { + uint32_t signature; + uint32_t revision; + uint32_t command; + uint64_t result; +}; + +/* VAR_CHECK_POLICY_COMM_IS_ENABLED_PARAMS */ +struct QEMU_PACKED mm_check_policy_is_enabled { + uint8_t state; +}; + +/* VAR_CHECK_POLICY_COMM_DUMP_PARAMS */ +struct QEMU_PACKED mm_check_policy_dump_params { + uint32_t page_requested; + uint32_t total_size; + uint32_t page_size; + uint8_t has_more; +}; + +/* --- Edk2VariablePolicyProtocol ------------------------------------ */ + +#define VARIABLE_POLICY_ENTRY_REVISION 0x00010000 + +#define VARIABLE_POLICY_TYPE_NO_LOCK 0 +#define VARIABLE_POLICY_TYPE_LOCK_NOW 1 +#define VARIABLE_POLICY_TYPE_LOCK_ON_CREATE 2 +#define VARIABLE_POLICY_TYPE_LOCK_ON_VAR_STATE 3 + +typedef struct variable_policy_entry variable_policy_entry; +typedef struct variable_lock_on_var_state variable_lock_on_var_state; + +/* VARIABLE_POLICY_ENTRY */ +struct variable_policy_entry { + uint32_t version; + uint16_t size; + uint16_t offset_to_name; + QemuUUID namespace; + uint32_t min_size; + uint32_t max_size; + uint32_t attributes_must_have; + uint32_t attributes_cant_have; + uint8_t lock_policy_type; + uint8_t padding[3]; + /* LockPolicy */ + /* Name */ +}; + +/* VARIABLE_LOCK_ON_VAR_STATE_POLICY */ +struct variable_lock_on_var_state { + QemuUUID namespace; + uint8_t value; + uint8_t padding; + /* Name */ +}; + +/* --- variable authentication --------------------------------------- */ + +#define WIN_CERT_TYPE_EFI_GUID 0x0EF1 + +typedef struct efi_time efi_time; +typedef struct efi_siglist efi_siglist; +typedef struct variable_auth_2 variable_auth_2; + +/* EFI_TIME */ +struct efi_time { + uint16_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t minute; + uint8_t second; + uint8_t pad1; + uint32_t nanosecond; + int16_t timezone; + uint8_t daylight; + uint8_t pad2; +}; + +/* EFI_SIGNATURE_LIST */ +struct efi_siglist { + QemuUUID guid_type; + uint32_t siglist_size; + uint32_t header_size; + uint32_t sig_size; +}; + +/* EFI_VARIABLE_AUTHENTICATION_2 */ +struct variable_auth_2 { + struct efi_time timestamp; + + /* WIN_CERTIFICATE_UEFI_GUID */ + uint32_t hdr_length; + uint16_t hdr_revision; + uint16_t hdr_cert_type; + QemuUUID guid_cert_type; + uint8_t cert_data[]; +}; + +#endif /* QEMU_UEFI_VAR_SERVICE_EDK2_H */ diff --git a/include/hw/uefi/var-service.h b/include/hw/uefi/var-service.h new file mode 100644 index 0000000000..f7ceac4ce2 --- /dev/null +++ b/include/hw/uefi/var-service.h @@ -0,0 +1,191 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * uefi-vars device - state struct and function prototypes + */ +#ifndef QEMU_UEFI_VAR_SERVICE_H +#define QEMU_UEFI_VAR_SERVICE_H + +#include "qemu/uuid.h" +#include "qemu/queue.h" + +#include "hw/uefi/var-service-edk2.h" + +#define MAX_BUFFER_SIZE (64 * 1024) + +typedef struct uefi_variable uefi_variable; +typedef struct uefi_var_policy uefi_var_policy; +typedef struct uefi_vars_state uefi_vars_state; + +typedef struct uefi_vars_cert uefi_vars_cert; +typedef struct uefi_vars_hash uefi_vars_hash; +typedef struct uefi_vars_siglist uefi_vars_siglist; + +struct uefi_variable { + QemuUUID guid; + uint16_t *name; + uint32_t name_size; + uint32_t attributes; + void *data; + uint32_t data_size; + efi_time time; + void *digest; + uint32_t digest_size; + QTAILQ_ENTRY(uefi_variable) next; +}; + +struct uefi_var_policy { + variable_policy_entry *entry; + uint32_t entry_size; + uint16_t *name; + uint32_t name_size; + + /* number of hashmarks (wildcard character) in name */ + uint32_t hashmarks; + + QTAILQ_ENTRY(uefi_var_policy) next; +}; + +struct uefi_vars_state { + MemoryRegion mr; + uint16_t sts; + uint32_t buf_size; + uint32_t buf_addr_lo; + uint32_t buf_addr_hi; + uint8_t *buffer; + QTAILQ_HEAD(, uefi_variable) variables; + QTAILQ_HEAD(, uefi_var_policy) var_policies; + + /* pio transfer buffer */ + uint32_t pio_xfer_offset; + uint8_t *pio_xfer_buffer; + + /* boot phases */ + bool end_of_dxe; + bool ready_to_boot; + bool exit_boot_service; + bool policy_locked; + + /* storage accounting */ + uint64_t max_storage; + uint64_t used_storage; + + /* config options */ + char *jsonfile; + int jsonfd; + bool force_secure_boot; + bool disable_custom_mode; + bool use_pio; +}; + +struct uefi_vars_cert { + QTAILQ_ENTRY(uefi_vars_cert) next; + QemuUUID owner; + uint64_t size; + uint8_t data[]; +}; + +struct uefi_vars_hash { + QTAILQ_ENTRY(uefi_vars_hash) next; + QemuUUID owner; + uint8_t data[]; +}; + +struct uefi_vars_siglist { + QTAILQ_HEAD(, uefi_vars_cert) x509; + QTAILQ_HEAD(, uefi_vars_hash) sha256; +}; + +/* vars-service-guid.c */ +extern const QemuUUID EfiGlobalVariable; +extern const QemuUUID EfiImageSecurityDatabase; +extern const QemuUUID EfiCustomModeEnable; +extern const QemuUUID EfiSecureBootEnableDisable; + +extern const QemuUUID EfiCertSha256Guid; +extern const QemuUUID EfiCertSha384Guid; +extern const QemuUUID EfiCertSha512Guid; +extern const QemuUUID EfiCertRsa2048Guid; +extern const QemuUUID EfiCertX509Guid; +extern const QemuUUID EfiCertTypePkcs7Guid; + +extern const QemuUUID EfiSmmVariableProtocolGuid; +extern const QemuUUID VarCheckPolicyLibMmiHandlerGuid; + +extern const QemuUUID EfiEndOfDxeEventGroupGuid; +extern const QemuUUID EfiEventReadyToBootGuid; +extern const QemuUUID EfiEventExitBootServicesGuid; + +/* vars-service-utils.c */ +gboolean uefi_str_is_valid(const uint16_t *str, size_t len, + gboolean must_be_null_terminated); +size_t uefi_strlen(const uint16_t *str, size_t len); +gboolean uefi_str_equal_ex(const uint16_t *a, size_t alen, + const uint16_t *b, size_t blen, + gboolean wildcards_in_a); +gboolean uefi_str_equal(const uint16_t *a, size_t alen, + const uint16_t *b, size_t blen); +char *uefi_ucs2_to_ascii(const uint16_t *ucs2, uint64_t ucs2_size); +int uefi_time_compare(efi_time *a, efi_time *b); +void uefi_trace_variable(const char *action, QemuUUID guid, + const uint16_t *name, uint64_t name_size); +void uefi_trace_status(const char *action, efi_status status); + +/* vars-service-core.c */ +extern const VMStateDescription vmstate_uefi_vars; +void uefi_vars_init(Object *obj, uefi_vars_state *uv); +void uefi_vars_realize(uefi_vars_state *uv, Error **errp); +void uefi_vars_hard_reset(uefi_vars_state *uv); + +/* vars-service-json.c */ +void uefi_vars_json_init(uefi_vars_state *uv, Error **errp); +void uefi_vars_json_save(uefi_vars_state *uv); +void uefi_vars_json_load(uefi_vars_state *uv, Error **errp); + +/* vars-service-vars.c */ +extern const VMStateDescription vmstate_uefi_variable; +uefi_variable *uefi_vars_find_variable(uefi_vars_state *uv, QemuUUID guid, + const uint16_t *name, + uint64_t name_size); +void uefi_vars_set_variable(uefi_vars_state *uv, QemuUUID guid, + const uint16_t *name, uint64_t name_size, + uint32_t attributes, + void *data, uint64_t data_size); +void uefi_vars_clear_volatile(uefi_vars_state *uv); +void uefi_vars_clear_all(uefi_vars_state *uv); +void uefi_vars_update_storage(uefi_vars_state *uv); +uint32_t uefi_vars_mm_vars_proto(uefi_vars_state *uv); + +/* vars-service-auth.c */ +bool uefi_vars_is_sb_pk(uefi_variable *var); +bool uefi_vars_is_sb_any(uefi_variable *var); +efi_status uefi_vars_check_auth_2(uefi_vars_state *uv, uefi_variable *var, + mm_variable_access *va, void *data); +efi_status uefi_vars_check_secure_boot(uefi_vars_state *uv, uefi_variable *var); +void uefi_vars_auth_init(uefi_vars_state *uv); + +/* vars-service-pkcs7.c */ +efi_status uefi_vars_check_pkcs7_2(uefi_variable *siglist, + void **digest, uint32_t *digest_size, + mm_variable_access *va, void *data); + +/* vars-service-siglist.c */ +void uefi_vars_siglist_init(uefi_vars_siglist *siglist); +void uefi_vars_siglist_free(uefi_vars_siglist *siglist); +void uefi_vars_siglist_parse(uefi_vars_siglist *siglist, + void *data, uint64_t size); +uint64_t uefi_vars_siglist_blob_size(uefi_vars_siglist *siglist); +void uefi_vars_siglist_blob_generate(uefi_vars_siglist *siglist, + void *data, uint64_t size); + +/* vars-service-policy.c */ +extern const VMStateDescription vmstate_uefi_var_policy; +efi_status uefi_vars_policy_check(uefi_vars_state *uv, + uefi_variable *var, + gboolean is_newvar); +void uefi_vars_policies_clear(uefi_vars_state *uv); +uefi_var_policy *uefi_vars_add_policy(uefi_vars_state *uv, + variable_policy_entry *pe); +uint32_t uefi_vars_mm_check_policy_proto(uefi_vars_state *uv); + +#endif /* QEMU_UEFI_VAR_SERVICE_H */ diff --git a/include/hw/virtio/virtio-blk.h b/include/hw/virtio/virtio-blk.h index 8a16218c40..3d8dee7ec1 100644 --- a/include/hw/virtio/virtio-blk.h +++ b/include/hw/virtio/virtio-blk.h @@ -24,7 +24,7 @@ #include "qapi/qapi-types-virtio.h" #define TYPE_VIRTIO_BLK "virtio-blk-device" -OBJECT_DECLARE_SIMPLE_TYPE(VirtIOBlock, VIRTIO_BLK) +OBJECT_DECLARE_TYPE(VirtIOBlock, VirtIOBlkClass, VIRTIO_BLK) /* This is the last element of the write scatter-gather list */ struct virtio_blk_inhdr @@ -100,6 +100,15 @@ typedef struct MultiReqBuffer { bool is_write; } MultiReqBuffer; +typedef struct VirtIOBlkClass { + /*< private >*/ + VirtioDeviceClass parent; + /*< public >*/ + bool (*handle_unknown_request)(VirtIOBlockReq *req, MultiReqBuffer *mrb, + uint32_t type); +} VirtIOBlkClass; + void virtio_blk_handle_vq(VirtIOBlock *s, VirtQueue *vq); +void virtio_blk_req_complete(VirtIOBlockReq *req, unsigned char status); #endif diff --git a/include/hw/vmapple/vmapple.h b/include/hw/vmapple/vmapple.h new file mode 100644 index 0000000000..9c1ad1bd8c --- /dev/null +++ b/include/hw/vmapple/vmapple.h @@ -0,0 +1,23 @@ +/* + * Devices specific to the VMApple machine type + * + * Copyright © 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_VMAPPLE_VMAPPLE_H +#define HW_VMAPPLE_VMAPPLE_H + +#define TYPE_APPLE_AES "apple-aes" + +#define TYPE_VMAPPLE_BDIF "vmapple-bdif" + +#define TYPE_VMAPPLE_CFG "vmapple-cfg" + +#define TYPE_VMAPPLE_VIRTIO_BLK_PCI "vmapple-virtio-blk-pci" + +#endif /* HW_VMAPPLE_VMAPPLE_H */ diff --git a/include/hw/xen/xen-bus.h b/include/hw/xen/xen-bus.h index 2adb2af839..bdbf1ed6fd 100644 --- a/include/hw/xen/xen-bus.h +++ b/include/hw/xen/xen-bus.h @@ -8,9 +8,10 @@ #ifndef HW_XEN_BUS_H #define HW_XEN_BUS_H +#include "hw/qdev-core.h" #include "hw/xen/xen_backend_ops.h" -#include "hw/sysbus.h" #include "qemu/notify.h" +#include "qemu/queue.h" #include "qom/object.h" typedef struct XenEventChannel XenEventChannel; diff --git a/include/hw/xen/xen-hvm-common.h b/include/hw/xen/xen-hvm-common.h index c1ea2c0d78..19df5600a3 100644 --- a/include/hw/xen/xen-hvm-common.h +++ b/include/hw/xen/xen-hvm-common.h @@ -1,18 +1,10 @@ #ifndef HW_XEN_HVM_COMMON_H #define HW_XEN_HVM_COMMON_H -#include "qemu/units.h" - -#include "cpu.h" -#include "hw/pci/pci.h" -#include "hw/hw.h" +#include "qemu/queue.h" +#include "exec/hwaddr.h" #include "hw/xen/xen_native.h" -#include "hw/xen/xen-legacy-backend.h" -#include "system/runstate.h" -#include "system/system.h" -#include "system/xen.h" -#include "system/xen-mapcache.h" -#include "qemu/error-report.h" +#include "hw/xen/xen_backend_ops.h" #include <xen/hvm/ioreq.h> extern MemoryRegion xen_memory; diff --git a/include/hw/xen/xen-legacy-backend.h b/include/hw/xen/xen-legacy-backend.h index e198b120c5..2d0cbfecad 100644 --- a/include/hw/xen/xen-legacy-backend.h +++ b/include/hw/xen/xen-legacy-backend.h @@ -3,7 +3,6 @@ #include "hw/xen/xen_backend_ops.h" #include "hw/xen/xen_pvdev.h" -#include "net/net.h" #include "qom/object.h" #define TYPE_XENSYSDEV "xen-sysdev" diff --git a/include/hw/xen/xen-pvh-common.h b/include/hw/xen/xen-pvh-common.h index 5cdd23c2f4..17c5a58a5a 100644 --- a/include/hw/xen/xen-pvh-common.h +++ b/include/hw/xen/xen-pvh-common.h @@ -9,11 +9,11 @@ #ifndef XEN_PVH_COMMON_H__ #define XEN_PVH_COMMON_H__ -#include <assert.h> -#include "hw/sysbus.h" -#include "hw/hw.h" -#include "hw/xen/xen-hvm-common.h" +#include "exec/memory.h" +#include "qom/object.h" +#include "hw/boards.h" #include "hw/pci-host/gpex.h" +#include "hw/xen/xen-hvm-common.h" #define TYPE_XEN_PVH_MACHINE MACHINE_TYPE_NAME("xen-pvh-base") OBJECT_DECLARE_TYPE(XenPVHMachineState, XenPVHMachineClass, diff --git a/include/qemu/cutils.h b/include/qemu/cutils.h index 34a9b9b220..36c68ce86c 100644 --- a/include/qemu/cutils.h +++ b/include/qemu/cutils.h @@ -302,4 +302,19 @@ GString *qemu_hexdump_line(GString *str, const void *buf, size_t len, void qemu_hexdump(FILE *fp, const char *prefix, const void *bufptr, size_t size); +/** + * qemu_hexdump_to_buffer: + * @buffer: output string buffer + * @buffer_size: amount of available space in buffer. Must be at least + * data_size*2+1. + * @data: input bytes + * @data_size: number of bytes in data + * + * Converts the @data_size bytes in @data into hex digit pairs, writing them to + * @buffer. Finally, a nul terminating character is written; @buffer therefore + * needs space for (data_size*2+1) chars. + */ +void qemu_hexdump_to_buffer(char *restrict buffer, size_t buffer_size, + const uint8_t *restrict data, size_t data_size); + #endif diff --git a/meson.build b/meson.build index 0a2c61d2bf..8b9fda4d95 100644 --- a/meson.build +++ b/meson.build @@ -3601,10 +3601,12 @@ if have_system 'hw/ssi', 'hw/timer', 'hw/tpm', + 'hw/uefi', 'hw/ufs', 'hw/usb', 'hw/vfio', 'hw/virtio', + 'hw/vmapple', 'hw/watchdog', 'hw/xen', 'hw/gpio', diff --git a/qapi/char.json b/qapi/char.json index f02b66c06b..dde2f9538f 100644 --- a/qapi/char.json +++ b/qapi/char.json @@ -337,7 +337,7 @@ # # Configuration info for hub chardevs. # -# @chardevs: List of chardev IDs, which should be added to this hub +# @chardevs: IDs to be added to this hub (maximum 4 devices). # # Since: 10.0 ## diff --git a/qapi/meson.build b/qapi/meson.build index e7bc54e5d0..eadde4db30 100644 --- a/qapi/meson.build +++ b/qapi/meson.build @@ -65,6 +65,7 @@ if have_system 'pci', 'rocker', 'tpm', + 'uefi', ] endif if have_system or have_tools diff --git a/qapi/qapi-schema.json b/qapi/qapi-schema.json index b1581988e4..2877aff73d 100644 --- a/qapi/qapi-schema.json +++ b/qapi/qapi-schema.json @@ -81,3 +81,4 @@ { 'include': 'vfio.json' } { 'include': 'cryptodev.json' } { 'include': 'cxl.json' } +{ 'include': 'uefi.json' } diff --git a/qapi/uefi.json b/qapi/uefi.json new file mode 100644 index 0000000000..bdfcabe1df --- /dev/null +++ b/qapi/uefi.json @@ -0,0 +1,64 @@ +# -*- Mode: Python -*- +# vim: filetype=python +# + +## +# = UEFI Variable Store +# +# The qemu efi variable store implementation (hw/uefi/) uses this to +# store non-volatile variables in json format on disk. +# +# This is an existing format already supported by (at least) two other +# projects, specifically https://gitlab.com/kraxel/virt-firmware and +# https://github.com/awslabs/python-uefivars. +## + +## +# @UefiVariable: +# +# UEFI Variable. Check the UEFI specifification for more detailed +# information on the fields. +# +# @guid: variable namespace GUID +# +# @name: variable name, in UTF-8 encoding. +# +# @attr: variable attributes. +# +# @data: variable value, encoded as hex string. +# +# @time: variable modification time. EFI_TIME struct, encoded as hex +# string. Used only for authenticated variables, where the +# EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS attribute bit +# is set. +# +# @digest: variable certificate digest. Used to verify the signature +# of updates for authenticated variables. UEFI has two kinds of +# authenticated variables. The secure boot variables ('PK', +# 'KEK', 'db' and 'dbx') have hard coded signature checking rules. +# For other authenticated variables the firmware stores a digest +# of the signing certificate at variable creation time, and any +# updates must be signed with the same certificate. +# +# Since: 10.0 +## +{ 'struct' : 'UefiVariable', + 'data' : { 'guid' : 'str', + 'name' : 'str', + 'attr' : 'int', + 'data' : 'str', + '*time' : 'str', + '*digest' : 'str'}} + +## +# @UefiVarStore: +# +# @version: currently always 2 +# +# @variables: list of UEFI variables +# +# Since: 10.0 +## +{ 'struct' : 'UefiVarStore', + 'data' : { 'version' : 'int', + 'variables' : [ 'UefiVariable' ] }} diff --git a/qapi/virtio.json b/qapi/virtio.json index 2529c2d8b2..d351d2166e 100644 --- a/qapi/virtio.json +++ b/qapi/virtio.json @@ -992,3 +992,17 @@ ## { 'enum': 'GranuleMode', 'data': [ '4k', '8k', '16k', '64k', 'host' ] } + +## +# @VMAppleVirtioBlkVariant: +# +# @unspecified: The default, not a valid setting. +# +# @root: Block device holding the root volume +# +# @aux: Block device holding auxiliary data required for boot +# +# Since: 9.2 +## +{ 'enum': 'VMAppleVirtioBlkVariant', + 'data': [ 'unspecified', 'root', 'aux' ] } diff --git a/scripts/qapi/backend.py b/scripts/qapi/backend.py new file mode 100644 index 0000000000..14e60aa67a --- /dev/null +++ b/scripts/qapi/backend.py @@ -0,0 +1,63 @@ +# This work is licensed under the terms of the GNU GPL, version 2 or later. +# See the COPYING file in the top-level directory. + +from abc import ABC, abstractmethod + +from .commands import gen_commands +from .events import gen_events +from .features import gen_features +from .introspect import gen_introspect +from .schema import QAPISchema +from .types import gen_types +from .visit import gen_visit + + +class QAPIBackend(ABC): + + @abstractmethod + def generate(self, + schema: QAPISchema, + output_dir: str, + prefix: str, + unmask: bool, + builtins: bool, + gen_tracing: bool) -> None: + """ + Generate code for the given schema into the target directory. + + :param schema: The primary QAPI schema object. + :param output_dir: The output directory to store generated code. + :param prefix: Optional C-code prefix for symbol names. + :param unmask: Expose non-ABI names through introspection? + :param builtins: Generate code for built-in types? + + :raise QAPIError: On failures. + """ + + +class QAPICBackend(QAPIBackend): + + def generate(self, + schema: QAPISchema, + output_dir: str, + prefix: str, + unmask: bool, + builtins: bool, + gen_tracing: bool) -> None: + """ + Generate C code for the given schema into the target directory. + + :param schema_file: The primary QAPI schema file. + :param output_dir: The output directory to store generated code. + :param prefix: Optional C-code prefix for symbol names. + :param unmask: Expose non-ABI names through introspection? + :param builtins: Generate code for built-in types? + + :raise QAPIError: On failures. + """ + gen_types(schema, output_dir, prefix, builtins) + gen_features(schema, output_dir, prefix) + gen_visit(schema, output_dir, prefix, builtins) + gen_commands(schema, output_dir, prefix, gen_tracing) + gen_events(schema, output_dir, prefix) + gen_introspect(schema, output_dir, prefix, unmask) diff --git a/scripts/qapi/main.py b/scripts/qapi/main.py index 324081b9fc..5b4679abcf 100644 --- a/scripts/qapi/main.py +++ b/scripts/qapi/main.py @@ -8,18 +8,14 @@ This is the main entry point for generating C code from the QAPI schema. """ import argparse +from importlib import import_module import sys from typing import Optional -from .commands import gen_commands +from .backend import QAPIBackend, QAPICBackend from .common import must_match from .error import QAPIError -from .events import gen_events -from .features import gen_features -from .introspect import gen_introspect from .schema import QAPISchema -from .types import gen_types -from .visit import gen_visit def invalid_prefix_char(prefix: str) -> Optional[str]: @@ -29,32 +25,42 @@ def invalid_prefix_char(prefix: str) -> Optional[str]: return None -def generate(schema_file: str, - output_dir: str, - prefix: str, - unmask: bool = False, - builtins: bool = False, - gen_tracing: bool = False) -> None: - """ - Generate C code for the given schema into the target directory. +def create_backend(path: str) -> QAPIBackend: + if path is None: + return QAPICBackend() - :param schema_file: The primary QAPI schema file. - :param output_dir: The output directory to store generated code. - :param prefix: Optional C-code prefix for symbol names. - :param unmask: Expose non-ABI names through introspection? - :param builtins: Generate code for built-in types? + module_path, dot, class_name = path.rpartition('.') + if not dot: + print("argument of -B must be of the form MODULE.CLASS", + file=sys.stderr) + sys.exit(1) - :raise QAPIError: On failures. - """ - assert invalid_prefix_char(prefix) is None + try: + mod = import_module(module_path) + except Exception as ex: + print(f"unable to import '{module_path}': {ex}", file=sys.stderr) + sys.exit(1) + + try: + klass = getattr(mod, class_name) + except AttributeError: + print(f"module '{module_path}' has no class '{class_name}'", + file=sys.stderr) + sys.exit(1) + + try: + backend = klass() + except Exception as ex: + print(f"backend '{path}' cannot be instantiated: {ex}", + file=sys.stderr) + sys.exit(1) + + if not isinstance(backend, QAPIBackend): + print(f"backend '{path}' must be an instance of QAPIBackend", + file=sys.stderr) + sys.exit(1) - schema = QAPISchema(schema_file) - gen_types(schema, output_dir, prefix, builtins) - gen_features(schema, output_dir, prefix) - gen_visit(schema, output_dir, prefix, builtins) - gen_commands(schema, output_dir, prefix, gen_tracing) - gen_events(schema, output_dir, prefix) - gen_introspect(schema, output_dir, prefix, unmask) + return backend def main() -> int: @@ -77,6 +83,8 @@ def main() -> int: parser.add_argument('-u', '--unmask-non-abi-names', action='store_true', dest='unmask', help="expose non-ABI names in introspection") + parser.add_argument('-B', '--backend', default=None, + help="Python module name for code generator") # Option --suppress-tracing exists so we can avoid solving build system # problems. TODO Drop it when we no longer need it. @@ -93,12 +101,14 @@ def main() -> int: return 1 try: - generate(args.schema, - output_dir=args.output_dir, - prefix=args.prefix, - unmask=args.unmask, - builtins=args.builtins, - gen_tracing=not args.suppress_tracing) + schema = QAPISchema(args.schema) + backend = create_backend(args.backend) + backend.generate(schema, + output_dir=args.output_dir, + prefix=args.prefix, + unmask=args.unmask, + builtins=args.builtins, + gen_tracing=not args.suppress_tracing) except QAPIError as err: print(err, file=sys.stderr) return 1 diff --git a/scripts/qapi/pylintrc b/scripts/qapi/pylintrc index c028a1f9f5..d24eece741 100644 --- a/scripts/qapi/pylintrc +++ b/scripts/qapi/pylintrc @@ -17,6 +17,7 @@ disable=consider-using-f-string, too-many-arguments, too-many-branches, too-many-instance-attributes, + too-many-positional-arguments, too-many-statements, useless-option-value, diff --git a/tests/qapi-schema/doc-good.json b/tests/qapi-schema/doc-good.json index f64bf38d85..0a4f139f83 100644 --- a/tests/qapi-schema/doc-good.json +++ b/tests/qapi-schema/doc-good.json @@ -12,6 +12,10 @@ ## ## +# Just text, no heading. +## + +## # == Subsection # # *with emphasis* diff --git a/tests/qapi-schema/doc-good.out b/tests/qapi-schema/doc-good.out index ec277be91e..0a9da3efde 100644 --- a/tests/qapi-schema/doc-good.out +++ b/tests/qapi-schema/doc-good.out @@ -58,6 +58,9 @@ doc freeform = Section doc freeform body= +Just text, no heading. +doc freeform + body= == Subsection *with emphasis* diff --git a/tests/qtest/ufs-test.c b/tests/qtest/ufs-test.c index d5076bdeb5..4867ccf08a 100644 --- a/tests/qtest/ufs-test.c +++ b/tests/qtest/ufs-test.c @@ -784,6 +784,30 @@ static void ufstest_query_attr_request(void *obj, void *data, g_assert_cmpuint(rsp_upiu.header.response, ==, UFS_COMMAND_RESULT_SUCCESS); g_assert_cmpuint(rsp_upiu.qr.value, ==, cpu_to_be32(0x00)); + ocs = ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, + UFS_UPIU_QUERY_OPCODE_READ_ATTR, + UFS_QUERY_ATTR_IDN_CASE_ROUGH_TEMP, 0, 0, 0, + &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS); + g_assert_cmpuint(rsp_upiu.header.response, ==, UFS_COMMAND_RESULT_SUCCESS); + g_assert_cmpuint(rsp_upiu.qr.value, ==, cpu_to_be32(0x00)); + + ocs = ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, + UFS_UPIU_QUERY_OPCODE_READ_ATTR, + UFS_QUERY_ATTR_IDN_HIGH_TEMP_BOUND, 0, 0, 0, + &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS); + g_assert_cmpuint(rsp_upiu.header.response, ==, UFS_COMMAND_RESULT_SUCCESS); + g_assert_cmpuint(rsp_upiu.qr.value, ==, cpu_to_be32(160)); + + ocs = ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, + UFS_UPIU_QUERY_OPCODE_READ_ATTR, + UFS_QUERY_ATTR_IDN_LOW_TEMP_BOUND, 0, 0, 0, + &rsp_upiu); + g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS); + g_assert_cmpuint(rsp_upiu.header.response, ==, UFS_COMMAND_RESULT_SUCCESS); + g_assert_cmpuint(rsp_upiu.qr.value, ==, cpu_to_be32(60)); + /* Write Writable Attributes & Read Again */ ocs = ufs_send_query(ufs, UFS_UPIU_QUERY_FUNC_STANDARD_WRITE_REQUEST, UFS_UPIU_QUERY_OPCODE_WRITE_ATTR, diff --git a/util/hexdump.c b/util/hexdump.c index ae0d4992dc..f29ffceb74 100644 --- a/util/hexdump.c +++ b/util/hexdump.c @@ -15,6 +15,7 @@ #include "qemu/osdep.h" #include "qemu/cutils.h" +#include "qemu/host-utils.h" static inline char hexdump_nibble(unsigned x) { @@ -97,3 +98,20 @@ void qemu_hexdump(FILE *fp, const char *prefix, } } + +void qemu_hexdump_to_buffer(char *restrict buffer, size_t buffer_size, + const uint8_t *restrict data, size_t data_size) +{ + size_t i; + uint64_t required_buffer_size; + bool overflow = umul64_overflow(data_size, 2, &required_buffer_size); + overflow |= uadd64_overflow(required_buffer_size, 1, &required_buffer_size); + assert(!overflow && buffer_size >= required_buffer_size); + + for (i = 0; i < data_size; i++) { + uint8_t val = data[i]; + *(buffer++) = hexdump_nibble(val >> 4); + *(buffer++) = hexdump_nibble(val & 0xf); + } + *buffer = '\0'; +} |