diff options
Diffstat (limited to 'hw')
84 files changed, 6750 insertions, 350 deletions
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/loongarch_extioi.c b/hw/intc/loongarch_extioi.c index f3055ec4d2..a51a215e6e 100644 --- a/hw/intc/loongarch_extioi.c +++ b/hw/intc/loongarch_extioi.c @@ -343,7 +343,7 @@ static void loongarch_extioi_realize(DeviceState *dev, Error **errp) LoongArchExtIOIClass *lec = LOONGARCH_EXTIOI_GET_CLASS(dev); SysBusDevice *sbd = SYS_BUS_DEVICE(dev); Error *local_err = NULL; - int i, pin; + int i; lec->parent_realize(dev, &local_err); if (local_err) { @@ -368,12 +368,6 @@ static void loongarch_extioi_realize(DeviceState *dev, Error **errp) } else { s->status |= BIT(EXTIOI_ENABLE); } - - for (i = 0; i < s->num_cpu; i++) { - for (pin = 0; pin < LS3A_INTC_IP; pin++) { - qdev_init_gpio_out(dev, &s->cpu[i].parent_irq[pin], 1); - } - } } static void loongarch_extioi_unrealize(DeviceState *dev) diff --git a/hw/intc/loongarch_extioi_common.c b/hw/intc/loongarch_extioi_common.c index fd56253d10..ff3974f2a1 100644 --- a/hw/intc/loongarch_extioi_common.c +++ b/hw/intc/loongarch_extioi_common.c @@ -4,11 +4,82 @@ * Copyright (C) 2024 Loongson Technology Corporation Limited */ #include "qemu/osdep.h" +#include "qemu/error-report.h" #include "qemu/module.h" #include "qapi/error.h" #include "hw/qdev-properties.h" #include "hw/intc/loongarch_extioi_common.h" #include "migration/vmstate.h" +#include "target/loongarch/cpu.h" + +static ExtIOICore *loongarch_extioi_get_cpu(LoongArchExtIOICommonState *s, + DeviceState *dev) +{ + CPUClass *k = CPU_GET_CLASS(dev); + uint64_t arch_id = k->get_arch_id(CPU(dev)); + int i; + + for (i = 0; i < s->num_cpu; i++) { + if (s->cpu[i].arch_id == arch_id) { + return &s->cpu[i]; + } + } + + return NULL; +} + +static void loongarch_extioi_cpu_plug(HotplugHandler *hotplug_dev, + DeviceState *dev, Error **errp) +{ + LoongArchExtIOICommonState *s = LOONGARCH_EXTIOI_COMMON(hotplug_dev); + Object *obj = OBJECT(dev); + ExtIOICore *core; + int pin, index; + + if (!object_dynamic_cast(obj, TYPE_LOONGARCH_CPU)) { + warn_report("LoongArch extioi: Invalid %s device type", + object_get_typename(obj)); + return; + } + + core = loongarch_extioi_get_cpu(s, dev); + if (!core) { + return; + } + + core->cpu = CPU(dev); + index = core - s->cpu; + + /* + * connect extioi irq to the cpu irq + * cpu_pin[LS3A_INTC_IP + 2 : 2] <= intc_pin[LS3A_INTC_IP : 0] + */ + for (pin = 0; pin < LS3A_INTC_IP; pin++) { + qdev_connect_gpio_out(DEVICE(s), index * LS3A_INTC_IP + pin, + qdev_get_gpio_in(dev, pin + 2)); + } +} + +static void loongarch_extioi_cpu_unplug(HotplugHandler *hotplug_dev, + DeviceState *dev, Error **errp) +{ + LoongArchExtIOICommonState *s = LOONGARCH_EXTIOI_COMMON(hotplug_dev); + Object *obj = OBJECT(dev); + ExtIOICore *core; + + if (!object_dynamic_cast(obj, TYPE_LOONGARCH_CPU)) { + warn_report("LoongArch extioi: Invalid %s device type", + object_get_typename(obj)); + return; + } + + core = loongarch_extioi_get_cpu(s, dev); + if (!core) { + return; + } + + core->cpu = NULL; +} static void loongarch_extioi_common_realize(DeviceState *dev, Error **errp) { @@ -16,7 +87,7 @@ static void loongarch_extioi_common_realize(DeviceState *dev, Error **errp) MachineState *machine = MACHINE(qdev_get_machine()); MachineClass *mc = MACHINE_GET_CLASS(machine); const CPUArchIdList *id_list; - int i; + int i, pin; assert(mc->possible_cpu_arch_ids); id_list = mc->possible_cpu_arch_ids(machine); @@ -30,6 +101,10 @@ static void loongarch_extioi_common_realize(DeviceState *dev, Error **errp) for (i = 0; i < s->num_cpu; i++) { s->cpu[i].arch_id = id_list->cpus[i].arch_id; s->cpu[i].cpu = CPU(id_list->cpus[i].cpu); + + for (pin = 0; pin < LS3A_INTC_IP; pin++) { + qdev_init_gpio_out(dev, &s->cpu[i].parent_irq[pin], 1); + } } } @@ -103,11 +178,14 @@ static void loongarch_extioi_common_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); LoongArchExtIOICommonClass *lecc = LOONGARCH_EXTIOI_COMMON_CLASS(klass); + HotplugHandlerClass *hc = HOTPLUG_HANDLER_CLASS(klass); device_class_set_parent_realize(dc, loongarch_extioi_common_realize, &lecc->parent_realize); device_class_set_props(dc, extioi_properties); dc->vmsd = &vmstate_loongarch_extioi; + hc->plug = loongarch_extioi_cpu_plug; + hc->unplug = loongarch_extioi_cpu_unplug; } static const TypeInfo loongarch_extioi_common_types[] = { @@ -117,6 +195,10 @@ static const TypeInfo loongarch_extioi_common_types[] = { .instance_size = sizeof(LoongArchExtIOICommonState), .class_size = sizeof(LoongArchExtIOICommonClass), .class_init = loongarch_extioi_common_class_init, + .interfaces = (InterfaceInfo[]) { + { TYPE_HOTPLUG_HANDLER }, + { } + }, .abstract = true, } }; diff --git a/hw/intc/loongarch_ipi.c b/hw/intc/loongarch_ipi.c index 5376f1e084..b10641dd03 100644 --- a/hw/intc/loongarch_ipi.c +++ b/hw/intc/loongarch_ipi.c @@ -6,6 +6,7 @@ */ #include "qemu/osdep.h" +#include "qemu/error-report.h" #include "hw/boards.h" #include "qapi/error.h" #include "hw/intc/loongarch_ipi.h" @@ -48,6 +49,22 @@ static int loongarch_cpu_by_arch_id(LoongsonIPICommonState *lics, return MEMTX_ERROR; } +static IPICore *loongarch_ipi_get_cpu(LoongsonIPICommonState *lics, + DeviceState *dev) +{ + CPUClass *k = CPU_GET_CLASS(dev); + uint64_t arch_id = k->get_arch_id(CPU(dev)); + int i; + + for (i = 0; i < lics->num_cpu; i++) { + if (lics->cpu[i].arch_id == arch_id) { + return &lics->cpu[i]; + } + } + + return NULL; +} + static void loongarch_ipi_realize(DeviceState *dev, Error **errp) { LoongsonIPICommonState *lics = LOONGSON_IPI_COMMON(dev); @@ -76,9 +93,57 @@ static void loongarch_ipi_realize(DeviceState *dev, Error **errp) } } +static void loongarch_ipi_cpu_plug(HotplugHandler *hotplug_dev, + DeviceState *dev, Error **errp) +{ + LoongsonIPICommonState *lics = LOONGSON_IPI_COMMON(hotplug_dev); + Object *obj = OBJECT(dev); + IPICore *core; + int index; + + if (!object_dynamic_cast(obj, TYPE_LOONGARCH_CPU)) { + warn_report("LoongArch extioi: Invalid %s device type", + object_get_typename(obj)); + return; + } + + core = loongarch_ipi_get_cpu(lics, dev); + if (!core) { + return; + } + + core->cpu = CPU(dev); + index = core - lics->cpu; + + /* connect ipi irq to cpu irq */ + qdev_connect_gpio_out(DEVICE(lics), index, qdev_get_gpio_in(dev, IRQ_IPI)); +} + +static void loongarch_ipi_cpu_unplug(HotplugHandler *hotplug_dev, + DeviceState *dev, Error **errp) +{ + LoongsonIPICommonState *lics = LOONGSON_IPI_COMMON(hotplug_dev); + Object *obj = OBJECT(dev); + IPICore *core; + + if (!object_dynamic_cast(obj, TYPE_LOONGARCH_CPU)) { + warn_report("LoongArch extioi: Invalid %s device type", + object_get_typename(obj)); + return; + } + + core = loongarch_ipi_get_cpu(lics, dev); + if (!core) { + return; + } + + core->cpu = NULL; +} + static void loongarch_ipi_class_init(ObjectClass *klass, void *data) { LoongsonIPICommonClass *licc = LOONGSON_IPI_COMMON_CLASS(klass); + HotplugHandlerClass *hc = HOTPLUG_HANDLER_CLASS(klass); LoongarchIPIClass *lic = LOONGARCH_IPI_CLASS(klass); DeviceClass *dc = DEVICE_CLASS(klass); @@ -86,6 +151,8 @@ static void loongarch_ipi_class_init(ObjectClass *klass, void *data) &lic->parent_realize); licc->get_iocsr_as = get_iocsr_as; licc->cpu_by_arch_id = loongarch_cpu_by_arch_id; + hc->plug = loongarch_ipi_cpu_plug; + hc->unplug = loongarch_ipi_cpu_unplug; } static const TypeInfo loongarch_ipi_types[] = { @@ -95,6 +162,10 @@ static const TypeInfo loongarch_ipi_types[] = { .instance_size = sizeof(LoongarchIPIState), .class_size = sizeof(LoongarchIPIClass), .class_init = loongarch_ipi_class_init, + .interfaces = (InterfaceInfo[]) { + { TYPE_HOTPLUG_HANDLER }, + { } + }, } }; 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/intc/riscv_aplic.c b/hw/intc/riscv_aplic.c index 4866649115..5964cde7e0 100644 --- a/hw/intc/riscv_aplic.c +++ b/hw/intc/riscv_aplic.c @@ -181,8 +181,10 @@ void riscv_aplic_set_kvm_msicfgaddr(RISCVAPLICState *aplic, hwaddr addr) { #ifdef CONFIG_KVM if (riscv_use_emulated_aplic(aplic->msimode)) { + addr >>= APLIC_xMSICFGADDR_PPN_SHIFT; aplic->kvm_msicfgaddr = extract64(addr, 0, 32); - aplic->kvm_msicfgaddrH = extract64(addr, 32, 32); + aplic->kvm_msicfgaddrH = extract64(addr, 32, 32) & + APLIC_xMSICFGADDRH_VALID_MASK; } #endif } @@ -403,12 +405,17 @@ static void riscv_aplic_msi_send(RISCVAPLICState *aplic, } } - if (aplic->mmode) { - msicfgaddr = aplic_m->mmsicfgaddr; - msicfgaddrH = aplic_m->mmsicfgaddrH; + if (aplic->kvm_splitmode) { + msicfgaddr = aplic->kvm_msicfgaddr; + msicfgaddrH = ((uint64_t)aplic->kvm_msicfgaddrH << 32); } else { - msicfgaddr = aplic_m->smsicfgaddr; - msicfgaddrH = aplic_m->smsicfgaddrH; + if (aplic->mmode) { + msicfgaddr = aplic_m->mmsicfgaddr; + msicfgaddrH = aplic_m->mmsicfgaddrH; + } else { + msicfgaddr = aplic_m->smsicfgaddr; + msicfgaddrH = aplic_m->smsicfgaddrH; + } } lhxs = (msicfgaddrH >> APLIC_xMSICFGADDRH_LHXS_SHIFT) & @@ -421,7 +428,6 @@ static void riscv_aplic_msi_send(RISCVAPLICState *aplic, APLIC_xMSICFGADDRH_HHXW_MASK; group_idx = hart_idx >> lhxw; - hart_idx &= APLIC_xMSICFGADDR_PPN_LHX_MASK(lhxw); addr = msicfgaddr; addr |= ((uint64_t)(msicfgaddrH & APLIC_xMSICFGADDRH_BAPPN_MASK)) << 32; @@ -432,11 +438,6 @@ static void riscv_aplic_msi_send(RISCVAPLICState *aplic, addr |= (uint64_t)(guest_idx & APLIC_xMSICFGADDR_PPN_HART(lhxs)); addr <<= APLIC_xMSICFGADDR_PPN_SHIFT; - if (aplic->kvm_splitmode) { - addr |= aplic->kvm_msicfgaddr; - addr |= ((uint64_t)aplic->kvm_msicfgaddrH << 32); - } - address_space_stl_le(&address_space_memory, addr, eiid, MEMTXATTRS_UNSPECIFIED, &result); if (result != MEMTX_OK) { @@ -894,6 +895,26 @@ static void riscv_aplic_realize(DeviceState *dev, Error **errp) RISCVAPLICState *aplic = RISCV_APLIC(dev); if (riscv_use_emulated_aplic(aplic->msimode)) { + /* Create output IRQ lines for non-MSI mode */ + if (!aplic->msimode) { + /* Claim the CPU interrupt to be triggered by this APLIC */ + for (i = 0; i < aplic->num_harts; i++) { + RISCVCPU *cpu; + + cpu = RISCV_CPU(cpu_by_arch_id(aplic->hartid_base + i)); + if (riscv_cpu_claim_interrupts(cpu, + (aplic->mmode) ? MIP_MEIP : MIP_SEIP) < 0) { + error_report("%s already claimed", + (aplic->mmode) ? "MEIP" : "SEIP"); + exit(1); + } + } + + aplic->external_irqs = g_malloc(sizeof(qemu_irq) * + aplic->num_harts); + qdev_init_gpio_out(dev, aplic->external_irqs, aplic->num_harts); + } + aplic->bitfield_words = (aplic->num_irqs + 31) >> 5; aplic->sourcecfg = g_new0(uint32_t, aplic->num_irqs); aplic->state = g_new0(uint32_t, aplic->num_irqs); @@ -928,23 +949,6 @@ static void riscv_aplic_realize(DeviceState *dev, Error **errp) } } - /* Create output IRQ lines for non-MSI mode */ - if (!aplic->msimode) { - aplic->external_irqs = g_malloc(sizeof(qemu_irq) * aplic->num_harts); - qdev_init_gpio_out(dev, aplic->external_irqs, aplic->num_harts); - - /* Claim the CPU interrupt to be triggered by this APLIC */ - for (i = 0; i < aplic->num_harts; i++) { - RISCVCPU *cpu = RISCV_CPU(cpu_by_arch_id(aplic->hartid_base + i)); - if (riscv_cpu_claim_interrupts(cpu, - (aplic->mmode) ? MIP_MEIP : MIP_SEIP) < 0) { - error_report("%s already claimed", - (aplic->mmode) ? "MEIP" : "SEIP"); - exit(1); - } - } - } - msi_nonbroken = true; } @@ -1068,15 +1072,15 @@ DeviceState *riscv_aplic_create(hwaddr addr, hwaddr size, if (riscv_use_emulated_aplic(msimode)) { sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, addr); - } - if (!msimode) { - for (i = 0; i < num_harts; i++) { - CPUState *cpu = cpu_by_arch_id(hartid_base + i); + if (!msimode) { + for (i = 0; i < num_harts; i++) { + CPUState *cpu = cpu_by_arch_id(hartid_base + i); - qdev_connect_gpio_out_named(dev, NULL, i, - qdev_get_gpio_in(DEVICE(cpu), + qdev_connect_gpio_out_named(dev, NULL, i, + qdev_get_gpio_in(DEVICE(cpu), (mmode) ? IRQ_M_EXT : IRQ_S_EXT)); + } } } diff --git a/hw/intc/riscv_imsic.c b/hw/intc/riscv_imsic.c index dc8162c0a7..241b12fef0 100644 --- a/hw/intc/riscv_imsic.c +++ b/hw/intc/riscv_imsic.c @@ -349,7 +349,19 @@ static void riscv_imsic_realize(DeviceState *dev, Error **errp) CPUState *cpu = cpu_by_arch_id(imsic->hartid); CPURISCVState *env = cpu ? cpu_env(cpu) : NULL; + /* Claim the CPU interrupt to be triggered by this IMSIC */ + if (riscv_cpu_claim_interrupts(rcpu, + (imsic->mmode) ? MIP_MEIP : MIP_SEIP) < 0) { + error_setg(errp, "%s already claimed", + (imsic->mmode) ? "MEIP" : "SEIP"); + return; + } + if (!kvm_irqchip_in_kernel()) { + /* Create output IRQ lines */ + imsic->external_irqs = g_malloc(sizeof(qemu_irq) * imsic->num_pages); + qdev_init_gpio_out(dev, imsic->external_irqs, imsic->num_pages); + imsic->num_eistate = imsic->num_pages * imsic->num_irqs; imsic->eidelivery = g_new0(uint32_t, imsic->num_pages); imsic->eithreshold = g_new0(uint32_t, imsic->num_pages); @@ -361,18 +373,6 @@ static void riscv_imsic_realize(DeviceState *dev, Error **errp) IMSIC_MMIO_SIZE(imsic->num_pages)); sysbus_init_mmio(SYS_BUS_DEVICE(dev), &imsic->mmio); - /* Claim the CPU interrupt to be triggered by this IMSIC */ - if (riscv_cpu_claim_interrupts(rcpu, - (imsic->mmode) ? MIP_MEIP : MIP_SEIP) < 0) { - error_setg(errp, "%s already claimed", - (imsic->mmode) ? "MEIP" : "SEIP"); - return; - } - - /* Create output IRQ lines */ - imsic->external_irqs = g_malloc(sizeof(qemu_irq) * imsic->num_pages); - qdev_init_gpio_out(dev, imsic->external_irqs, imsic->num_pages); - /* Force select AIA feature and setup CSR read-modify-write callback */ if (env) { if (!imsic->mmode) { @@ -381,8 +381,11 @@ static void riscv_imsic_realize(DeviceState *dev, Error **errp) } else { rcpu->cfg.ext_smaia = true; } - riscv_cpu_set_aia_ireg_rmw_fn(env, (imsic->mmode) ? PRV_M : PRV_S, - riscv_imsic_rmw, imsic); + + if (!kvm_irqchip_in_kernel()) { + riscv_cpu_set_aia_ireg_rmw_fn(env, (imsic->mmode) ? PRV_M : PRV_S, + riscv_imsic_rmw, imsic); + } } msi_nonbroken = true; @@ -464,15 +467,17 @@ DeviceState *riscv_imsic_create(hwaddr addr, uint32_t hartid, bool mmode, sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, addr); - for (i = 0; i < num_pages; i++) { - if (!i) { - qdev_connect_gpio_out_named(dev, NULL, i, - qdev_get_gpio_in(DEVICE(cpu), + if (!kvm_irqchip_in_kernel()) { + for (i = 0; i < num_pages; i++) { + if (!i) { + qdev_connect_gpio_out_named(dev, NULL, i, + qdev_get_gpio_in(DEVICE(cpu), (mmode) ? IRQ_M_EXT : IRQ_S_EXT)); - } else { - qdev_connect_gpio_out_named(dev, NULL, i, - qdev_get_gpio_in(DEVICE(cpu), + } else { + qdev_connect_gpio_out_named(dev, NULL, i, + qdev_get_gpio_in(DEVICE(cpu), IRQ_LOCAL_MAX + i - 1)); + } } } diff --git a/hw/loongarch/Kconfig b/hw/loongarch/Kconfig index fe1c6feac1..bb2838b7b5 100644 --- a/hw/loongarch/Kconfig +++ b/hw/loongarch/Kconfig @@ -17,6 +17,7 @@ config LOONGARCH_VIRT select LOONGARCH_EXTIOI select LS7A_RTC select SMBIOS + select ACPI_CPU_HOTPLUG select ACPI_PCI select ACPI_HW_REDUCED select FW_CFG_DMA diff --git a/hw/loongarch/virt-acpi-build.c b/hw/loongarch/virt-acpi-build.c index 9ca88d63ae..fced6c445a 100644 --- a/hw/loongarch/virt-acpi-build.c +++ b/hw/loongarch/virt-acpi-build.c @@ -47,6 +47,22 @@ #define ACPI_BUILD_DPRINTF(fmt, ...) #endif +static void virt_madt_cpu_entry(int uid, + const CPUArchIdList *apic_ids, + GArray *entry, bool force_enabled) +{ + uint32_t flags, apic_id = apic_ids->cpus[uid].arch_id; + + flags = apic_ids->cpus[uid].cpu || force_enabled ? 1 /* Enabled */ : 0; + + /* Rev 1.0b, Table 5-13 Processor Local APIC Structure */ + build_append_int_noprefix(entry, 0, 1); /* Type */ + build_append_int_noprefix(entry, 8, 1); /* Length */ + build_append_int_noprefix(entry, uid, 1); /* ACPI Processor ID */ + build_append_int_noprefix(entry, apic_id, 1); /* APIC ID */ + build_append_int_noprefix(entry, flags, 4); /* Flags */ +} + /* build FADT */ static void init_common_fadt_data(AcpiFadtData *data) { @@ -112,7 +128,7 @@ build_madt(GArray *table_data, BIOSLinker *linker, MachineState *ms = MACHINE(lvms); MachineClass *mc = MACHINE_GET_CLASS(ms); const CPUArchIdList *arch_ids = mc->possible_cpu_arch_ids(ms); - int i, arch_id; + int i, arch_id, flags; AcpiTable table = { .sig = "APIC", .rev = 1, .oem_id = lvms->oem_id, .oem_table_id = lvms->oem_table_id }; @@ -125,13 +141,13 @@ build_madt(GArray *table_data, BIOSLinker *linker, for (i = 0; i < arch_ids->len; i++) { /* Processor Core Interrupt Controller Structure */ arch_id = arch_ids->cpus[i].arch_id; - + flags = arch_ids->cpus[i].cpu ? 1 : 0; build_append_int_noprefix(table_data, 17, 1); /* Type */ build_append_int_noprefix(table_data, 15, 1); /* Length */ build_append_int_noprefix(table_data, 1, 1); /* Version */ build_append_int_noprefix(table_data, i, 4); /* ACPI Processor ID */ build_append_int_noprefix(table_data, arch_id, 4); /* Core ID */ - build_append_int_noprefix(table_data, 1, 4); /* Flags */ + build_append_int_noprefix(table_data, flags, 4); /* Flags */ } /* Extend I/O Interrupt Controller Structure */ @@ -338,6 +354,7 @@ build_la_ged_aml(Aml *dsdt, MachineState *machine) { uint32_t event; LoongArchVirtMachineState *lvms = LOONGARCH_VIRT_MACHINE(machine); + CPUHotplugFeatures opts; build_ged_aml(dsdt, "\\_SB."GED_DEVICE, HOTPLUG_HANDLER(lvms->acpi_ged), @@ -350,6 +367,18 @@ build_la_ged_aml(Aml *dsdt, MachineState *machine) AML_SYSTEM_MEMORY, VIRT_GED_MEM_ADDR); } + + if (event & ACPI_GED_CPU_HOTPLUG_EVT) { + opts.acpi_1_compatible = false; + opts.has_legacy_cphp = false; + opts.fw_unplugs_cpu = false; + opts.smi_path = NULL; + + build_cpus_aml(dsdt, machine, opts, virt_madt_cpu_entry, + VIRT_GED_CPUHP_ADDR, "\\_SB", + AML_GED_EVT_CPU_SCAN_METHOD, AML_SYSTEM_MEMORY); + } + acpi_dsdt_add_power_button(dsdt); } diff --git a/hw/loongarch/virt.c b/hw/loongarch/virt.c index 59533b058b..a5840ff968 100644 --- a/hw/loongarch/virt.c +++ b/hw/loongarch/virt.c @@ -187,11 +187,17 @@ static DeviceState *create_acpi_ged(DeviceState *pch_pic, { DeviceState *dev; MachineState *ms = MACHINE(lvms); + MachineClass *mc = MACHINE_GET_CLASS(lvms); uint32_t event = ACPI_GED_PWR_DOWN_EVT; if (ms->ram_slots) { event |= ACPI_GED_MEM_HOTPLUG_EVT; } + + if (mc->has_hotpluggable_cpus) { + event |= ACPI_GED_CPU_HOTPLUG_EVT; + } + dev = qdev_new(TYPE_ACPI_GED); qdev_prop_set_uint32(dev, "ged-event", event); sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); @@ -203,6 +209,10 @@ static DeviceState *create_acpi_ged(DeviceState *pch_pic, /* ged regs used for reset and power down */ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 2, VIRT_GED_REG_ADDR); + if (mc->has_hotpluggable_cpus) { + sysbus_mmio_map(SYS_BUS_DEVICE(dev), 3, VIRT_GED_CPUHP_ADDR); + } + sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, qdev_get_gpio_in(pch_pic, VIRT_SCI_IRQ - VIRT_GSI_BASE)); return dev; @@ -312,11 +322,12 @@ static void virt_devices_init(DeviceState *pch_pic, static void virt_cpu_irq_init(LoongArchVirtMachineState *lvms) { - int num, pin; + int num; MachineState *ms = MACHINE(lvms); MachineClass *mc = MACHINE_GET_CLASS(ms); const CPUArchIdList *possible_cpus; CPUState *cs; + Error *err = NULL; /* cpu nodes */ possible_cpus = mc->possible_cpu_arch_ids(ms); @@ -326,18 +337,8 @@ static void virt_cpu_irq_init(LoongArchVirtMachineState *lvms) continue; } - /* connect ipi irq to cpu irq */ - qdev_connect_gpio_out(lvms->ipi, num, - qdev_get_gpio_in(DEVICE(cs), IRQ_IPI)); - - /* - * connect ext irq to the cpu irq - * cpu_pin[9:2] <= intc_pin[7:0] - */ - for (pin = 0; pin < LS3A_INTC_IP; pin++) { - qdev_connect_gpio_out(lvms->extioi, (num * LS3A_INTC_IP + pin), - qdev_get_gpio_in(DEVICE(cs), pin + 2)); - } + hotplug_handler_plug(HOTPLUG_HANDLER(lvms->ipi), DEVICE(cs), &err); + hotplug_handler_plug(HOTPLUG_HANDLER(lvms->extioi), DEVICE(cs), &err); } } @@ -656,15 +657,13 @@ static void fw_cfg_add_memory(MachineState *ms) static void virt_init(MachineState *machine) { - LoongArchCPU *lacpu; const char *cpu_model = machine->cpu_type; MemoryRegion *address_space_mem = get_system_memory(); LoongArchVirtMachineState *lvms = LOONGARCH_VIRT_MACHINE(machine); int i; hwaddr base, size, ram_size = machine->ram_size; - const CPUArchIdList *possible_cpus; MachineClass *mc = MACHINE_GET_CLASS(machine); - CPUState *cpu; + Object *cpuobj; if (!cpu_model) { cpu_model = LOONGARCH_CPU_TYPE_NAME("la464"); @@ -680,14 +679,15 @@ static void virt_init(MachineState *machine) memory_region_add_subregion(&lvms->system_iocsr, 0, &lvms->iocsr_mem); /* Init CPUs */ - possible_cpus = mc->possible_cpu_arch_ids(machine); - for (i = 0; i < possible_cpus->len; i++) { - cpu = cpu_create(machine->cpu_type); - cpu->cpu_index = i; - machine->possible_cpus->cpus[i].cpu = cpu; - lacpu = LOONGARCH_CPU(cpu); - lacpu->phy_id = machine->possible_cpus->cpus[i].arch_id; - lacpu->env.address_space_iocsr = &lvms->as_iocsr; + mc->possible_cpu_arch_ids(machine); + for (i = 0; i < machine->smp.cpus; i++) { + cpuobj = object_new(machine->cpu_type); + if (cpuobj == NULL) { + error_report("Fail to create object with type %s ", + machine->cpu_type); + exit(EXIT_FAILURE); + } + qdev_realize_and_unref(DEVICE(cpuobj), NULL, &error_fatal); } fw_cfg_add_memory(machine); @@ -783,6 +783,232 @@ static void virt_initfn(Object *obj) virt_flash_create(lvms); } +static void virt_get_topo_from_index(MachineState *ms, + LoongArchCPUTopo *topo, int index) +{ + topo->socket_id = index / (ms->smp.cores * ms->smp.threads); + topo->core_id = index / ms->smp.threads % ms->smp.cores; + topo->thread_id = index % ms->smp.threads; +} + +static unsigned int topo_align_up(unsigned int count) +{ + g_assert(count >= 1); + count -= 1; + return BIT(count ? 32 - clz32(count) : 0); +} + +/* + * LoongArch Reference Manual Vol1, Chapter 7.4.12 CPU Identity + * For CPU architecture, bit0 .. bit8 is valid for CPU id, max cpuid is 512 + * However for IPI/Eiointc interrupt controller, max supported cpu id for + * irq routingis 256 + * + * Here max cpu id is 256 for virt machine + */ +static int virt_get_arch_id_from_topo(MachineState *ms, LoongArchCPUTopo *topo) +{ + int arch_id, threads, cores, sockets; + + threads = topo_align_up(ms->smp.threads); + cores = topo_align_up(ms->smp.cores); + sockets = topo_align_up(ms->smp.sockets); + if ((threads * cores * sockets) > 256) { + error_report("Exceeding max cpuid 256 with sockets[%d] cores[%d]" + " threads[%d]", ms->smp.sockets, ms->smp.cores, + ms->smp.threads); + exit(1); + } + + arch_id = topo->thread_id + topo->core_id * threads; + arch_id += topo->socket_id * threads * cores; + return arch_id; +} + +/* Find cpu slot in machine->possible_cpus by arch_id */ +static CPUArchId *virt_find_cpu_slot(MachineState *ms, int arch_id) +{ + int n; + for (n = 0; n < ms->possible_cpus->len; n++) { + if (ms->possible_cpus->cpus[n].arch_id == arch_id) { + return &ms->possible_cpus->cpus[n]; + } + } + + return NULL; +} + +/* Find cpu slot for cold-plut CPU object where cpu is NULL */ +static CPUArchId *virt_find_empty_cpu_slot(MachineState *ms) +{ + int n; + for (n = 0; n < ms->possible_cpus->len; n++) { + if (ms->possible_cpus->cpus[n].cpu == NULL) { + return &ms->possible_cpus->cpus[n]; + } + } + + return NULL; +} + +static void virt_cpu_pre_plug(HotplugHandler *hotplug_dev, + DeviceState *dev, Error **errp) +{ + LoongArchVirtMachineState *lvms = LOONGARCH_VIRT_MACHINE(hotplug_dev); + MachineState *ms = MACHINE(OBJECT(hotplug_dev)); + LoongArchCPU *cpu = LOONGARCH_CPU(dev); + CPUState *cs = CPU(dev); + CPUArchId *cpu_slot; + Error *err = NULL; + LoongArchCPUTopo topo; + int arch_id; + + if (lvms->acpi_ged) { + if ((cpu->thread_id < 0) || (cpu->thread_id >= ms->smp.threads)) { + error_setg(&err, + "Invalid thread-id %u specified, must be in range 1:%u", + cpu->thread_id, ms->smp.threads - 1); + goto out; + } + + if ((cpu->core_id < 0) || (cpu->core_id >= ms->smp.cores)) { + error_setg(&err, + "Invalid core-id %u specified, must be in range 1:%u", + cpu->core_id, ms->smp.cores - 1); + goto out; + } + + if ((cpu->socket_id < 0) || (cpu->socket_id >= ms->smp.sockets)) { + error_setg(&err, + "Invalid socket-id %u specified, must be in range 1:%u", + cpu->socket_id, ms->smp.sockets - 1); + goto out; + } + + topo.socket_id = cpu->socket_id; + topo.core_id = cpu->core_id; + topo.thread_id = cpu->thread_id; + arch_id = virt_get_arch_id_from_topo(ms, &topo); + cpu_slot = virt_find_cpu_slot(ms, arch_id); + if (CPU(cpu_slot->cpu)) { + error_setg(&err, + "cpu(id%d=%d:%d:%d) with arch-id %" PRIu64 " exists", + cs->cpu_index, cpu->socket_id, cpu->core_id, + cpu->thread_id, cpu_slot->arch_id); + goto out; + } + } else { + /* For cold-add cpu, find empty cpu slot */ + cpu_slot = virt_find_empty_cpu_slot(ms); + topo.socket_id = cpu_slot->props.socket_id; + topo.core_id = cpu_slot->props.core_id; + topo.thread_id = cpu_slot->props.thread_id; + object_property_set_int(OBJECT(dev), "socket-id", topo.socket_id, NULL); + object_property_set_int(OBJECT(dev), "core-id", topo.core_id, NULL); + object_property_set_int(OBJECT(dev), "thread-id", topo.thread_id, NULL); + } + + cpu->env.address_space_iocsr = &lvms->as_iocsr; + cpu->phy_id = cpu_slot->arch_id; + cs->cpu_index = cpu_slot - ms->possible_cpus->cpus; + numa_cpu_pre_plug(cpu_slot, dev, &err); +out: + if (err) { + error_propagate(errp, err); + } +} + +static void virt_cpu_unplug_request(HotplugHandler *hotplug_dev, + DeviceState *dev, Error **errp) +{ + LoongArchVirtMachineState *lvms = LOONGARCH_VIRT_MACHINE(hotplug_dev); + Error *err = NULL; + LoongArchCPU *cpu = LOONGARCH_CPU(dev); + CPUState *cs = CPU(dev); + + if (cs->cpu_index == 0) { + error_setg(&err, "hot-unplug of boot cpu(id%d=%d:%d:%d) not supported", + cs->cpu_index, cpu->socket_id, + cpu->core_id, cpu->thread_id); + error_propagate(errp, err); + return; + } + + hotplug_handler_unplug_request(HOTPLUG_HANDLER(lvms->acpi_ged), dev, &err); + if (err) { + error_propagate(errp, err); + } +} + +static void virt_cpu_unplug(HotplugHandler *hotplug_dev, + DeviceState *dev, Error **errp) +{ + CPUArchId *cpu_slot; + Error *err = NULL; + LoongArchCPU *cpu = LOONGARCH_CPU(dev); + LoongArchVirtMachineState *lvms = LOONGARCH_VIRT_MACHINE(hotplug_dev); + + /* Notify ipi and extioi irqchip to remove interrupt routing to CPU */ + hotplug_handler_unplug(HOTPLUG_HANDLER(lvms->ipi), dev, &err); + if (err) { + error_propagate(errp, err); + return; + } + + hotplug_handler_unplug(HOTPLUG_HANDLER(lvms->extioi), dev, &err); + if (err) { + error_propagate(errp, err); + return; + } + + /* Notify acpi ged CPU removed */ + hotplug_handler_unplug(HOTPLUG_HANDLER(lvms->acpi_ged), dev, &err); + if (err) { + error_propagate(errp, err); + return; + } + + cpu_slot = virt_find_cpu_slot(MACHINE(lvms), cpu->phy_id); + cpu_slot->cpu = NULL; + return; +} + +static void virt_cpu_plug(HotplugHandler *hotplug_dev, + DeviceState *dev, Error **errp) +{ + CPUArchId *cpu_slot; + LoongArchCPU *cpu = LOONGARCH_CPU(dev); + LoongArchVirtMachineState *lvms = LOONGARCH_VIRT_MACHINE(hotplug_dev); + Error *err = NULL; + + cpu_slot = virt_find_cpu_slot(MACHINE(lvms), cpu->phy_id); + cpu_slot->cpu = CPU(dev); + if (lvms->ipi) { + hotplug_handler_plug(HOTPLUG_HANDLER(lvms->ipi), dev, &err); + if (err) { + error_propagate(errp, err); + return; + } + } + + if (lvms->extioi) { + hotplug_handler_plug(HOTPLUG_HANDLER(lvms->extioi), dev, &err); + if (err) { + error_propagate(errp, err); + return; + } + } + + if (lvms->acpi_ged) { + hotplug_handler_plug(HOTPLUG_HANDLER(lvms->acpi_ged), dev, &err); + if (err) { + error_propagate(errp, err); + } + } + + return; +} + static bool memhp_type_supported(DeviceState *dev) { /* we only support pc dimm now */ @@ -801,6 +1027,8 @@ static void virt_device_pre_plug(HotplugHandler *hotplug_dev, { if (memhp_type_supported(dev)) { virt_mem_pre_plug(hotplug_dev, dev, errp); + } else if (object_dynamic_cast(OBJECT(dev), TYPE_LOONGARCH_CPU)) { + virt_cpu_pre_plug(hotplug_dev, dev, errp); } } @@ -819,6 +1047,8 @@ static void virt_device_unplug_request(HotplugHandler *hotplug_dev, { if (memhp_type_supported(dev)) { virt_mem_unplug_request(hotplug_dev, dev, errp); + } else if (object_dynamic_cast(OBJECT(dev), TYPE_LOONGARCH_CPU)) { + virt_cpu_unplug_request(hotplug_dev, dev, errp); } } @@ -837,6 +1067,8 @@ static void virt_device_unplug(HotplugHandler *hotplug_dev, { if (memhp_type_supported(dev)) { virt_mem_unplug(hotplug_dev, dev, errp); + } else if (object_dynamic_cast(OBJECT(dev), TYPE_LOONGARCH_CPU)) { + virt_cpu_unplug(hotplug_dev, dev, errp); } } @@ -864,6 +1096,8 @@ static void virt_device_plug_cb(HotplugHandler *hotplug_dev, } } else if (memhp_type_supported(dev)) { virt_mem_plug(hotplug_dev, dev, errp); + } else if (object_dynamic_cast(OBJECT(dev), TYPE_LOONGARCH_CPU)) { + virt_cpu_plug(hotplug_dev, dev, errp); } } @@ -873,6 +1107,7 @@ static HotplugHandler *virt_get_hotplug_handler(MachineState *machine, MachineClass *mc = MACHINE_GET_CLASS(machine); if (device_is_dynamic_sysbus(mc, dev) || + object_dynamic_cast(OBJECT(dev), TYPE_LOONGARCH_CPU) || object_dynamic_cast(OBJECT(dev), TYPE_VIRTIO_IOMMU_PCI) || memhp_type_supported(dev)) { return HOTPLUG_HANDLER(machine); @@ -882,8 +1117,9 @@ static HotplugHandler *virt_get_hotplug_handler(MachineState *machine, static const CPUArchIdList *virt_possible_cpu_arch_ids(MachineState *ms) { - int n; + int n, arch_id; unsigned int max_cpus = ms->smp.max_cpus; + LoongArchCPUTopo topo; if (ms->possible_cpus) { assert(ms->possible_cpus->len == max_cpus); @@ -894,17 +1130,17 @@ static const CPUArchIdList *virt_possible_cpu_arch_ids(MachineState *ms) sizeof(CPUArchId) * max_cpus); ms->possible_cpus->len = max_cpus; for (n = 0; n < ms->possible_cpus->len; n++) { + virt_get_topo_from_index(ms, &topo, n); + arch_id = virt_get_arch_id_from_topo(ms, &topo); ms->possible_cpus->cpus[n].type = ms->cpu_type; - ms->possible_cpus->cpus[n].arch_id = n; - + ms->possible_cpus->cpus[n].arch_id = arch_id; + ms->possible_cpus->cpus[n].vcpus_count = 1; ms->possible_cpus->cpus[n].props.has_socket_id = true; - ms->possible_cpus->cpus[n].props.socket_id = - n / (ms->smp.cores * ms->smp.threads); + ms->possible_cpus->cpus[n].props.socket_id = topo.socket_id; ms->possible_cpus->cpus[n].props.has_core_id = true; - ms->possible_cpus->cpus[n].props.core_id = - n / ms->smp.threads % ms->smp.cores; + ms->possible_cpus->cpus[n].props.core_id = topo.core_id; ms->possible_cpus->cpus[n].props.has_thread_id = true; - ms->possible_cpus->cpus[n].props.thread_id = n % ms->smp.threads; + ms->possible_cpus->cpus[n].props.thread_id = topo.thread_id; } return ms->possible_cpus; } @@ -952,6 +1188,7 @@ static void virt_class_init(ObjectClass *oc, void *data) mc->numa_mem_supported = true; mc->auto_enable_numa_with_memhp = true; mc->auto_enable_numa_with_memdev = true; + mc->has_hotpluggable_cpus = true; mc->get_hotplug_handler = virt_get_hotplug_handler; mc->default_nic = "virtio-net-pci"; hc->plug = virt_device_plug_cb; 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/riscv/meson.build b/hw/riscv/meson.build index 3c7e083aca..c22f3a7216 100644 --- a/hw/riscv/meson.build +++ b/hw/riscv/meson.build @@ -10,7 +10,8 @@ riscv_ss.add(when: 'CONFIG_SIFIVE_U', if_true: files('sifive_u.c')) riscv_ss.add(when: 'CONFIG_SPIKE', if_true: files('spike.c')) riscv_ss.add(when: 'CONFIG_MICROCHIP_PFSOC', if_true: files('microchip_pfsoc.c')) riscv_ss.add(when: 'CONFIG_ACPI', if_true: files('virt-acpi-build.c')) -riscv_ss.add(when: 'CONFIG_RISCV_IOMMU', if_true: files('riscv-iommu.c', 'riscv-iommu-pci.c', 'riscv-iommu-sys.c')) +riscv_ss.add(when: 'CONFIG_RISCV_IOMMU', if_true: files( + 'riscv-iommu.c', 'riscv-iommu-pci.c', 'riscv-iommu-sys.c', 'riscv-iommu-hpm.c')) riscv_ss.add(when: 'CONFIG_MICROBLAZE_V', if_true: files('microblaze-v-generic.c')) hw_arch += {'riscv': riscv_ss} diff --git a/hw/riscv/riscv-iommu-bits.h b/hw/riscv/riscv-iommu-bits.h index 485f36b9c9..b7cb1bc736 100644 --- a/hw/riscv/riscv-iommu-bits.h +++ b/hw/riscv/riscv-iommu-bits.h @@ -50,8 +50,14 @@ struct riscv_iommu_pq_record { #define RISCV_IOMMU_PREQ_HDR_PRIV BIT_ULL(33) #define RISCV_IOMMU_PREQ_HDR_EXEC BIT_ULL(34) #define RISCV_IOMMU_PREQ_HDR_DID GENMASK_ULL(63, 40) + /* Payload fields */ +#define RISCV_IOMMU_PREQ_PAYLOAD_R BIT_ULL(0) +#define RISCV_IOMMU_PREQ_PAYLOAD_W BIT_ULL(1) +#define RISCV_IOMMU_PREQ_PAYLOAD_L BIT_ULL(2) #define RISCV_IOMMU_PREQ_PAYLOAD_M GENMASK_ULL(2, 0) +#define RISCV_IOMMU_PREQ_PRG_INDEX GENMASK_ULL(11, 3) +#define RISCV_IOMMU_PREQ_UADDR GENMASK_ULL(63, 12) /* Common field positions */ #define RISCV_IOMMU_PPN_FIELD GENMASK_ULL(53, 10) @@ -82,6 +88,7 @@ struct riscv_iommu_pq_record { #define RISCV_IOMMU_CAP_ATS BIT_ULL(25) #define RISCV_IOMMU_CAP_T2GPA BIT_ULL(26) #define RISCV_IOMMU_CAP_IGS GENMASK_ULL(29, 28) +#define RISCV_IOMMU_CAP_HPM BIT_ULL(30) #define RISCV_IOMMU_CAP_DBG BIT_ULL(31) #define RISCV_IOMMU_CAP_PAS GENMASK_ULL(37, 32) #define RISCV_IOMMU_CAP_PD8 BIT_ULL(38) @@ -191,6 +198,52 @@ enum { RISCV_IOMMU_INTR_COUNT }; +#define RISCV_IOMMU_IOCOUNT_NUM 31 + +/* 5.19 Performance monitoring counter overflow status (32bits) */ +#define RISCV_IOMMU_REG_IOCOUNTOVF 0x0058 +#define RISCV_IOMMU_IOCOUNTOVF_CY BIT(0) + +/* 5.20 Performance monitoring counter inhibits (32bits) */ +#define RISCV_IOMMU_REG_IOCOUNTINH 0x005C +#define RISCV_IOMMU_IOCOUNTINH_CY BIT(0) + +/* 5.21 Performance monitoring cycles counter (64bits) */ +#define RISCV_IOMMU_REG_IOHPMCYCLES 0x0060 +#define RISCV_IOMMU_IOHPMCYCLES_COUNTER GENMASK_ULL(62, 0) +#define RISCV_IOMMU_IOHPMCYCLES_OVF BIT_ULL(63) + +/* 5.22 Performance monitoring event counters (31 * 64bits) */ +#define RISCV_IOMMU_REG_IOHPMCTR_BASE 0x0068 +#define RISCV_IOMMU_REG_IOHPMCTR(_n) \ + (RISCV_IOMMU_REG_IOHPMCTR_BASE + (_n * 0x8)) + +/* 5.23 Performance monitoring event selectors (31 * 64bits) */ +#define RISCV_IOMMU_REG_IOHPMEVT_BASE 0x0160 +#define RISCV_IOMMU_REG_IOHPMEVT(_n) \ + (RISCV_IOMMU_REG_IOHPMEVT_BASE + (_n * 0x8)) +#define RISCV_IOMMU_IOHPMEVT_EVENT_ID GENMASK_ULL(14, 0) +#define RISCV_IOMMU_IOHPMEVT_DMASK BIT_ULL(15) +#define RISCV_IOMMU_IOHPMEVT_PID_PSCID GENMASK_ULL(35, 16) +#define RISCV_IOMMU_IOHPMEVT_DID_GSCID GENMASK_ULL(59, 36) +#define RISCV_IOMMU_IOHPMEVT_PV_PSCV BIT_ULL(60) +#define RISCV_IOMMU_IOHPMEVT_DV_GSCV BIT_ULL(61) +#define RISCV_IOMMU_IOHPMEVT_IDT BIT_ULL(62) +#define RISCV_IOMMU_IOHPMEVT_OF BIT_ULL(63) + +enum RISCV_IOMMU_HPMEVENT_id { + RISCV_IOMMU_HPMEVENT_INVALID = 0, + RISCV_IOMMU_HPMEVENT_URQ = 1, + RISCV_IOMMU_HPMEVENT_TRQ = 2, + RISCV_IOMMU_HPMEVENT_ATS_RQ = 3, + RISCV_IOMMU_HPMEVENT_TLB_MISS = 4, + RISCV_IOMMU_HPMEVENT_DD_WALK = 5, + RISCV_IOMMU_HPMEVENT_PD_WALK = 6, + RISCV_IOMMU_HPMEVENT_S_VS_WALKS = 7, + RISCV_IOMMU_HPMEVENT_G_WALKS = 8, + RISCV_IOMMU_HPMEVENT_MAX = 9 +}; + /* 5.24 Translation request IOVA (64bits) */ #define RISCV_IOMMU_REG_TR_REQ_IOVA 0x0258 @@ -382,22 +435,6 @@ enum riscv_iommu_fq_ttypes { RISCV_IOMMU_FW_TTYPE_PCIE_MSG_REQ = 9, }; -/* Header fields */ -#define RISCV_IOMMU_PREQ_HDR_PID GENMASK_ULL(31, 12) -#define RISCV_IOMMU_PREQ_HDR_PV BIT_ULL(32) -#define RISCV_IOMMU_PREQ_HDR_PRIV BIT_ULL(33) -#define RISCV_IOMMU_PREQ_HDR_EXEC BIT_ULL(34) -#define RISCV_IOMMU_PREQ_HDR_DID GENMASK_ULL(63, 40) - -/* Payload fields */ -#define RISCV_IOMMU_PREQ_PAYLOAD_R BIT_ULL(0) -#define RISCV_IOMMU_PREQ_PAYLOAD_W BIT_ULL(1) -#define RISCV_IOMMU_PREQ_PAYLOAD_L BIT_ULL(2) -#define RISCV_IOMMU_PREQ_PAYLOAD_M GENMASK_ULL(2, 0) -#define RISCV_IOMMU_PREQ_PRG_INDEX GENMASK_ULL(11, 3) -#define RISCV_IOMMU_PREQ_UADDR GENMASK_ULL(63, 12) - - /* * struct riscv_iommu_msi_pte - MSI Page Table Entry */ diff --git a/hw/riscv/riscv-iommu-hpm.c b/hw/riscv/riscv-iommu-hpm.c new file mode 100644 index 0000000000..c5034bff79 --- /dev/null +++ b/hw/riscv/riscv-iommu-hpm.c @@ -0,0 +1,381 @@ +/* + * RISC-V IOMMU - Hardware Performance Monitor (HPM) helpers + * + * Copyright (C) 2022-2023 Rivos Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2 or later, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "qemu/osdep.h" +#include "qemu/timer.h" +#include "cpu_bits.h" +#include "riscv-iommu-hpm.h" +#include "riscv-iommu.h" +#include "riscv-iommu-bits.h" +#include "trace.h" + +/* For now we assume IOMMU HPM frequency to be 1GHz so 1-cycle is of 1-ns. */ +static inline uint64_t get_cycles(void) +{ + return qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); +} + +uint64_t riscv_iommu_hpmcycle_read(RISCVIOMMUState *s) +{ + const uint64_t cycle = riscv_iommu_reg_get64( + s, RISCV_IOMMU_REG_IOHPMCYCLES); + const uint32_t inhibit = riscv_iommu_reg_get32( + s, RISCV_IOMMU_REG_IOCOUNTINH); + const uint64_t ctr_prev = s->hpmcycle_prev; + const uint64_t ctr_val = s->hpmcycle_val; + + trace_riscv_iommu_hpm_read(cycle, inhibit, ctr_prev, ctr_val); + + if (get_field(inhibit, RISCV_IOMMU_IOCOUNTINH_CY)) { + /* + * Counter should not increment if inhibit bit is set. We can't really + * stop the QEMU_CLOCK_VIRTUAL, so we just return the last updated + * counter value to indicate that counter was not incremented. + */ + return (ctr_val & RISCV_IOMMU_IOHPMCYCLES_COUNTER) | + (cycle & RISCV_IOMMU_IOHPMCYCLES_OVF); + } + + return (ctr_val + get_cycles() - ctr_prev) | + (cycle & RISCV_IOMMU_IOHPMCYCLES_OVF); +} + +static void hpm_incr_ctr(RISCVIOMMUState *s, uint32_t ctr_idx) +{ + const uint32_t off = ctr_idx << 3; + uint64_t cntr_val; + + cntr_val = ldq_le_p(&s->regs_rw[RISCV_IOMMU_REG_IOHPMCTR_BASE + off]); + stq_le_p(&s->regs_rw[RISCV_IOMMU_REG_IOHPMCTR_BASE + off], cntr_val + 1); + + trace_riscv_iommu_hpm_incr_ctr(cntr_val); + + /* Handle the overflow scenario. */ + if (cntr_val == UINT64_MAX) { + /* + * Generate interrupt only if OF bit is clear. +1 to offset the cycle + * register OF bit. + */ + const uint32_t ovf = + riscv_iommu_reg_mod32(s, RISCV_IOMMU_REG_IOCOUNTOVF, + BIT(ctr_idx + 1), 0); + if (!get_field(ovf, BIT(ctr_idx + 1))) { + riscv_iommu_reg_mod64(s, + RISCV_IOMMU_REG_IOHPMEVT_BASE + off, + RISCV_IOMMU_IOHPMEVT_OF, + 0); + riscv_iommu_notify(s, RISCV_IOMMU_INTR_PM); + } + } +} + +void riscv_iommu_hpm_incr_ctr(RISCVIOMMUState *s, RISCVIOMMUContext *ctx, + unsigned event_id) +{ + const uint32_t inhibit = riscv_iommu_reg_get32( + s, RISCV_IOMMU_REG_IOCOUNTINH); + uint32_t did_gscid; + uint32_t pid_pscid; + uint32_t ctr_idx; + gpointer value; + uint32_t ctrs; + uint64_t evt; + + if (!(s->cap & RISCV_IOMMU_CAP_HPM)) { + return; + } + + value = g_hash_table_lookup(s->hpm_event_ctr_map, + GUINT_TO_POINTER(event_id)); + if (value == NULL) { + return; + } + + for (ctrs = GPOINTER_TO_UINT(value); ctrs != 0; ctrs &= ctrs - 1) { + ctr_idx = ctz32(ctrs); + if (get_field(inhibit, BIT(ctr_idx + 1))) { + continue; + } + + evt = riscv_iommu_reg_get64(s, + RISCV_IOMMU_REG_IOHPMEVT_BASE + (ctr_idx << 3)); + + /* + * It's quite possible that event ID has been changed in counter + * but hashtable hasn't been updated yet. We don't want to increment + * counter for the old event ID. + */ + if (event_id != get_field(evt, RISCV_IOMMU_IOHPMEVT_EVENT_ID)) { + continue; + } + + if (get_field(evt, RISCV_IOMMU_IOHPMEVT_IDT)) { + did_gscid = get_field(ctx->gatp, RISCV_IOMMU_DC_IOHGATP_GSCID); + pid_pscid = get_field(ctx->ta, RISCV_IOMMU_DC_TA_PSCID); + } else { + did_gscid = ctx->devid; + pid_pscid = ctx->process_id; + } + + if (get_field(evt, RISCV_IOMMU_IOHPMEVT_PV_PSCV)) { + /* + * If the transaction does not have a valid process_id, counter + * increments if device_id matches DID_GSCID. If the transaction + * has a valid process_id, counter increments if device_id + * matches DID_GSCID and process_id matches PID_PSCID. See + * IOMMU Specification, Chapter 5.23. Performance-monitoring + * event selector. + */ + if (ctx->process_id && + get_field(evt, RISCV_IOMMU_IOHPMEVT_PID_PSCID) != pid_pscid) { + continue; + } + } + + if (get_field(evt, RISCV_IOMMU_IOHPMEVT_DV_GSCV)) { + uint32_t mask = ~0; + + if (get_field(evt, RISCV_IOMMU_IOHPMEVT_DMASK)) { + /* + * 1001 1011 mask = GSCID + * 0000 0111 mask = mask ^ (mask + 1) + * 1111 1000 mask = ~mask; + */ + mask = get_field(evt, RISCV_IOMMU_IOHPMEVT_DID_GSCID); + mask = mask ^ (mask + 1); + mask = ~mask; + } + + if ((get_field(evt, RISCV_IOMMU_IOHPMEVT_DID_GSCID) & mask) != + (did_gscid & mask)) { + continue; + } + } + + hpm_incr_ctr(s, ctr_idx); + } +} + +/* Timer callback for cycle counter overflow. */ +void riscv_iommu_hpm_timer_cb(void *priv) +{ + RISCVIOMMUState *s = priv; + const uint32_t inhibit = riscv_iommu_reg_get32( + s, RISCV_IOMMU_REG_IOCOUNTINH); + uint32_t ovf; + + if (get_field(inhibit, RISCV_IOMMU_IOCOUNTINH_CY)) { + return; + } + + if (s->irq_overflow_left > 0) { + uint64_t irq_trigger_at = + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + s->irq_overflow_left; + timer_mod_anticipate_ns(s->hpm_timer, irq_trigger_at); + s->irq_overflow_left = 0; + return; + } + + ovf = riscv_iommu_reg_get32(s, RISCV_IOMMU_REG_IOCOUNTOVF); + if (!get_field(ovf, RISCV_IOMMU_IOCOUNTOVF_CY)) { + /* + * We don't need to set hpmcycle_val to zero and update hpmcycle_prev to + * current clock value. The way we calculate iohpmcycs will overflow + * and return the correct value. This avoids the need to synchronize + * timer callback and write callback. + */ + riscv_iommu_reg_mod32(s, RISCV_IOMMU_REG_IOCOUNTOVF, + RISCV_IOMMU_IOCOUNTOVF_CY, 0); + riscv_iommu_reg_mod64(s, RISCV_IOMMU_REG_IOHPMCYCLES, + RISCV_IOMMU_IOHPMCYCLES_OVF, 0); + riscv_iommu_notify(s, RISCV_IOMMU_INTR_PM); + } +} + +static void hpm_setup_timer(RISCVIOMMUState *s, uint64_t value) +{ + const uint32_t inhibit = riscv_iommu_reg_get32( + s, RISCV_IOMMU_REG_IOCOUNTINH); + uint64_t overflow_at, overflow_ns; + + if (get_field(inhibit, RISCV_IOMMU_IOCOUNTINH_CY)) { + return; + } + + /* + * We are using INT64_MAX here instead to UINT64_MAX because cycle counter + * has 63-bit precision and INT64_MAX is the maximum it can store. + */ + if (value) { + overflow_ns = INT64_MAX - value + 1; + } else { + overflow_ns = INT64_MAX; + } + + overflow_at = (uint64_t)qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + overflow_ns; + + if (overflow_at > INT64_MAX) { + s->irq_overflow_left = overflow_at - INT64_MAX; + overflow_at = INT64_MAX; + } + + timer_mod_anticipate_ns(s->hpm_timer, overflow_at); +} + +/* Updates the internal cycle counter state when iocntinh:CY is changed. */ +void riscv_iommu_process_iocntinh_cy(RISCVIOMMUState *s, bool prev_cy_inh) +{ + const uint32_t inhibit = riscv_iommu_reg_get32( + s, RISCV_IOMMU_REG_IOCOUNTINH); + + /* We only need to process CY bit toggle. */ + if (!(inhibit ^ prev_cy_inh)) { + return; + } + + trace_riscv_iommu_hpm_iocntinh_cy(prev_cy_inh); + + if (!(inhibit & RISCV_IOMMU_IOCOUNTINH_CY)) { + /* + * Cycle counter is enabled. Just start the timer again and update + * the clock snapshot value to point to the current time to make + * sure iohpmcycles read is correct. + */ + s->hpmcycle_prev = get_cycles(); + hpm_setup_timer(s, s->hpmcycle_val); + } else { + /* + * Cycle counter is disabled. Stop the timer and update the cycle + * counter to record the current value which is last programmed + * value + the cycles passed so far. + */ + s->hpmcycle_val = s->hpmcycle_val + (get_cycles() - s->hpmcycle_prev); + timer_del(s->hpm_timer); + } +} + +void riscv_iommu_process_hpmcycle_write(RISCVIOMMUState *s) +{ + const uint64_t val = riscv_iommu_reg_get64(s, RISCV_IOMMU_REG_IOHPMCYCLES); + const uint32_t ovf = riscv_iommu_reg_get32(s, RISCV_IOMMU_REG_IOCOUNTOVF); + + trace_riscv_iommu_hpm_cycle_write(ovf, val); + + /* + * Clear OF bit in IOCNTOVF if it's being cleared in IOHPMCYCLES register. + */ + if (get_field(ovf, RISCV_IOMMU_IOCOUNTOVF_CY) && + !get_field(val, RISCV_IOMMU_IOHPMCYCLES_OVF)) { + riscv_iommu_reg_mod32(s, RISCV_IOMMU_REG_IOCOUNTOVF, 0, + RISCV_IOMMU_IOCOUNTOVF_CY); + } + + s->hpmcycle_val = val & ~RISCV_IOMMU_IOHPMCYCLES_OVF; + s->hpmcycle_prev = get_cycles(); + hpm_setup_timer(s, s->hpmcycle_val); +} + +static inline bool check_valid_event_id(unsigned event_id) +{ + return event_id > RISCV_IOMMU_HPMEVENT_INVALID && + event_id < RISCV_IOMMU_HPMEVENT_MAX; +} + +static gboolean hpm_event_equal(gpointer key, gpointer value, gpointer udata) +{ + uint32_t *pair = udata; + + if (GPOINTER_TO_UINT(value) & (1 << pair[0])) { + pair[1] = GPOINTER_TO_UINT(key); + return true; + } + + return false; +} + +/* Caller must check ctr_idx against hpm_ctrs to see if its supported or not. */ +static void update_event_map(RISCVIOMMUState *s, uint64_t value, + uint32_t ctr_idx) +{ + unsigned event_id = get_field(value, RISCV_IOMMU_IOHPMEVT_EVENT_ID); + uint32_t pair[2] = { ctr_idx, RISCV_IOMMU_HPMEVENT_INVALID }; + uint32_t new_value = 1 << ctr_idx; + gpointer data; + + /* + * If EventID field is RISCV_IOMMU_HPMEVENT_INVALID + * remove the current mapping. + */ + if (event_id == RISCV_IOMMU_HPMEVENT_INVALID) { + data = g_hash_table_find(s->hpm_event_ctr_map, hpm_event_equal, pair); + + new_value = GPOINTER_TO_UINT(data) & ~(new_value); + if (new_value != 0) { + g_hash_table_replace(s->hpm_event_ctr_map, + GUINT_TO_POINTER(pair[1]), + GUINT_TO_POINTER(new_value)); + } else { + g_hash_table_remove(s->hpm_event_ctr_map, + GUINT_TO_POINTER(pair[1])); + } + + return; + } + + /* Update the counter mask if the event is already enabled. */ + if (g_hash_table_lookup_extended(s->hpm_event_ctr_map, + GUINT_TO_POINTER(event_id), + NULL, + &data)) { + new_value |= GPOINTER_TO_UINT(data); + } + + g_hash_table_insert(s->hpm_event_ctr_map, + GUINT_TO_POINTER(event_id), + GUINT_TO_POINTER(new_value)); +} + +void riscv_iommu_process_hpmevt_write(RISCVIOMMUState *s, uint32_t evt_reg) +{ + const uint32_t ctr_idx = (evt_reg - RISCV_IOMMU_REG_IOHPMEVT_BASE) >> 3; + const uint32_t ovf = riscv_iommu_reg_get32(s, RISCV_IOMMU_REG_IOCOUNTOVF); + uint64_t val = riscv_iommu_reg_get64(s, evt_reg); + + if (ctr_idx >= s->hpm_cntrs) { + return; + } + + trace_riscv_iommu_hpm_evt_write(ctr_idx, ovf, val); + + /* Clear OF bit in IOCNTOVF if it's being cleared in IOHPMEVT register. */ + if (get_field(ovf, BIT(ctr_idx + 1)) && + !get_field(val, RISCV_IOMMU_IOHPMEVT_OF)) { + /* +1 to offset CYCLE register OF bit. */ + riscv_iommu_reg_mod32( + s, RISCV_IOMMU_REG_IOCOUNTOVF, 0, BIT(ctr_idx + 1)); + } + + if (!check_valid_event_id(get_field(val, RISCV_IOMMU_IOHPMEVT_EVENT_ID))) { + /* Reset EventID (WARL) field to invalid. */ + val = set_field(val, RISCV_IOMMU_IOHPMEVT_EVENT_ID, + RISCV_IOMMU_HPMEVENT_INVALID); + riscv_iommu_reg_set64(s, evt_reg, val); + } + + update_event_map(s, val, ctr_idx); +} diff --git a/hw/riscv/riscv-iommu-hpm.h b/hw/riscv/riscv-iommu-hpm.h new file mode 100644 index 0000000000..5fc4ef2e8b --- /dev/null +++ b/hw/riscv/riscv-iommu-hpm.h @@ -0,0 +1,33 @@ +/* + * RISC-V IOMMU - Hardware Performance Monitor (HPM) helpers + * + * Copyright (C) 2022-2023 Rivos Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2 or later, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef HW_RISCV_IOMMU_HPM_H +#define HW_RISCV_IOMMU_HPM_H + +#include "qom/object.h" +#include "hw/riscv/riscv-iommu.h" + +uint64_t riscv_iommu_hpmcycle_read(RISCVIOMMUState *s); +void riscv_iommu_hpm_incr_ctr(RISCVIOMMUState *s, RISCVIOMMUContext *ctx, + unsigned event_id); +void riscv_iommu_hpm_timer_cb(void *priv); +void riscv_iommu_process_iocntinh_cy(RISCVIOMMUState *s, bool prev_cy_inh); +void riscv_iommu_process_hpmcycle_write(RISCVIOMMUState *s); +void riscv_iommu_process_hpmevt_write(RISCVIOMMUState *s, uint32_t evt_reg); + +#endif diff --git a/hw/riscv/riscv-iommu.c b/hw/riscv/riscv-iommu.c index e7568ca227..d46beb2d64 100644 --- a/hw/riscv/riscv-iommu.c +++ b/hw/riscv/riscv-iommu.c @@ -29,6 +29,7 @@ #include "cpu_bits.h" #include "riscv-iommu.h" #include "riscv-iommu-bits.h" +#include "riscv-iommu-hpm.h" #include "trace.h" #define LIMIT_CACHE_CTX (1U << 7) @@ -38,7 +39,6 @@ #define PPN_PHYS(ppn) ((ppn) << TARGET_PAGE_BITS) #define PPN_DOWN(phy) ((phy) >> TARGET_PAGE_BITS) -typedef struct RISCVIOMMUContext RISCVIOMMUContext; typedef struct RISCVIOMMUEntry RISCVIOMMUEntry; /* Device assigned I/O address space */ @@ -51,19 +51,6 @@ struct RISCVIOMMUSpace { QLIST_ENTRY(RISCVIOMMUSpace) list; }; -/* Device translation context state. */ -struct RISCVIOMMUContext { - uint64_t devid:24; /* Requester Id, AKA device_id */ - uint64_t process_id:20; /* Process ID. PASID for PCIe */ - uint64_t tc; /* Translation Control */ - uint64_t ta; /* Translation Attributes */ - uint64_t satp; /* S-Stage address translation and protection */ - uint64_t gatp; /* G-Stage address translation and protection */ - uint64_t msi_addr_mask; /* MSI filtering - address mask */ - uint64_t msi_addr_pattern; /* MSI filtering - address pattern */ - uint64_t msiptp; /* MSI redirection page table pointer */ -}; - typedef enum RISCVIOMMUTransTag { RISCV_IOMMU_TRANS_TAG_BY, /* Bypass */ RISCV_IOMMU_TRANS_TAG_SS, /* Single Stage */ @@ -100,7 +87,7 @@ static uint8_t riscv_iommu_get_icvec_vector(uint32_t icvec, uint32_t vec_type) } } -static void riscv_iommu_notify(RISCVIOMMUState *s, int vec_type) +void riscv_iommu_notify(RISCVIOMMUState *s, int vec_type) { uint32_t ipsr, icvec, vector; @@ -422,6 +409,13 @@ static int riscv_iommu_spa_fetch(RISCVIOMMUState *s, RISCVIOMMUContext *ctx, } } + + if (pass == S_STAGE) { + riscv_iommu_hpm_incr_ctr(s, ctx, RISCV_IOMMU_HPMEVENT_S_VS_WALKS); + } else { + riscv_iommu_hpm_incr_ctr(s, ctx, RISCV_IOMMU_HPMEVENT_G_WALKS); + } + /* Read page table entry */ if (sc[pass].ptesize == 4) { uint32_t pte32 = 0; @@ -940,6 +934,7 @@ static int riscv_iommu_ctx_fetch(RISCVIOMMUState *s, RISCVIOMMUContext *ctx) /* Device directory tree walk */ for (; depth-- > 0; ) { + riscv_iommu_hpm_incr_ctr(s, ctx, RISCV_IOMMU_HPMEVENT_DD_WALK); /* * Select device id index bits based on device directory tree level * and device context format. @@ -967,6 +962,8 @@ static int riscv_iommu_ctx_fetch(RISCVIOMMUState *s, RISCVIOMMUContext *ctx) addr = PPN_PHYS(get_field(de, RISCV_IOMMU_DDTE_PPN)); } + riscv_iommu_hpm_incr_ctr(s, ctx, RISCV_IOMMU_HPMEVENT_DD_WALK); + /* index into device context entry page */ addr |= (ctx->devid * dc_len) & ~TARGET_PAGE_MASK; @@ -1032,6 +1029,8 @@ static int riscv_iommu_ctx_fetch(RISCVIOMMUState *s, RISCVIOMMUContext *ctx) } for (depth = mode - RISCV_IOMMU_DC_FSC_PDTP_MODE_PD8; depth-- > 0; ) { + riscv_iommu_hpm_incr_ctr(s, ctx, RISCV_IOMMU_HPMEVENT_PD_WALK); + /* * Select process id index bits based on process directory tree * level. See IOMMU Specification, 2.2. Process-Directory-Table. @@ -1049,6 +1048,8 @@ static int riscv_iommu_ctx_fetch(RISCVIOMMUState *s, RISCVIOMMUContext *ctx) addr = PPN_PHYS(get_field(de, RISCV_IOMMU_PC_FSC_PPN)); } + riscv_iommu_hpm_incr_ctr(s, ctx, RISCV_IOMMU_HPMEVENT_PD_WALK); + /* Leaf entry in PDT */ addr |= (ctx->process_id << 4) & ~TARGET_PAGE_MASK; if (dma_memory_read(s->target_as, addr, &dc.ta, sizeof(uint64_t) * 2, @@ -1418,6 +1419,8 @@ static int riscv_iommu_translate(RISCVIOMMUState *s, RISCVIOMMUContext *ctx, GHashTable *iot_cache; int fault; + riscv_iommu_hpm_incr_ctr(s, ctx, RISCV_IOMMU_HPMEVENT_URQ); + iot_cache = g_hash_table_ref(s->iot_cache); /* * TC[32] is reserved for custom extensions, used here to temporarily @@ -1428,6 +1431,7 @@ static int riscv_iommu_translate(RISCVIOMMUState *s, RISCVIOMMUContext *ctx, /* Check for ATS request. */ if (iotlb->perm == IOMMU_NONE) { + riscv_iommu_hpm_incr_ctr(s, ctx, RISCV_IOMMU_HPMEVENT_ATS_RQ); /* Check if ATS is disabled. */ if (!(ctx->tc & RISCV_IOMMU_DC_TC_EN_ATS)) { enable_pri = false; @@ -1446,6 +1450,8 @@ static int riscv_iommu_translate(RISCVIOMMUState *s, RISCVIOMMUContext *ctx, goto done; } + riscv_iommu_hpm_incr_ctr(s, ctx, RISCV_IOMMU_HPMEVENT_TLB_MISS); + /* Translate using device directory / page table information. */ fault = riscv_iommu_spa_fetch(s, ctx, iotlb); @@ -2018,6 +2024,27 @@ static void riscv_iommu_update_ipsr(RISCVIOMMUState *s, uint64_t data) riscv_iommu_reg_mod32(s, RISCV_IOMMU_REG_IPSR, ipsr_set, ipsr_clr); } +static void riscv_iommu_process_hpm_writes(RISCVIOMMUState *s, + uint32_t regb, + bool prev_cy_inh) +{ + switch (regb) { + case RISCV_IOMMU_REG_IOCOUNTINH: + riscv_iommu_process_iocntinh_cy(s, prev_cy_inh); + break; + + case RISCV_IOMMU_REG_IOHPMCYCLES: + case RISCV_IOMMU_REG_IOHPMCYCLES + 4: + riscv_iommu_process_hpmcycle_write(s); + break; + + case RISCV_IOMMU_REG_IOHPMEVT_BASE ... + RISCV_IOMMU_REG_IOHPMEVT(RISCV_IOMMU_IOCOUNT_NUM) + 4: + riscv_iommu_process_hpmevt_write(s, regb & ~7); + break; + } +} + /* * Write the resulting value of 'data' for the reg specified * by 'reg_addr', after considering read-only/read-write/write-clear @@ -2045,6 +2072,7 @@ static MemTxResult riscv_iommu_mmio_write(void *opaque, hwaddr addr, uint32_t regb = addr & ~3; uint32_t busy = 0; uint64_t val = 0; + bool cy_inh = false; if ((addr & (size - 1)) != 0) { /* Unsupported MMIO alignment or access size */ @@ -2112,6 +2140,16 @@ static MemTxResult riscv_iommu_mmio_write(void *opaque, hwaddr addr, busy = RISCV_IOMMU_TR_REQ_CTL_GO_BUSY; break; + case RISCV_IOMMU_REG_IOCOUNTINH: + if (addr != RISCV_IOMMU_REG_IOCOUNTINH) { + break; + } + /* Store previous value of CY bit. */ + cy_inh = !!(riscv_iommu_reg_get32(s, RISCV_IOMMU_REG_IOCOUNTINH) & + RISCV_IOMMU_IOCOUNTINH_CY); + break; + + default: break; } @@ -2130,6 +2168,12 @@ static MemTxResult riscv_iommu_mmio_write(void *opaque, hwaddr addr, stl_le_p(&s->regs_rw[regb], rw | busy); } + /* Process HPM writes and update any internal state if needed. */ + if (regb >= RISCV_IOMMU_REG_IOCOUNTOVF && + regb <= (RISCV_IOMMU_REG_IOHPMEVT(RISCV_IOMMU_IOCOUNT_NUM) + 4)) { + riscv_iommu_process_hpm_writes(s, regb, cy_inh); + } + if (process_fn) { process_fn(s); } @@ -2153,7 +2197,28 @@ static MemTxResult riscv_iommu_mmio_read(void *opaque, hwaddr addr, return MEMTX_ACCESS_ERROR; } - ptr = &s->regs_rw[addr]; + /* Compute cycle register value. */ + if ((addr & ~7) == RISCV_IOMMU_REG_IOHPMCYCLES) { + val = riscv_iommu_hpmcycle_read(s); + ptr = (uint8_t *)&val + (addr & 7); + } else if ((addr & ~3) == RISCV_IOMMU_REG_IOCOUNTOVF) { + /* + * Software can read RISCV_IOMMU_REG_IOCOUNTOVF before timer + * callback completes. In which case CY_OF bit in + * RISCV_IOMMU_IOHPMCYCLES_OVF would be 0. Here we take the + * CY_OF bit state from RISCV_IOMMU_REG_IOHPMCYCLES register as + * it's not dependent over the timer callback and is computed + * from cycle overflow. + */ + val = ldq_le_p(&s->regs_rw[addr]); + val |= (riscv_iommu_hpmcycle_read(s) & RISCV_IOMMU_IOHPMCYCLES_OVF) + ? RISCV_IOMMU_IOCOUNTOVF_CY + : 0; + ptr = (uint8_t *)&val + (addr & 3); + } else { + ptr = &s->regs_rw[addr]; + } + val = ldn_le_p(ptr, size); *data = val; @@ -2292,6 +2357,15 @@ static void riscv_iommu_realize(DeviceState *dev, Error **errp) RISCV_IOMMU_CAP_SV48X4 | RISCV_IOMMU_CAP_SV57X4; } + if (s->hpm_cntrs > 0) { + /* Clip number of HPM counters to maximum supported (31). */ + if (s->hpm_cntrs > RISCV_IOMMU_IOCOUNT_NUM) { + s->hpm_cntrs = RISCV_IOMMU_IOCOUNT_NUM; + } + /* Enable hardware performance monitor interface */ + s->cap |= RISCV_IOMMU_CAP_HPM; + } + /* Out-of-reset translation mode: OFF (DMA disabled) BARE (passthrough) */ s->ddtp = set_field(0, RISCV_IOMMU_DDTP_MODE, s->enable_off ? RISCV_IOMMU_DDTP_MODE_OFF : RISCV_IOMMU_DDTP_MODE_BARE); @@ -2339,6 +2413,18 @@ static void riscv_iommu_realize(DeviceState *dev, Error **errp) RISCV_IOMMU_TR_REQ_CTL_GO_BUSY); } + /* If HPM registers are enabled. */ + if (s->cap & RISCV_IOMMU_CAP_HPM) { + /* +1 for cycle counter bit. */ + stl_le_p(&s->regs_ro[RISCV_IOMMU_REG_IOCOUNTINH], + ~((2 << s->hpm_cntrs) - 1)); + stq_le_p(&s->regs_ro[RISCV_IOMMU_REG_IOHPMCYCLES], 0); + memset(&s->regs_ro[RISCV_IOMMU_REG_IOHPMCTR_BASE], + 0x00, s->hpm_cntrs * 8); + memset(&s->regs_ro[RISCV_IOMMU_REG_IOHPMEVT_BASE], + 0x00, s->hpm_cntrs * 8); + } + /* Memory region for downstream access, if specified. */ if (s->target_mr) { s->target_as = g_new0(AddressSpace, 1); @@ -2353,6 +2439,12 @@ static void riscv_iommu_realize(DeviceState *dev, Error **errp) memory_region_init_io(&s->trap_mr, OBJECT(dev), &riscv_iommu_trap_ops, s, "riscv-iommu-trap", ~0ULL); address_space_init(&s->trap_as, &s->trap_mr, "riscv-iommu-trap-as"); + + if (s->cap & RISCV_IOMMU_CAP_HPM) { + s->hpm_timer = + timer_new_ns(QEMU_CLOCK_VIRTUAL, riscv_iommu_hpm_timer_cb, s); + s->hpm_event_ctr_map = g_hash_table_new(g_direct_hash, g_direct_equal); + } } static void riscv_iommu_unrealize(DeviceState *dev) @@ -2361,6 +2453,11 @@ static void riscv_iommu_unrealize(DeviceState *dev) g_hash_table_unref(s->iot_cache); g_hash_table_unref(s->ctx_cache); + + if (s->cap & RISCV_IOMMU_CAP_HPM) { + g_hash_table_unref(s->hpm_event_ctr_map); + timer_free(s->hpm_timer); + } } void riscv_iommu_reset(RISCVIOMMUState *s) @@ -2411,6 +2508,8 @@ static const Property riscv_iommu_properties[] = { DEFINE_PROP_BOOL("g-stage", RISCVIOMMUState, enable_g_stage, TRUE), DEFINE_PROP_LINK("downstream-mr", RISCVIOMMUState, target_mr, TYPE_MEMORY_REGION, MemoryRegion *), + DEFINE_PROP_UINT8("hpm-counters", RISCVIOMMUState, hpm_cntrs, + RISCV_IOMMU_IOCOUNT_NUM), }; static void riscv_iommu_class_init(ObjectClass *klass, void* data) diff --git a/hw/riscv/riscv-iommu.h b/hw/riscv/riscv-iommu.h index 9424989df4..a31aa62144 100644 --- a/hw/riscv/riscv-iommu.h +++ b/hw/riscv/riscv-iommu.h @@ -20,6 +20,8 @@ #define HW_RISCV_IOMMU_STATE_H #include "qom/object.h" +#include "hw/qdev-properties.h" +#include "system/dma.h" #include "hw/riscv/iommu.h" #include "hw/riscv/riscv-iommu-bits.h" @@ -58,11 +60,6 @@ struct RISCVIOMMUState { /* interrupt notifier */ void (*notify)(RISCVIOMMUState *iommu, unsigned vector); - /* IOMMU State Machine */ - QemuThread core_proc; /* Background processing thread */ - QemuCond core_cond; /* Background processing wake up signal */ - unsigned core_exec; /* Processing thread execution actions */ - /* IOMMU target address space */ AddressSpace *target_as; MemoryRegion *target_mr; @@ -84,12 +81,37 @@ struct RISCVIOMMUState { QLIST_ENTRY(RISCVIOMMUState) iommus; QLIST_HEAD(, RISCVIOMMUSpace) spaces; + + /* HPM cycle counter */ + QEMUTimer *hpm_timer; + uint64_t hpmcycle_val; /* Current value of cycle register */ + uint64_t hpmcycle_prev; /* Saved value of QEMU_CLOCK_VIRTUAL clock */ + uint64_t irq_overflow_left; /* Value beyond INT64_MAX after overflow */ + + /* HPM event counters */ + GHashTable *hpm_event_ctr_map; /* Mapping of events to counters */ + uint8_t hpm_cntrs; }; void riscv_iommu_pci_setup_iommu(RISCVIOMMUState *iommu, PCIBus *bus, Error **errp); void riscv_iommu_set_cap_igs(RISCVIOMMUState *s, riscv_iommu_igs_mode mode); void riscv_iommu_reset(RISCVIOMMUState *s); +void riscv_iommu_notify(RISCVIOMMUState *s, int vec_type); + +typedef struct RISCVIOMMUContext RISCVIOMMUContext; +/* Device translation context state. */ +struct RISCVIOMMUContext { + uint64_t devid:24; /* Requester Id, AKA device_id */ + uint64_t process_id:20; /* Process ID. PASID for PCIe */ + uint64_t tc; /* Translation Control */ + uint64_t ta; /* Translation Attributes */ + uint64_t satp; /* S-Stage address translation and protection */ + uint64_t gatp; /* G-Stage address translation and protection */ + uint64_t msi_addr_mask; /* MSI filtering - address mask */ + uint64_t msi_addr_pattern; /* MSI filtering - address pattern */ + uint64_t msiptp; /* MSI redirection page table pointer */ +}; /* private helpers */ diff --git a/hw/riscv/trace-events b/hw/riscv/trace-events index 7bcbb03d08..b50b14a654 100644 --- a/hw/riscv/trace-events +++ b/hw/riscv/trace-events @@ -19,3 +19,8 @@ riscv_iommu_sys_irq_sent(uint32_t vector) "IRQ sent to vector %u" riscv_iommu_sys_msi_sent(uint32_t vector, uint64_t msi_addr, uint32_t msi_data, uint32_t result) "MSI sent to vector %u msi_addr 0x%"PRIx64" msi_data 0x%x result %u" riscv_iommu_sys_reset_hold(int reset_type) "reset type %d" riscv_iommu_pci_reset_hold(int reset_type) "reset type %d" +riscv_iommu_hpm_read(uint64_t cycle, uint32_t inhibit, uint64_t ctr_prev, uint64_t ctr_val) "cycle 0x%"PRIx64" inhibit 0x%x ctr_prev 0x%"PRIx64" ctr_val 0x%"PRIx64 +riscv_iommu_hpm_incr_ctr(uint64_t cntr_val) "cntr_val 0x%"PRIx64 +riscv_iommu_hpm_iocntinh_cy(bool prev_cy_inh) "prev_cy_inh %d" +riscv_iommu_hpm_cycle_write(uint32_t ovf, uint64_t val) "ovf 0x%x val 0x%"PRIx64 +riscv_iommu_hpm_evt_write(uint32_t ctr_idx, uint32_t ovf, uint64_t val) "ctr_idx 0x%x ovf 0x%x val 0x%"PRIx64 diff --git a/hw/riscv/virt.c b/hw/riscv/virt.c index 241389d72f..dae46f4733 100644 --- a/hw/riscv/virt.c +++ b/hw/riscv/virt.c @@ -971,6 +971,7 @@ static void create_fdt_uart(RISCVVirtState *s, const MemMapEntry *memmap, } qemu_fdt_setprop_string(ms->fdt, "/chosen", "stdout-path", name); + qemu_fdt_setprop_string(ms->fdt, "/aliases", "serial0", name); } static void create_fdt_rtc(RISCVVirtState *s, const MemMapEntry *memmap, @@ -1180,6 +1181,8 @@ static void create_fdt(RISCVVirtState *s, const MemMapEntry *memmap) qemu_fdt_setprop(ms->fdt, "/chosen", "rng-seed", rng_seed, sizeof(rng_seed)); + qemu_fdt_add_subnode(ms->fdt, "/aliases"); + create_fdt_flash(s, memmap); create_fdt_fw_cfg(s, memmap); create_fdt_pmu(s); diff --git a/hw/rtc/goldfish_rtc.c b/hw/rtc/goldfish_rtc.c index fa1d9051f4..0f1b53e0e4 100644 --- a/hw/rtc/goldfish_rtc.c +++ b/hw/rtc/goldfish_rtc.c @@ -178,38 +178,21 @@ static void goldfish_rtc_write(void *opaque, hwaddr offset, trace_goldfish_rtc_write(offset, value); } -static int goldfish_rtc_pre_save(void *opaque) -{ - uint64_t delta; - GoldfishRTCState *s = opaque; - - /* - * We want to migrate this offset, which sounds straightforward. - * Unfortunately, we cannot directly pass tick_offset because - * rtc_clock on destination Host might not be same source Host. - * - * To tackle, this we pass tick_offset relative to vm_clock from - * source Host and make it relative to rtc_clock at destination Host. - */ - delta = qemu_clock_get_ns(rtc_clock) - - qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); - s->tick_offset_vmstate = s->tick_offset + delta; - - return 0; -} - static int goldfish_rtc_post_load(void *opaque, int version_id) { - uint64_t delta; GoldfishRTCState *s = opaque; - /* - * We extract tick_offset from tick_offset_vmstate by doing - * reverse math compared to pre_save() function. - */ - delta = qemu_clock_get_ns(rtc_clock) - - qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); - s->tick_offset = s->tick_offset_vmstate - delta; + if (version_id < 3) { + /* + * Previous versions didn't migrate tick_offset directly. Instead, they + * migrated tick_offset_vmstate, which is a recalculation based on + * QEMU_CLOCK_VIRTUAL. We use tick_offset_vmstate when migrating from + * older versions. + */ + uint64_t delta = qemu_clock_get_ns(rtc_clock) - + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + s->tick_offset = s->tick_offset_vmstate - delta; + } goldfish_rtc_set_alarm(s); @@ -239,8 +222,7 @@ static const MemoryRegionOps goldfish_rtc_ops[2] = { static const VMStateDescription goldfish_rtc_vmstate = { .name = TYPE_GOLDFISH_RTC, - .version_id = 2, - .pre_save = goldfish_rtc_pre_save, + .version_id = 3, .post_load = goldfish_rtc_post_load, .fields = (const VMStateField[]) { VMSTATE_UINT64(tick_offset_vmstate, GoldfishRTCState), @@ -249,6 +231,7 @@ static const VMStateDescription goldfish_rtc_vmstate = { VMSTATE_UINT32(irq_pending, GoldfishRTCState), VMSTATE_UINT32(irq_enabled, GoldfishRTCState), VMSTATE_UINT32(time_high, GoldfishRTCState), + VMSTATE_UINT64_V(tick_offset, GoldfishRTCState, 3), VMSTATE_END_OF_LIST() } }; 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" |