diff options
Diffstat (limited to 'hw')
80 files changed, 5772 insertions, 2769 deletions
diff --git a/hw/9pfs/9p.c b/hw/9pfs/9p.c index 5a6e2c9d3d..134806db52 100644 --- a/hw/9pfs/9p.c +++ b/hw/9pfs/9p.c @@ -25,7 +25,6 @@ #include "coth.h" #include "trace.h" #include "migration/blocker.h" -#include "sysemu/qtest.h" #include "qemu/xxhash.h" #include <math.h> #include <linux/limits.h> diff --git a/hw/Kconfig b/hw/Kconfig index 8ea26479c4..ff40bd3f7b 100644 --- a/hw/Kconfig +++ b/hw/Kconfig @@ -31,7 +31,6 @@ source remote/Kconfig source rtc/Kconfig source scsi/Kconfig source sd/Kconfig -source semihosting/Kconfig source smbios/Kconfig source ssi/Kconfig source timer/Kconfig diff --git a/hw/adc/npcm7xx_adc.c b/hw/adc/npcm7xx_adc.c index 870a6d50c2..0f0a9f63e2 100644 --- a/hw/adc/npcm7xx_adc.c +++ b/hw/adc/npcm7xx_adc.c @@ -238,7 +238,7 @@ static void npcm7xx_adc_init(Object *obj) memory_region_init_io(&s->iomem, obj, &npcm7xx_adc_ops, s, TYPE_NPCM7XX_ADC, 4 * KiB); sysbus_init_mmio(sbd, &s->iomem); - s->clock = qdev_init_clock_in(DEVICE(s), "clock", NULL, NULL); + s->clock = qdev_init_clock_in(DEVICE(s), "clock", NULL, NULL, 0); for (i = 0; i < NPCM7XX_ADC_NUM_INPUTS; ++i) { object_property_add_uint32_ptr(obj, "adci[*]", diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index 4e6f4ffe90..8c37cf00da 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -353,6 +353,7 @@ config XLNX_ZYNQMP_ARM select SSI_M25P80 select XILINX_AXI select XILINX_SPIPS + select XLNX_CSU_DMA select XLNX_ZYNQMP select XLNX_ZDMA @@ -505,6 +506,7 @@ config ARM11MPCORE config ARMSSE bool select ARM_V7M + select ARMSSE_CPU_PWRCTRL select ARMSSE_CPUID select ARMSSE_MHU select CMSDK_APB_TIMER @@ -520,9 +522,5 @@ config ARMSSE select TZ_MSC select TZ_PPC select UNIMP - -config ARMSSE_CPUID - bool - -config ARMSSE_MHU - bool + select SSE_COUNTER + select SSE_TIMER diff --git a/hw/arm/armsse.c b/hw/arm/armsse.c index 26e1a8c95b..e5aeb9e485 100644 --- a/hw/arm/armsse.c +++ b/hw/arm/armsse.c @@ -19,29 +19,58 @@ #include "migration/vmstate.h" #include "hw/registerfields.h" #include "hw/arm/armsse.h" +#include "hw/arm/armsse-version.h" #include "hw/arm/boot.h" #include "hw/irq.h" #include "hw/qdev-clock.h" -/* Format of the System Information block SYS_CONFIG register */ -typedef enum SysConfigFormat { - IoTKitFormat, - SSE200Format, -} SysConfigFormat; +/* + * The SSE-300 puts some devices in different places to the + * SSE-200 (and original IoTKit). We use an array of these structs + * to define how each variant lays out these devices. (Parts of the + * SoC that are the same for all variants aren't handled via these + * data structures.) + */ + +#define NO_IRQ -1 +#define NO_PPC -1 +/* + * Special values for ARMSSEDeviceInfo::irq to indicate that this + * device uses one of the inputs to the OR gate that feeds into the + * CPU NMI input. + */ +#define NMI_0 10000 +#define NMI_1 10001 + +typedef struct ARMSSEDeviceInfo { + const char *name; /* name to use for the QOM object; NULL terminates list */ + const char *type; /* QOM type name */ + unsigned int index; /* Which of the N devices of this type is this ? */ + hwaddr addr; + hwaddr size; /* only needed for TYPE_UNIMPLEMENTED_DEVICE */ + int ppc; /* Index of APB PPC this device is wired up to, or NO_PPC */ + int ppc_port; /* Port number of this device on the PPC */ + int irq; /* NO_IRQ, or 0..NUM_SSE_IRQS-1, or NMI_0 or NMI_1 */ + bool slowclk; /* true if device uses the slow 32KHz clock */ +} ARMSSEDeviceInfo; struct ARMSSEInfo { const char *name; + uint32_t sse_version; int sram_banks; int num_cpus; uint32_t sys_version; + uint32_t iidr; uint32_t cpuwait_rst; - SysConfigFormat sys_config_format; bool has_mhus; - bool has_ppus; bool has_cachectrl; bool has_cpusecctrl; bool has_cpuid; + bool has_cpu_pwrctrl; + bool has_sse_counter; Property *props; + const ARMSSEDeviceInfo *devinfo; + const bool *irq_is_common; }; static Property iotkit_properties[] = { @@ -68,34 +97,449 @@ static Property armsse_properties[] = { DEFINE_PROP_END_OF_LIST() }; +static const ARMSSEDeviceInfo iotkit_devices[] = { + { + .name = "timer0", + .type = TYPE_CMSDK_APB_TIMER, + .index = 0, + .addr = 0x40000000, + .ppc = 0, + .ppc_port = 0, + .irq = 3, + }, + { + .name = "timer1", + .type = TYPE_CMSDK_APB_TIMER, + .index = 1, + .addr = 0x40001000, + .ppc = 0, + .ppc_port = 1, + .irq = 4, + }, + { + .name = "s32ktimer", + .type = TYPE_CMSDK_APB_TIMER, + .index = 2, + .addr = 0x4002f000, + .ppc = 1, + .ppc_port = 0, + .irq = 2, + .slowclk = true, + }, + { + .name = "dualtimer", + .type = TYPE_CMSDK_APB_DUALTIMER, + .index = 0, + .addr = 0x40002000, + .ppc = 0, + .ppc_port = 2, + .irq = 5, + }, + { + .name = "s32kwatchdog", + .type = TYPE_CMSDK_APB_WATCHDOG, + .index = 0, + .addr = 0x5002e000, + .ppc = NO_PPC, + .irq = NMI_0, + .slowclk = true, + }, + { + .name = "nswatchdog", + .type = TYPE_CMSDK_APB_WATCHDOG, + .index = 1, + .addr = 0x40081000, + .ppc = NO_PPC, + .irq = 1, + }, + { + .name = "swatchdog", + .type = TYPE_CMSDK_APB_WATCHDOG, + .index = 2, + .addr = 0x50081000, + .ppc = NO_PPC, + .irq = NMI_1, + }, + { + .name = "armsse-sysinfo", + .type = TYPE_IOTKIT_SYSINFO, + .index = 0, + .addr = 0x40020000, + .ppc = NO_PPC, + .irq = NO_IRQ, + }, + { + .name = "armsse-sysctl", + .type = TYPE_IOTKIT_SYSCTL, + .index = 0, + .addr = 0x50021000, + .ppc = NO_PPC, + .irq = NO_IRQ, + }, + { + .name = NULL, + } +}; + +static const ARMSSEDeviceInfo sse200_devices[] = { + { + .name = "timer0", + .type = TYPE_CMSDK_APB_TIMER, + .index = 0, + .addr = 0x40000000, + .ppc = 0, + .ppc_port = 0, + .irq = 3, + }, + { + .name = "timer1", + .type = TYPE_CMSDK_APB_TIMER, + .index = 1, + .addr = 0x40001000, + .ppc = 0, + .ppc_port = 1, + .irq = 4, + }, + { + .name = "s32ktimer", + .type = TYPE_CMSDK_APB_TIMER, + .index = 2, + .addr = 0x4002f000, + .ppc = 1, + .ppc_port = 0, + .irq = 2, + .slowclk = true, + }, + { + .name = "dualtimer", + .type = TYPE_CMSDK_APB_DUALTIMER, + .index = 0, + .addr = 0x40002000, + .ppc = 0, + .ppc_port = 2, + .irq = 5, + }, + { + .name = "s32kwatchdog", + .type = TYPE_CMSDK_APB_WATCHDOG, + .index = 0, + .addr = 0x5002e000, + .ppc = NO_PPC, + .irq = NMI_0, + .slowclk = true, + }, + { + .name = "nswatchdog", + .type = TYPE_CMSDK_APB_WATCHDOG, + .index = 1, + .addr = 0x40081000, + .ppc = NO_PPC, + .irq = 1, + }, + { + .name = "swatchdog", + .type = TYPE_CMSDK_APB_WATCHDOG, + .index = 2, + .addr = 0x50081000, + .ppc = NO_PPC, + .irq = NMI_1, + }, + { + .name = "armsse-sysinfo", + .type = TYPE_IOTKIT_SYSINFO, + .index = 0, + .addr = 0x40020000, + .ppc = NO_PPC, + .irq = NO_IRQ, + }, + { + .name = "armsse-sysctl", + .type = TYPE_IOTKIT_SYSCTL, + .index = 0, + .addr = 0x50021000, + .ppc = NO_PPC, + .irq = NO_IRQ, + }, + { + .name = "CPU0CORE_PPU", + .type = TYPE_UNIMPLEMENTED_DEVICE, + .index = 0, + .addr = 0x50023000, + .size = 0x1000, + .ppc = NO_PPC, + .irq = NO_IRQ, + }, + { + .name = "CPU1CORE_PPU", + .type = TYPE_UNIMPLEMENTED_DEVICE, + .index = 1, + .addr = 0x50025000, + .size = 0x1000, + .ppc = NO_PPC, + .irq = NO_IRQ, + }, + { + .name = "DBG_PPU", + .type = TYPE_UNIMPLEMENTED_DEVICE, + .index = 2, + .addr = 0x50029000, + .size = 0x1000, + .ppc = NO_PPC, + .irq = NO_IRQ, + }, + { + .name = "RAM0_PPU", + .type = TYPE_UNIMPLEMENTED_DEVICE, + .index = 3, + .addr = 0x5002a000, + .size = 0x1000, + .ppc = NO_PPC, + .irq = NO_IRQ, + }, + { + .name = "RAM1_PPU", + .type = TYPE_UNIMPLEMENTED_DEVICE, + .index = 4, + .addr = 0x5002b000, + .size = 0x1000, + .ppc = NO_PPC, + .irq = NO_IRQ, + }, + { + .name = "RAM2_PPU", + .type = TYPE_UNIMPLEMENTED_DEVICE, + .index = 5, + .addr = 0x5002c000, + .size = 0x1000, + .ppc = NO_PPC, + .irq = NO_IRQ, + }, + { + .name = "RAM3_PPU", + .type = TYPE_UNIMPLEMENTED_DEVICE, + .index = 6, + .addr = 0x5002d000, + .size = 0x1000, + .ppc = NO_PPC, + .irq = NO_IRQ, + }, + { + .name = "SYS_PPU", + .type = TYPE_UNIMPLEMENTED_DEVICE, + .index = 7, + .addr = 0x50022000, + .size = 0x1000, + .ppc = NO_PPC, + .irq = NO_IRQ, + }, + { + .name = NULL, + } +}; + +static const ARMSSEDeviceInfo sse300_devices[] = { + { + .name = "timer0", + .type = TYPE_SSE_TIMER, + .index = 0, + .addr = 0x48000000, + .ppc = 0, + .ppc_port = 0, + .irq = 3, + }, + { + .name = "timer1", + .type = TYPE_SSE_TIMER, + .index = 1, + .addr = 0x48001000, + .ppc = 0, + .ppc_port = 1, + .irq = 4, + }, + { + .name = "timer2", + .type = TYPE_SSE_TIMER, + .index = 2, + .addr = 0x48002000, + .ppc = 0, + .ppc_port = 2, + .irq = 5, + }, + { + .name = "timer3", + .type = TYPE_SSE_TIMER, + .index = 3, + .addr = 0x48003000, + .ppc = 0, + .ppc_port = 5, + .irq = 27, + }, + { + .name = "s32ktimer", + .type = TYPE_CMSDK_APB_TIMER, + .index = 0, + .addr = 0x4802f000, + .ppc = 1, + .ppc_port = 0, + .irq = 2, + .slowclk = true, + }, + { + .name = "s32kwatchdog", + .type = TYPE_CMSDK_APB_WATCHDOG, + .index = 0, + .addr = 0x4802e000, + .ppc = NO_PPC, + .irq = NMI_0, + .slowclk = true, + }, + { + .name = "watchdog", + .type = TYPE_UNIMPLEMENTED_DEVICE, + .index = 0, + .addr = 0x48040000, + .size = 0x2000, + .ppc = NO_PPC, + .irq = NO_IRQ, + }, + { + .name = "armsse-sysinfo", + .type = TYPE_IOTKIT_SYSINFO, + .index = 0, + .addr = 0x48020000, + .ppc = NO_PPC, + .irq = NO_IRQ, + }, + { + .name = "armsse-sysctl", + .type = TYPE_IOTKIT_SYSCTL, + .index = 0, + .addr = 0x58021000, + .ppc = NO_PPC, + .irq = NO_IRQ, + }, + { + .name = "SYS_PPU", + .type = TYPE_UNIMPLEMENTED_DEVICE, + .index = 1, + .addr = 0x58022000, + .size = 0x1000, + .ppc = NO_PPC, + .irq = NO_IRQ, + }, + { + .name = "CPU0CORE_PPU", + .type = TYPE_UNIMPLEMENTED_DEVICE, + .index = 2, + .addr = 0x50023000, + .size = 0x1000, + .ppc = NO_PPC, + .irq = NO_IRQ, + }, + { + .name = "MGMT_PPU", + .type = TYPE_UNIMPLEMENTED_DEVICE, + .index = 3, + .addr = 0x50028000, + .size = 0x1000, + .ppc = NO_PPC, + .irq = NO_IRQ, + }, + { + .name = "DEBUG_PPU", + .type = TYPE_UNIMPLEMENTED_DEVICE, + .index = 4, + .addr = 0x50029000, + .size = 0x1000, + .ppc = NO_PPC, + .irq = NO_IRQ, + }, + { + .name = NULL, + } +}; + +/* Is internal IRQ n shared between CPUs in a multi-core SSE ? */ +static const bool sse200_irq_is_common[32] = { + [0 ... 5] = true, + /* 6, 7: per-CPU MHU interrupts */ + [8 ... 12] = true, + /* 13: per-CPU icache interrupt */ + /* 14: reserved */ + [15 ... 20] = true, + /* 21: reserved */ + [22 ... 26] = true, + /* 27: reserved */ + /* 28, 29: per-CPU CTI interrupts */ + /* 30, 31: reserved */ +}; + +static const bool sse300_irq_is_common[32] = { + [0 ... 5] = true, + /* 6, 7: per-CPU MHU interrupts */ + [8 ... 12] = true, + /* 13: reserved */ + [14 ... 16] = true, + /* 17-25: reserved */ + [26 ... 27] = true, + /* 28, 29: per-CPU CTI interrupts */ + /* 30, 31: reserved */ +}; + static const ARMSSEInfo armsse_variants[] = { { .name = TYPE_IOTKIT, + .sse_version = ARMSSE_IOTKIT, .sram_banks = 1, .num_cpus = 1, .sys_version = 0x41743, + .iidr = 0, .cpuwait_rst = 0, - .sys_config_format = IoTKitFormat, .has_mhus = false, - .has_ppus = false, .has_cachectrl = false, .has_cpusecctrl = false, .has_cpuid = false, + .has_cpu_pwrctrl = false, + .has_sse_counter = false, .props = iotkit_properties, + .devinfo = iotkit_devices, + .irq_is_common = sse200_irq_is_common, }, { .name = TYPE_SSE200, + .sse_version = ARMSSE_SSE200, .sram_banks = 4, .num_cpus = 2, .sys_version = 0x22041743, + .iidr = 0, .cpuwait_rst = 2, - .sys_config_format = SSE200Format, .has_mhus = true, - .has_ppus = true, .has_cachectrl = true, .has_cpusecctrl = true, .has_cpuid = true, + .has_cpu_pwrctrl = false, + .has_sse_counter = false, + .props = armsse_properties, + .devinfo = sse200_devices, + .irq_is_common = sse200_irq_is_common, + }, + { + .name = TYPE_SSE300, + .sse_version = ARMSSE_SSE300, + .sram_banks = 2, + .num_cpus = 1, + .sys_version = 0x7e00043b, + .iidr = 0x74a0043b, + .cpuwait_rst = 0, + .has_mhus = false, + .has_cachectrl = false, + .has_cpusecctrl = true, + .has_cpuid = true, + .has_cpu_pwrctrl = true, + .has_sse_counter = true, .props = armsse_properties, + .devinfo = sse300_devices, + .irq_is_common = sse300_irq_is_common, }, }; @@ -104,13 +548,13 @@ static uint32_t armsse_sys_config_value(ARMSSE *s, const ARMSSEInfo *info) /* Return the SYS_CONFIG value for this SSE */ uint32_t sys_config; - switch (info->sys_config_format) { - case IoTKitFormat: + switch (info->sse_version) { + case ARMSSE_IOTKIT: sys_config = 0; sys_config = deposit32(sys_config, 0, 4, info->sram_banks); sys_config = deposit32(sys_config, 4, 4, s->sram_addr_width - 12); break; - case SSE200Format: + case ARMSSE_SSE200: sys_config = 0; sys_config = deposit32(sys_config, 0, 4, info->sram_banks); sys_config = deposit32(sys_config, 4, 5, s->sram_addr_width); @@ -121,6 +565,12 @@ static uint32_t armsse_sys_config_value(ARMSSE *s, const ARMSSEInfo *info) sys_config = deposit32(sys_config, 28, 4, 2); } break; + case ARMSSE_SSE300: + sys_config = 0; + sys_config = deposit32(sys_config, 0, 4, info->sram_banks); + sys_config = deposit32(sys_config, 4, 5, s->sram_addr_width); + sys_config = deposit32(sys_config, 16, 3, 3); /* CPU0 = Cortex-M55 */ + break; default: g_assert_not_reached(); } @@ -130,21 +580,6 @@ static uint32_t armsse_sys_config_value(ARMSSE *s, const ARMSSEInfo *info) /* Clock frequency in HZ of the 32KHz "slow clock" */ #define S32KCLK (32 * 1000) -/* Is internal IRQ n shared between CPUs in a multi-core SSE ? */ -static bool irq_is_common[32] = { - [0 ... 5] = true, - /* 6, 7: per-CPU MHU interrupts */ - [8 ... 12] = true, - /* 13: per-CPU icache interrupt */ - /* 14: reserved */ - [15 ... 20] = true, - /* 21: reserved */ - [22 ... 26] = true, - /* 27: reserved */ - /* 28, 29: per-CPU CTI interrupts */ - /* 30, 31: reserved */ -}; - /* * Create an alias region in @container of @size bytes starting at @base * which mirrors the memory starting at @orig. @@ -230,9 +665,10 @@ static void armsse_forward_sec_resp_cfg(ARMSSE *s) qdev_connect_gpio_out(dev_splitter, 2, s->sec_resp_cfg_in); } -static void armsse_mainclk_update(void *opaque) +static void armsse_mainclk_update(void *opaque, ClockEvent event) { ARMSSE *s = ARM_SSE(opaque); + /* * Set system_clock_scale from our Clock input; this is what * controls the tick rate of the CPU SysTick timer. @@ -245,14 +681,15 @@ static void armsse_init(Object *obj) ARMSSE *s = ARM_SSE(obj); ARMSSEClass *asc = ARM_SSE_GET_CLASS(obj); const ARMSSEInfo *info = asc->info; + const ARMSSEDeviceInfo *devinfo; int i; assert(info->sram_banks <= MAX_SRAM_BANKS); assert(info->num_cpus <= SSE_MAX_CPUS); s->mainclk = qdev_init_clock_in(DEVICE(s), "MAINCLK", - armsse_mainclk_update, s); - s->s32kclk = qdev_init_clock_in(DEVICE(s), "S32KCLK", NULL, NULL); + armsse_mainclk_update, s, ClockUpdate); + s->s32kclk = qdev_init_clock_in(DEVICE(s), "S32KCLK", NULL, NULL, 0); memory_region_init(&s->container, obj, "armsse-container", UINT64_MAX); @@ -285,9 +722,52 @@ static void armsse_init(Object *obj) } } + for (devinfo = info->devinfo; devinfo->name; devinfo++) { + assert(devinfo->ppc == NO_PPC || devinfo->ppc < ARRAY_SIZE(s->apb_ppc)); + if (!strcmp(devinfo->type, TYPE_CMSDK_APB_TIMER)) { + assert(devinfo->index < ARRAY_SIZE(s->timer)); + object_initialize_child(obj, devinfo->name, + &s->timer[devinfo->index], + TYPE_CMSDK_APB_TIMER); + } else if (!strcmp(devinfo->type, TYPE_CMSDK_APB_DUALTIMER)) { + assert(devinfo->index == 0); + object_initialize_child(obj, devinfo->name, &s->dualtimer, + TYPE_CMSDK_APB_DUALTIMER); + } else if (!strcmp(devinfo->type, TYPE_SSE_TIMER)) { + assert(devinfo->index < ARRAY_SIZE(s->sse_timer)); + object_initialize_child(obj, devinfo->name, + &s->sse_timer[devinfo->index], + TYPE_SSE_TIMER); + } else if (!strcmp(devinfo->type, TYPE_CMSDK_APB_WATCHDOG)) { + assert(devinfo->index < ARRAY_SIZE(s->cmsdk_watchdog)); + object_initialize_child(obj, devinfo->name, + &s->cmsdk_watchdog[devinfo->index], + TYPE_CMSDK_APB_WATCHDOG); + } else if (!strcmp(devinfo->type, TYPE_IOTKIT_SYSINFO)) { + assert(devinfo->index == 0); + object_initialize_child(obj, devinfo->name, &s->sysinfo, + TYPE_IOTKIT_SYSINFO); + } else if (!strcmp(devinfo->type, TYPE_IOTKIT_SYSCTL)) { + assert(devinfo->index == 0); + object_initialize_child(obj, devinfo->name, &s->sysctl, + TYPE_IOTKIT_SYSCTL); + } else if (!strcmp(devinfo->type, TYPE_UNIMPLEMENTED_DEVICE)) { + assert(devinfo->index < ARRAY_SIZE(s->unimp)); + object_initialize_child(obj, devinfo->name, + &s->unimp[devinfo->index], + TYPE_UNIMPLEMENTED_DEVICE); + } else { + g_assert_not_reached(); + } + } + object_initialize_child(obj, "secctl", &s->secctl, TYPE_IOTKIT_SECCTL); - object_initialize_child(obj, "apb-ppc0", &s->apb_ppc0, TYPE_TZ_PPC); - object_initialize_child(obj, "apb-ppc1", &s->apb_ppc1, TYPE_TZ_PPC); + + for (i = 0; i < ARRAY_SIZE(s->apb_ppc); i++) { + g_autofree char *name = g_strdup_printf("apb-ppc%d", i); + object_initialize_child(obj, name, &s->apb_ppc[i], TYPE_TZ_PPC); + } + for (i = 0; i < info->sram_banks; i++) { char *name = g_strdup_printf("mpc%d", i); object_initialize_child(obj, name, &s->mpc[i], TYPE_TZ_MPC); @@ -303,46 +783,11 @@ static void armsse_init(Object *obj) object_initialize_child(obj, name, splitter, TYPE_SPLIT_IRQ); g_free(name); } - object_initialize_child(obj, "timer0", &s->timer0, TYPE_CMSDK_APB_TIMER); - object_initialize_child(obj, "timer1", &s->timer1, TYPE_CMSDK_APB_TIMER); - object_initialize_child(obj, "s32ktimer", &s->s32ktimer, - TYPE_CMSDK_APB_TIMER); - object_initialize_child(obj, "dualtimer", &s->dualtimer, - TYPE_CMSDK_APB_DUALTIMER); - object_initialize_child(obj, "s32kwatchdog", &s->s32kwatchdog, - TYPE_CMSDK_APB_WATCHDOG); - object_initialize_child(obj, "nswatchdog", &s->nswatchdog, - TYPE_CMSDK_APB_WATCHDOG); - object_initialize_child(obj, "swatchdog", &s->swatchdog, - TYPE_CMSDK_APB_WATCHDOG); - object_initialize_child(obj, "armsse-sysctl", &s->sysctl, - TYPE_IOTKIT_SYSCTL); - object_initialize_child(obj, "armsse-sysinfo", &s->sysinfo, - TYPE_IOTKIT_SYSINFO); + if (info->has_mhus) { object_initialize_child(obj, "mhu0", &s->mhu[0], TYPE_ARMSSE_MHU); object_initialize_child(obj, "mhu1", &s->mhu[1], TYPE_ARMSSE_MHU); } - if (info->has_ppus) { - for (i = 0; i < info->num_cpus; i++) { - char *name = g_strdup_printf("CPU%dCORE_PPU", i); - int ppuidx = CPU0CORE_PPU + i; - - object_initialize_child(obj, name, &s->ppu[ppuidx], - TYPE_UNIMPLEMENTED_DEVICE); - g_free(name); - } - object_initialize_child(obj, "DBG_PPU", &s->ppu[DBG_PPU], - TYPE_UNIMPLEMENTED_DEVICE); - for (i = 0; i < info->sram_banks; i++) { - char *name = g_strdup_printf("RAM%d_PPU", i); - int ppuidx = RAM0_PPU + i; - - object_initialize_child(obj, name, &s->ppu[ppuidx], - TYPE_UNIMPLEMENTED_DEVICE); - g_free(name); - } - } if (info->has_cachectrl) { for (i = 0; i < info->num_cpus; i++) { char *name = g_strdup_printf("cachectrl%d", i); @@ -370,6 +815,20 @@ static void armsse_init(Object *obj) g_free(name); } } + if (info->has_cpu_pwrctrl) { + for (i = 0; i < info->num_cpus; i++) { + char *name = g_strdup_printf("cpu_pwrctrl%d", i); + + object_initialize_child(obj, name, &s->cpu_pwrctrl[i], + TYPE_ARMSSE_CPU_PWRCTRL); + g_free(name); + } + } + if (info->has_sse_counter) { + object_initialize_child(obj, "sse-counter", &s->sse_counter, + TYPE_SSE_COUNTER); + } + object_initialize_child(obj, "nmi-orgate", &s->nmi_orgate, TYPE_OR_IRQ); object_initialize_child(obj, "ppc-irq-orgate", &s->ppc_irq_orgate, TYPE_OR_IRQ); @@ -384,7 +843,7 @@ static void armsse_init(Object *obj) } if (info->num_cpus > 1) { for (i = 0; i < ARRAY_SIZE(s->cpu_irq_splitter); i++) { - if (irq_is_common[i]) { + if (info->irq_is_common[i]) { char *name = g_strdup_printf("cpu-irq-splitter%d", i); SplitIRQ *splitter = &s->cpu_irq_splitter[i]; @@ -417,7 +876,7 @@ static qemu_irq armsse_get_common_irq_in(ARMSSE *s, int irqno) ARMSSEClass *asc = ARM_SSE_GET_CLASS(s); const ARMSSEInfo *info = asc->info; - assert(irq_is_common[irqno]); + assert(info->irq_is_common[irqno]); if (info->num_cpus == 1) { /* Only one CPU -- just connect directly to it */ @@ -428,22 +887,12 @@ static qemu_irq armsse_get_common_irq_in(ARMSSE *s, int irqno) } } -static void map_ppu(ARMSSE *s, int ppuidx, const char *name, hwaddr addr) -{ - /* Map a PPU unimplemented device stub */ - DeviceState *dev = DEVICE(&s->ppu[ppuidx]); - - qdev_prop_set_string(dev, "name", name); - qdev_prop_set_uint64(dev, "size", 0x1000); - sysbus_realize(SYS_BUS_DEVICE(dev), &error_fatal); - sysbus_mmio_map(SYS_BUS_DEVICE(&s->ppu[ppuidx]), 0, addr); -} - static void armsse_realize(DeviceState *dev, Error **errp) { ARMSSE *s = ARM_SSE(dev); ARMSSEClass *asc = ARM_SSE_GET_CLASS(dev); const ARMSSEInfo *info = asc->info; + const ARMSSEDeviceInfo *devinfo; int i; MemoryRegion *mr; Error *err = NULL; @@ -522,7 +971,7 @@ static void armsse_realize(DeviceState *dev, Error **errp) int j; char *gpioname; - qdev_prop_set_uint32(cpudev, "num-irq", s->exp_numirq + 32); + qdev_prop_set_uint32(cpudev, "num-irq", s->exp_numirq + NUM_SSE_IRQS); /* * In real hardware the initial Secure VTOR is set from the INITSVTOR* * registers in the IoT Kit System Control Register block. In QEMU @@ -593,7 +1042,7 @@ static void armsse_realize(DeviceState *dev, Error **errp) /* Connect EXP_IRQ/EXP_CPUn_IRQ GPIOs to the NVIC's lines 32 and up */ s->exp_irqs[i] = g_new(qemu_irq, s->exp_numirq); for (j = 0; j < s->exp_numirq; j++) { - s->exp_irqs[i][j] = qdev_get_gpio_in(cpudev, j + 32); + s->exp_irqs[i][j] = qdev_get_gpio_in(cpudev, j + NUM_SSE_IRQS); } if (i == 0) { gpioname = g_strdup("EXP_IRQ"); @@ -609,7 +1058,7 @@ static void armsse_realize(DeviceState *dev, Error **errp) /* Wire up the splitters that connect common IRQs to all CPUs */ if (info->num_cpus > 1) { for (i = 0; i < ARRAY_SIZE(s->cpu_irq_splitter); i++) { - if (irq_is_common[i]) { + if (info->irq_is_common[i]) { Object *splitter = OBJECT(&s->cpu_irq_splitter[i]); DeviceState *devs = DEVICE(splitter); int cpunum; @@ -649,6 +1098,8 @@ static void armsse_realize(DeviceState *dev, Error **errp) } /* Security controller */ + object_property_set_int(OBJECT(&s->secctl), "sse-version", + info->sse_version, &error_abort); if (!sysbus_realize(SYS_BUS_DEVICE(&s->secctl), errp)) { return; } @@ -715,6 +1166,36 @@ static void armsse_realize(DeviceState *dev, Error **errp) qdev_connect_gpio_out(DEVICE(&s->mpc_irq_orgate), 0, armsse_get_common_irq_in(s, 9)); + /* This OR gate wires together outputs from the secure watchdogs to NMI */ + if (!object_property_set_int(OBJECT(&s->nmi_orgate), "num-lines", 2, + errp)) { + return; + } + if (!qdev_realize(DEVICE(&s->nmi_orgate), NULL, errp)) { + return; + } + qdev_connect_gpio_out(DEVICE(&s->nmi_orgate), 0, + qdev_get_gpio_in_named(DEVICE(&s->armv7m), "NMI", 0)); + + /* The SSE-300 has a System Counter / System Timestamp Generator */ + if (info->has_sse_counter) { + SysBusDevice *sbd = SYS_BUS_DEVICE(&s->sse_counter); + + qdev_connect_clock_in(DEVICE(sbd), "CLK", s->mainclk); + if (!sysbus_realize(sbd, errp)) { + return; + } + /* + * The control frame is only in the Secure region; + * the status frame is in the NS region (and visible in the + * S region via the alias mapping). + */ + memory_region_add_subregion(&s->container, 0x58100000, + sysbus_mmio_get_region(sbd, 0)); + memory_region_add_subregion(&s->container, 0x48101000, + sysbus_mmio_get_region(sbd, 1)); + } + /* Devices behind APB PPC0: * 0x40000000: timer0 * 0x40001000: timer1 @@ -725,35 +1206,127 @@ static void armsse_realize(DeviceState *dev, Error **errp) * it to the appropriate PPC port; then we can realize the PPC and * map its upstream ends to the right place in the container. */ - qdev_connect_clock_in(DEVICE(&s->timer0), "pclk", s->mainclk); - if (!sysbus_realize(SYS_BUS_DEVICE(&s->timer0), errp)) { - return; - } - sysbus_connect_irq(SYS_BUS_DEVICE(&s->timer0), 0, - armsse_get_common_irq_in(s, 3)); - mr = sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->timer0), 0); - object_property_set_link(OBJECT(&s->apb_ppc0), "port[0]", OBJECT(mr), - &error_abort); - - qdev_connect_clock_in(DEVICE(&s->timer1), "pclk", s->mainclk); - if (!sysbus_realize(SYS_BUS_DEVICE(&s->timer1), errp)) { - return; - } - sysbus_connect_irq(SYS_BUS_DEVICE(&s->timer1), 0, - armsse_get_common_irq_in(s, 4)); - mr = sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->timer1), 0); - object_property_set_link(OBJECT(&s->apb_ppc0), "port[1]", OBJECT(mr), - &error_abort); - - qdev_connect_clock_in(DEVICE(&s->dualtimer), "TIMCLK", s->mainclk); - if (!sysbus_realize(SYS_BUS_DEVICE(&s->dualtimer), errp)) { - return; + for (devinfo = info->devinfo; devinfo->name; devinfo++) { + SysBusDevice *sbd; + qemu_irq irq; + + if (!strcmp(devinfo->type, TYPE_CMSDK_APB_TIMER)) { + sbd = SYS_BUS_DEVICE(&s->timer[devinfo->index]); + + qdev_connect_clock_in(DEVICE(sbd), "pclk", + devinfo->slowclk ? s->s32kclk : s->mainclk); + if (!sysbus_realize(sbd, errp)) { + return; + } + mr = sysbus_mmio_get_region(sbd, 0); + } else if (!strcmp(devinfo->type, TYPE_CMSDK_APB_DUALTIMER)) { + sbd = SYS_BUS_DEVICE(&s->dualtimer); + + qdev_connect_clock_in(DEVICE(sbd), "TIMCLK", s->mainclk); + if (!sysbus_realize(sbd, errp)) { + return; + } + mr = sysbus_mmio_get_region(sbd, 0); + } else if (!strcmp(devinfo->type, TYPE_SSE_TIMER)) { + sbd = SYS_BUS_DEVICE(&s->sse_timer[devinfo->index]); + + assert(info->has_sse_counter); + object_property_set_link(OBJECT(sbd), "counter", + OBJECT(&s->sse_counter), &error_abort); + if (!sysbus_realize(sbd, errp)) { + return; + } + mr = sysbus_mmio_get_region(sbd, 0); + } else if (!strcmp(devinfo->type, TYPE_CMSDK_APB_WATCHDOG)) { + sbd = SYS_BUS_DEVICE(&s->cmsdk_watchdog[devinfo->index]); + + qdev_connect_clock_in(DEVICE(sbd), "WDOGCLK", + devinfo->slowclk ? s->s32kclk : s->mainclk); + if (!sysbus_realize(sbd, errp)) { + return; + } + mr = sysbus_mmio_get_region(sbd, 0); + } else if (!strcmp(devinfo->type, TYPE_IOTKIT_SYSINFO)) { + sbd = SYS_BUS_DEVICE(&s->sysinfo); + + object_property_set_int(OBJECT(&s->sysinfo), "SYS_VERSION", + info->sys_version, &error_abort); + object_property_set_int(OBJECT(&s->sysinfo), "SYS_CONFIG", + armsse_sys_config_value(s, info), + &error_abort); + object_property_set_int(OBJECT(&s->sysinfo), "sse-version", + info->sse_version, &error_abort); + object_property_set_int(OBJECT(&s->sysinfo), "IIDR", + info->iidr, &error_abort); + if (!sysbus_realize(sbd, errp)) { + return; + } + mr = sysbus_mmio_get_region(sbd, 0); + } else if (!strcmp(devinfo->type, TYPE_IOTKIT_SYSCTL)) { + /* System control registers */ + sbd = SYS_BUS_DEVICE(&s->sysctl); + + object_property_set_int(OBJECT(&s->sysctl), "sse-version", + info->sse_version, &error_abort); + object_property_set_int(OBJECT(&s->sysctl), "CPUWAIT_RST", + info->cpuwait_rst, &error_abort); + object_property_set_int(OBJECT(&s->sysctl), "INITSVTOR0_RST", + s->init_svtor, &error_abort); + object_property_set_int(OBJECT(&s->sysctl), "INITSVTOR1_RST", + s->init_svtor, &error_abort); + if (!sysbus_realize(sbd, errp)) { + return; + } + mr = sysbus_mmio_get_region(sbd, 0); + } else if (!strcmp(devinfo->type, TYPE_UNIMPLEMENTED_DEVICE)) { + sbd = SYS_BUS_DEVICE(&s->unimp[devinfo->index]); + + qdev_prop_set_string(DEVICE(sbd), "name", devinfo->name); + qdev_prop_set_uint64(DEVICE(sbd), "size", devinfo->size); + if (!sysbus_realize(sbd, errp)) { + return; + } + mr = sysbus_mmio_get_region(sbd, 0); + } else { + g_assert_not_reached(); + } + + switch (devinfo->irq) { + case NO_IRQ: + irq = NULL; + break; + case 0 ... NUM_SSE_IRQS - 1: + irq = armsse_get_common_irq_in(s, devinfo->irq); + break; + case NMI_0: + case NMI_1: + irq = qdev_get_gpio_in(DEVICE(&s->nmi_orgate), + devinfo->irq - NMI_0); + break; + default: + g_assert_not_reached(); + } + + if (irq) { + sysbus_connect_irq(sbd, 0, irq); + } + + /* + * Devices connected to a PPC are connected to the port here; + * we will map the upstream end of that port to the right address + * in the container later after the PPC has been realized. + * Devices not connected to a PPC can be mapped immediately. + */ + if (devinfo->ppc != NO_PPC) { + TZPPC *ppc = &s->apb_ppc[devinfo->ppc]; + g_autofree char *portname = g_strdup_printf("port[%d]", + devinfo->ppc_port); + object_property_set_link(OBJECT(ppc), portname, OBJECT(mr), + &error_abort); + } else { + memory_region_add_subregion(&s->container, devinfo->addr, mr); + } } - sysbus_connect_irq(SYS_BUS_DEVICE(&s->dualtimer), 0, - armsse_get_common_irq_in(s, 5)); - mr = sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->dualtimer), 0); - object_property_set_link(OBJECT(&s->apb_ppc0), "port[2]", OBJECT(mr), - &error_abort); if (info->has_mhus) { /* @@ -775,7 +1348,7 @@ static void armsse_realize(DeviceState *dev, Error **errp) } port = g_strdup_printf("port[%d]", i + 3); mr = sysbus_mmio_get_region(mhu_sbd, 0); - object_property_set_link(OBJECT(&s->apb_ppc0), port, OBJECT(mr), + object_property_set_link(OBJECT(&s->apb_ppc[0]), port, OBJECT(mr), &error_abort); g_free(port); @@ -795,19 +1368,13 @@ static void armsse_realize(DeviceState *dev, Error **errp) } } - if (!sysbus_realize(SYS_BUS_DEVICE(&s->apb_ppc0), errp)) { + if (!sysbus_realize(SYS_BUS_DEVICE(&s->apb_ppc[0]), errp)) { return; } - sbd_apb_ppc0 = SYS_BUS_DEVICE(&s->apb_ppc0); - dev_apb_ppc0 = DEVICE(&s->apb_ppc0); + sbd_apb_ppc0 = SYS_BUS_DEVICE(&s->apb_ppc[0]); + dev_apb_ppc0 = DEVICE(&s->apb_ppc[0]); - mr = sysbus_mmio_get_region(sbd_apb_ppc0, 0); - memory_region_add_subregion(&s->container, 0x40000000, mr); - mr = sysbus_mmio_get_region(sbd_apb_ppc0, 1); - memory_region_add_subregion(&s->container, 0x40001000, mr); - mr = sysbus_mmio_get_region(sbd_apb_ppc0, 2); - memory_region_add_subregion(&s->container, 0x40002000, mr); if (info->has_mhus) { mr = sysbus_mmio_get_region(sbd_apb_ppc0, 3); memory_region_add_subregion(&s->container, 0x40003000, mr); @@ -852,6 +1419,8 @@ static void armsse_realize(DeviceState *dev, Error **errp) * 0x50010000: L1 icache control registers * 0x50011000: CPUSECCTRL (CPU local security control registers) * 0x4001f000 and 0x5001f000: CPU_IDENTITY register block + * The SSE-300 has an extra: + * 0x40012000 and 0x50012000: CPU_PWRCTRL register block */ if (info->has_cachectrl) { for (i = 0; i < info->num_cpus; i++) { @@ -898,28 +1467,24 @@ static void armsse_realize(DeviceState *dev, Error **errp) memory_region_add_subregion(&s->cpu_container[i], 0x4001F000, mr); } } + if (info->has_cpu_pwrctrl) { + for (i = 0; i < info->num_cpus; i++) { + MemoryRegion *mr; - /* 0x40020000 .. 0x4002ffff : ARMSSE system control peripheral region */ - /* Devices behind APB PPC1: - * 0x4002f000: S32K timer - */ - qdev_connect_clock_in(DEVICE(&s->s32ktimer), "pclk", s->s32kclk); - if (!sysbus_realize(SYS_BUS_DEVICE(&s->s32ktimer), errp)) { - return; + if (!sysbus_realize(SYS_BUS_DEVICE(&s->cpu_pwrctrl[i]), errp)) { + return; + } + + mr = sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->cpu_pwrctrl[i]), 0); + memory_region_add_subregion(&s->cpu_container[i], 0x40012000, mr); + } } - sysbus_connect_irq(SYS_BUS_DEVICE(&s->s32ktimer), 0, - armsse_get_common_irq_in(s, 2)); - mr = sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->s32ktimer), 0); - object_property_set_link(OBJECT(&s->apb_ppc1), "port[0]", OBJECT(mr), - &error_abort); - if (!sysbus_realize(SYS_BUS_DEVICE(&s->apb_ppc1), errp)) { + if (!sysbus_realize(SYS_BUS_DEVICE(&s->apb_ppc[1]), errp)) { return; } - mr = sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->apb_ppc1), 0); - memory_region_add_subregion(&s->container, 0x4002f000, mr); - dev_apb_ppc1 = DEVICE(&s->apb_ppc1); + dev_apb_ppc1 = DEVICE(&s->apb_ppc[1]); qdev_connect_gpio_out_named(dev_secctl, "apb_ppc1_nonsec", 0, qdev_get_gpio_in_named(dev_apb_ppc1, "cfg_nonsec", 0)); @@ -936,92 +1501,23 @@ static void armsse_realize(DeviceState *dev, Error **errp) qdev_get_gpio_in_named(dev_apb_ppc1, "cfg_sec_resp", 0)); - if (!object_property_set_int(OBJECT(&s->sysinfo), "SYS_VERSION", - info->sys_version, errp)) { - return; - } - if (!object_property_set_int(OBJECT(&s->sysinfo), "SYS_CONFIG", - armsse_sys_config_value(s, info), errp)) { - return; - } - if (!sysbus_realize(SYS_BUS_DEVICE(&s->sysinfo), errp)) { - return; - } - /* System information registers */ - sysbus_mmio_map(SYS_BUS_DEVICE(&s->sysinfo), 0, 0x40020000); - /* System control registers */ - object_property_set_int(OBJECT(&s->sysctl), "SYS_VERSION", - info->sys_version, &error_abort); - object_property_set_int(OBJECT(&s->sysctl), "CPUWAIT_RST", - info->cpuwait_rst, &error_abort); - object_property_set_int(OBJECT(&s->sysctl), "INITSVTOR0_RST", - s->init_svtor, &error_abort); - object_property_set_int(OBJECT(&s->sysctl), "INITSVTOR1_RST", - s->init_svtor, &error_abort); - if (!sysbus_realize(SYS_BUS_DEVICE(&s->sysctl), errp)) { - return; - } - sysbus_mmio_map(SYS_BUS_DEVICE(&s->sysctl), 0, 0x50021000); - - if (info->has_ppus) { - /* CPUnCORE_PPU for each CPU */ - for (i = 0; i < info->num_cpus; i++) { - char *name = g_strdup_printf("CPU%dCORE_PPU", i); - - map_ppu(s, CPU0CORE_PPU + i, name, 0x50023000 + i * 0x2000); - /* - * We don't support CPU debug so don't create the - * CPU0DEBUG_PPU at 0x50024000 and 0x50026000. - */ - g_free(name); - } - map_ppu(s, DBG_PPU, "DBG_PPU", 0x50029000); - - for (i = 0; i < info->sram_banks; i++) { - char *name = g_strdup_printf("RAM%d_PPU", i); + /* + * Now both PPCs are realized we can map the upstream ends of + * ports which correspond to entries in the devinfo array. + * The ports which are connected to non-devinfo devices have + * already been mapped. + */ + for (devinfo = info->devinfo; devinfo->name; devinfo++) { + SysBusDevice *ppc_sbd; - map_ppu(s, RAM0_PPU + i, name, 0x5002a000 + i * 0x1000); - g_free(name); + if (devinfo->ppc == NO_PPC) { + continue; } + ppc_sbd = SYS_BUS_DEVICE(&s->apb_ppc[devinfo->ppc]); + mr = sysbus_mmio_get_region(ppc_sbd, devinfo->ppc_port); + memory_region_add_subregion(&s->container, devinfo->addr, mr); } - /* This OR gate wires together outputs from the secure watchdogs to NMI */ - if (!object_property_set_int(OBJECT(&s->nmi_orgate), "num-lines", 2, - errp)) { - return; - } - if (!qdev_realize(DEVICE(&s->nmi_orgate), NULL, errp)) { - return; - } - qdev_connect_gpio_out(DEVICE(&s->nmi_orgate), 0, - qdev_get_gpio_in_named(DEVICE(&s->armv7m), "NMI", 0)); - - qdev_connect_clock_in(DEVICE(&s->s32kwatchdog), "WDOGCLK", s->s32kclk); - if (!sysbus_realize(SYS_BUS_DEVICE(&s->s32kwatchdog), errp)) { - return; - } - sysbus_connect_irq(SYS_BUS_DEVICE(&s->s32kwatchdog), 0, - qdev_get_gpio_in(DEVICE(&s->nmi_orgate), 0)); - sysbus_mmio_map(SYS_BUS_DEVICE(&s->s32kwatchdog), 0, 0x5002e000); - - /* 0x40080000 .. 0x4008ffff : ARMSSE second Base peripheral region */ - - qdev_connect_clock_in(DEVICE(&s->nswatchdog), "WDOGCLK", s->mainclk); - if (!sysbus_realize(SYS_BUS_DEVICE(&s->nswatchdog), errp)) { - return; - } - sysbus_connect_irq(SYS_BUS_DEVICE(&s->nswatchdog), 0, - armsse_get_common_irq_in(s, 1)); - sysbus_mmio_map(SYS_BUS_DEVICE(&s->nswatchdog), 0, 0x40081000); - - qdev_connect_clock_in(DEVICE(&s->swatchdog), "WDOGCLK", s->mainclk); - if (!sysbus_realize(SYS_BUS_DEVICE(&s->swatchdog), errp)) { - return; - } - sysbus_connect_irq(SYS_BUS_DEVICE(&s->swatchdog), 0, - qdev_get_gpio_in(DEVICE(&s->nmi_orgate), 1)); - sysbus_mmio_map(SYS_BUS_DEVICE(&s->swatchdog), 0, 0x50081000); - for (i = 0; i < ARRAY_SIZE(s->ppc_irq_splitter); i++) { Object *splitter = OBJECT(&s->ppc_irq_splitter[i]); @@ -1052,7 +1548,7 @@ static void armsse_realize(DeviceState *dev, Error **errp) DeviceState *devs = DEVICE(&s->ppc_irq_splitter[i]); char *gpioname = g_strdup_printf("apb_ppc%d_irq_status", i - NUM_EXTERNAL_PPCS); - TZPPC *ppc = (i == NUM_EXTERNAL_PPCS) ? &s->apb_ppc0 : &s->apb_ppc1; + TZPPC *ppc = &s->apb_ppc[i - NUM_EXTERNAL_PPCS]; qdev_connect_gpio_out(devs, 0, qdev_get_gpio_in_named(dev_secctl, gpioname, 0)); @@ -1120,7 +1616,7 @@ static void armsse_realize(DeviceState *dev, Error **errp) sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->container); /* Set initial system_clock_scale from MAINCLK */ - armsse_mainclk_update(s); + armsse_mainclk_update(s, ClockUpdate); } static void armsse_idau_check(IDAUInterface *ii, uint32_t address, diff --git a/hw/arm/armv7m.c b/hw/arm/armv7m.c index 8224d4ade9..6dd10d8470 100644 --- a/hw/arm/armv7m.c +++ b/hw/arm/armv7m.c @@ -16,7 +16,6 @@ #include "hw/loader.h" #include "hw/qdev-properties.h" #include "elf.h" -#include "sysemu/qtest.h" #include "sysemu/reset.h" #include "qemu/error-report.h" #include "qemu/module.h" diff --git a/hw/arm/aspeed_ast2600.c b/hw/arm/aspeed_ast2600.c index bf31ca351f..bc87e754a3 100644 --- a/hw/arm/aspeed_ast2600.c +++ b/hw/arm/aspeed_ast2600.c @@ -65,7 +65,7 @@ static const hwaddr aspeed_soc_ast2600_memmap[] = { #define ASPEED_A7MPCORE_ADDR 0x40460000 -#define ASPEED_SOC_AST2600_MAX_IRQ 128 +#define AST2600_MAX_IRQ 197 /* Shared Peripheral Interrupt values below are offset by -32 from datasheet */ static const int aspeed_soc_ast2600_irqmap[] = { @@ -98,13 +98,13 @@ static const int aspeed_soc_ast2600_irqmap[] = { [ASPEED_DEV_WDT] = 24, [ASPEED_DEV_PWM] = 44, [ASPEED_DEV_LPC] = 35, - [ASPEED_DEV_IBT] = 35, /* LPC */ + [ASPEED_DEV_IBT] = 143, [ASPEED_DEV_I2C] = 110, /* 110 -> 125 */ [ASPEED_DEV_ETH1] = 2, [ASPEED_DEV_ETH2] = 3, [ASPEED_DEV_ETH3] = 32, [ASPEED_DEV_ETH4] = 33, - + [ASPEED_DEV_KCS] = 138, /* 138 -> 142 */ }; static qemu_irq aspeed_soc_get_irq(AspeedSoCState *s, int ctrl) @@ -211,6 +211,8 @@ static void aspeed_soc_ast2600_init(Object *obj) object_initialize_child(obj, "emmc-controller.sdhci", &s->emmc.slots[0], TYPE_SYSBUS_SDHCI); + + object_initialize_child(obj, "lpc", &s->lpc, TYPE_ASPEED_LPC); } /* @@ -241,8 +243,6 @@ static void aspeed_soc_ast2600_realize(DeviceState *dev, Error **errp) /* CPU */ for (i = 0; i < sc->num_cpus; i++) { - object_property_set_int(OBJECT(&s->cpu[i]), "psci-conduit", - QEMU_PSCI_CONDUIT_SMC, &error_abort); if (sc->num_cpus > 1) { object_property_set_int(OBJECT(&s->cpu[i]), "reset-cbar", ASPEED_A7MPCORE_ADDR, &error_abort); @@ -253,11 +253,6 @@ static void aspeed_soc_ast2600_realize(DeviceState *dev, Error **errp) object_property_set_int(OBJECT(&s->cpu[i]), "cntfrq", 1125000000, &error_abort); - /* - * TODO: the secondary CPUs are started and a boot helper - * is needed when using -kernel - */ - if (!qdev_realize(DEVICE(&s->cpu[i]), NULL, errp)) { return; } @@ -267,7 +262,7 @@ static void aspeed_soc_ast2600_realize(DeviceState *dev, Error **errp) object_property_set_int(OBJECT(&s->a7mpcore), "num-cpu", sc->num_cpus, &error_abort); object_property_set_int(OBJECT(&s->a7mpcore), "num-irq", - ASPEED_SOC_AST2600_MAX_IRQ + GIC_INTERNAL, + ROUND_UP(AST2600_MAX_IRQ + GIC_INTERNAL, 32), &error_abort); sysbus_realize(SYS_BUS_DEVICE(&s->a7mpcore), &error_abort); @@ -469,6 +464,40 @@ static void aspeed_soc_ast2600_realize(DeviceState *dev, Error **errp) sysbus_mmio_map(SYS_BUS_DEVICE(&s->emmc), 0, sc->memmap[ASPEED_DEV_EMMC]); sysbus_connect_irq(SYS_BUS_DEVICE(&s->emmc), 0, aspeed_soc_get_irq(s, ASPEED_DEV_EMMC)); + + /* LPC */ + if (!sysbus_realize(SYS_BUS_DEVICE(&s->lpc), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->lpc), 0, sc->memmap[ASPEED_DEV_LPC]); + + /* Connect the LPC IRQ to the GIC. It is otherwise unused. */ + sysbus_connect_irq(SYS_BUS_DEVICE(&s->lpc), 0, + aspeed_soc_get_irq(s, ASPEED_DEV_LPC)); + + /* + * On the AST2600 LPC subdevice IRQs are connected straight to the GIC. + * + * LPC subdevice IRQ sources are offset from 1 because the LPC model caters + * to the AST2400 and AST2500. SoCs before the AST2600 have one LPC IRQ + * shared across the subdevices, and the shared IRQ output to the VIC is at + * offset 0. + */ + sysbus_connect_irq(SYS_BUS_DEVICE(&s->lpc), 1 + aspeed_lpc_kcs_1, + qdev_get_gpio_in(DEVICE(&s->a7mpcore), + sc->irqmap[ASPEED_DEV_KCS] + aspeed_lpc_kcs_1)); + + sysbus_connect_irq(SYS_BUS_DEVICE(&s->lpc), 1 + aspeed_lpc_kcs_2, + qdev_get_gpio_in(DEVICE(&s->a7mpcore), + sc->irqmap[ASPEED_DEV_KCS] + aspeed_lpc_kcs_2)); + + sysbus_connect_irq(SYS_BUS_DEVICE(&s->lpc), 1 + aspeed_lpc_kcs_3, + qdev_get_gpio_in(DEVICE(&s->a7mpcore), + sc->irqmap[ASPEED_DEV_KCS] + aspeed_lpc_kcs_3)); + + sysbus_connect_irq(SYS_BUS_DEVICE(&s->lpc), 1 + aspeed_lpc_kcs_4, + qdev_get_gpio_in(DEVICE(&s->a7mpcore), + sc->irqmap[ASPEED_DEV_KCS] + aspeed_lpc_kcs_4)); } static void aspeed_soc_ast2600_class_init(ObjectClass *oc, void *data) diff --git a/hw/arm/aspeed_soc.c b/hw/arm/aspeed_soc.c index 7eefd54ac0..057d053c84 100644 --- a/hw/arm/aspeed_soc.c +++ b/hw/arm/aspeed_soc.c @@ -112,7 +112,6 @@ static const int aspeed_soc_ast2400_irqmap[] = { [ASPEED_DEV_WDT] = 27, [ASPEED_DEV_PWM] = 28, [ASPEED_DEV_LPC] = 8, - [ASPEED_DEV_IBT] = 8, /* LPC */ [ASPEED_DEV_I2C] = 12, [ASPEED_DEV_ETH1] = 2, [ASPEED_DEV_ETH2] = 3, @@ -211,6 +210,8 @@ static void aspeed_soc_init(Object *obj) object_initialize_child(obj, "sdhci[*]", &s->sdhci.slots[i], TYPE_SYSBUS_SDHCI); } + + object_initialize_child(obj, "lpc", &s->lpc, TYPE_ASPEED_LPC); } static void aspeed_soc_realize(DeviceState *dev, Error **errp) @@ -393,6 +394,37 @@ static void aspeed_soc_realize(DeviceState *dev, Error **errp) sc->memmap[ASPEED_DEV_SDHCI]); sysbus_connect_irq(SYS_BUS_DEVICE(&s->sdhci), 0, aspeed_soc_get_irq(s, ASPEED_DEV_SDHCI)); + + /* LPC */ + if (!sysbus_realize(SYS_BUS_DEVICE(&s->lpc), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->lpc), 0, sc->memmap[ASPEED_DEV_LPC]); + + /* Connect the LPC IRQ to the VIC */ + sysbus_connect_irq(SYS_BUS_DEVICE(&s->lpc), 0, + aspeed_soc_get_irq(s, ASPEED_DEV_LPC)); + + /* + * On the AST2400 and AST2500 the one LPC IRQ is shared between all of the + * subdevices. Connect the LPC subdevice IRQs to the LPC controller IRQ (by + * contrast, on the AST2600, the subdevice IRQs are connected straight to + * the GIC). + * + * LPC subdevice IRQ sources are offset from 1 because the shared IRQ output + * to the VIC is at offset 0. + */ + sysbus_connect_irq(SYS_BUS_DEVICE(&s->lpc), 1 + aspeed_lpc_kcs_1, + qdev_get_gpio_in(DEVICE(&s->lpc), aspeed_lpc_kcs_1)); + + sysbus_connect_irq(SYS_BUS_DEVICE(&s->lpc), 1 + aspeed_lpc_kcs_2, + qdev_get_gpio_in(DEVICE(&s->lpc), aspeed_lpc_kcs_2)); + + sysbus_connect_irq(SYS_BUS_DEVICE(&s->lpc), 1 + aspeed_lpc_kcs_3, + qdev_get_gpio_in(DEVICE(&s->lpc), aspeed_lpc_kcs_3)); + + sysbus_connect_irq(SYS_BUS_DEVICE(&s->lpc), 1 + aspeed_lpc_kcs_4, + qdev_get_gpio_in(DEVICE(&s->lpc), aspeed_lpc_kcs_4)); } static Property aspeed_soc_properties[] = { DEFINE_PROP_LINK("dram", AspeedSoCState, dram_mr, TYPE_MEMORY_REGION, diff --git a/hw/arm/mainstone.c b/hw/arm/mainstone.c index 6bc643651b..8454b65458 100644 --- a/hw/arm/mainstone.c +++ b/hw/arm/mainstone.c @@ -22,7 +22,6 @@ #include "hw/block/flash.h" #include "hw/sysbus.h" #include "exec/address-spaces.h" -#include "sysemu/qtest.h" #include "cpu.h" /* Device addresses */ diff --git a/hw/arm/mps2-tz.c b/hw/arm/mps2-tz.c index 72da8cb1a1..3fbe3d29f9 100644 --- a/hw/arm/mps2-tz.c +++ b/hw/arm/mps2-tz.c @@ -17,6 +17,7 @@ * "mps2-an505" -- Cortex-M33 as documented in ARM Application Note AN505 * "mps2-an521" -- Dual Cortex-M33 as documented in Application Note AN521 * "mps2-an524" -- Dual Cortex-M33 as documented in Application Note AN524 + * "mps2-an547" -- Single Cortex-M55 as documented in Application Note AN547 * * Links to the TRM for the board itself and to the various Application * Notes which document the FPGA images can be found here: @@ -30,6 +31,8 @@ * https://developer.arm.com/documentation/dai0521/latest/ * Application Note AN524: * https://developer.arm.com/documentation/dai0524/latest/ + * Application Note AN547: + * https://developer.arm.com/-/media/Arm%20Developer%20Community/PDF/DAI0547B_SSE300_PLUS_U55_FPGA_for_mps3.pdf * * The AN505 defers to the Cortex-M33 processor ARMv8M IoT Kit FVP User Guide * (ARM ECM0601256) for the details of some of the device layout: @@ -37,6 +40,8 @@ * Similarly, the AN521 and AN524 use the SSE-200, and the SSE-200 TRM defines * most of the device layout: * https://developer.arm.com/documentation/101104/latest/ + * and the AN547 uses the SSE-300, whose layout is in the SSE-300 TRM: + * https://developer.arm.com/documentation/101773/latest/ */ #include "qemu/osdep.h" @@ -68,13 +73,14 @@ #include "hw/qdev-clock.h" #include "qom/object.h" -#define MPS2TZ_NUMIRQ_MAX 95 -#define MPS2TZ_RAM_MAX 4 +#define MPS2TZ_NUMIRQ_MAX 96 +#define MPS2TZ_RAM_MAX 5 typedef enum MPS2TZFPGAType { FPGA_AN505, FPGA_AN521, FPGA_AN524, + FPGA_AN547, } MPS2TZFPGAType; /* @@ -106,11 +112,15 @@ struct MPS2TZMachineClass { MPS2TZFPGAType fpga_type; uint32_t scc_id; uint32_t sysclk_frq; /* Main SYSCLK frequency in Hz */ + uint32_t apb_periph_frq; /* APB peripheral frequency in Hz */ uint32_t len_oscclk; const uint32_t *oscclk; uint32_t fpgaio_num_leds; /* Number of LEDs in FPGAIO LED0 register */ bool fpgaio_has_switches; /* Does FPGAIO have SWITCH register? */ + bool fpgaio_has_dbgctrl; /* Does FPGAIO have DBGCTRL register? */ int numirq; /* Number of external interrupts */ + int uart_overflow_irq; /* number of the combined UART overflow IRQ */ + uint32_t init_svtor; /* init-svtor setting for SSE */ const RAMInfo *raminfo; const char *armsse_type; }; @@ -149,6 +159,7 @@ struct MPS2TZMachineState { #define TYPE_MPS2TZ_AN505_MACHINE MACHINE_TYPE_NAME("mps2-an505") #define TYPE_MPS2TZ_AN521_MACHINE MACHINE_TYPE_NAME("mps2-an521") #define TYPE_MPS3TZ_AN524_MACHINE MACHINE_TYPE_NAME("mps3-an524") +#define TYPE_MPS3TZ_AN547_MACHINE MACHINE_TYPE_NAME("mps3-an547") OBJECT_DECLARE_TYPE(MPS2TZMachineState, MPS2TZMachineClass, MPS2TZ_MACHINE) @@ -248,6 +259,49 @@ static const RAMInfo an524_raminfo[] = { { }, }; +static const RAMInfo an547_raminfo[] = { { + .name = "itcm", + .base = 0x00000000, + .size = 512 * KiB, + .mpc = -1, + .mrindex = 0, + }, { + .name = "sram", + .base = 0x01000000, + .size = 2 * MiB, + .mpc = 0, + .mrindex = 1, + }, { + .name = "dtcm", + .base = 0x20000000, + .size = 4 * 128 * KiB, + .mpc = -1, + .mrindex = 2, + }, { + .name = "sram 2", + .base = 0x21000000, + .size = 4 * MiB, + .mpc = -1, + .mrindex = 3, + }, { + /* We don't model QSPI flash yet; for now expose it as simple ROM */ + .name = "QSPI", + .base = 0x28000000, + .size = 8 * MiB, + .mpc = 1, + .mrindex = 4, + .flags = IS_ROM, + }, { + .name = "DDR", + .base = 0x60000000, + .size = MPS3_DDR_SIZE, + .mpc = 2, + .mrindex = -1, + }, { + .name = NULL, + }, +}; + static const RAMInfo *find_raminfo_for_mpc(MPS2TZMachineState *mms, int mpc) { MPS2TZMachineClass *mmc = MPS2TZ_MACHINE_GET_CLASS(mms); @@ -377,7 +431,7 @@ static MemoryRegion *make_uart(MPS2TZMachineState *mms, void *opaque, object_initialize_child(OBJECT(mms), name, uart, TYPE_CMSDK_APB_UART); qdev_prop_set_chr(DEVICE(uart), "chardev", serial_hd(i)); - qdev_prop_set_uint32(DEVICE(uart), "pclk-frq", mmc->sysclk_frq); + qdev_prop_set_uint32(DEVICE(uart), "pclk-frq", mmc->apb_periph_frq); sysbus_realize(SYS_BUS_DEVICE(uart), &error_fatal); s = SYS_BUS_DEVICE(uart); sysbus_connect_irq(s, 0, get_sse_irq_in(mms, irqs[0])); @@ -421,6 +475,7 @@ static MemoryRegion *make_fpgaio(MPS2TZMachineState *mms, void *opaque, object_initialize_child(OBJECT(mms), "fpgaio", fpgaio, TYPE_MPS2_FPGAIO); qdev_prop_set_uint32(DEVICE(fpgaio), "num-leds", mmc->fpgaio_num_leds); qdev_prop_set_bit(DEVICE(fpgaio), "has-switches", mmc->fpgaio_has_switches); + qdev_prop_set_bit(DEVICE(fpgaio), "has-dbgctrl", mmc->fpgaio_has_dbgctrl); sysbus_realize(SYS_BUS_DEVICE(fpgaio), &error_fatal); return sysbus_mmio_get_region(SYS_BUS_DEVICE(fpgaio), 0); } @@ -696,6 +751,7 @@ static void mps2tz_common_init(MachineState *machine) object_property_set_link(OBJECT(&mms->iotkit), "memory", OBJECT(system_memory), &error_abort); qdev_prop_set_uint32(iotkitdev, "EXP_NUMIRQ", mmc->numirq); + qdev_prop_set_uint32(iotkitdev, "init-svtor", mmc->init_svtor); qdev_connect_clock_in(iotkitdev, "MAINCLK", mms->sysclk); qdev_connect_clock_in(iotkitdev, "S32KCLK", mms->s32kclk); sysbus_realize(SYS_BUS_DEVICE(&mms->iotkit), &error_fatal); @@ -770,7 +826,7 @@ static void mps2tz_common_init(MachineState *machine) &error_fatal); qdev_realize(DEVICE(&mms->uart_irq_orgate), NULL, &error_fatal); qdev_connect_gpio_out(DEVICE(&mms->uart_irq_orgate), 0, - get_sse_irq_in(mms, 47)); + get_sse_irq_in(mms, mmc->uart_overflow_irq)); /* Most of the devices in the FPGA are behind Peripheral Protection * Controllers. The required order for initializing things is: @@ -887,6 +943,55 @@ static void mps2tz_common_init(MachineState *machine) }, }; + const PPCInfo an547_ppcs[] = { { + .name = "apb_ppcexp0", + .ports = { + { "ssram-mpc", make_mpc, &mms->mpc[0], 0x57000000, 0x1000 }, + { "qspi-mpc", make_mpc, &mms->mpc[1], 0x57001000, 0x1000 }, + { "ddr-mpc", make_mpc, &mms->mpc[2], 0x57002000, 0x1000 }, + }, + }, { + .name = "apb_ppcexp1", + .ports = { + { "i2c0", make_i2c, &mms->i2c[0], 0x49200000, 0x1000 }, + { "i2c1", make_i2c, &mms->i2c[1], 0x49201000, 0x1000 }, + { "spi0", make_spi, &mms->spi[0], 0x49202000, 0x1000, { 53 } }, + { "spi1", make_spi, &mms->spi[1], 0x49203000, 0x1000, { 54 } }, + { "spi2", make_spi, &mms->spi[2], 0x49204000, 0x1000, { 55 } }, + { "i2c2", make_i2c, &mms->i2c[2], 0x49205000, 0x1000 }, + { "i2c3", make_i2c, &mms->i2c[3], 0x49206000, 0x1000 }, + { /* port 7 reserved */ }, + { "i2c4", make_i2c, &mms->i2c[4], 0x49208000, 0x1000 }, + }, + }, { + .name = "apb_ppcexp2", + .ports = { + { "scc", make_scc, &mms->scc, 0x49300000, 0x1000 }, + { "i2s-audio", make_unimp_dev, &mms->i2s_audio, 0x49301000, 0x1000 }, + { "fpgaio", make_fpgaio, &mms->fpgaio, 0x49302000, 0x1000 }, + { "uart0", make_uart, &mms->uart[0], 0x49303000, 0x1000, { 33, 34, 43 } }, + { "uart1", make_uart, &mms->uart[1], 0x49304000, 0x1000, { 35, 36, 44 } }, + { "uart2", make_uart, &mms->uart[2], 0x49305000, 0x1000, { 37, 38, 45 } }, + { "uart3", make_uart, &mms->uart[3], 0x49306000, 0x1000, { 39, 40, 46 } }, + { "uart4", make_uart, &mms->uart[4], 0x49307000, 0x1000, { 41, 42, 47 } }, + { "uart5", make_uart, &mms->uart[5], 0x49308000, 0x1000, { 125, 126, 127 } }, + + { /* port 9 reserved */ }, + { "clcd", make_unimp_dev, &mms->cldc, 0x4930a000, 0x1000 }, + { "rtc", make_rtc, &mms->rtc, 0x4930b000, 0x1000 }, + }, + }, { + .name = "ahb_ppcexp0", + .ports = { + { "gpio0", make_unimp_dev, &mms->gpio[0], 0x41100000, 0x1000 }, + { "gpio1", make_unimp_dev, &mms->gpio[1], 0x41101000, 0x1000 }, + { "gpio2", make_unimp_dev, &mms->gpio[2], 0x41102000, 0x1000 }, + { "gpio3", make_unimp_dev, &mms->gpio[3], 0x41103000, 0x1000 }, + { "eth-usb", make_eth_usb, NULL, 0x41400000, 0x200000, { 49 } }, + }, + }, + }; + switch (mmc->fpga_type) { case FPGA_AN505: case FPGA_AN521: @@ -897,6 +1002,10 @@ static void mps2tz_common_init(MachineState *machine) ppcs = an524_ppcs; num_ppcs = ARRAY_SIZE(an524_ppcs); break; + case FPGA_AN547: + ppcs = an547_ppcs; + num_ppcs = ARRAY_SIZE(an547_ppcs); + break; default: g_assert_not_reached(); } @@ -975,6 +1084,11 @@ static void mps2tz_common_init(MachineState *machine) create_unimplemented_device("FPGA NS PC", 0x48007000, 0x1000); + if (mmc->fpga_type == FPGA_AN547) { + create_unimplemented_device("U55 timing adapter 0", 0x48102000, 0x1000); + create_unimplemented_device("U55 timing adapter 1", 0x48103000, 0x1000); + } + create_non_mpc_ram(mms); armv7m_load_kernel(ARM_CPU(first_cpu), machine->kernel_filename, @@ -1041,11 +1155,15 @@ static void mps2tz_an505_class_init(ObjectClass *oc, void *data) mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-m33"); mmc->scc_id = 0x41045050; mmc->sysclk_frq = 20 * 1000 * 1000; /* 20MHz */ + mmc->apb_periph_frq = mmc->sysclk_frq; mmc->oscclk = an505_oscclk; mmc->len_oscclk = ARRAY_SIZE(an505_oscclk); mmc->fpgaio_num_leds = 2; mmc->fpgaio_has_switches = false; + mmc->fpgaio_has_dbgctrl = false; mmc->numirq = 92; + mmc->uart_overflow_irq = 47; + mmc->init_svtor = 0x10000000; mmc->raminfo = an505_raminfo; mmc->armsse_type = TYPE_IOTKIT; mps2tz_set_default_ram_info(mmc); @@ -1064,11 +1182,15 @@ static void mps2tz_an521_class_init(ObjectClass *oc, void *data) mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-m33"); mmc->scc_id = 0x41045210; mmc->sysclk_frq = 20 * 1000 * 1000; /* 20MHz */ + mmc->apb_periph_frq = mmc->sysclk_frq; mmc->oscclk = an505_oscclk; /* AN521 is the same as AN505 here */ mmc->len_oscclk = ARRAY_SIZE(an505_oscclk); mmc->fpgaio_num_leds = 2; mmc->fpgaio_has_switches = false; + mmc->fpgaio_has_dbgctrl = false; mmc->numirq = 92; + mmc->uart_overflow_irq = 47; + mmc->init_svtor = 0x10000000; mmc->raminfo = an505_raminfo; /* AN521 is the same as AN505 here */ mmc->armsse_type = TYPE_SSE200; mps2tz_set_default_ram_info(mmc); @@ -1087,16 +1209,47 @@ static void mps3tz_an524_class_init(ObjectClass *oc, void *data) mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-m33"); mmc->scc_id = 0x41045240; mmc->sysclk_frq = 32 * 1000 * 1000; /* 32MHz */ + mmc->apb_periph_frq = mmc->sysclk_frq; mmc->oscclk = an524_oscclk; mmc->len_oscclk = ARRAY_SIZE(an524_oscclk); mmc->fpgaio_num_leds = 10; mmc->fpgaio_has_switches = true; + mmc->fpgaio_has_dbgctrl = false; mmc->numirq = 95; + mmc->uart_overflow_irq = 47; + mmc->init_svtor = 0x10000000; mmc->raminfo = an524_raminfo; mmc->armsse_type = TYPE_SSE200; mps2tz_set_default_ram_info(mmc); } +static void mps3tz_an547_class_init(ObjectClass *oc, void *data) +{ + MachineClass *mc = MACHINE_CLASS(oc); + MPS2TZMachineClass *mmc = MPS2TZ_MACHINE_CLASS(oc); + + mc->desc = "ARM MPS3 with AN547 FPGA image for Cortex-M55"; + mc->default_cpus = 1; + mc->min_cpus = mc->default_cpus; + mc->max_cpus = mc->default_cpus; + mmc->fpga_type = FPGA_AN547; + mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-m55"); + mmc->scc_id = 0x41055470; + mmc->sysclk_frq = 32 * 1000 * 1000; /* 32MHz */ + mmc->apb_periph_frq = 25 * 1000 * 1000; /* 25MHz */ + mmc->oscclk = an524_oscclk; /* same as AN524 */ + mmc->len_oscclk = ARRAY_SIZE(an524_oscclk); + mmc->fpgaio_num_leds = 10; + mmc->fpgaio_has_switches = true; + mmc->fpgaio_has_dbgctrl = true; + mmc->numirq = 96; + mmc->uart_overflow_irq = 48; + mmc->init_svtor = 0x00000000; + mmc->raminfo = an547_raminfo; + mmc->armsse_type = TYPE_SSE300; + mps2tz_set_default_ram_info(mmc); +} + static const TypeInfo mps2tz_info = { .name = TYPE_MPS2TZ_MACHINE, .parent = TYPE_MACHINE, @@ -1128,12 +1281,19 @@ static const TypeInfo mps3tz_an524_info = { .class_init = mps3tz_an524_class_init, }; +static const TypeInfo mps3tz_an547_info = { + .name = TYPE_MPS3TZ_AN547_MACHINE, + .parent = TYPE_MPS2TZ_MACHINE, + .class_init = mps3tz_an547_class_init, +}; + static void mps2tz_machine_init(void) { type_register_static(&mps2tz_info); type_register_static(&mps2tz_an505_info); type_register_static(&mps2tz_an521_info); type_register_static(&mps3tz_an524_info); + type_register_static(&mps3tz_an547_info); } type_init(mps2tz_machine_init); diff --git a/hw/arm/virt.c b/hw/arm/virt.c index 371147f3ae..c08bf11297 100644 --- a/hw/arm/virt.c +++ b/hw/arm/virt.c @@ -218,14 +218,14 @@ static bool cpu_type_valid(const char *cpu) return false; } -static void create_kaslr_seed(VirtMachineState *vms, const char *node) +static void create_kaslr_seed(MachineState *ms, const char *node) { uint64_t seed; if (qemu_guest_getrandom(&seed, sizeof(seed), NULL)) { return; } - qemu_fdt_setprop_u64(vms->fdt, node, "kaslr-seed", seed); + qemu_fdt_setprop_u64(ms->fdt, node, "kaslr-seed", seed); } static void create_fdt(VirtMachineState *vms) @@ -239,7 +239,7 @@ static void create_fdt(VirtMachineState *vms) exit(1); } - vms->fdt = fdt; + ms->fdt = fdt; /* Header */ qemu_fdt_setprop_string(fdt, "/", "compatible", "linux,dummy-virt"); @@ -248,11 +248,11 @@ static void create_fdt(VirtMachineState *vms) /* /chosen must exist for load_dtb to fill in necessary properties later */ qemu_fdt_add_subnode(fdt, "/chosen"); - create_kaslr_seed(vms, "/chosen"); + create_kaslr_seed(ms, "/chosen"); if (vms->secure) { qemu_fdt_add_subnode(fdt, "/secure-chosen"); - create_kaslr_seed(vms, "/secure-chosen"); + create_kaslr_seed(ms, "/secure-chosen"); } /* Clock node, for the benefit of the UART. The kernel device tree @@ -316,6 +316,7 @@ static void fdt_add_timer_nodes(const VirtMachineState *vms) ARMCPU *armcpu; VirtMachineClass *vmc = VIRT_MACHINE_GET_CLASS(vms); uint32_t irqflags = GIC_FDT_IRQ_FLAGS_LEVEL_HI; + MachineState *ms = MACHINE(vms); if (vmc->claim_edge_triggered_timers) { irqflags = GIC_FDT_IRQ_FLAGS_EDGE_LO_HI; @@ -327,19 +328,19 @@ static void fdt_add_timer_nodes(const VirtMachineState *vms) (1 << MACHINE(vms)->smp.cpus) - 1); } - qemu_fdt_add_subnode(vms->fdt, "/timer"); + qemu_fdt_add_subnode(ms->fdt, "/timer"); armcpu = ARM_CPU(qemu_get_cpu(0)); if (arm_feature(&armcpu->env, ARM_FEATURE_V8)) { const char compat[] = "arm,armv8-timer\0arm,armv7-timer"; - qemu_fdt_setprop(vms->fdt, "/timer", "compatible", + qemu_fdt_setprop(ms->fdt, "/timer", "compatible", compat, sizeof(compat)); } else { - qemu_fdt_setprop_string(vms->fdt, "/timer", "compatible", + qemu_fdt_setprop_string(ms->fdt, "/timer", "compatible", "arm,armv7-timer"); } - qemu_fdt_setprop(vms->fdt, "/timer", "always-on", NULL, 0); - qemu_fdt_setprop_cells(vms->fdt, "/timer", "interrupts", + qemu_fdt_setprop(ms->fdt, "/timer", "always-on", NULL, 0); + qemu_fdt_setprop_cells(ms->fdt, "/timer", "interrupts", GIC_FDT_IRQ_TYPE_PPI, ARCH_TIMER_S_EL1_IRQ, irqflags, GIC_FDT_IRQ_TYPE_PPI, ARCH_TIMER_NS_EL1_IRQ, irqflags, GIC_FDT_IRQ_TYPE_PPI, ARCH_TIMER_VIRT_IRQ, irqflags, @@ -375,35 +376,35 @@ static void fdt_add_cpu_nodes(const VirtMachineState *vms) } } - qemu_fdt_add_subnode(vms->fdt, "/cpus"); - qemu_fdt_setprop_cell(vms->fdt, "/cpus", "#address-cells", addr_cells); - qemu_fdt_setprop_cell(vms->fdt, "/cpus", "#size-cells", 0x0); + qemu_fdt_add_subnode(ms->fdt, "/cpus"); + qemu_fdt_setprop_cell(ms->fdt, "/cpus", "#address-cells", addr_cells); + qemu_fdt_setprop_cell(ms->fdt, "/cpus", "#size-cells", 0x0); for (cpu = smp_cpus - 1; cpu >= 0; cpu--) { char *nodename = g_strdup_printf("/cpus/cpu@%d", cpu); ARMCPU *armcpu = ARM_CPU(qemu_get_cpu(cpu)); CPUState *cs = CPU(armcpu); - qemu_fdt_add_subnode(vms->fdt, nodename); - qemu_fdt_setprop_string(vms->fdt, nodename, "device_type", "cpu"); - qemu_fdt_setprop_string(vms->fdt, nodename, "compatible", + qemu_fdt_add_subnode(ms->fdt, nodename); + qemu_fdt_setprop_string(ms->fdt, nodename, "device_type", "cpu"); + qemu_fdt_setprop_string(ms->fdt, nodename, "compatible", armcpu->dtb_compatible); if (vms->psci_conduit != QEMU_PSCI_CONDUIT_DISABLED && smp_cpus > 1) { - qemu_fdt_setprop_string(vms->fdt, nodename, + qemu_fdt_setprop_string(ms->fdt, nodename, "enable-method", "psci"); } if (addr_cells == 2) { - qemu_fdt_setprop_u64(vms->fdt, nodename, "reg", + qemu_fdt_setprop_u64(ms->fdt, nodename, "reg", armcpu->mp_affinity); } else { - qemu_fdt_setprop_cell(vms->fdt, nodename, "reg", + qemu_fdt_setprop_cell(ms->fdt, nodename, "reg", armcpu->mp_affinity); } if (ms->possible_cpus->cpus[cs->cpu_index].props.has_node_id) { - qemu_fdt_setprop_cell(vms->fdt, nodename, "numa-node-id", + qemu_fdt_setprop_cell(ms->fdt, nodename, "numa-node-id", ms->possible_cpus->cpus[cs->cpu_index].props.node_id); } @@ -414,71 +415,74 @@ static void fdt_add_cpu_nodes(const VirtMachineState *vms) static void fdt_add_its_gic_node(VirtMachineState *vms) { char *nodename; + MachineState *ms = MACHINE(vms); - vms->msi_phandle = qemu_fdt_alloc_phandle(vms->fdt); + vms->msi_phandle = qemu_fdt_alloc_phandle(ms->fdt); nodename = g_strdup_printf("/intc/its@%" PRIx64, vms->memmap[VIRT_GIC_ITS].base); - qemu_fdt_add_subnode(vms->fdt, nodename); - qemu_fdt_setprop_string(vms->fdt, nodename, "compatible", + qemu_fdt_add_subnode(ms->fdt, nodename); + qemu_fdt_setprop_string(ms->fdt, nodename, "compatible", "arm,gic-v3-its"); - qemu_fdt_setprop(vms->fdt, nodename, "msi-controller", NULL, 0); - qemu_fdt_setprop_sized_cells(vms->fdt, nodename, "reg", + qemu_fdt_setprop(ms->fdt, nodename, "msi-controller", NULL, 0); + qemu_fdt_setprop_sized_cells(ms->fdt, nodename, "reg", 2, vms->memmap[VIRT_GIC_ITS].base, 2, vms->memmap[VIRT_GIC_ITS].size); - qemu_fdt_setprop_cell(vms->fdt, nodename, "phandle", vms->msi_phandle); + qemu_fdt_setprop_cell(ms->fdt, nodename, "phandle", vms->msi_phandle); g_free(nodename); } static void fdt_add_v2m_gic_node(VirtMachineState *vms) { + MachineState *ms = MACHINE(vms); char *nodename; nodename = g_strdup_printf("/intc/v2m@%" PRIx64, vms->memmap[VIRT_GIC_V2M].base); - vms->msi_phandle = qemu_fdt_alloc_phandle(vms->fdt); - qemu_fdt_add_subnode(vms->fdt, nodename); - qemu_fdt_setprop_string(vms->fdt, nodename, "compatible", + vms->msi_phandle = qemu_fdt_alloc_phandle(ms->fdt); + qemu_fdt_add_subnode(ms->fdt, nodename); + qemu_fdt_setprop_string(ms->fdt, nodename, "compatible", "arm,gic-v2m-frame"); - qemu_fdt_setprop(vms->fdt, nodename, "msi-controller", NULL, 0); - qemu_fdt_setprop_sized_cells(vms->fdt, nodename, "reg", + qemu_fdt_setprop(ms->fdt, nodename, "msi-controller", NULL, 0); + qemu_fdt_setprop_sized_cells(ms->fdt, nodename, "reg", 2, vms->memmap[VIRT_GIC_V2M].base, 2, vms->memmap[VIRT_GIC_V2M].size); - qemu_fdt_setprop_cell(vms->fdt, nodename, "phandle", vms->msi_phandle); + qemu_fdt_setprop_cell(ms->fdt, nodename, "phandle", vms->msi_phandle); g_free(nodename); } static void fdt_add_gic_node(VirtMachineState *vms) { + MachineState *ms = MACHINE(vms); char *nodename; - vms->gic_phandle = qemu_fdt_alloc_phandle(vms->fdt); - qemu_fdt_setprop_cell(vms->fdt, "/", "interrupt-parent", vms->gic_phandle); + vms->gic_phandle = qemu_fdt_alloc_phandle(ms->fdt); + qemu_fdt_setprop_cell(ms->fdt, "/", "interrupt-parent", vms->gic_phandle); nodename = g_strdup_printf("/intc@%" PRIx64, vms->memmap[VIRT_GIC_DIST].base); - qemu_fdt_add_subnode(vms->fdt, nodename); - qemu_fdt_setprop_cell(vms->fdt, nodename, "#interrupt-cells", 3); - qemu_fdt_setprop(vms->fdt, nodename, "interrupt-controller", NULL, 0); - qemu_fdt_setprop_cell(vms->fdt, nodename, "#address-cells", 0x2); - qemu_fdt_setprop_cell(vms->fdt, nodename, "#size-cells", 0x2); - qemu_fdt_setprop(vms->fdt, nodename, "ranges", NULL, 0); + qemu_fdt_add_subnode(ms->fdt, nodename); + qemu_fdt_setprop_cell(ms->fdt, nodename, "#interrupt-cells", 3); + qemu_fdt_setprop(ms->fdt, nodename, "interrupt-controller", NULL, 0); + qemu_fdt_setprop_cell(ms->fdt, nodename, "#address-cells", 0x2); + qemu_fdt_setprop_cell(ms->fdt, nodename, "#size-cells", 0x2); + qemu_fdt_setprop(ms->fdt, nodename, "ranges", NULL, 0); if (vms->gic_version == VIRT_GIC_VERSION_3) { int nb_redist_regions = virt_gicv3_redist_region_count(vms); - qemu_fdt_setprop_string(vms->fdt, nodename, "compatible", + qemu_fdt_setprop_string(ms->fdt, nodename, "compatible", "arm,gic-v3"); - qemu_fdt_setprop_cell(vms->fdt, nodename, + qemu_fdt_setprop_cell(ms->fdt, nodename, "#redistributor-regions", nb_redist_regions); if (nb_redist_regions == 1) { - qemu_fdt_setprop_sized_cells(vms->fdt, nodename, "reg", + qemu_fdt_setprop_sized_cells(ms->fdt, nodename, "reg", 2, vms->memmap[VIRT_GIC_DIST].base, 2, vms->memmap[VIRT_GIC_DIST].size, 2, vms->memmap[VIRT_GIC_REDIST].base, 2, vms->memmap[VIRT_GIC_REDIST].size); } else { - qemu_fdt_setprop_sized_cells(vms->fdt, nodename, "reg", + qemu_fdt_setprop_sized_cells(ms->fdt, nodename, "reg", 2, vms->memmap[VIRT_GIC_DIST].base, 2, vms->memmap[VIRT_GIC_DIST].size, 2, vms->memmap[VIRT_GIC_REDIST].base, @@ -488,22 +492,22 @@ static void fdt_add_gic_node(VirtMachineState *vms) } if (vms->virt) { - qemu_fdt_setprop_cells(vms->fdt, nodename, "interrupts", + qemu_fdt_setprop_cells(ms->fdt, nodename, "interrupts", GIC_FDT_IRQ_TYPE_PPI, ARCH_GIC_MAINT_IRQ, GIC_FDT_IRQ_FLAGS_LEVEL_HI); } } else { /* 'cortex-a15-gic' means 'GIC v2' */ - qemu_fdt_setprop_string(vms->fdt, nodename, "compatible", + qemu_fdt_setprop_string(ms->fdt, nodename, "compatible", "arm,cortex-a15-gic"); if (!vms->virt) { - qemu_fdt_setprop_sized_cells(vms->fdt, nodename, "reg", + qemu_fdt_setprop_sized_cells(ms->fdt, nodename, "reg", 2, vms->memmap[VIRT_GIC_DIST].base, 2, vms->memmap[VIRT_GIC_DIST].size, 2, vms->memmap[VIRT_GIC_CPU].base, 2, vms->memmap[VIRT_GIC_CPU].size); } else { - qemu_fdt_setprop_sized_cells(vms->fdt, nodename, "reg", + qemu_fdt_setprop_sized_cells(ms->fdt, nodename, "reg", 2, vms->memmap[VIRT_GIC_DIST].base, 2, vms->memmap[VIRT_GIC_DIST].size, 2, vms->memmap[VIRT_GIC_CPU].base, @@ -512,13 +516,13 @@ static void fdt_add_gic_node(VirtMachineState *vms) 2, vms->memmap[VIRT_GIC_HYP].size, 2, vms->memmap[VIRT_GIC_VCPU].base, 2, vms->memmap[VIRT_GIC_VCPU].size); - qemu_fdt_setprop_cells(vms->fdt, nodename, "interrupts", + qemu_fdt_setprop_cells(ms->fdt, nodename, "interrupts", GIC_FDT_IRQ_TYPE_PPI, ARCH_GIC_MAINT_IRQ, GIC_FDT_IRQ_FLAGS_LEVEL_HI); } } - qemu_fdt_setprop_cell(vms->fdt, nodename, "phandle", vms->gic_phandle); + qemu_fdt_setprop_cell(ms->fdt, nodename, "phandle", vms->gic_phandle); g_free(nodename); } @@ -526,6 +530,7 @@ static void fdt_add_pmu_nodes(const VirtMachineState *vms) { ARMCPU *armcpu = ARM_CPU(first_cpu); uint32_t irqflags = GIC_FDT_IRQ_FLAGS_LEVEL_HI; + MachineState *ms = MACHINE(vms); if (!arm_feature(&armcpu->env, ARM_FEATURE_PMU)) { assert(!object_property_get_bool(OBJECT(armcpu), "pmu", NULL)); @@ -538,12 +543,12 @@ static void fdt_add_pmu_nodes(const VirtMachineState *vms) (1 << MACHINE(vms)->smp.cpus) - 1); } - qemu_fdt_add_subnode(vms->fdt, "/pmu"); + qemu_fdt_add_subnode(ms->fdt, "/pmu"); if (arm_feature(&armcpu->env, ARM_FEATURE_V8)) { const char compat[] = "arm,armv8-pmuv3"; - qemu_fdt_setprop(vms->fdt, "/pmu", "compatible", + qemu_fdt_setprop(ms->fdt, "/pmu", "compatible", compat, sizeof(compat)); - qemu_fdt_setprop_cells(vms->fdt, "/pmu", "interrupts", + qemu_fdt_setprop_cells(ms->fdt, "/pmu", "interrupts", GIC_FDT_IRQ_TYPE_PPI, VIRTUAL_PMU_IRQ, irqflags); } } @@ -749,6 +754,7 @@ static void create_uart(const VirtMachineState *vms, int uart, const char clocknames[] = "uartclk\0apb_pclk"; DeviceState *dev = qdev_new(TYPE_PL011); SysBusDevice *s = SYS_BUS_DEVICE(dev); + MachineState *ms = MACHINE(vms); qdev_prop_set_chr(dev, "chardev", chr); sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); @@ -757,28 +763,28 @@ static void create_uart(const VirtMachineState *vms, int uart, sysbus_connect_irq(s, 0, qdev_get_gpio_in(vms->gic, irq)); nodename = g_strdup_printf("/pl011@%" PRIx64, base); - qemu_fdt_add_subnode(vms->fdt, nodename); + qemu_fdt_add_subnode(ms->fdt, nodename); /* Note that we can't use setprop_string because of the embedded NUL */ - qemu_fdt_setprop(vms->fdt, nodename, "compatible", + qemu_fdt_setprop(ms->fdt, nodename, "compatible", compat, sizeof(compat)); - qemu_fdt_setprop_sized_cells(vms->fdt, nodename, "reg", + qemu_fdt_setprop_sized_cells(ms->fdt, nodename, "reg", 2, base, 2, size); - qemu_fdt_setprop_cells(vms->fdt, nodename, "interrupts", + qemu_fdt_setprop_cells(ms->fdt, nodename, "interrupts", GIC_FDT_IRQ_TYPE_SPI, irq, GIC_FDT_IRQ_FLAGS_LEVEL_HI); - qemu_fdt_setprop_cells(vms->fdt, nodename, "clocks", + qemu_fdt_setprop_cells(ms->fdt, nodename, "clocks", vms->clock_phandle, vms->clock_phandle); - qemu_fdt_setprop(vms->fdt, nodename, "clock-names", + qemu_fdt_setprop(ms->fdt, nodename, "clock-names", clocknames, sizeof(clocknames)); if (uart == VIRT_UART) { - qemu_fdt_setprop_string(vms->fdt, "/chosen", "stdout-path", nodename); + qemu_fdt_setprop_string(ms->fdt, "/chosen", "stdout-path", nodename); } else { /* Mark as not usable by the normal world */ - qemu_fdt_setprop_string(vms->fdt, nodename, "status", "disabled"); - qemu_fdt_setprop_string(vms->fdt, nodename, "secure-status", "okay"); + qemu_fdt_setprop_string(ms->fdt, nodename, "status", "disabled"); + qemu_fdt_setprop_string(ms->fdt, nodename, "secure-status", "okay"); - qemu_fdt_setprop_string(vms->fdt, "/secure-chosen", "stdout-path", + qemu_fdt_setprop_string(ms->fdt, "/secure-chosen", "stdout-path", nodename); } @@ -792,19 +798,20 @@ static void create_rtc(const VirtMachineState *vms) hwaddr size = vms->memmap[VIRT_RTC].size; int irq = vms->irqmap[VIRT_RTC]; const char compat[] = "arm,pl031\0arm,primecell"; + MachineState *ms = MACHINE(vms); sysbus_create_simple("pl031", base, qdev_get_gpio_in(vms->gic, irq)); nodename = g_strdup_printf("/pl031@%" PRIx64, base); - qemu_fdt_add_subnode(vms->fdt, nodename); - qemu_fdt_setprop(vms->fdt, nodename, "compatible", compat, sizeof(compat)); - qemu_fdt_setprop_sized_cells(vms->fdt, nodename, "reg", + qemu_fdt_add_subnode(ms->fdt, nodename); + qemu_fdt_setprop(ms->fdt, nodename, "compatible", compat, sizeof(compat)); + qemu_fdt_setprop_sized_cells(ms->fdt, nodename, "reg", 2, base, 2, size); - qemu_fdt_setprop_cells(vms->fdt, nodename, "interrupts", + qemu_fdt_setprop_cells(ms->fdt, nodename, "interrupts", GIC_FDT_IRQ_TYPE_SPI, irq, GIC_FDT_IRQ_FLAGS_LEVEL_HI); - qemu_fdt_setprop_cell(vms->fdt, nodename, "clocks", vms->clock_phandle); - qemu_fdt_setprop_string(vms->fdt, nodename, "clock-names", "apb_pclk"); + qemu_fdt_setprop_cell(ms->fdt, nodename, "clocks", vms->clock_phandle); + qemu_fdt_setprop_string(ms->fdt, nodename, "clock-names", "apb_pclk"); g_free(nodename); } @@ -821,32 +828,30 @@ static void virt_powerdown_req(Notifier *n, void *opaque) } } -static void create_gpio_keys(const VirtMachineState *vms, - DeviceState *pl061_dev, +static void create_gpio_keys(char *fdt, DeviceState *pl061_dev, uint32_t phandle) { gpio_key_dev = sysbus_create_simple("gpio-key", -1, qdev_get_gpio_in(pl061_dev, 3)); - qemu_fdt_add_subnode(vms->fdt, "/gpio-keys"); - qemu_fdt_setprop_string(vms->fdt, "/gpio-keys", "compatible", "gpio-keys"); - qemu_fdt_setprop_cell(vms->fdt, "/gpio-keys", "#size-cells", 0); - qemu_fdt_setprop_cell(vms->fdt, "/gpio-keys", "#address-cells", 1); + qemu_fdt_add_subnode(fdt, "/gpio-keys"); + qemu_fdt_setprop_string(fdt, "/gpio-keys", "compatible", "gpio-keys"); + qemu_fdt_setprop_cell(fdt, "/gpio-keys", "#size-cells", 0); + qemu_fdt_setprop_cell(fdt, "/gpio-keys", "#address-cells", 1); - qemu_fdt_add_subnode(vms->fdt, "/gpio-keys/poweroff"); - qemu_fdt_setprop_string(vms->fdt, "/gpio-keys/poweroff", + qemu_fdt_add_subnode(fdt, "/gpio-keys/poweroff"); + qemu_fdt_setprop_string(fdt, "/gpio-keys/poweroff", "label", "GPIO Key Poweroff"); - qemu_fdt_setprop_cell(vms->fdt, "/gpio-keys/poweroff", "linux,code", + qemu_fdt_setprop_cell(fdt, "/gpio-keys/poweroff", "linux,code", KEY_POWER); - qemu_fdt_setprop_cells(vms->fdt, "/gpio-keys/poweroff", + qemu_fdt_setprop_cells(fdt, "/gpio-keys/poweroff", "gpios", phandle, 3, 0); } #define SECURE_GPIO_POWEROFF 0 #define SECURE_GPIO_RESET 1 -static void create_secure_gpio_pwr(const VirtMachineState *vms, - DeviceState *pl061_dev, +static void create_secure_gpio_pwr(char *fdt, DeviceState *pl061_dev, uint32_t phandle) { DeviceState *gpio_pwr_dev; @@ -860,22 +865,22 @@ static void create_secure_gpio_pwr(const VirtMachineState *vms, qdev_connect_gpio_out(pl061_dev, SECURE_GPIO_POWEROFF, qdev_get_gpio_in_named(gpio_pwr_dev, "shutdown", 0)); - qemu_fdt_add_subnode(vms->fdt, "/gpio-poweroff"); - qemu_fdt_setprop_string(vms->fdt, "/gpio-poweroff", "compatible", + qemu_fdt_add_subnode(fdt, "/gpio-poweroff"); + qemu_fdt_setprop_string(fdt, "/gpio-poweroff", "compatible", "gpio-poweroff"); - qemu_fdt_setprop_cells(vms->fdt, "/gpio-poweroff", + qemu_fdt_setprop_cells(fdt, "/gpio-poweroff", "gpios", phandle, SECURE_GPIO_POWEROFF, 0); - qemu_fdt_setprop_string(vms->fdt, "/gpio-poweroff", "status", "disabled"); - qemu_fdt_setprop_string(vms->fdt, "/gpio-poweroff", "secure-status", + qemu_fdt_setprop_string(fdt, "/gpio-poweroff", "status", "disabled"); + qemu_fdt_setprop_string(fdt, "/gpio-poweroff", "secure-status", "okay"); - qemu_fdt_add_subnode(vms->fdt, "/gpio-restart"); - qemu_fdt_setprop_string(vms->fdt, "/gpio-restart", "compatible", + qemu_fdt_add_subnode(fdt, "/gpio-restart"); + qemu_fdt_setprop_string(fdt, "/gpio-restart", "compatible", "gpio-restart"); - qemu_fdt_setprop_cells(vms->fdt, "/gpio-restart", + qemu_fdt_setprop_cells(fdt, "/gpio-restart", "gpios", phandle, SECURE_GPIO_RESET, 0); - qemu_fdt_setprop_string(vms->fdt, "/gpio-restart", "status", "disabled"); - qemu_fdt_setprop_string(vms->fdt, "/gpio-restart", "secure-status", + qemu_fdt_setprop_string(fdt, "/gpio-restart", "status", "disabled"); + qemu_fdt_setprop_string(fdt, "/gpio-restart", "secure-status", "okay"); } @@ -889,6 +894,7 @@ static void create_gpio_devices(const VirtMachineState *vms, int gpio, int irq = vms->irqmap[gpio]; const char compat[] = "arm,pl061\0arm,primecell"; SysBusDevice *s; + MachineState *ms = MACHINE(vms); pl061_dev = qdev_new("pl061"); s = SYS_BUS_DEVICE(pl061_dev); @@ -896,33 +902,33 @@ static void create_gpio_devices(const VirtMachineState *vms, int gpio, memory_region_add_subregion(mem, base, sysbus_mmio_get_region(s, 0)); sysbus_connect_irq(s, 0, qdev_get_gpio_in(vms->gic, irq)); - uint32_t phandle = qemu_fdt_alloc_phandle(vms->fdt); + uint32_t phandle = qemu_fdt_alloc_phandle(ms->fdt); nodename = g_strdup_printf("/pl061@%" PRIx64, base); - qemu_fdt_add_subnode(vms->fdt, nodename); - qemu_fdt_setprop_sized_cells(vms->fdt, nodename, "reg", + qemu_fdt_add_subnode(ms->fdt, nodename); + qemu_fdt_setprop_sized_cells(ms->fdt, nodename, "reg", 2, base, 2, size); - qemu_fdt_setprop(vms->fdt, nodename, "compatible", compat, sizeof(compat)); - qemu_fdt_setprop_cell(vms->fdt, nodename, "#gpio-cells", 2); - qemu_fdt_setprop(vms->fdt, nodename, "gpio-controller", NULL, 0); - qemu_fdt_setprop_cells(vms->fdt, nodename, "interrupts", + qemu_fdt_setprop(ms->fdt, nodename, "compatible", compat, sizeof(compat)); + qemu_fdt_setprop_cell(ms->fdt, nodename, "#gpio-cells", 2); + qemu_fdt_setprop(ms->fdt, nodename, "gpio-controller", NULL, 0); + qemu_fdt_setprop_cells(ms->fdt, nodename, "interrupts", GIC_FDT_IRQ_TYPE_SPI, irq, GIC_FDT_IRQ_FLAGS_LEVEL_HI); - qemu_fdt_setprop_cell(vms->fdt, nodename, "clocks", vms->clock_phandle); - qemu_fdt_setprop_string(vms->fdt, nodename, "clock-names", "apb_pclk"); - qemu_fdt_setprop_cell(vms->fdt, nodename, "phandle", phandle); + qemu_fdt_setprop_cell(ms->fdt, nodename, "clocks", vms->clock_phandle); + qemu_fdt_setprop_string(ms->fdt, nodename, "clock-names", "apb_pclk"); + qemu_fdt_setprop_cell(ms->fdt, nodename, "phandle", phandle); if (gpio != VIRT_GPIO) { /* Mark as not usable by the normal world */ - qemu_fdt_setprop_string(vms->fdt, nodename, "status", "disabled"); - qemu_fdt_setprop_string(vms->fdt, nodename, "secure-status", "okay"); + qemu_fdt_setprop_string(ms->fdt, nodename, "status", "disabled"); + qemu_fdt_setprop_string(ms->fdt, nodename, "secure-status", "okay"); } g_free(nodename); /* Child gpio devices */ if (gpio == VIRT_GPIO) { - create_gpio_keys(vms, pl061_dev, phandle); + create_gpio_keys(ms->fdt, pl061_dev, phandle); } else { - create_secure_gpio_pwr(vms, pl061_dev, phandle); + create_secure_gpio_pwr(ms->fdt, pl061_dev, phandle); } } @@ -930,6 +936,7 @@ static void create_virtio_devices(const VirtMachineState *vms) { int i; hwaddr size = vms->memmap[VIRT_MMIO].size; + MachineState *ms = MACHINE(vms); /* We create the transports in forwards order. Since qbus_realize() * prepends (not appends) new child buses, the incrementing loop below will @@ -979,15 +986,15 @@ static void create_virtio_devices(const VirtMachineState *vms) hwaddr base = vms->memmap[VIRT_MMIO].base + i * size; nodename = g_strdup_printf("/virtio_mmio@%" PRIx64, base); - qemu_fdt_add_subnode(vms->fdt, nodename); - qemu_fdt_setprop_string(vms->fdt, nodename, + qemu_fdt_add_subnode(ms->fdt, nodename); + qemu_fdt_setprop_string(ms->fdt, nodename, "compatible", "virtio,mmio"); - qemu_fdt_setprop_sized_cells(vms->fdt, nodename, "reg", + qemu_fdt_setprop_sized_cells(ms->fdt, nodename, "reg", 2, base, 2, size); - qemu_fdt_setprop_cells(vms->fdt, nodename, "interrupts", + qemu_fdt_setprop_cells(ms->fdt, nodename, "interrupts", GIC_FDT_IRQ_TYPE_SPI, irq, GIC_FDT_IRQ_FLAGS_EDGE_LO_HI); - qemu_fdt_setprop(vms->fdt, nodename, "dma-coherent", NULL, 0); + qemu_fdt_setprop(ms->fdt, nodename, "dma-coherent", NULL, 0); g_free(nodename); } } @@ -1068,17 +1075,18 @@ static void virt_flash_fdt(VirtMachineState *vms, { hwaddr flashsize = vms->memmap[VIRT_FLASH].size / 2; hwaddr flashbase = vms->memmap[VIRT_FLASH].base; + MachineState *ms = MACHINE(vms); char *nodename; if (sysmem == secure_sysmem) { /* Report both flash devices as a single node in the DT */ nodename = g_strdup_printf("/flash@%" PRIx64, flashbase); - qemu_fdt_add_subnode(vms->fdt, nodename); - qemu_fdt_setprop_string(vms->fdt, nodename, "compatible", "cfi-flash"); - qemu_fdt_setprop_sized_cells(vms->fdt, nodename, "reg", + qemu_fdt_add_subnode(ms->fdt, nodename); + qemu_fdt_setprop_string(ms->fdt, nodename, "compatible", "cfi-flash"); + qemu_fdt_setprop_sized_cells(ms->fdt, nodename, "reg", 2, flashbase, 2, flashsize, 2, flashbase + flashsize, 2, flashsize); - qemu_fdt_setprop_cell(vms->fdt, nodename, "bank-width", 4); + qemu_fdt_setprop_cell(ms->fdt, nodename, "bank-width", 4); g_free(nodename); } else { /* @@ -1086,21 +1094,21 @@ static void virt_flash_fdt(VirtMachineState *vms, * only visible to the secure world. */ nodename = g_strdup_printf("/secflash@%" PRIx64, flashbase); - qemu_fdt_add_subnode(vms->fdt, nodename); - qemu_fdt_setprop_string(vms->fdt, nodename, "compatible", "cfi-flash"); - qemu_fdt_setprop_sized_cells(vms->fdt, nodename, "reg", + qemu_fdt_add_subnode(ms->fdt, nodename); + qemu_fdt_setprop_string(ms->fdt, nodename, "compatible", "cfi-flash"); + qemu_fdt_setprop_sized_cells(ms->fdt, nodename, "reg", 2, flashbase, 2, flashsize); - qemu_fdt_setprop_cell(vms->fdt, nodename, "bank-width", 4); - qemu_fdt_setprop_string(vms->fdt, nodename, "status", "disabled"); - qemu_fdt_setprop_string(vms->fdt, nodename, "secure-status", "okay"); + qemu_fdt_setprop_cell(ms->fdt, nodename, "bank-width", 4); + qemu_fdt_setprop_string(ms->fdt, nodename, "status", "disabled"); + qemu_fdt_setprop_string(ms->fdt, nodename, "secure-status", "okay"); g_free(nodename); nodename = g_strdup_printf("/flash@%" PRIx64, flashbase); - qemu_fdt_add_subnode(vms->fdt, nodename); - qemu_fdt_setprop_string(vms->fdt, nodename, "compatible", "cfi-flash"); - qemu_fdt_setprop_sized_cells(vms->fdt, nodename, "reg", + qemu_fdt_add_subnode(ms->fdt, nodename); + qemu_fdt_setprop_string(ms->fdt, nodename, "compatible", "cfi-flash"); + qemu_fdt_setprop_sized_cells(ms->fdt, nodename, "reg", 2, flashbase + flashsize, 2, flashsize); - qemu_fdt_setprop_cell(vms->fdt, nodename, "bank-width", 4); + qemu_fdt_setprop_cell(ms->fdt, nodename, "bank-width", 4); g_free(nodename); } } @@ -1167,17 +1175,17 @@ static FWCfgState *create_fw_cfg(const VirtMachineState *vms, AddressSpace *as) fw_cfg_add_i16(fw_cfg, FW_CFG_NB_CPUS, (uint16_t)ms->smp.cpus); nodename = g_strdup_printf("/fw-cfg@%" PRIx64, base); - qemu_fdt_add_subnode(vms->fdt, nodename); - qemu_fdt_setprop_string(vms->fdt, nodename, + qemu_fdt_add_subnode(ms->fdt, nodename); + qemu_fdt_setprop_string(ms->fdt, nodename, "compatible", "qemu,fw-cfg-mmio"); - qemu_fdt_setprop_sized_cells(vms->fdt, nodename, "reg", + qemu_fdt_setprop_sized_cells(ms->fdt, nodename, "reg", 2, base, 2, size); - qemu_fdt_setprop(vms->fdt, nodename, "dma-coherent", NULL, 0); + qemu_fdt_setprop(ms->fdt, nodename, "dma-coherent", NULL, 0); g_free(nodename); return fw_cfg; } -static void create_pcie_irq_map(const VirtMachineState *vms, +static void create_pcie_irq_map(const MachineState *ms, uint32_t gic_phandle, int first_irq, const char *nodename) { @@ -1205,10 +1213,10 @@ static void create_pcie_irq_map(const VirtMachineState *vms, } } - qemu_fdt_setprop(vms->fdt, nodename, "interrupt-map", + qemu_fdt_setprop(ms->fdt, nodename, "interrupt-map", full_irq_map, sizeof(full_irq_map)); - qemu_fdt_setprop_cells(vms->fdt, nodename, "interrupt-map-mask", + qemu_fdt_setprop_cells(ms->fdt, nodename, "interrupt-map-mask", cpu_to_be16(PCI_DEVFN(3, 0)), /* Slot 3 */ 0, 0, 0x7 /* PCI irq */); @@ -1225,6 +1233,7 @@ static void create_smmu(const VirtMachineState *vms, hwaddr size = vms->memmap[VIRT_SMMU].size; const char irq_names[] = "eventq\0priq\0cmdq-sync\0gerror"; DeviceState *dev; + MachineState *ms = MACHINE(vms); if (vms->iommu != VIRT_IOMMU_SMMUV3 || !vms->iommu_phandle) { return; @@ -1242,26 +1251,26 @@ static void create_smmu(const VirtMachineState *vms, } node = g_strdup_printf("/smmuv3@%" PRIx64, base); - qemu_fdt_add_subnode(vms->fdt, node); - qemu_fdt_setprop(vms->fdt, node, "compatible", compat, sizeof(compat)); - qemu_fdt_setprop_sized_cells(vms->fdt, node, "reg", 2, base, 2, size); + qemu_fdt_add_subnode(ms->fdt, node); + qemu_fdt_setprop(ms->fdt, node, "compatible", compat, sizeof(compat)); + qemu_fdt_setprop_sized_cells(ms->fdt, node, "reg", 2, base, 2, size); - qemu_fdt_setprop_cells(vms->fdt, node, "interrupts", + qemu_fdt_setprop_cells(ms->fdt, node, "interrupts", GIC_FDT_IRQ_TYPE_SPI, irq , GIC_FDT_IRQ_FLAGS_EDGE_LO_HI, GIC_FDT_IRQ_TYPE_SPI, irq + 1, GIC_FDT_IRQ_FLAGS_EDGE_LO_HI, GIC_FDT_IRQ_TYPE_SPI, irq + 2, GIC_FDT_IRQ_FLAGS_EDGE_LO_HI, GIC_FDT_IRQ_TYPE_SPI, irq + 3, GIC_FDT_IRQ_FLAGS_EDGE_LO_HI); - qemu_fdt_setprop(vms->fdt, node, "interrupt-names", irq_names, + qemu_fdt_setprop(ms->fdt, node, "interrupt-names", irq_names, sizeof(irq_names)); - qemu_fdt_setprop_cell(vms->fdt, node, "clocks", vms->clock_phandle); - qemu_fdt_setprop_string(vms->fdt, node, "clock-names", "apb_pclk"); - qemu_fdt_setprop(vms->fdt, node, "dma-coherent", NULL, 0); + qemu_fdt_setprop_cell(ms->fdt, node, "clocks", vms->clock_phandle); + qemu_fdt_setprop_string(ms->fdt, node, "clock-names", "apb_pclk"); + qemu_fdt_setprop(ms->fdt, node, "dma-coherent", NULL, 0); - qemu_fdt_setprop_cell(vms->fdt, node, "#iommu-cells", 1); + qemu_fdt_setprop_cell(ms->fdt, node, "#iommu-cells", 1); - qemu_fdt_setprop_cell(vms->fdt, node, "phandle", vms->iommu_phandle); + qemu_fdt_setprop_cell(ms->fdt, node, "phandle", vms->iommu_phandle); g_free(node); } @@ -1269,22 +1278,23 @@ static void create_virtio_iommu_dt_bindings(VirtMachineState *vms) { const char compat[] = "virtio,pci-iommu"; uint16_t bdf = vms->virtio_iommu_bdf; + MachineState *ms = MACHINE(vms); char *node; - vms->iommu_phandle = qemu_fdt_alloc_phandle(vms->fdt); + vms->iommu_phandle = qemu_fdt_alloc_phandle(ms->fdt); node = g_strdup_printf("%s/virtio_iommu@%d", vms->pciehb_nodename, bdf); - qemu_fdt_add_subnode(vms->fdt, node); - qemu_fdt_setprop(vms->fdt, node, "compatible", compat, sizeof(compat)); - qemu_fdt_setprop_sized_cells(vms->fdt, node, "reg", + qemu_fdt_add_subnode(ms->fdt, node); + qemu_fdt_setprop(ms->fdt, node, "compatible", compat, sizeof(compat)); + qemu_fdt_setprop_sized_cells(ms->fdt, node, "reg", 1, bdf << 8, 1, 0, 1, 0, 1, 0, 1, 0); - qemu_fdt_setprop_cell(vms->fdt, node, "#iommu-cells", 1); - qemu_fdt_setprop_cell(vms->fdt, node, "phandle", vms->iommu_phandle); + qemu_fdt_setprop_cell(ms->fdt, node, "#iommu-cells", 1); + qemu_fdt_setprop_cell(ms->fdt, node, "phandle", vms->iommu_phandle); g_free(node); - qemu_fdt_setprop_cells(vms->fdt, vms->pciehb_nodename, "iommu-map", + qemu_fdt_setprop_cells(ms->fdt, vms->pciehb_nodename, "iommu-map", 0x0, vms->iommu_phandle, 0x0, bdf, bdf + 1, vms->iommu_phandle, bdf + 1, 0xffff - bdf); } @@ -1309,6 +1319,7 @@ static void create_pcie(VirtMachineState *vms) char *nodename; int i, ecam_id; PCIHostState *pci; + MachineState *ms = MACHINE(vms); dev = qdev_new(TYPE_GPEX_HOST); sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); @@ -1369,27 +1380,27 @@ static void create_pcie(VirtMachineState *vms) } nodename = vms->pciehb_nodename = g_strdup_printf("/pcie@%" PRIx64, base); - qemu_fdt_add_subnode(vms->fdt, nodename); - qemu_fdt_setprop_string(vms->fdt, nodename, + qemu_fdt_add_subnode(ms->fdt, nodename); + qemu_fdt_setprop_string(ms->fdt, nodename, "compatible", "pci-host-ecam-generic"); - qemu_fdt_setprop_string(vms->fdt, nodename, "device_type", "pci"); - qemu_fdt_setprop_cell(vms->fdt, nodename, "#address-cells", 3); - qemu_fdt_setprop_cell(vms->fdt, nodename, "#size-cells", 2); - qemu_fdt_setprop_cell(vms->fdt, nodename, "linux,pci-domain", 0); - qemu_fdt_setprop_cells(vms->fdt, nodename, "bus-range", 0, + qemu_fdt_setprop_string(ms->fdt, nodename, "device_type", "pci"); + qemu_fdt_setprop_cell(ms->fdt, nodename, "#address-cells", 3); + qemu_fdt_setprop_cell(ms->fdt, nodename, "#size-cells", 2); + qemu_fdt_setprop_cell(ms->fdt, nodename, "linux,pci-domain", 0); + qemu_fdt_setprop_cells(ms->fdt, nodename, "bus-range", 0, nr_pcie_buses - 1); - qemu_fdt_setprop(vms->fdt, nodename, "dma-coherent", NULL, 0); + qemu_fdt_setprop(ms->fdt, nodename, "dma-coherent", NULL, 0); if (vms->msi_phandle) { - qemu_fdt_setprop_cells(vms->fdt, nodename, "msi-parent", + qemu_fdt_setprop_cells(ms->fdt, nodename, "msi-parent", vms->msi_phandle); } - qemu_fdt_setprop_sized_cells(vms->fdt, nodename, "reg", + qemu_fdt_setprop_sized_cells(ms->fdt, nodename, "reg", 2, base_ecam, 2, size_ecam); if (vms->highmem) { - qemu_fdt_setprop_sized_cells(vms->fdt, nodename, "ranges", + qemu_fdt_setprop_sized_cells(ms->fdt, nodename, "ranges", 1, FDT_PCI_RANGE_IOPORT, 2, 0, 2, base_pio, 2, size_pio, 1, FDT_PCI_RANGE_MMIO, 2, base_mmio, @@ -1398,23 +1409,23 @@ static void create_pcie(VirtMachineState *vms) 2, base_mmio_high, 2, base_mmio_high, 2, size_mmio_high); } else { - qemu_fdt_setprop_sized_cells(vms->fdt, nodename, "ranges", + qemu_fdt_setprop_sized_cells(ms->fdt, nodename, "ranges", 1, FDT_PCI_RANGE_IOPORT, 2, 0, 2, base_pio, 2, size_pio, 1, FDT_PCI_RANGE_MMIO, 2, base_mmio, 2, base_mmio, 2, size_mmio); } - qemu_fdt_setprop_cell(vms->fdt, nodename, "#interrupt-cells", 1); - create_pcie_irq_map(vms, vms->gic_phandle, irq, nodename); + qemu_fdt_setprop_cell(ms->fdt, nodename, "#interrupt-cells", 1); + create_pcie_irq_map(ms, vms->gic_phandle, irq, nodename); if (vms->iommu) { - vms->iommu_phandle = qemu_fdt_alloc_phandle(vms->fdt); + vms->iommu_phandle = qemu_fdt_alloc_phandle(ms->fdt); switch (vms->iommu) { case VIRT_IOMMU_SMMUV3: create_smmu(vms, vms->bus); - qemu_fdt_setprop_cells(vms->fdt, nodename, "iommu-map", + qemu_fdt_setprop_cells(ms->fdt, nodename, "iommu-map", 0x0, vms->iommu_phandle, 0x0, 0x10000); break; default: @@ -1466,17 +1477,18 @@ static void create_secure_ram(VirtMachineState *vms, char *nodename; hwaddr base = vms->memmap[VIRT_SECURE_MEM].base; hwaddr size = vms->memmap[VIRT_SECURE_MEM].size; + MachineState *ms = MACHINE(vms); memory_region_init_ram(secram, NULL, "virt.secure-ram", size, &error_fatal); memory_region_add_subregion(secure_sysmem, base, secram); nodename = g_strdup_printf("/secram@%" PRIx64, base); - qemu_fdt_add_subnode(vms->fdt, nodename); - qemu_fdt_setprop_string(vms->fdt, nodename, "device_type", "memory"); - qemu_fdt_setprop_sized_cells(vms->fdt, nodename, "reg", 2, base, 2, size); - qemu_fdt_setprop_string(vms->fdt, nodename, "status", "disabled"); - qemu_fdt_setprop_string(vms->fdt, nodename, "secure-status", "okay"); + qemu_fdt_add_subnode(ms->fdt, nodename); + qemu_fdt_setprop_string(ms->fdt, nodename, "device_type", "memory"); + qemu_fdt_setprop_sized_cells(ms->fdt, nodename, "reg", 2, base, 2, size); + qemu_fdt_setprop_string(ms->fdt, nodename, "status", "disabled"); + qemu_fdt_setprop_string(ms->fdt, nodename, "secure-status", "okay"); if (secure_tag_sysmem) { create_tag_ram(secure_tag_sysmem, base, size, "mach-virt.secure-tag"); @@ -1489,9 +1501,11 @@ static void *machvirt_dtb(const struct arm_boot_info *binfo, int *fdt_size) { const VirtMachineState *board = container_of(binfo, VirtMachineState, bootinfo); + MachineState *ms = MACHINE(board); + *fdt_size = board->fdt_size; - return board->fdt; + return ms->fdt; } static void virt_build_smbios(VirtMachineState *vms) @@ -1539,7 +1553,7 @@ void virt_machine_done(Notifier *notifier, void *data) * while qemu takes charge of the qom stuff. */ if (info->dtb_filename == NULL) { - platform_bus_add_all_fdt_nodes(vms->fdt, "/intc", + platform_bus_add_all_fdt_nodes(ms->fdt, "/intc", vms->memmap[VIRT_PLATFORM_BUS].base, vms->memmap[VIRT_PLATFORM_BUS].size, vms->irqmap[VIRT_PLATFORM_BUS]); diff --git a/hw/arm/xlnx-zcu102.c b/hw/arm/xlnx-zcu102.c index c9713638c5..a9db25eb99 100644 --- a/hw/arm/xlnx-zcu102.c +++ b/hw/arm/xlnx-zcu102.c @@ -22,7 +22,6 @@ #include "hw/boards.h" #include "qemu/error-report.h" #include "qemu/log.h" -#include "sysemu/qtest.h" #include "sysemu/device_tree.h" #include "qom/object.h" #include "net/can_emu.h" diff --git a/hw/arm/xlnx-zynqmp.c b/hw/arm/xlnx-zynqmp.c index 46030c1ef8..7f01284a5c 100644 --- a/hw/arm/xlnx-zynqmp.c +++ b/hw/arm/xlnx-zynqmp.c @@ -50,6 +50,7 @@ #define QSPI_ADDR 0xff0f0000 #define LQSPI_ADDR 0xc0000000 #define QSPI_IRQ 15 +#define QSPI_DMA_ADDR 0xff0f0800 #define DP_ADDR 0xfd4a0000 #define DP_IRQ 113 @@ -284,6 +285,8 @@ static void xlnx_zynqmp_init(Object *obj) for (i = 0; i < XLNX_ZYNQMP_NUM_ADMA_CH; i++) { object_initialize_child(obj, "adma[*]", &s->adma[i], TYPE_XLNX_ZDMA); } + + object_initialize_child(obj, "qspi-dma", &s->qspi_dma, TYPE_XLNX_CSU_DMA); } static void xlnx_zynqmp_realize(DeviceState *dev, Error **errp) @@ -301,11 +304,13 @@ static void xlnx_zynqmp_realize(DeviceState *dev, Error **errp) ram_size = memory_region_size(s->ddr_ram); - /* Create the DDR Memory Regions. User friendly checks should happen at + /* + * Create the DDR Memory Regions. User friendly checks should happen at * the board level */ if (ram_size > XLNX_ZYNQMP_MAX_LOW_RAM_SIZE) { - /* The RAM size is above the maximum available for the low DDR. + /* + * The RAM size is above the maximum available for the low DDR. * Create the high DDR memory region as well. */ assert(ram_size <= XLNX_ZYNQMP_MAX_RAM_SIZE); @@ -521,7 +526,8 @@ static void xlnx_zynqmp_realize(DeviceState *dev, Error **errp) SysBusDevice *sbd = SYS_BUS_DEVICE(&s->sdhci[i]); Object *sdhci = OBJECT(&s->sdhci[i]); - /* Compatible with: + /* + * Compatible with: * - SD Host Controller Specification Version 3.00 * - SDIO Specification Version 3.0 * - eMMC Specification Version 4.51 @@ -635,6 +641,15 @@ static void xlnx_zynqmp_realize(DeviceState *dev, Error **errp) sysbus_connect_irq(SYS_BUS_DEVICE(&s->adma[i]), 0, gic_spi[adma_ch_intr[i]]); } + + if (!sysbus_realize(SYS_BUS_DEVICE(&s->qspi_dma), errp)) { + return; + } + + sysbus_mmio_map(SYS_BUS_DEVICE(&s->qspi_dma), 0, QSPI_DMA_ADDR); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->qspi_dma), 0, gic_spi[QSPI_IRQ]); + object_property_set_link(OBJECT(&s->qspi), "stream-connected-dma", + OBJECT(&s->qspi_dma), errp); } static Property xlnx_zynqmp_props[] = { diff --git a/hw/arm/z2.c b/hw/arm/z2.c index 308c4da956..5099bd8380 100644 --- a/hw/arm/z2.c +++ b/hw/arm/z2.c @@ -24,7 +24,6 @@ #include "hw/audio/wm8750.h" #include "audio/audio.h" #include "exec/address-spaces.h" -#include "sysemu/qtest.h" #include "cpu.h" #include "qom/object.h" diff --git a/hw/block/meson.build b/hw/block/meson.build index 4bf994c64f..5492829155 100644 --- a/hw/block/meson.build +++ b/hw/block/meson.build @@ -13,7 +13,7 @@ softmmu_ss.add(when: 'CONFIG_SSI_M25P80', if_true: files('m25p80.c')) softmmu_ss.add(when: 'CONFIG_SWIM', if_true: files('swim.c')) softmmu_ss.add(when: 'CONFIG_XEN', if_true: files('xen-block.c')) softmmu_ss.add(when: 'CONFIG_TC58128', if_true: files('tc58128.c')) -softmmu_ss.add(when: 'CONFIG_NVME_PCI', if_true: files('nvme.c', 'nvme-ns.c')) +softmmu_ss.add(when: 'CONFIG_NVME_PCI', if_true: files('nvme.c', 'nvme-ns.c', 'nvme-subsys.c')) specific_ss.add(when: 'CONFIG_VIRTIO_BLK', if_true: files('virtio-blk.c')) specific_ss.add(when: 'CONFIG_VHOST_USER_BLK', if_true: files('vhost-user-blk.c')) diff --git a/hw/block/nvme-ns.c b/hw/block/nvme-ns.c index 93ac6e107a..eda6a0c003 100644 --- a/hw/block/nvme-ns.c +++ b/hw/block/nvme-ns.c @@ -63,6 +63,15 @@ static int nvme_ns_init(NvmeNamespace *ns, Error **errp) id_ns->npda = id_ns->npdg = npdg - 1; + if (nvme_ns_shared(ns)) { + id_ns->nmic |= NVME_NMIC_NS_SHARED; + } + + /* simple copy */ + id_ns->mssrl = cpu_to_le16(ns->params.mssrl); + id_ns->mcl = cpu_to_le32(ns->params.mcl); + id_ns->msrc = ns->params.msrc; + return 0; } @@ -154,6 +163,18 @@ static int nvme_ns_zoned_check_calc_geometry(NvmeNamespace *ns, Error **errp) return -1; } + if (ns->params.max_active_zones) { + if (ns->params.max_open_zones > ns->params.max_active_zones) { + error_setg(errp, "max_open_zones (%u) exceeds max_active_zones (%u)", + ns->params.max_open_zones, ns->params.max_active_zones); + return -1; + } + + if (!ns->params.max_open_zones) { + ns->params.max_open_zones = ns->params.max_active_zones; + } + } + if (ns->params.zd_extension_size) { if (ns->params.zd_extension_size & 0x3f) { error_setg(errp, @@ -363,16 +384,27 @@ static void nvme_ns_realize(DeviceState *dev, Error **errp) return; } - if (nvme_register_namespace(n, ns, errp)) { - return; + if (ns->subsys) { + if (nvme_subsys_register_ns(ns, errp)) { + return; + } + } else { + if (nvme_register_namespace(n, ns, errp)) { + return; + } } - } static Property nvme_ns_props[] = { DEFINE_BLOCK_PROPERTIES(NvmeNamespace, blkconf), + DEFINE_PROP_LINK("subsys", NvmeNamespace, subsys, TYPE_NVME_SUBSYS, + NvmeSubsystem *), + DEFINE_PROP_BOOL("detached", NvmeNamespace, params.detached, false), DEFINE_PROP_UINT32("nsid", NvmeNamespace, params.nsid, 0), DEFINE_PROP_UUID("uuid", NvmeNamespace, params.uuid), + DEFINE_PROP_UINT16("mssrl", NvmeNamespace, params.mssrl, 128), + DEFINE_PROP_UINT32("mcl", NvmeNamespace, params.mcl, 128), + DEFINE_PROP_UINT8("msrc", NvmeNamespace, params.msrc, 127), DEFINE_PROP_BOOL("zoned", NvmeNamespace, params.zoned, false), DEFINE_PROP_SIZE("zoned.zone_size", NvmeNamespace, params.zone_size_bs, NVME_DEFAULT_ZONE_SIZE), diff --git a/hw/block/nvme-ns.h b/hw/block/nvme-ns.h index 293ac990e3..318d3aebe1 100644 --- a/hw/block/nvme-ns.h +++ b/hw/block/nvme-ns.h @@ -26,9 +26,14 @@ typedef struct NvmeZone { } NvmeZone; typedef struct NvmeNamespaceParams { + bool detached; uint32_t nsid; QemuUUID uuid; + uint16_t mssrl; + uint32_t mcl; + uint8_t msrc; + bool zoned; bool cross_zone_read; uint64_t zone_size_bs; @@ -47,6 +52,9 @@ typedef struct NvmeNamespace { const uint32_t *iocs; uint8_t csi; + NvmeSubsystem *subsys; + QTAILQ_ENTRY(NvmeNamespace) entry; + NvmeIdNsZoned *id_ns_zoned; NvmeZone *zone_array; QTAILQ_HEAD(, NvmeZone) exp_open_zones; @@ -77,6 +85,11 @@ static inline uint32_t nvme_nsid(NvmeNamespace *ns) return -1; } +static inline bool nvme_ns_shared(NvmeNamespace *ns) +{ + return !!ns->subsys; +} + static inline NvmeLBAF *nvme_ns_lbaf(NvmeNamespace *ns) { NvmeIdNs *id_ns = &ns->id_ns; diff --git a/hw/block/nvme-subsys.c b/hw/block/nvme-subsys.c new file mode 100644 index 0000000000..af4804a819 --- /dev/null +++ b/hw/block/nvme-subsys.c @@ -0,0 +1,116 @@ +/* + * QEMU NVM Express Subsystem: nvme-subsys + * + * Copyright (c) 2021 Minwoo Im <minwoo.im.dev@gmail.com> + * + * This code is licensed under the GNU GPL v2. Refer COPYING. + */ + +#include "qemu/units.h" +#include "qemu/osdep.h" +#include "qemu/uuid.h" +#include "qemu/iov.h" +#include "qemu/cutils.h" +#include "qapi/error.h" +#include "hw/qdev-properties.h" +#include "hw/qdev-core.h" +#include "hw/block/block.h" +#include "block/aio.h" +#include "block/accounting.h" +#include "sysemu/sysemu.h" +#include "hw/pci/pci.h" +#include "nvme.h" +#include "nvme-subsys.h" + +int nvme_subsys_register_ctrl(NvmeCtrl *n, Error **errp) +{ + NvmeSubsystem *subsys = n->subsys; + int cntlid; + + for (cntlid = 0; cntlid < ARRAY_SIZE(subsys->ctrls); cntlid++) { + if (!subsys->ctrls[cntlid]) { + break; + } + } + + if (cntlid == ARRAY_SIZE(subsys->ctrls)) { + error_setg(errp, "no more free controller id"); + return -1; + } + + subsys->ctrls[cntlid] = n; + + return cntlid; +} + +int nvme_subsys_register_ns(NvmeNamespace *ns, Error **errp) +{ + NvmeSubsystem *subsys = ns->subsys; + NvmeCtrl *n; + int i; + + if (subsys->namespaces[nvme_nsid(ns)]) { + error_setg(errp, "namespace %d already registerd to subsy %s", + nvme_nsid(ns), subsys->parent_obj.id); + return -1; + } + + subsys->namespaces[nvme_nsid(ns)] = ns; + + for (i = 0; i < ARRAY_SIZE(subsys->ctrls); i++) { + n = subsys->ctrls[i]; + + if (n && nvme_register_namespace(n, ns, errp)) { + return -1; + } + } + + return 0; +} + +static void nvme_subsys_setup(NvmeSubsystem *subsys) +{ + const char *nqn = subsys->params.nqn ? + subsys->params.nqn : subsys->parent_obj.id; + + snprintf((char *)subsys->subnqn, sizeof(subsys->subnqn), + "nqn.2019-08.org.qemu:%s", nqn); +} + +static void nvme_subsys_realize(DeviceState *dev, Error **errp) +{ + NvmeSubsystem *subsys = NVME_SUBSYS(dev); + + nvme_subsys_setup(subsys); +} + +static Property nvme_subsystem_props[] = { + DEFINE_PROP_STRING("nqn", NvmeSubsystem, params.nqn), + DEFINE_PROP_END_OF_LIST(), +}; + +static void nvme_subsys_class_init(ObjectClass *oc, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + set_bit(DEVICE_CATEGORY_STORAGE, dc->categories); + + dc->realize = nvme_subsys_realize; + dc->desc = "Virtual NVMe subsystem"; + + device_class_set_props(dc, nvme_subsystem_props); +} + +static const TypeInfo nvme_subsys_info = { + .name = TYPE_NVME_SUBSYS, + .parent = TYPE_DEVICE, + .class_init = nvme_subsys_class_init, + .instance_size = sizeof(NvmeSubsystem), +}; + +static void nvme_subsys_register_types(void) +{ + type_register_static(&nvme_subsys_info); +} + +type_init(nvme_subsys_register_types) diff --git a/hw/block/nvme-subsys.h b/hw/block/nvme-subsys.h new file mode 100644 index 0000000000..fb66ae752a --- /dev/null +++ b/hw/block/nvme-subsys.h @@ -0,0 +1,60 @@ +/* + * QEMU NVM Express Subsystem: nvme-subsys + * + * Copyright (c) 2021 Minwoo Im <minwoo.im.dev@gmail.com> + * + * This code is licensed under the GNU GPL v2. Refer COPYING. + */ + +#ifndef NVME_SUBSYS_H +#define NVME_SUBSYS_H + +#define TYPE_NVME_SUBSYS "nvme-subsys" +#define NVME_SUBSYS(obj) \ + OBJECT_CHECK(NvmeSubsystem, (obj), TYPE_NVME_SUBSYS) + +#define NVME_SUBSYS_MAX_CTRLS 32 +#define NVME_SUBSYS_MAX_NAMESPACES 256 + +typedef struct NvmeCtrl NvmeCtrl; +typedef struct NvmeNamespace NvmeNamespace; +typedef struct NvmeSubsystem { + DeviceState parent_obj; + uint8_t subnqn[256]; + + NvmeCtrl *ctrls[NVME_SUBSYS_MAX_CTRLS]; + /* Allocated namespaces for this subsystem */ + NvmeNamespace *namespaces[NVME_SUBSYS_MAX_NAMESPACES + 1]; + + struct { + char *nqn; + } params; +} NvmeSubsystem; + +int nvme_subsys_register_ctrl(NvmeCtrl *n, Error **errp); +int nvme_subsys_register_ns(NvmeNamespace *ns, Error **errp); + +static inline NvmeCtrl *nvme_subsys_ctrl(NvmeSubsystem *subsys, + uint32_t cntlid) +{ + if (!subsys) { + return NULL; + } + + return subsys->ctrls[cntlid]; +} + +/* + * Return allocated namespace of the specified nsid in the subsystem. + */ +static inline NvmeNamespace *nvme_subsys_ns(NvmeSubsystem *subsys, + uint32_t nsid) +{ + if (!subsys) { + return NULL; + } + + return subsys->namespaces[nsid]; +} + +#endif /* NVME_SUBSYS_H */ diff --git a/hw/block/nvme.c b/hw/block/nvme.c index fb83636abd..d439e44db8 100644 --- a/hw/block/nvme.c +++ b/hw/block/nvme.c @@ -17,14 +17,17 @@ /** * Usage: add options: * -drive file=<file>,if=none,id=<drive_id> + * -device nvme-subsys,id=<subsys_id>,nqn=<nqn_id> * -device nvme,serial=<serial>,id=<bus_name>, \ * cmb_size_mb=<cmb_size_mb[optional]>, \ * [pmrdev=<mem_backend_file_id>,] \ * max_ioqpairs=<N[optional]>, \ - * aerl=<N[optional]>, aer_max_queued=<N[optional]>, \ - * mdts=<N[optional]>,zoned.append_size_limit=<N[optional]> \ + * aerl=<N[optional]>,aer_max_queued=<N[optional]>, \ + * mdts=<N[optional]>,zoned.zasl=<N[optional]>, \ + * subsys=<subsys_id> * -device nvme-ns,drive=<drive_id>,bus=<bus_name>,nsid=<nsid>,\ - * zoned=<true|false[optional]> + * zoned=<true|false[optional]>, \ + * subsys=<subsys_id>,detached=<true|false[optional]> * * Note cmb_size_mb denotes size of CMB in MB. CMB is assumed to be at * offset 0 in BAR2 and supports only WDS, RDS and SQS for now. By default, the @@ -38,9 +41,27 @@ * * The PMR will use BAR 4/5 exclusively. * + * To place controller(s) and namespace(s) to a subsystem, then provide + * nvme-subsys device as above. + * + * nvme subsystem device parameters + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * - `nqn` + * This parameter provides the `<nqn_id>` part of the string + * `nqn.2019-08.org.qemu:<nqn_id>` which will be reported in the SUBNQN field + * of subsystem controllers. Note that `<nqn_id>` should be unique per + * subsystem, but this is not enforced by QEMU. If not specified, it will + * default to the value of the `id` parameter (`<subsys_id>`). * * nvme device parameters * ~~~~~~~~~~~~~~~~~~~~~~ + * - `subsys` + * Specifying this parameter attaches the controller to the subsystem and + * the SUBNQN field in the controller will report the NQN of the subsystem + * device. This also enables multi controller capability represented in + * Identify Controller data structure in CMIC (Controller Multi-path I/O and + * Namesapce Sharing Capabilities). + * * - `aerl` * The Asynchronous Event Request Limit (AERL). Indicates the maximum number * of concurrently outstanding Asynchronous Event Request commands support @@ -51,13 +72,31 @@ * completion when there are no outstanding AERs. When the maximum number of * enqueued events are reached, subsequent events will be dropped. * - * - `zoned.append_size_limit` - * The maximum I/O size in bytes that is allowed in Zone Append command. - * The default is 128KiB. Since internally this this value is maintained as - * ZASL = log2(<maximum append size> / <page size>), some values assigned - * to this property may be rounded down and result in a lower maximum ZA - * data size being in effect. By setting this property to 0, users can make - * ZASL to be equal to MDTS. This property only affects zoned namespaces. + * - `mdts` + * Indicates the maximum data transfer size for a command that transfers data + * between host-accessible memory and the controller. The value is specified + * as a power of two (2^n) and is in units of the minimum memory page size + * (CAP.MPSMIN). The default value is 7 (i.e. 512 KiB). + * + * - `zoned.zasl` + * Indicates the maximum data transfer size for the Zone Append command. Like + * `mdts`, the value is specified as a power of two (2^n) and is in units of + * the minimum memory page size (CAP.MPSMIN). The default value is 0 (i.e. + * defaulting to the value of `mdts`). + * + * nvme namespace device parameters + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * - `subsys` + * If given, the namespace will be attached to all controllers in the + * subsystem. Otherwise, `bus` must be given to attach this namespace to a + * specific controller as a non-shared namespace. + * + * - `detached` + * This parameter is only valid together with the `subsys` parameter. If left + * at the default value (`false/off`), the namespace will be attached to all + * controllers in the NVMe subsystem at boot-up. If set to `true/on`, the + * namespace will be be available in the subsystem not not attached to any + * controllers. * * Setting `zoned` to true selects Zoned Command Set at the namespace. * In this case, the following namespace properties are available to configure @@ -157,6 +196,7 @@ static const uint32_t nvme_cse_acs[256] = { [NVME_ADM_CMD_SET_FEATURES] = NVME_CMD_EFF_CSUPP, [NVME_ADM_CMD_GET_FEATURES] = NVME_CMD_EFF_CSUPP, [NVME_ADM_CMD_ASYNC_EV_REQ] = NVME_CMD_EFF_CSUPP, + [NVME_ADM_CMD_NS_ATTACHMENT] = NVME_CMD_EFF_CSUPP | NVME_CMD_EFF_NIC, }; static const uint32_t nvme_cse_iocs_none[256]; @@ -167,6 +207,7 @@ static const uint32_t nvme_cse_iocs_nvm[256] = { [NVME_CMD_WRITE] = NVME_CMD_EFF_CSUPP | NVME_CMD_EFF_LBCC, [NVME_CMD_READ] = NVME_CMD_EFF_CSUPP, [NVME_CMD_DSM] = NVME_CMD_EFF_CSUPP | NVME_CMD_EFF_LBCC, + [NVME_CMD_COPY] = NVME_CMD_EFF_CSUPP | NVME_CMD_EFF_LBCC, [NVME_CMD_COMPARE] = NVME_CMD_EFF_CSUPP, }; @@ -176,6 +217,7 @@ static const uint32_t nvme_cse_iocs_zoned[256] = { [NVME_CMD_WRITE] = NVME_CMD_EFF_CSUPP | NVME_CMD_EFF_LBCC, [NVME_CMD_READ] = NVME_CMD_EFF_CSUPP, [NVME_CMD_DSM] = NVME_CMD_EFF_CSUPP | NVME_CMD_EFF_LBCC, + [NVME_CMD_COPY] = NVME_CMD_EFF_CSUPP | NVME_CMD_EFF_LBCC, [NVME_CMD_COMPARE] = NVME_CMD_EFF_CSUPP, [NVME_CMD_ZONE_APPEND] = NVME_CMD_EFF_CSUPP | NVME_CMD_EFF_LBCC, [NVME_CMD_ZONE_MGMT_SEND] = NVME_CMD_EFF_CSUPP | NVME_CMD_EFF_LBCC, @@ -407,15 +449,31 @@ static void nvme_req_clear(NvmeRequest *req) req->status = NVME_SUCCESS; } -static void nvme_req_exit(NvmeRequest *req) +static inline void nvme_sg_init(NvmeCtrl *n, NvmeSg *sg, bool dma) { - if (req->qsg.sg) { - qemu_sglist_destroy(&req->qsg); + if (dma) { + pci_dma_sglist_init(&sg->qsg, &n->parent_obj, 0); + sg->flags = NVME_SG_DMA; + } else { + qemu_iovec_init(&sg->iov, 0); } - if (req->iov.iov) { - qemu_iovec_destroy(&req->iov); + sg->flags |= NVME_SG_ALLOC; +} + +static inline void nvme_sg_unmap(NvmeSg *sg) +{ + if (!(sg->flags & NVME_SG_ALLOC)) { + return; } + + if (sg->flags & NVME_SG_DMA) { + qemu_sglist_destroy(&sg->qsg); + } else { + qemu_iovec_destroy(&sg->iov); + } + + memset(sg, 0x0, sizeof(*sg)); } static uint16_t nvme_map_addr_cmb(NvmeCtrl *n, QEMUIOVector *iov, hwaddr addr, @@ -452,8 +510,7 @@ static uint16_t nvme_map_addr_pmr(NvmeCtrl *n, QEMUIOVector *iov, hwaddr addr, return NVME_SUCCESS; } -static uint16_t nvme_map_addr(NvmeCtrl *n, QEMUSGList *qsg, QEMUIOVector *iov, - hwaddr addr, size_t len) +static uint16_t nvme_map_addr(NvmeCtrl *n, NvmeSg *sg, hwaddr addr, size_t len) { bool cmb = false, pmr = false; @@ -470,40 +527,33 @@ static uint16_t nvme_map_addr(NvmeCtrl *n, QEMUSGList *qsg, QEMUIOVector *iov, } if (cmb || pmr) { - if (qsg && qsg->sg) { + if (sg->flags & NVME_SG_DMA) { return NVME_INVALID_USE_OF_CMB | NVME_DNR; } - assert(iov); - - if (!iov->iov) { - qemu_iovec_init(iov, 1); - } - if (cmb) { - return nvme_map_addr_cmb(n, iov, addr, len); + return nvme_map_addr_cmb(n, &sg->iov, addr, len); } else { - return nvme_map_addr_pmr(n, iov, addr, len); + return nvme_map_addr_pmr(n, &sg->iov, addr, len); } } - if (iov && iov->iov) { + if (!(sg->flags & NVME_SG_DMA)) { return NVME_INVALID_USE_OF_CMB | NVME_DNR; } - assert(qsg); - - if (!qsg->sg) { - pci_dma_sglist_init(qsg, &n->parent_obj, 1); - } - - qemu_sglist_add(qsg, addr, len); + qemu_sglist_add(&sg->qsg, addr, len); return NVME_SUCCESS; } -static uint16_t nvme_map_prp(NvmeCtrl *n, uint64_t prp1, uint64_t prp2, - uint32_t len, NvmeRequest *req) +static inline bool nvme_addr_is_dma(NvmeCtrl *n, hwaddr addr) +{ + return !(nvme_addr_is_cmb(n, addr) || nvme_addr_is_pmr(n, addr)); +} + +static uint16_t nvme_map_prp(NvmeCtrl *n, NvmeSg *sg, uint64_t prp1, + uint64_t prp2, uint32_t len) { hwaddr trans_len = n->page_size - (prp1 % n->page_size); trans_len = MIN(len, trans_len); @@ -511,20 +561,13 @@ static uint16_t nvme_map_prp(NvmeCtrl *n, uint64_t prp1, uint64_t prp2, uint16_t status; int ret; - QEMUSGList *qsg = &req->qsg; - QEMUIOVector *iov = &req->iov; - trace_pci_nvme_map_prp(trans_len, len, prp1, prp2, num_prps); - if (nvme_addr_is_cmb(n, prp1) || (nvme_addr_is_pmr(n, prp1))) { - qemu_iovec_init(iov, num_prps); - } else { - pci_dma_sglist_init(qsg, &n->parent_obj, num_prps); - } + nvme_sg_init(n, sg, nvme_addr_is_dma(n, prp1)); - status = nvme_map_addr(n, qsg, iov, prp1, trans_len); + status = nvme_map_addr(n, sg, prp1, trans_len); if (status) { - return status; + goto unmap; } len -= trans_len; @@ -539,7 +582,8 @@ static uint16_t nvme_map_prp(NvmeCtrl *n, uint64_t prp1, uint64_t prp2, ret = nvme_addr_read(n, prp2, (void *)prp_list, prp_trans); if (ret) { trace_pci_nvme_err_addr_read(prp2); - return NVME_DATA_TRAS_ERROR; + status = NVME_DATA_TRAS_ERROR; + goto unmap; } while (len != 0) { uint64_t prp_ent = le64_to_cpu(prp_list[i]); @@ -547,7 +591,8 @@ static uint16_t nvme_map_prp(NvmeCtrl *n, uint64_t prp1, uint64_t prp2, if (i == n->max_prp_ents - 1 && len > n->page_size) { if (unlikely(prp_ent & (n->page_size - 1))) { trace_pci_nvme_err_invalid_prplist_ent(prp_ent); - return NVME_INVALID_PRP_OFFSET | NVME_DNR; + status = NVME_INVALID_PRP_OFFSET | NVME_DNR; + goto unmap; } i = 0; @@ -557,20 +602,22 @@ static uint16_t nvme_map_prp(NvmeCtrl *n, uint64_t prp1, uint64_t prp2, prp_trans); if (ret) { trace_pci_nvme_err_addr_read(prp_ent); - return NVME_DATA_TRAS_ERROR; + status = NVME_DATA_TRAS_ERROR; + goto unmap; } prp_ent = le64_to_cpu(prp_list[i]); } if (unlikely(prp_ent & (n->page_size - 1))) { trace_pci_nvme_err_invalid_prplist_ent(prp_ent); - return NVME_INVALID_PRP_OFFSET | NVME_DNR; + status = NVME_INVALID_PRP_OFFSET | NVME_DNR; + goto unmap; } trans_len = MIN(len, n->page_size); - status = nvme_map_addr(n, qsg, iov, prp_ent, trans_len); + status = nvme_map_addr(n, sg, prp_ent, trans_len); if (status) { - return status; + goto unmap; } len -= trans_len; @@ -579,26 +626,30 @@ static uint16_t nvme_map_prp(NvmeCtrl *n, uint64_t prp1, uint64_t prp2, } else { if (unlikely(prp2 & (n->page_size - 1))) { trace_pci_nvme_err_invalid_prp2_align(prp2); - return NVME_INVALID_PRP_OFFSET | NVME_DNR; + status = NVME_INVALID_PRP_OFFSET | NVME_DNR; + goto unmap; } - status = nvme_map_addr(n, qsg, iov, prp2, len); + status = nvme_map_addr(n, sg, prp2, len); if (status) { - return status; + goto unmap; } } } return NVME_SUCCESS; + +unmap: + nvme_sg_unmap(sg); + return status; } /* * Map 'nsgld' data descriptors from 'segment'. The function will subtract the * number of bytes mapped in len. */ -static uint16_t nvme_map_sgl_data(NvmeCtrl *n, QEMUSGList *qsg, - QEMUIOVector *iov, +static uint16_t nvme_map_sgl_data(NvmeCtrl *n, NvmeSg *sg, NvmeSglDescriptor *segment, uint64_t nsgld, - size_t *len, NvmeRequest *req) + size_t *len, NvmeCmd *cmd) { dma_addr_t addr, trans_len; uint32_t dlen; @@ -609,7 +660,7 @@ static uint16_t nvme_map_sgl_data(NvmeCtrl *n, QEMUSGList *qsg, switch (type) { case NVME_SGL_DESCR_TYPE_BIT_BUCKET: - if (req->cmd.opcode == NVME_CMD_WRITE) { + if (cmd->opcode == NVME_CMD_WRITE) { continue; } case NVME_SGL_DESCR_TYPE_DATA_BLOCK: @@ -638,7 +689,7 @@ static uint16_t nvme_map_sgl_data(NvmeCtrl *n, QEMUSGList *qsg, break; } - trace_pci_nvme_err_invalid_sgl_excess_length(nvme_cid(req)); + trace_pci_nvme_err_invalid_sgl_excess_length(dlen); return NVME_DATA_SGL_LEN_INVALID | NVME_DNR; } @@ -654,7 +705,7 @@ static uint16_t nvme_map_sgl_data(NvmeCtrl *n, QEMUSGList *qsg, return NVME_DATA_SGL_LEN_INVALID | NVME_DNR; } - status = nvme_map_addr(n, qsg, iov, addr, trans_len); + status = nvme_map_addr(n, sg, addr, trans_len); if (status) { return status; } @@ -666,9 +717,8 @@ next: return NVME_SUCCESS; } -static uint16_t nvme_map_sgl(NvmeCtrl *n, QEMUSGList *qsg, QEMUIOVector *iov, - NvmeSglDescriptor sgl, size_t len, - NvmeRequest *req) +static uint16_t nvme_map_sgl(NvmeCtrl *n, NvmeSg *sg, NvmeSglDescriptor sgl, + size_t len, NvmeCmd *cmd) { /* * Read the segment in chunks of 256 descriptors (one 4k page) to avoid @@ -689,14 +739,16 @@ static uint16_t nvme_map_sgl(NvmeCtrl *n, QEMUSGList *qsg, QEMUIOVector *iov, sgld = &sgl; addr = le64_to_cpu(sgl.addr); - trace_pci_nvme_map_sgl(nvme_cid(req), NVME_SGL_TYPE(sgl.type), len); + trace_pci_nvme_map_sgl(NVME_SGL_TYPE(sgl.type), len); + + nvme_sg_init(n, sg, nvme_addr_is_dma(n, addr)); /* * If the entire transfer can be described with a single data block it can * be mapped directly. */ if (NVME_SGL_TYPE(sgl.type) == NVME_SGL_DESCR_TYPE_DATA_BLOCK) { - status = nvme_map_sgl_data(n, qsg, iov, sgld, 1, &len, req); + status = nvme_map_sgl_data(n, sg, sgld, 1, &len, cmd); if (status) { goto unmap; } @@ -734,8 +786,8 @@ static uint16_t nvme_map_sgl(NvmeCtrl *n, QEMUSGList *qsg, QEMUIOVector *iov, goto unmap; } - status = nvme_map_sgl_data(n, qsg, iov, segment, SEG_CHUNK_SIZE, - &len, req); + status = nvme_map_sgl_data(n, sg, segment, SEG_CHUNK_SIZE, + &len, cmd); if (status) { goto unmap; } @@ -761,7 +813,7 @@ static uint16_t nvme_map_sgl(NvmeCtrl *n, QEMUSGList *qsg, QEMUIOVector *iov, switch (NVME_SGL_TYPE(last_sgld->type)) { case NVME_SGL_DESCR_TYPE_DATA_BLOCK: case NVME_SGL_DESCR_TYPE_BIT_BUCKET: - status = nvme_map_sgl_data(n, qsg, iov, segment, nsgld, &len, req); + status = nvme_map_sgl_data(n, sg, segment, nsgld, &len, cmd); if (status) { goto unmap; } @@ -788,7 +840,7 @@ static uint16_t nvme_map_sgl(NvmeCtrl *n, QEMUSGList *qsg, QEMUIOVector *iov, * Do not map the last descriptor; it will be a Segment or Last Segment * descriptor and is handled by the next iteration. */ - status = nvme_map_sgl_data(n, qsg, iov, segment, nsgld - 1, &len, req); + status = nvme_map_sgl_data(n, sg, segment, nsgld - 1, &len, cmd); if (status) { goto unmap; } @@ -804,83 +856,120 @@ out: return NVME_SUCCESS; unmap: - if (iov->iov) { - qemu_iovec_destroy(iov); - } - - if (qsg->sg) { - qemu_sglist_destroy(qsg); - } - + nvme_sg_unmap(sg); return status; } -static uint16_t nvme_map_dptr(NvmeCtrl *n, size_t len, NvmeRequest *req) +static uint16_t nvme_map_dptr(NvmeCtrl *n, NvmeSg *sg, size_t len, + NvmeCmd *cmd) { uint64_t prp1, prp2; - switch (NVME_CMD_FLAGS_PSDT(req->cmd.flags)) { + switch (NVME_CMD_FLAGS_PSDT(cmd->flags)) { case NVME_PSDT_PRP: - prp1 = le64_to_cpu(req->cmd.dptr.prp1); - prp2 = le64_to_cpu(req->cmd.dptr.prp2); + prp1 = le64_to_cpu(cmd->dptr.prp1); + prp2 = le64_to_cpu(cmd->dptr.prp2); - return nvme_map_prp(n, prp1, prp2, len, req); + return nvme_map_prp(n, sg, prp1, prp2, len); case NVME_PSDT_SGL_MPTR_CONTIGUOUS: case NVME_PSDT_SGL_MPTR_SGL: - /* SGLs shall not be used for Admin commands in NVMe over PCIe */ - if (!req->sq->sqid) { - return NVME_INVALID_FIELD | NVME_DNR; - } - - return nvme_map_sgl(n, &req->qsg, &req->iov, req->cmd.dptr.sgl, len, - req); + return nvme_map_sgl(n, sg, cmd->dptr.sgl, len, cmd); default: return NVME_INVALID_FIELD; } } -static uint16_t nvme_dma(NvmeCtrl *n, uint8_t *ptr, uint32_t len, - DMADirection dir, NvmeRequest *req) -{ - uint16_t status = NVME_SUCCESS; +typedef enum NvmeTxDirection { + NVME_TX_DIRECTION_TO_DEVICE = 0, + NVME_TX_DIRECTION_FROM_DEVICE = 1, +} NvmeTxDirection; - status = nvme_map_dptr(n, len, req); - if (status) { - return status; - } - - /* assert that only one of qsg and iov carries data */ - assert((req->qsg.nsg > 0) != (req->iov.niov > 0)); +static uint16_t nvme_tx(NvmeCtrl *n, NvmeSg *sg, uint8_t *ptr, uint32_t len, + NvmeTxDirection dir) +{ + assert(sg->flags & NVME_SG_ALLOC); - if (req->qsg.nsg > 0) { + if (sg->flags & NVME_SG_DMA) { uint64_t residual; - if (dir == DMA_DIRECTION_TO_DEVICE) { - residual = dma_buf_write(ptr, len, &req->qsg); + if (dir == NVME_TX_DIRECTION_TO_DEVICE) { + residual = dma_buf_write(ptr, len, &sg->qsg); } else { - residual = dma_buf_read(ptr, len, &req->qsg); + residual = dma_buf_read(ptr, len, &sg->qsg); } if (unlikely(residual)) { trace_pci_nvme_err_invalid_dma(); - status = NVME_INVALID_FIELD | NVME_DNR; + return NVME_INVALID_FIELD | NVME_DNR; } } else { size_t bytes; - if (dir == DMA_DIRECTION_TO_DEVICE) { - bytes = qemu_iovec_to_buf(&req->iov, 0, ptr, len); + if (dir == NVME_TX_DIRECTION_TO_DEVICE) { + bytes = qemu_iovec_to_buf(&sg->iov, 0, ptr, len); } else { - bytes = qemu_iovec_from_buf(&req->iov, 0, ptr, len); + bytes = qemu_iovec_from_buf(&sg->iov, 0, ptr, len); } if (unlikely(bytes != len)) { trace_pci_nvme_err_invalid_dma(); - status = NVME_INVALID_FIELD | NVME_DNR; + return NVME_INVALID_FIELD | NVME_DNR; } } - return status; + return NVME_SUCCESS; +} + +static inline uint16_t nvme_c2h(NvmeCtrl *n, uint8_t *ptr, uint32_t len, + NvmeRequest *req) +{ + uint16_t status; + + status = nvme_map_dptr(n, &req->sg, len, &req->cmd); + if (status) { + return status; + } + + return nvme_tx(n, &req->sg, ptr, len, NVME_TX_DIRECTION_FROM_DEVICE); +} + +static inline uint16_t nvme_h2c(NvmeCtrl *n, uint8_t *ptr, uint32_t len, + NvmeRequest *req) +{ + uint16_t status; + + status = nvme_map_dptr(n, &req->sg, len, &req->cmd); + if (status) { + return status; + } + + return nvme_tx(n, &req->sg, ptr, len, NVME_TX_DIRECTION_TO_DEVICE); +} + +static inline void nvme_blk_read(BlockBackend *blk, int64_t offset, + BlockCompletionFunc *cb, NvmeRequest *req) +{ + assert(req->sg.flags & NVME_SG_ALLOC); + + if (req->sg.flags & NVME_SG_DMA) { + req->aiocb = dma_blk_read(blk, &req->sg.qsg, offset, BDRV_SECTOR_SIZE, + cb, req); + } else { + req->aiocb = blk_aio_preadv(blk, offset, &req->sg.iov, 0, cb, req); + } +} + +static inline void nvme_blk_write(BlockBackend *blk, int64_t offset, + BlockCompletionFunc *cb, NvmeRequest *req) +{ + assert(req->sg.flags & NVME_SG_ALLOC); + + if (req->sg.flags & NVME_SG_DMA) { + req->aiocb = dma_blk_write(blk, &req->sg.qsg, offset, BDRV_SECTOR_SIZE, + cb, req); + } else { + req->aiocb = blk_aio_pwritev(blk, offset, &req->sg.iov, 0, cb, req); + } } static void nvme_post_cqes(void *opaque) @@ -913,7 +1002,7 @@ static void nvme_post_cqes(void *opaque) } QTAILQ_REMOVE(&cq->req_list, req, entry); nvme_inc_cq_tail(cq); - nvme_req_exit(req); + nvme_sg_unmap(&req->sg); QTAILQ_INSERT_TAIL(&sq->req_list, req, entry); } if (cq->tail != cq->head) { @@ -1048,6 +1137,7 @@ static inline uint16_t nvme_check_mdts(NvmeCtrl *n, size_t len) uint8_t mdts = n->params.mdts; if (mdts && len > n->page_size << mdts) { + trace_pci_nvme_err_mdts(len); return NVME_INVALID_FIELD | NVME_DNR; } @@ -1129,7 +1219,7 @@ static void nvme_aio_err(NvmeRequest *req, int ret) break; } - trace_pci_nvme_err_aio(nvme_cid(req), strerror(ret), status); + trace_pci_nvme_err_aio(nvme_cid(req), strerror(-ret), status); error_setg_errno(&local_err, -ret, "aio failed"); error_report_err(local_err); @@ -1185,9 +1275,8 @@ static uint16_t nvme_check_zone_state_for_write(NvmeZone *zone) return NVME_INTERNAL_DEV_ERROR; } -static uint16_t nvme_check_zone_write(NvmeCtrl *n, NvmeNamespace *ns, - NvmeZone *zone, uint64_t slba, - uint32_t nlb) +static uint16_t nvme_check_zone_write(NvmeNamespace *ns, NvmeZone *zone, + uint64_t slba, uint32_t nlb) { uint64_t zcap = nvme_zone_wr_boundary(zone); uint16_t status; @@ -1212,8 +1301,6 @@ static uint16_t nvme_check_zone_write(NvmeCtrl *n, NvmeNamespace *ns, static uint16_t nvme_check_zone_state_for_read(NvmeZone *zone) { - uint16_t status; - switch (nvme_get_zone_state(zone)) { case NVME_ZONE_STATE_EMPTY: case NVME_ZONE_STATE_IMPLICITLY_OPEN: @@ -1221,16 +1308,15 @@ static uint16_t nvme_check_zone_state_for_read(NvmeZone *zone) case NVME_ZONE_STATE_FULL: case NVME_ZONE_STATE_CLOSED: case NVME_ZONE_STATE_READ_ONLY: - status = NVME_SUCCESS; - break; + return NVME_SUCCESS; case NVME_ZONE_STATE_OFFLINE: - status = NVME_ZONE_OFFLINE; - break; + trace_pci_nvme_err_zone_is_offline(zone->d.zslba); + return NVME_ZONE_OFFLINE; default: assert(false); } - return status; + return NVME_INTERNAL_DEV_ERROR; } static uint16_t nvme_check_zone_read(NvmeNamespace *ns, uint64_t slba, @@ -1265,7 +1351,45 @@ static uint16_t nvme_check_zone_read(NvmeNamespace *ns, uint64_t slba, return status; } -static void nvme_auto_transition_zone(NvmeNamespace *ns) +static uint16_t nvme_zrm_finish(NvmeNamespace *ns, NvmeZone *zone) +{ + switch (nvme_get_zone_state(zone)) { + case NVME_ZONE_STATE_FULL: + return NVME_SUCCESS; + + case NVME_ZONE_STATE_IMPLICITLY_OPEN: + case NVME_ZONE_STATE_EXPLICITLY_OPEN: + nvme_aor_dec_open(ns); + /* fallthrough */ + case NVME_ZONE_STATE_CLOSED: + nvme_aor_dec_active(ns); + /* fallthrough */ + case NVME_ZONE_STATE_EMPTY: + nvme_assign_zone_state(ns, zone, NVME_ZONE_STATE_FULL); + return NVME_SUCCESS; + + default: + return NVME_ZONE_INVAL_TRANSITION; + } +} + +static uint16_t nvme_zrm_close(NvmeNamespace *ns, NvmeZone *zone) +{ + switch (nvme_get_zone_state(zone)) { + case NVME_ZONE_STATE_EXPLICITLY_OPEN: + case NVME_ZONE_STATE_IMPLICITLY_OPEN: + nvme_aor_dec_open(ns); + nvme_assign_zone_state(ns, zone, NVME_ZONE_STATE_CLOSED); + /* fall through */ + case NVME_ZONE_STATE_CLOSED: + return NVME_SUCCESS; + + default: + return NVME_ZONE_INVAL_TRANSITION; + } +} + +static void nvme_zrm_auto_transition_zone(NvmeNamespace *ns) { NvmeZone *zone; @@ -1277,85 +1401,92 @@ static void nvme_auto_transition_zone(NvmeNamespace *ns) * Automatically close this implicitly open zone. */ QTAILQ_REMOVE(&ns->imp_open_zones, zone, entry); - nvme_aor_dec_open(ns); - nvme_assign_zone_state(ns, zone, NVME_ZONE_STATE_CLOSED); + nvme_zrm_close(ns, zone); } } } -static uint16_t nvme_auto_open_zone(NvmeNamespace *ns, NvmeZone *zone) +static uint16_t __nvme_zrm_open(NvmeNamespace *ns, NvmeZone *zone, + bool implicit) { - uint16_t status = NVME_SUCCESS; - uint8_t zs = nvme_get_zone_state(zone); + int act = 0; + uint16_t status; - if (zs == NVME_ZONE_STATE_EMPTY) { - nvme_auto_transition_zone(ns); - status = nvme_aor_check(ns, 1, 1); - } else if (zs == NVME_ZONE_STATE_CLOSED) { - nvme_auto_transition_zone(ns); - status = nvme_aor_check(ns, 0, 1); - } + switch (nvme_get_zone_state(zone)) { + case NVME_ZONE_STATE_EMPTY: + act = 1; - return status; + /* fallthrough */ + + case NVME_ZONE_STATE_CLOSED: + nvme_zrm_auto_transition_zone(ns); + status = nvme_aor_check(ns, act, 1); + if (status) { + return status; + } + + if (act) { + nvme_aor_inc_active(ns); + } + + nvme_aor_inc_open(ns); + + if (implicit) { + nvme_assign_zone_state(ns, zone, NVME_ZONE_STATE_IMPLICITLY_OPEN); + return NVME_SUCCESS; + } + + /* fallthrough */ + + case NVME_ZONE_STATE_IMPLICITLY_OPEN: + if (implicit) { + return NVME_SUCCESS; + } + + nvme_assign_zone_state(ns, zone, NVME_ZONE_STATE_EXPLICITLY_OPEN); + + /* fallthrough */ + + case NVME_ZONE_STATE_EXPLICITLY_OPEN: + return NVME_SUCCESS; + + default: + return NVME_ZONE_INVAL_TRANSITION; + } } -static void nvme_finalize_zoned_write(NvmeNamespace *ns, NvmeRequest *req, - bool failed) +static inline uint16_t nvme_zrm_auto(NvmeNamespace *ns, NvmeZone *zone) { - NvmeRwCmd *rw = (NvmeRwCmd *)&req->cmd; - NvmeZone *zone; - NvmeZonedResult *res = (NvmeZonedResult *)&req->cqe; - uint64_t slba; - uint32_t nlb; + return __nvme_zrm_open(ns, zone, true); +} - slba = le64_to_cpu(rw->slba); - nlb = le16_to_cpu(rw->nlb) + 1; - zone = nvme_get_zone_by_slba(ns, slba); +static inline uint16_t nvme_zrm_open(NvmeNamespace *ns, NvmeZone *zone) +{ + return __nvme_zrm_open(ns, zone, false); +} +static void __nvme_advance_zone_wp(NvmeNamespace *ns, NvmeZone *zone, + uint32_t nlb) +{ zone->d.wp += nlb; - if (failed) { - res->slba = 0; - } - if (zone->d.wp == nvme_zone_wr_boundary(zone)) { - switch (nvme_get_zone_state(zone)) { - case NVME_ZONE_STATE_IMPLICITLY_OPEN: - case NVME_ZONE_STATE_EXPLICITLY_OPEN: - nvme_aor_dec_open(ns); - /* fall through */ - case NVME_ZONE_STATE_CLOSED: - nvme_aor_dec_active(ns); - /* fall through */ - case NVME_ZONE_STATE_EMPTY: - nvme_assign_zone_state(ns, zone, NVME_ZONE_STATE_FULL); - /* fall through */ - case NVME_ZONE_STATE_FULL: - break; - default: - assert(false); - } + nvme_zrm_finish(ns, zone); } } -static void nvme_advance_zone_wp(NvmeNamespace *ns, NvmeZone *zone, - uint32_t nlb) +static void nvme_finalize_zoned_write(NvmeNamespace *ns, NvmeRequest *req) { - uint8_t zs; + NvmeRwCmd *rw = (NvmeRwCmd *)&req->cmd; + NvmeZone *zone; + uint64_t slba; + uint32_t nlb; - zone->w_ptr += nlb; + slba = le64_to_cpu(rw->slba); + nlb = le16_to_cpu(rw->nlb) + 1; + zone = nvme_get_zone_by_slba(ns, slba); - if (zone->w_ptr < nvme_zone_wr_boundary(zone)) { - zs = nvme_get_zone_state(zone); - switch (zs) { - case NVME_ZONE_STATE_EMPTY: - nvme_aor_inc_active(ns); - /* fall through */ - case NVME_ZONE_STATE_CLOSED: - nvme_aor_inc_open(ns); - nvme_assign_zone_state(ns, zone, NVME_ZONE_STATE_IMPLICITLY_OPEN); - } - } + __nvme_advance_zone_wp(ns, zone, nlb); } static inline bool nvme_is_write(NvmeRequest *req) @@ -1379,9 +1510,37 @@ static void nvme_rw_cb(void *opaque, int ret) trace_pci_nvme_rw_cb(nvme_cid(req), blk_name(blk)); if (ns->params.zoned && nvme_is_write(req)) { - nvme_finalize_zoned_write(ns, req, ret != 0); + nvme_finalize_zoned_write(ns, req); + } + + if (!ret) { + block_acct_done(stats, acct); + } else { + block_acct_failed(stats, acct); + nvme_aio_err(req, ret); } + nvme_enqueue_req_completion(nvme_cq(req), req); +} + +struct nvme_aio_flush_ctx { + NvmeRequest *req; + NvmeNamespace *ns; + BlockAcctCookie acct; +}; + +static void nvme_aio_flush_cb(void *opaque, int ret) +{ + struct nvme_aio_flush_ctx *ctx = opaque; + NvmeRequest *req = ctx->req; + uintptr_t *num_flushes = (uintptr_t *)&req->opaque; + + BlockBackend *blk = ctx->ns->blkconf.blk; + BlockAcctCookie *acct = &ctx->acct; + BlockAcctStats *stats = blk_get_stats(blk); + + trace_pci_nvme_aio_flush_cb(nvme_cid(req), blk_name(blk)); + if (!ret) { block_acct_done(stats, acct); } else { @@ -1389,6 +1548,13 @@ static void nvme_rw_cb(void *opaque, int ret) nvme_aio_err(req, ret); } + (*num_flushes)--; + g_free(ctx); + + if (*num_flushes) { + return; + } + nvme_enqueue_req_completion(nvme_cq(req), req); } @@ -1459,10 +1625,139 @@ static void nvme_aio_zone_reset_cb(void *opaque, int ret) nvme_enqueue_req_completion(nvme_cq(req), req); } +struct nvme_copy_ctx { + int copies; + uint8_t *bounce; + uint32_t nlb; +}; + +struct nvme_copy_in_ctx { + NvmeRequest *req; + QEMUIOVector iov; +}; + +static void nvme_copy_cb(void *opaque, int ret) +{ + NvmeRequest *req = opaque; + NvmeNamespace *ns = req->ns; + struct nvme_copy_ctx *ctx = req->opaque; + + trace_pci_nvme_copy_cb(nvme_cid(req)); + + if (ns->params.zoned) { + NvmeCopyCmd *copy = (NvmeCopyCmd *)&req->cmd; + uint64_t sdlba = le64_to_cpu(copy->sdlba); + NvmeZone *zone = nvme_get_zone_by_slba(ns, sdlba); + + __nvme_advance_zone_wp(ns, zone, ctx->nlb); + } + + if (!ret) { + block_acct_done(blk_get_stats(ns->blkconf.blk), &req->acct); + } else { + block_acct_failed(blk_get_stats(ns->blkconf.blk), &req->acct); + nvme_aio_err(req, ret); + } + + g_free(ctx->bounce); + g_free(ctx); + + nvme_enqueue_req_completion(nvme_cq(req), req); +} + +static void nvme_copy_in_complete(NvmeRequest *req) +{ + NvmeNamespace *ns = req->ns; + NvmeCopyCmd *copy = (NvmeCopyCmd *)&req->cmd; + struct nvme_copy_ctx *ctx = req->opaque; + uint64_t sdlba = le64_to_cpu(copy->sdlba); + uint16_t status; + + trace_pci_nvme_copy_in_complete(nvme_cid(req)); + + block_acct_done(blk_get_stats(ns->blkconf.blk), &req->acct); + + status = nvme_check_bounds(ns, sdlba, ctx->nlb); + if (status) { + trace_pci_nvme_err_invalid_lba_range(sdlba, ctx->nlb, ns->id_ns.nsze); + goto invalid; + } + + if (ns->params.zoned) { + NvmeZone *zone = nvme_get_zone_by_slba(ns, sdlba); + + status = nvme_check_zone_write(ns, zone, sdlba, ctx->nlb); + if (status) { + goto invalid; + } + + status = nvme_zrm_auto(ns, zone); + if (status) { + goto invalid; + } + + zone->w_ptr += ctx->nlb; + } + + qemu_iovec_init(&req->sg.iov, 1); + qemu_iovec_add(&req->sg.iov, ctx->bounce, nvme_l2b(ns, ctx->nlb)); + + block_acct_start(blk_get_stats(ns->blkconf.blk), &req->acct, 0, + BLOCK_ACCT_WRITE); + + req->aiocb = blk_aio_pwritev(ns->blkconf.blk, nvme_l2b(ns, sdlba), + &req->sg.iov, 0, nvme_copy_cb, req); + + return; + +invalid: + req->status = status; + + g_free(ctx->bounce); + g_free(ctx); + + nvme_enqueue_req_completion(nvme_cq(req), req); +} + +static void nvme_aio_copy_in_cb(void *opaque, int ret) +{ + struct nvme_copy_in_ctx *in_ctx = opaque; + NvmeRequest *req = in_ctx->req; + NvmeNamespace *ns = req->ns; + struct nvme_copy_ctx *ctx = req->opaque; + + qemu_iovec_destroy(&in_ctx->iov); + g_free(in_ctx); + + trace_pci_nvme_aio_copy_in_cb(nvme_cid(req)); + + if (ret) { + nvme_aio_err(req, ret); + } + + ctx->copies--; + + if (ctx->copies) { + return; + } + + if (req->status) { + block_acct_failed(blk_get_stats(ns->blkconf.blk), &req->acct); + + g_free(ctx->bounce); + g_free(ctx); + + nvme_enqueue_req_completion(nvme_cq(req), req); + + return; + } + + nvme_copy_in_complete(req); +} + struct nvme_compare_ctx { QEMUIOVector iov; uint8_t *bounce; - size_t len; }; static void nvme_compare_cb(void *opaque, int ret) @@ -1483,16 +1778,15 @@ static void nvme_compare_cb(void *opaque, int ret) goto out; } - buf = g_malloc(ctx->len); + buf = g_malloc(ctx->iov.size); - status = nvme_dma(nvme_ctrl(req), buf, ctx->len, DMA_DIRECTION_TO_DEVICE, - req); + status = nvme_h2c(nvme_ctrl(req), buf, ctx->iov.size, req); if (status) { req->status = status; goto out; } - if (memcmp(buf, ctx->bounce, ctx->len)) { + if (memcmp(buf, ctx->bounce, ctx->iov.size)) { req->status = NVME_CMP_FAILURE; } @@ -1522,8 +1816,7 @@ static uint16_t nvme_dsm(NvmeCtrl *n, NvmeRequest *req) NvmeDsmRange range[nr]; uintptr_t *discards = (uintptr_t *)&req->opaque; - status = nvme_dma(n, (uint8_t *)range, sizeof(range), - DMA_DIRECTION_TO_DEVICE, req); + status = nvme_h2c(n, (uint8_t *)range, sizeof(range), req); if (status) { return status; } @@ -1548,6 +1841,10 @@ static uint16_t nvme_dsm(NvmeCtrl *n, NvmeRequest *req) trace_pci_nvme_dsm_deallocate(nvme_cid(req), nvme_nsid(ns), slba, nlb); + if (nlb > n->dmrsl) { + trace_pci_nvme_dsm_single_range_limit_exceeded(nlb, n->dmrsl); + } + offset = nvme_l2b(ns, slba); len = nvme_l2b(ns, nlb); @@ -1577,6 +1874,121 @@ static uint16_t nvme_dsm(NvmeCtrl *n, NvmeRequest *req) return status; } +static uint16_t nvme_copy(NvmeCtrl *n, NvmeRequest *req) +{ + NvmeNamespace *ns = req->ns; + NvmeCopyCmd *copy = (NvmeCopyCmd *)&req->cmd; + g_autofree NvmeCopySourceRange *range = NULL; + + uint16_t nr = copy->nr + 1; + uint8_t format = copy->control[0] & 0xf; + uint32_t nlb = 0; + + uint8_t *bounce = NULL, *bouncep = NULL; + struct nvme_copy_ctx *ctx; + uint16_t status; + int i; + + trace_pci_nvme_copy(nvme_cid(req), nvme_nsid(ns), nr, format); + + if (!(n->id_ctrl.ocfs & (1 << format))) { + trace_pci_nvme_err_copy_invalid_format(format); + return NVME_INVALID_FIELD | NVME_DNR; + } + + if (nr > ns->id_ns.msrc + 1) { + return NVME_CMD_SIZE_LIMIT | NVME_DNR; + } + + range = g_new(NvmeCopySourceRange, nr); + + status = nvme_h2c(n, (uint8_t *)range, nr * sizeof(NvmeCopySourceRange), + req); + if (status) { + return status; + } + + for (i = 0; i < nr; i++) { + uint64_t slba = le64_to_cpu(range[i].slba); + uint32_t _nlb = le16_to_cpu(range[i].nlb) + 1; + + if (_nlb > le16_to_cpu(ns->id_ns.mssrl)) { + return NVME_CMD_SIZE_LIMIT | NVME_DNR; + } + + status = nvme_check_bounds(ns, slba, _nlb); + if (status) { + trace_pci_nvme_err_invalid_lba_range(slba, _nlb, ns->id_ns.nsze); + return status; + } + + if (NVME_ERR_REC_DULBE(ns->features.err_rec)) { + status = nvme_check_dulbe(ns, slba, _nlb); + if (status) { + return status; + } + } + + if (ns->params.zoned) { + status = nvme_check_zone_read(ns, slba, _nlb); + if (status) { + return status; + } + } + + nlb += _nlb; + } + + if (nlb > le32_to_cpu(ns->id_ns.mcl)) { + return NVME_CMD_SIZE_LIMIT | NVME_DNR; + } + + bounce = bouncep = g_malloc(nvme_l2b(ns, nlb)); + + block_acct_start(blk_get_stats(ns->blkconf.blk), &req->acct, 0, + BLOCK_ACCT_READ); + + ctx = g_new(struct nvme_copy_ctx, 1); + + ctx->bounce = bounce; + ctx->nlb = nlb; + ctx->copies = 1; + + req->opaque = ctx; + + for (i = 0; i < nr; i++) { + uint64_t slba = le64_to_cpu(range[i].slba); + uint32_t nlb = le16_to_cpu(range[i].nlb) + 1; + + size_t len = nvme_l2b(ns, nlb); + int64_t offset = nvme_l2b(ns, slba); + + trace_pci_nvme_copy_source_range(slba, nlb); + + struct nvme_copy_in_ctx *in_ctx = g_new(struct nvme_copy_in_ctx, 1); + in_ctx->req = req; + + qemu_iovec_init(&in_ctx->iov, 1); + qemu_iovec_add(&in_ctx->iov, bouncep, len); + + ctx->copies++; + + blk_aio_preadv(ns->blkconf.blk, offset, &in_ctx->iov, 0, + nvme_aio_copy_in_cb, in_ctx); + + bouncep += len; + } + + /* account for the 1-initialization */ + ctx->copies--; + + if (!ctx->copies) { + nvme_copy_in_complete(req); + } + + return NVME_NO_COMPLETE; +} + static uint16_t nvme_compare(NvmeCtrl *n, NvmeRequest *req) { NvmeRwCmd *rw = (NvmeRwCmd *)&req->cmd; @@ -1594,7 +2006,6 @@ static uint16_t nvme_compare(NvmeCtrl *n, NvmeRequest *req) status = nvme_check_mdts(n, len); if (status) { - trace_pci_nvme_err_mdts(nvme_cid(req), len); return status; } @@ -1615,7 +2026,6 @@ static uint16_t nvme_compare(NvmeCtrl *n, NvmeRequest *req) ctx = g_new(struct nvme_compare_ctx, 1); ctx->bounce = bounce; - ctx->len = len; req->opaque = ctx; @@ -1630,10 +2040,56 @@ static uint16_t nvme_compare(NvmeCtrl *n, NvmeRequest *req) static uint16_t nvme_flush(NvmeCtrl *n, NvmeRequest *req) { - block_acct_start(blk_get_stats(req->ns->blkconf.blk), &req->acct, 0, - BLOCK_ACCT_FLUSH); - req->aiocb = blk_aio_flush(req->ns->blkconf.blk, nvme_rw_cb, req); - return NVME_NO_COMPLETE; + uint32_t nsid = le32_to_cpu(req->cmd.nsid); + uintptr_t *num_flushes = (uintptr_t *)&req->opaque; + uint16_t status; + struct nvme_aio_flush_ctx *ctx; + NvmeNamespace *ns; + + trace_pci_nvme_flush(nvme_cid(req), nsid); + + if (nsid != NVME_NSID_BROADCAST) { + req->ns = nvme_ns(n, nsid); + if (unlikely(!req->ns)) { + return NVME_INVALID_FIELD | NVME_DNR; + } + + block_acct_start(blk_get_stats(req->ns->blkconf.blk), &req->acct, 0, + BLOCK_ACCT_FLUSH); + req->aiocb = blk_aio_flush(req->ns->blkconf.blk, nvme_rw_cb, req); + return NVME_NO_COMPLETE; + } + + /* 1-initialize; see comment in nvme_dsm */ + *num_flushes = 1; + + for (int i = 1; i <= n->num_namespaces; i++) { + ns = nvme_ns(n, i); + if (!ns) { + continue; + } + + ctx = g_new(struct nvme_aio_flush_ctx, 1); + ctx->req = req; + ctx->ns = ns; + + (*num_flushes)++; + + block_acct_start(blk_get_stats(ns->blkconf.blk), &ctx->acct, 0, + BLOCK_ACCT_FLUSH); + blk_aio_flush(ns->blkconf.blk, nvme_aio_flush_cb, ctx); + } + + /* account for the 1-initialization */ + (*num_flushes)--; + + if (*num_flushes) { + status = NVME_NO_COMPLETE; + } else { + status = req->status; + } + + return status; } static uint16_t nvme_read(NvmeCtrl *n, NvmeRequest *req) @@ -1651,7 +2107,6 @@ static uint16_t nvme_read(NvmeCtrl *n, NvmeRequest *req) status = nvme_check_mdts(n, data_size); if (status) { - trace_pci_nvme_err_mdts(nvme_cid(req), data_size); goto invalid; } @@ -1669,7 +2124,7 @@ static uint16_t nvme_read(NvmeCtrl *n, NvmeRequest *req) } } - status = nvme_map_dptr(n, data_size, req); + status = nvme_map_dptr(n, &req->sg, data_size, &req->cmd); if (status) { goto invalid; } @@ -1685,13 +2140,7 @@ static uint16_t nvme_read(NvmeCtrl *n, NvmeRequest *req) block_acct_start(blk_get_stats(blk), &req->acct, data_size, BLOCK_ACCT_READ); - if (req->qsg.sg) { - req->aiocb = dma_blk_read(blk, &req->qsg, data_offset, - BDRV_SECTOR_SIZE, nvme_rw_cb, req); - } else { - req->aiocb = blk_aio_preadv(blk, data_offset, &req->iov, 0, - nvme_rw_cb, req); - } + nvme_blk_read(blk, data_offset, nvme_rw_cb, req); return NVME_NO_COMPLETE; invalid: @@ -1719,7 +2168,6 @@ static uint16_t nvme_do_write(NvmeCtrl *n, NvmeRequest *req, bool append, if (!wrz) { status = nvme_check_mdts(n, data_size); if (status) { - trace_pci_nvme_err_mdts(nvme_cid(req), data_size); goto invalid; } } @@ -1740,48 +2188,40 @@ static uint16_t nvme_do_write(NvmeCtrl *n, NvmeRequest *req, bool append, goto invalid; } - if (nvme_l2b(ns, nlb) > (n->page_size << n->zasl)) { - trace_pci_nvme_err_append_too_large(slba, nlb, n->zasl); - status = NVME_INVALID_FIELD; - goto invalid; + if (n->params.zasl && data_size > n->page_size << n->params.zasl) { + trace_pci_nvme_err_zasl(data_size); + return NVME_INVALID_FIELD | NVME_DNR; } slba = zone->w_ptr; res->slba = cpu_to_le64(slba); } - status = nvme_check_zone_write(n, ns, zone, slba, nlb); + status = nvme_check_zone_write(ns, zone, slba, nlb); if (status) { goto invalid; } - status = nvme_auto_open_zone(ns, zone); + status = nvme_zrm_auto(ns, zone); if (status) { goto invalid; } - nvme_advance_zone_wp(ns, zone, nlb); + zone->w_ptr += nlb; } data_offset = nvme_l2b(ns, slba); if (!wrz) { - status = nvme_map_dptr(n, data_size, req); + status = nvme_map_dptr(n, &req->sg, data_size, &req->cmd); if (status) { goto invalid; } block_acct_start(blk_get_stats(blk), &req->acct, data_size, BLOCK_ACCT_WRITE); - if (req->qsg.sg) { - req->aiocb = dma_blk_write(blk, &req->qsg, data_offset, - BDRV_SECTOR_SIZE, nvme_rw_cb, req); - } else { - req->aiocb = blk_aio_pwritev(blk, data_offset, &req->iov, 0, - nvme_rw_cb, req); - } + nvme_blk_write(blk, data_offset, nvme_rw_cb, req); } else { - block_acct_start(blk_get_stats(blk), &req->acct, 0, BLOCK_ACCT_WRITE); req->aiocb = blk_aio_pwrite_zeroes(blk, data_offset, data_size, BDRV_REQ_MAY_UNMAP, nvme_rw_cb, req); @@ -1846,73 +2286,19 @@ enum NvmeZoneProcessingMask { static uint16_t nvme_open_zone(NvmeNamespace *ns, NvmeZone *zone, NvmeZoneState state, NvmeRequest *req) { - uint16_t status; - - switch (state) { - case NVME_ZONE_STATE_EMPTY: - status = nvme_aor_check(ns, 1, 0); - if (status) { - return status; - } - nvme_aor_inc_active(ns); - /* fall through */ - case NVME_ZONE_STATE_CLOSED: - status = nvme_aor_check(ns, 0, 1); - if (status) { - if (state == NVME_ZONE_STATE_EMPTY) { - nvme_aor_dec_active(ns); - } - return status; - } - nvme_aor_inc_open(ns); - /* fall through */ - case NVME_ZONE_STATE_IMPLICITLY_OPEN: - nvme_assign_zone_state(ns, zone, NVME_ZONE_STATE_EXPLICITLY_OPEN); - /* fall through */ - case NVME_ZONE_STATE_EXPLICITLY_OPEN: - return NVME_SUCCESS; - default: - return NVME_ZONE_INVAL_TRANSITION; - } + return nvme_zrm_open(ns, zone); } static uint16_t nvme_close_zone(NvmeNamespace *ns, NvmeZone *zone, NvmeZoneState state, NvmeRequest *req) { - switch (state) { - case NVME_ZONE_STATE_EXPLICITLY_OPEN: - case NVME_ZONE_STATE_IMPLICITLY_OPEN: - nvme_aor_dec_open(ns); - nvme_assign_zone_state(ns, zone, NVME_ZONE_STATE_CLOSED); - /* fall through */ - case NVME_ZONE_STATE_CLOSED: - return NVME_SUCCESS; - default: - return NVME_ZONE_INVAL_TRANSITION; - } + return nvme_zrm_close(ns, zone); } static uint16_t nvme_finish_zone(NvmeNamespace *ns, NvmeZone *zone, NvmeZoneState state, NvmeRequest *req) { - switch (state) { - case NVME_ZONE_STATE_EXPLICITLY_OPEN: - case NVME_ZONE_STATE_IMPLICITLY_OPEN: - nvme_aor_dec_open(ns); - /* fall through */ - case NVME_ZONE_STATE_CLOSED: - nvme_aor_dec_active(ns); - /* fall through */ - case NVME_ZONE_STATE_EMPTY: - zone->w_ptr = nvme_zone_wr_boundary(zone); - zone->d.wp = zone->w_ptr; - nvme_assign_zone_state(ns, zone, NVME_ZONE_STATE_FULL); - /* fall through */ - case NVME_ZONE_STATE_FULL: - return NVME_SUCCESS; - default: - return NVME_ZONE_INVAL_TRANSITION; - } + return nvme_zrm_finish(ns, zone); } static uint16_t nvme_reset_zone(NvmeNamespace *ns, NvmeZone *zone, @@ -2168,8 +2554,7 @@ static uint16_t nvme_zone_mgmt_send(NvmeCtrl *n, NvmeRequest *req) return NVME_INVALID_FIELD | NVME_DNR; } zd_ext = nvme_get_zd_extension(ns, zone_idx); - status = nvme_dma(n, zd_ext, ns->params.zd_extension_size, - DMA_DIRECTION_TO_DEVICE, req); + status = nvme_h2c(n, zd_ext, ns->params.zd_extension_size, req); if (status) { trace_pci_nvme_err_zd_extension_map_error(zone_idx); return status; @@ -2267,7 +2652,6 @@ static uint16_t nvme_zone_mgmt_recv(NvmeCtrl *n, NvmeRequest *req) status = nvme_check_mdts(n, data_size); if (status) { - trace_pci_nvme_err_mdts(nvme_cid(req), data_size); return status; } @@ -2324,8 +2708,7 @@ static uint16_t nvme_zone_mgmt_recv(NvmeCtrl *n, NvmeRequest *req) } } - status = nvme_dma(n, (uint8_t *)buf, data_size, - DMA_DIRECTION_FROM_DEVICE, req); + status = nvme_c2h(n, (uint8_t *)buf, data_size, req); g_free(buf); @@ -2343,6 +2726,29 @@ static uint16_t nvme_io_cmd(NvmeCtrl *n, NvmeRequest *req) return NVME_INVALID_NSID | NVME_DNR; } + /* + * In the base NVM command set, Flush may apply to all namespaces + * (indicated by NSID being set to 0xFFFFFFFF). But if that feature is used + * along with TP 4056 (Namespace Types), it may be pretty screwed up. + * + * If NSID is indeed set to 0xFFFFFFFF, we simply cannot associate the + * opcode with a specific command since we cannot determine a unique I/O + * command set. Opcode 0x0 could have any other meaning than something + * equivalent to flushing and say it DOES have completely different + * semantics in some other command set - does an NSID of 0xFFFFFFFF then + * mean "for all namespaces, apply whatever command set specific command + * that uses the 0x0 opcode?" Or does it mean "for all namespaces, apply + * whatever command that uses the 0x0 opcode if, and only if, it allows + * NSID to be 0xFFFFFFFF"? + * + * Anyway (and luckily), for now, we do not care about this since the + * device only supports namespace types that includes the NVM Flush command + * (NVM and Zoned), so always do an NVM Flush. + */ + if (req->cmd.opcode == NVME_CMD_FLUSH) { + return nvme_flush(n, req); + } + req->ns = nvme_ns(n, nsid); if (unlikely(!req->ns)) { return NVME_INVALID_FIELD | NVME_DNR; @@ -2354,8 +2760,6 @@ static uint16_t nvme_io_cmd(NvmeCtrl *n, NvmeRequest *req) } switch (req->cmd.opcode) { - case NVME_CMD_FLUSH: - return nvme_flush(n, req); case NVME_CMD_WRITE_ZEROES: return nvme_write_zeroes(n, req); case NVME_CMD_ZONE_APPEND: @@ -2368,6 +2772,8 @@ static uint16_t nvme_io_cmd(NvmeCtrl *n, NvmeRequest *req) return nvme_compare(n, req); case NVME_CMD_DSM: return nvme_dsm(n, req); + case NVME_CMD_COPY: + return nvme_copy(n, req); case NVME_CMD_ZONE_MGMT_SEND: return nvme_zone_mgmt_send(n, req); case NVME_CMD_ZONE_MGMT_RECV: @@ -2568,8 +2974,7 @@ static uint16_t nvme_smart_info(NvmeCtrl *n, uint8_t rae, uint32_t buf_len, nvme_clear_events(n, NVME_AER_TYPE_SMART); } - return nvme_dma(n, (uint8_t *) &smart + off, trans_len, - DMA_DIRECTION_FROM_DEVICE, req); + return nvme_c2h(n, (uint8_t *) &smart + off, trans_len, req); } static uint16_t nvme_fw_log_info(NvmeCtrl *n, uint32_t buf_len, uint64_t off, @@ -2587,8 +2992,7 @@ static uint16_t nvme_fw_log_info(NvmeCtrl *n, uint32_t buf_len, uint64_t off, strpadcpy((char *)&fw_log.frs1, sizeof(fw_log.frs1), "1.0", ' '); trans_len = MIN(sizeof(fw_log) - off, buf_len); - return nvme_dma(n, (uint8_t *) &fw_log + off, trans_len, - DMA_DIRECTION_FROM_DEVICE, req); + return nvme_c2h(n, (uint8_t *) &fw_log + off, trans_len, req); } static uint16_t nvme_error_info(NvmeCtrl *n, uint8_t rae, uint32_t buf_len, @@ -2608,8 +3012,49 @@ static uint16_t nvme_error_info(NvmeCtrl *n, uint8_t rae, uint32_t buf_len, memset(&errlog, 0x0, sizeof(errlog)); trans_len = MIN(sizeof(errlog) - off, buf_len); - return nvme_dma(n, (uint8_t *)&errlog, trans_len, - DMA_DIRECTION_FROM_DEVICE, req); + return nvme_c2h(n, (uint8_t *)&errlog, trans_len, req); +} + +static uint16_t nvme_changed_nslist(NvmeCtrl *n, uint8_t rae, uint32_t buf_len, + uint64_t off, NvmeRequest *req) +{ + uint32_t nslist[1024]; + uint32_t trans_len; + int i = 0; + uint32_t nsid; + + memset(nslist, 0x0, sizeof(nslist)); + trans_len = MIN(sizeof(nslist) - off, buf_len); + + while ((nsid = find_first_bit(n->changed_nsids, NVME_CHANGED_NSID_SIZE)) != + NVME_CHANGED_NSID_SIZE) { + /* + * If more than 1024 namespaces, the first entry in the log page should + * be set to 0xffffffff and the others to 0 as spec. + */ + if (i == ARRAY_SIZE(nslist)) { + memset(nslist, 0x0, sizeof(nslist)); + nslist[0] = 0xffffffff; + break; + } + + nslist[i++] = nsid; + clear_bit(nsid, n->changed_nsids); + } + + /* + * Remove all the remaining list entries in case returns directly due to + * more than 1024 namespaces. + */ + if (nslist[0] == 0xffffffff) { + bitmap_zero(n->changed_nsids, NVME_CHANGED_NSID_SIZE); + } + + if (!rae) { + nvme_clear_events(n, NVME_AER_TYPE_NOTICE); + } + + return nvme_c2h(n, ((uint8_t *)nslist) + off, trans_len, req); } static uint16_t nvme_cmd_effects(NvmeCtrl *n, uint8_t csi, uint32_t buf_len, @@ -2649,8 +3094,7 @@ static uint16_t nvme_cmd_effects(NvmeCtrl *n, uint8_t csi, uint32_t buf_len, trans_len = MIN(sizeof(log) - off, buf_len); - return nvme_dma(n, ((uint8_t *)&log) + off, trans_len, - DMA_DIRECTION_FROM_DEVICE, req); + return nvme_c2h(n, ((uint8_t *)&log) + off, trans_len, req); } static uint16_t nvme_get_log(NvmeCtrl *n, NvmeRequest *req) @@ -2686,7 +3130,6 @@ static uint16_t nvme_get_log(NvmeCtrl *n, NvmeRequest *req) status = nvme_check_mdts(n, len); if (status) { - trace_pci_nvme_err_mdts(nvme_cid(req), len); return status; } @@ -2697,6 +3140,8 @@ static uint16_t nvme_get_log(NvmeCtrl *n, NvmeRequest *req) return nvme_smart_info(n, rae, len, off, req); case NVME_LOG_FW_SLOT_INFO: return nvme_fw_log_info(n, len, off, req); + case NVME_LOG_CHANGED_NSLIST: + return nvme_changed_nslist(n, rae, len, off, req); case NVME_LOG_CMD_EFFECTS: return nvme_cmd_effects(n, csi, len, off, req); default: @@ -2819,7 +3264,7 @@ static uint16_t nvme_rpt_empty_id_struct(NvmeCtrl *n, NvmeRequest *req) { uint8_t id[NVME_IDENTIFY_DATA_SIZE] = {}; - return nvme_dma(n, id, sizeof(id), DMA_DIRECTION_FROM_DEVICE, req); + return nvme_c2h(n, id, sizeof(id), req); } static inline bool nvme_csi_has_nvm_support(NvmeNamespace *ns) @@ -2836,31 +3281,33 @@ static uint16_t nvme_identify_ctrl(NvmeCtrl *n, NvmeRequest *req) { trace_pci_nvme_identify_ctrl(); - return nvme_dma(n, (uint8_t *)&n->id_ctrl, sizeof(n->id_ctrl), - DMA_DIRECTION_FROM_DEVICE, req); + return nvme_c2h(n, (uint8_t *)&n->id_ctrl, sizeof(n->id_ctrl), req); } static uint16_t nvme_identify_ctrl_csi(NvmeCtrl *n, NvmeRequest *req) { NvmeIdentify *c = (NvmeIdentify *)&req->cmd; - NvmeIdCtrlZoned id = {}; + uint8_t id[NVME_IDENTIFY_DATA_SIZE] = {}; trace_pci_nvme_identify_ctrl_csi(c->csi); - if (c->csi == NVME_CSI_NVM) { - return nvme_rpt_empty_id_struct(n, req); - } else if (c->csi == NVME_CSI_ZONED) { - if (n->params.zasl_bs) { - id.zasl = n->zasl; - } - return nvme_dma(n, (uint8_t *)&id, sizeof(id), - DMA_DIRECTION_FROM_DEVICE, req); + switch (c->csi) { + case NVME_CSI_NVM: + ((NvmeIdCtrlNvm *)&id)->dmrsl = cpu_to_le32(n->dmrsl); + break; + + case NVME_CSI_ZONED: + ((NvmeIdCtrlZoned *)&id)->zasl = n->params.zasl; + break; + + default: + return NVME_INVALID_FIELD | NVME_DNR; } - return NVME_INVALID_FIELD | NVME_DNR; + return nvme_c2h(n, id, sizeof(id), req); } -static uint16_t nvme_identify_ns(NvmeCtrl *n, NvmeRequest *req) +static uint16_t nvme_identify_ns(NvmeCtrl *n, NvmeRequest *req, bool active) { NvmeNamespace *ns; NvmeIdentify *c = (NvmeIdentify *)&req->cmd; @@ -2874,18 +3321,64 @@ static uint16_t nvme_identify_ns(NvmeCtrl *n, NvmeRequest *req) ns = nvme_ns(n, nsid); if (unlikely(!ns)) { - return nvme_rpt_empty_id_struct(n, req); + if (!active) { + ns = nvme_subsys_ns(n->subsys, nsid); + if (!ns) { + return nvme_rpt_empty_id_struct(n, req); + } + } else { + return nvme_rpt_empty_id_struct(n, req); + } } if (c->csi == NVME_CSI_NVM && nvme_csi_has_nvm_support(ns)) { - return nvme_dma(n, (uint8_t *)&ns->id_ns, sizeof(NvmeIdNs), - DMA_DIRECTION_FROM_DEVICE, req); + return nvme_c2h(n, (uint8_t *)&ns->id_ns, sizeof(NvmeIdNs), req); } return NVME_INVALID_CMD_SET | NVME_DNR; } -static uint16_t nvme_identify_ns_csi(NvmeCtrl *n, NvmeRequest *req) +static uint16_t nvme_identify_ns_attached_list(NvmeCtrl *n, NvmeRequest *req) +{ + NvmeIdentify *c = (NvmeIdentify *)&req->cmd; + uint16_t min_id = le16_to_cpu(c->ctrlid); + uint16_t list[NVME_CONTROLLER_LIST_SIZE] = {}; + uint16_t *ids = &list[1]; + NvmeNamespace *ns; + NvmeCtrl *ctrl; + int cntlid, nr_ids = 0; + + trace_pci_nvme_identify_ns_attached_list(min_id); + + if (c->nsid == NVME_NSID_BROADCAST) { + return NVME_INVALID_FIELD | NVME_DNR; + } + + ns = nvme_subsys_ns(n->subsys, c->nsid); + if (!ns) { + return NVME_INVALID_FIELD | NVME_DNR; + } + + for (cntlid = min_id; cntlid < ARRAY_SIZE(n->subsys->ctrls); cntlid++) { + ctrl = nvme_subsys_ctrl(n->subsys, cntlid); + if (!ctrl) { + continue; + } + + if (!nvme_ns_is_attached(ctrl, ns)) { + continue; + } + + ids[nr_ids++] = cntlid; + } + + list[0] = nr_ids; + + return nvme_c2h(n, (uint8_t *)list, sizeof(list), req); +} + +static uint16_t nvme_identify_ns_csi(NvmeCtrl *n, NvmeRequest *req, + bool active) { NvmeNamespace *ns; NvmeIdentify *c = (NvmeIdentify *)&req->cmd; @@ -2899,20 +3392,28 @@ static uint16_t nvme_identify_ns_csi(NvmeCtrl *n, NvmeRequest *req) ns = nvme_ns(n, nsid); if (unlikely(!ns)) { - return nvme_rpt_empty_id_struct(n, req); + if (!active) { + ns = nvme_subsys_ns(n->subsys, nsid); + if (!ns) { + return nvme_rpt_empty_id_struct(n, req); + } + } else { + return nvme_rpt_empty_id_struct(n, req); + } } if (c->csi == NVME_CSI_NVM && nvme_csi_has_nvm_support(ns)) { return nvme_rpt_empty_id_struct(n, req); } else if (c->csi == NVME_CSI_ZONED && ns->csi == NVME_CSI_ZONED) { - return nvme_dma(n, (uint8_t *)ns->id_ns_zoned, sizeof(NvmeIdNsZoned), - DMA_DIRECTION_FROM_DEVICE, req); + return nvme_c2h(n, (uint8_t *)ns->id_ns_zoned, sizeof(NvmeIdNsZoned), + req); } return NVME_INVALID_FIELD | NVME_DNR; } -static uint16_t nvme_identify_nslist(NvmeCtrl *n, NvmeRequest *req) +static uint16_t nvme_identify_nslist(NvmeCtrl *n, NvmeRequest *req, + bool active) { NvmeNamespace *ns; NvmeIdentify *c = (NvmeIdentify *)&req->cmd; @@ -2937,7 +3438,14 @@ static uint16_t nvme_identify_nslist(NvmeCtrl *n, NvmeRequest *req) for (i = 1; i <= n->num_namespaces; i++) { ns = nvme_ns(n, i); if (!ns) { - continue; + if (!active) { + ns = nvme_subsys_ns(n->subsys, i); + if (!ns) { + continue; + } + } else { + continue; + } } if (ns->params.nsid <= min_nsid) { continue; @@ -2948,10 +3456,11 @@ static uint16_t nvme_identify_nslist(NvmeCtrl *n, NvmeRequest *req) } } - return nvme_dma(n, list, data_len, DMA_DIRECTION_FROM_DEVICE, req); + return nvme_c2h(n, list, data_len, req); } -static uint16_t nvme_identify_nslist_csi(NvmeCtrl *n, NvmeRequest *req) +static uint16_t nvme_identify_nslist_csi(NvmeCtrl *n, NvmeRequest *req, + bool active) { NvmeNamespace *ns; NvmeIdentify *c = (NvmeIdentify *)&req->cmd; @@ -2977,7 +3486,14 @@ static uint16_t nvme_identify_nslist_csi(NvmeCtrl *n, NvmeRequest *req) for (i = 1; i <= n->num_namespaces; i++) { ns = nvme_ns(n, i); if (!ns) { - continue; + if (!active) { + ns = nvme_subsys_ns(n->subsys, i); + if (!ns) { + continue; + } + } else { + continue; + } } if (ns->params.nsid <= min_nsid || c->csi != ns->csi) { continue; @@ -2988,7 +3504,7 @@ static uint16_t nvme_identify_nslist_csi(NvmeCtrl *n, NvmeRequest *req) } } - return nvme_dma(n, list, data_len, DMA_DIRECTION_FROM_DEVICE, req); + return nvme_c2h(n, list, data_len, req); } static uint16_t nvme_identify_ns_descr_list(NvmeCtrl *n, NvmeRequest *req) @@ -3035,7 +3551,7 @@ static uint16_t nvme_identify_ns_descr_list(NvmeCtrl *n, NvmeRequest *req) ns_descrs->csi.hdr.nidl = NVME_NIDL_CSI; ns_descrs->csi.v = ns->csi; - return nvme_dma(n, list, sizeof(list), DMA_DIRECTION_FROM_DEVICE, req); + return nvme_c2h(n, list, sizeof(list), req); } static uint16_t nvme_identify_cmd_set(NvmeCtrl *n, NvmeRequest *req) @@ -3048,34 +3564,39 @@ static uint16_t nvme_identify_cmd_set(NvmeCtrl *n, NvmeRequest *req) NVME_SET_CSI(*list, NVME_CSI_NVM); NVME_SET_CSI(*list, NVME_CSI_ZONED); - return nvme_dma(n, list, data_len, DMA_DIRECTION_FROM_DEVICE, req); + return nvme_c2h(n, list, data_len, req); } static uint16_t nvme_identify(NvmeCtrl *n, NvmeRequest *req) { NvmeIdentify *c = (NvmeIdentify *)&req->cmd; - switch (le32_to_cpu(c->cns)) { + trace_pci_nvme_identify(nvme_cid(req), c->cns, le16_to_cpu(c->ctrlid), + c->csi); + + switch (c->cns) { case NVME_ID_CNS_NS: - /* fall through */ + return nvme_identify_ns(n, req, true); case NVME_ID_CNS_NS_PRESENT: - return nvme_identify_ns(n, req); + return nvme_identify_ns(n, req, false); + case NVME_ID_CNS_NS_ATTACHED_CTRL_LIST: + return nvme_identify_ns_attached_list(n, req); case NVME_ID_CNS_CS_NS: - /* fall through */ + return nvme_identify_ns_csi(n, req, true); case NVME_ID_CNS_CS_NS_PRESENT: - return nvme_identify_ns_csi(n, req); + return nvme_identify_ns_csi(n, req, false); case NVME_ID_CNS_CTRL: return nvme_identify_ctrl(n, req); case NVME_ID_CNS_CS_CTRL: return nvme_identify_ctrl_csi(n, req); case NVME_ID_CNS_NS_ACTIVE_LIST: - /* fall through */ + return nvme_identify_nslist(n, req, true); case NVME_ID_CNS_NS_PRESENT_LIST: - return nvme_identify_nslist(n, req); + return nvme_identify_nslist(n, req, false); case NVME_ID_CNS_CS_NS_ACTIVE_LIST: - /* fall through */ + return nvme_identify_nslist_csi(n, req, true); case NVME_ID_CNS_CS_NS_PRESENT_LIST: - return nvme_identify_nslist_csi(n, req); + return nvme_identify_nslist_csi(n, req, false); case NVME_ID_CNS_NS_DESCR_LIST: return nvme_identify_ns_descr_list(n, req); case NVME_ID_CNS_IO_COMMAND_SET: @@ -3137,8 +3658,7 @@ static uint16_t nvme_get_feature_timestamp(NvmeCtrl *n, NvmeRequest *req) { uint64_t timestamp = nvme_get_timestamp(n); - return nvme_dma(n, (uint8_t *)×tamp, sizeof(timestamp), - DMA_DIRECTION_FROM_DEVICE, req); + return nvme_c2h(n, (uint8_t *)×tamp, sizeof(timestamp), req); } static uint16_t nvme_get_feature(NvmeCtrl *n, NvmeRequest *req) @@ -3299,8 +3819,7 @@ static uint16_t nvme_set_feature_timestamp(NvmeCtrl *n, NvmeRequest *req) uint16_t ret; uint64_t timestamp; - ret = nvme_dma(n, (uint8_t *)×tamp, sizeof(timestamp), - DMA_DIRECTION_TO_DEVICE, req); + ret = nvme_h2c(n, (uint8_t *)×tamp, sizeof(timestamp), req); if (ret) { return ret; } @@ -3472,6 +3991,71 @@ static uint16_t nvme_aer(NvmeCtrl *n, NvmeRequest *req) return NVME_NO_COMPLETE; } +static void __nvme_select_ns_iocs(NvmeCtrl *n, NvmeNamespace *ns); +static uint16_t nvme_ns_attachment(NvmeCtrl *n, NvmeRequest *req) +{ + NvmeNamespace *ns; + NvmeCtrl *ctrl; + uint16_t list[NVME_CONTROLLER_LIST_SIZE] = {}; + uint32_t nsid = le32_to_cpu(req->cmd.nsid); + uint32_t dw10 = le32_to_cpu(req->cmd.cdw10); + bool attach = !(dw10 & 0xf); + uint16_t *nr_ids = &list[0]; + uint16_t *ids = &list[1]; + uint16_t ret; + int i; + + trace_pci_nvme_ns_attachment(nvme_cid(req), dw10 & 0xf); + + ns = nvme_subsys_ns(n->subsys, nsid); + if (!ns) { + return NVME_INVALID_FIELD | NVME_DNR; + } + + ret = nvme_h2c(n, (uint8_t *)list, 4096, req); + if (ret) { + return ret; + } + + if (!*nr_ids) { + return NVME_NS_CTRL_LIST_INVALID | NVME_DNR; + } + + for (i = 0; i < *nr_ids; i++) { + ctrl = nvme_subsys_ctrl(n->subsys, ids[i]); + if (!ctrl) { + return NVME_NS_CTRL_LIST_INVALID | NVME_DNR; + } + + if (attach) { + if (nvme_ns_is_attached(ctrl, ns)) { + return NVME_NS_ALREADY_ATTACHED | NVME_DNR; + } + + nvme_ns_attach(ctrl, ns); + __nvme_select_ns_iocs(ctrl, ns); + } else { + if (!nvme_ns_is_attached(ctrl, ns)) { + return NVME_NS_NOT_ATTACHED | NVME_DNR; + } + + nvme_ns_detach(ctrl, ns); + } + + /* + * Add namespace id to the changed namespace id list for event clearing + * via Get Log Page command. + */ + if (!test_and_set_bit(nsid, ctrl->changed_nsids)) { + nvme_enqueue_event(ctrl, NVME_AER_TYPE_NOTICE, + NVME_AER_INFO_NOTICE_NS_ATTR_CHANGED, + NVME_LOG_CHANGED_NSLIST); + } + } + + return NVME_SUCCESS; +} + static uint16_t nvme_admin_cmd(NvmeCtrl *n, NvmeRequest *req) { trace_pci_nvme_admin_cmd(nvme_cid(req), nvme_sqid(req), req->cmd.opcode, @@ -3482,6 +4066,11 @@ static uint16_t nvme_admin_cmd(NvmeCtrl *n, NvmeRequest *req) return NVME_INVALID_OPCODE | NVME_DNR; } + /* SGLs shall not be used for Admin commands in NVMe over PCIe */ + if (NVME_CMD_FLAGS_PSDT(req->cmd.flags) != NVME_PSDT_PRP) { + return NVME_INVALID_FIELD | NVME_DNR; + } + switch (req->cmd.opcode) { case NVME_ADM_CMD_DELETE_SQ: return nvme_del_sq(n, req); @@ -3503,6 +4092,8 @@ static uint16_t nvme_admin_cmd(NvmeCtrl *n, NvmeRequest *req) return nvme_get_feature(n, req); case NVME_ADM_CMD_ASYNC_EV_REQ: return nvme_aer(n, req); + case NVME_ADM_CMD_NS_ATTACHMENT: + return nvme_ns_attachment(n, req); default: assert(false); } @@ -3604,6 +4195,25 @@ static void nvme_ctrl_shutdown(NvmeCtrl *n) } } +static void __nvme_select_ns_iocs(NvmeCtrl *n, NvmeNamespace *ns) +{ + ns->iocs = nvme_cse_iocs_none; + switch (ns->csi) { + case NVME_CSI_NVM: + if (NVME_CC_CSS(n->bar.cc) != NVME_CC_CSS_ADMIN_ONLY) { + ns->iocs = nvme_cse_iocs_nvm; + } + break; + case NVME_CSI_ZONED: + if (NVME_CC_CSS(n->bar.cc) == NVME_CC_CSS_CSI) { + ns->iocs = nvme_cse_iocs_zoned; + } else if (NVME_CC_CSS(n->bar.cc) == NVME_CC_CSS_NVM) { + ns->iocs = nvme_cse_iocs_nvm; + } + break; + } +} + static void nvme_select_ns_iocs(NvmeCtrl *n) { NvmeNamespace *ns; @@ -3614,21 +4224,8 @@ static void nvme_select_ns_iocs(NvmeCtrl *n) if (!ns) { continue; } - ns->iocs = nvme_cse_iocs_none; - switch (ns->csi) { - case NVME_CSI_NVM: - if (NVME_CC_CSS(n->bar.cc) != NVME_CC_CSS_ADMIN_ONLY) { - ns->iocs = nvme_cse_iocs_nvm; - } - break; - case NVME_CSI_ZONED: - if (NVME_CC_CSS(n->bar.cc) == NVME_CC_CSS_CSI) { - ns->iocs = nvme_cse_iocs_zoned; - } else if (NVME_CC_CSS(n->bar.cc) == NVME_CC_CSS_NVM) { - ns->iocs = nvme_cse_iocs_nvm; - } - break; - } + + __nvme_select_ns_iocs(n, ns); } } @@ -3726,17 +4323,6 @@ static int nvme_start_ctrl(NvmeCtrl *n) nvme_init_sq(&n->admin_sq, n, n->bar.asq, 0, 0, NVME_AQA_ASQS(n->bar.aqa) + 1); - if (!n->params.zasl_bs) { - n->zasl = n->params.mdts; - } else { - if (n->params.zasl_bs < n->page_size) { - trace_pci_nvme_err_startfail_zasl_too_small(n->params.zasl_bs, - n->page_size); - return -1; - } - n->zasl = 31 - clz32(n->params.zasl_bs / n->page_size); - } - nvme_set_timestamp(n, 0ULL); QTAILQ_INIT(&n->aer_queue); @@ -4245,11 +4831,10 @@ static void nvme_check_constraints(NvmeCtrl *n, Error **errp) host_memory_backend_set_mapped(n->pmr.dev, true); } - if (n->params.zasl_bs) { - if (!is_power_of_2(n->params.zasl_bs)) { - error_setg(errp, "zone append size limit has to be a power of 2"); - return; - } + if (n->params.zasl > n->params.mdts) { + error_setg(errp, "zoned.zasl (Zone Append Size Limit) must be less " + "than or equal to mdts (Maximum Data Transfer Size)"); + return; } } @@ -4267,6 +4852,20 @@ static void nvme_init_state(NvmeCtrl *n) n->aer_reqs = g_new0(NvmeRequest *, n->params.aerl + 1); } +static int nvme_attach_namespace(NvmeCtrl *n, NvmeNamespace *ns, Error **errp) +{ + if (nvme_ns_is_attached(n, ns)) { + error_setg(errp, + "namespace %d is already attached to controller %d", + nvme_nsid(ns), n->cntlid); + return -1; + } + + nvme_ns_attach(n, ns); + + return 0; +} + int nvme_register_namespace(NvmeCtrl *n, NvmeNamespace *ns, Error **errp) { uint32_t nsid = nvme_nsid(ns); @@ -4298,7 +4897,26 @@ int nvme_register_namespace(NvmeCtrl *n, NvmeNamespace *ns, Error **errp) trace_pci_nvme_register_namespace(nsid); - n->namespaces[nsid - 1] = ns; + /* + * If subsys is not given, namespae is always attached to the controller + * because there's no subsystem to manage namespace allocation. + */ + if (!n->subsys) { + if (ns->params.detached) { + error_setg(errp, + "detached needs nvme-subsys specified nvme or nvme-ns"); + return -1; + } + + return nvme_attach_namespace(n, ns, errp); + } else { + if (!ns->params.detached) { + return nvme_attach_namespace(n, ns, errp); + } + } + + n->dmrsl = MIN_NON_ZERO(n->dmrsl, + BDRV_REQUEST_MAX_BYTES / nvme_l2b(ns, 1)); return 0; } @@ -4405,24 +5023,49 @@ static int nvme_init_pci(NvmeCtrl *n, PCIDevice *pci_dev, Error **errp) return 0; } +static void nvme_init_subnqn(NvmeCtrl *n) +{ + NvmeSubsystem *subsys = n->subsys; + NvmeIdCtrl *id = &n->id_ctrl; + + if (!subsys) { + snprintf((char *)id->subnqn, sizeof(id->subnqn), + "nqn.2019-08.org.qemu:%s", n->params.serial); + } else { + pstrcpy((char *)id->subnqn, sizeof(id->subnqn), (char*)subsys->subnqn); + } +} + static void nvme_init_ctrl(NvmeCtrl *n, PCIDevice *pci_dev) { NvmeIdCtrl *id = &n->id_ctrl; uint8_t *pci_conf = pci_dev->config; - char *subnqn; id->vid = cpu_to_le16(pci_get_word(pci_conf + PCI_VENDOR_ID)); id->ssvid = cpu_to_le16(pci_get_word(pci_conf + PCI_SUBSYSTEM_VENDOR_ID)); strpadcpy((char *)id->mn, sizeof(id->mn), "QEMU NVMe Ctrl", ' '); strpadcpy((char *)id->fr, sizeof(id->fr), "1.0", ' '); strpadcpy((char *)id->sn, sizeof(id->sn), n->params.serial, ' '); + + id->cntlid = cpu_to_le16(n->cntlid); + + id->oaes = cpu_to_le32(NVME_OAES_NS_ATTR); + id->rab = 6; - id->ieee[0] = 0x00; - id->ieee[1] = 0x02; - id->ieee[2] = 0xb3; + + if (n->params.use_intel_id) { + id->ieee[0] = 0xb3; + id->ieee[1] = 0x02; + id->ieee[2] = 0x00; + } else { + id->ieee[0] = 0x00; + id->ieee[1] = 0x54; + id->ieee[2] = 0x52; + } + id->mdts = n->params.mdts; id->ver = cpu_to_le32(NVME_SPEC_VER); - id->oacs = cpu_to_le16(0); + id->oacs = cpu_to_le16(NVME_OACS_NS_MGMT); id->cntrltype = 0x1; /* @@ -4450,20 +5093,31 @@ static void nvme_init_ctrl(NvmeCtrl *n, PCIDevice *pci_dev) id->nn = cpu_to_le32(n->num_namespaces); id->oncs = cpu_to_le16(NVME_ONCS_WRITE_ZEROES | NVME_ONCS_TIMESTAMP | NVME_ONCS_FEATURES | NVME_ONCS_DSM | - NVME_ONCS_COMPARE); + NVME_ONCS_COMPARE | NVME_ONCS_COPY); - id->vwc = (0x2 << 1) | 0x1; + /* + * NOTE: If this device ever supports a command set that does NOT use 0x0 + * as a Flush-equivalent operation, support for the broadcast NSID in Flush + * should probably be removed. + * + * See comment in nvme_io_cmd. + */ + id->vwc = NVME_VWC_NSID_BROADCAST_SUPPORT | NVME_VWC_PRESENT; + + id->ocfs = cpu_to_le16(NVME_OCFS_COPY_FORMAT_0); id->sgls = cpu_to_le32(NVME_CTRL_SGLS_SUPPORT_NO_ALIGN | NVME_CTRL_SGLS_BITBUCKET); - subnqn = g_strdup_printf("nqn.2019-08.org.qemu:%s", n->params.serial); - strpadcpy((char *)id->subnqn, sizeof(id->subnqn), subnqn, '\0'); - g_free(subnqn); + nvme_init_subnqn(n); id->psd[0].mp = cpu_to_le16(0x9c4); id->psd[0].enlat = cpu_to_le32(0x10); id->psd[0].exlat = cpu_to_le32(0x4); + if (n->subsys) { + id->cmic |= NVME_CMIC_MULTI_CTRL; + } + NVME_CAP_SET_MQES(n->bar.cap, 0x7ff); NVME_CAP_SET_CQR(n->bar.cap, 1); NVME_CAP_SET_TO(n->bar.cap, 0xf); @@ -4478,6 +5132,24 @@ static void nvme_init_ctrl(NvmeCtrl *n, PCIDevice *pci_dev) n->bar.intmc = n->bar.intms = 0; } +static int nvme_init_subsys(NvmeCtrl *n, Error **errp) +{ + int cntlid; + + if (!n->subsys) { + return 0; + } + + cntlid = nvme_subsys_register_ctrl(n, errp); + if (cntlid < 0) { + return -1; + } + + n->cntlid = cntlid; + + return 0; +} + static void nvme_realize(PCIDevice *pci_dev, Error **errp) { NvmeCtrl *n = NVME(pci_dev); @@ -4498,6 +5170,10 @@ static void nvme_realize(PCIDevice *pci_dev, Error **errp) return; } + if (nvme_init_subsys(n, errp)) { + error_propagate(errp, local_err); + return; + } nvme_init_ctrl(n, pci_dev); /* setup a namespace if the controller drive property was given */ @@ -4550,6 +5226,8 @@ static Property nvme_props[] = { DEFINE_BLOCK_PROPERTIES(NvmeCtrl, namespace.blkconf), DEFINE_PROP_LINK("pmrdev", NvmeCtrl, pmr.dev, TYPE_MEMORY_BACKEND, HostMemoryBackend *), + DEFINE_PROP_LINK("subsys", NvmeCtrl, subsys, TYPE_NVME_SUBSYS, + NvmeSubsystem *), DEFINE_PROP_STRING("serial", NvmeCtrl, params.serial), DEFINE_PROP_UINT32("cmb_size_mb", NvmeCtrl, params.cmb_size_mb, 0), DEFINE_PROP_UINT32("num_queues", NvmeCtrl, params.num_queues, 0), @@ -4560,8 +5238,7 @@ static Property nvme_props[] = { DEFINE_PROP_UINT8("mdts", NvmeCtrl, params.mdts, 7), DEFINE_PROP_BOOL("use-intel-id", NvmeCtrl, params.use_intel_id, false), DEFINE_PROP_BOOL("legacy-cmb", NvmeCtrl, params.legacy_cmb, false), - DEFINE_PROP_SIZE32("zoned.append_size_limit", NvmeCtrl, params.zasl_bs, - NVME_DEFAULT_MAX_ZA_SIZE), + DEFINE_PROP_UINT8("zoned.zasl", NvmeCtrl, params.zasl, 0), DEFINE_PROP_END_OF_LIST(), }; diff --git a/hw/block/nvme.h b/hw/block/nvme.h index dee6092bd4..4955d649c7 100644 --- a/hw/block/nvme.h +++ b/hw/block/nvme.h @@ -2,6 +2,7 @@ #define HW_NVME_H #include "block/nvme.h" +#include "nvme-subsys.h" #include "nvme-ns.h" #define NVME_MAX_NAMESPACES 256 @@ -9,6 +10,12 @@ #define NVME_DEFAULT_ZONE_SIZE (128 * MiB) #define NVME_DEFAULT_MAX_ZA_SIZE (128 * KiB) +/* + * Subsystem namespace list for allocated namespaces should be larger than + * attached namespace list in a controller. + */ +QEMU_BUILD_BUG_ON(NVME_MAX_NAMESPACES > NVME_SUBSYS_MAX_NAMESPACES); + typedef struct NvmeParams { char *serial; uint32_t num_queues; /* deprecated since 5.1 */ @@ -19,7 +26,7 @@ typedef struct NvmeParams { uint32_t aer_max_queued; uint8_t mdts; bool use_intel_id; - uint32_t zasl_bs; + uint8_t zasl; bool legacy_cmb; } NvmeParams; @@ -28,6 +35,20 @@ typedef struct NvmeAsyncEvent { NvmeAerResult result; } NvmeAsyncEvent; +enum { + NVME_SG_ALLOC = 1 << 0, + NVME_SG_DMA = 1 << 1, +}; + +typedef struct NvmeSg { + int flags; + + union { + QEMUSGList qsg; + QEMUIOVector iov; + }; +} NvmeSg; + typedef struct NvmeRequest { struct NvmeSQueue *sq; struct NvmeNamespace *ns; @@ -37,8 +58,7 @@ typedef struct NvmeRequest { NvmeCqe cqe; NvmeCmd cmd; BlockAcctCookie acct; - QEMUSGList qsg; - QEMUIOVector iov; + NvmeSg sg; QTAILQ_ENTRY(NvmeRequest)entry; } NvmeRequest; @@ -68,6 +88,7 @@ static inline const char *nvme_io_opc_str(uint8_t opc) case NVME_CMD_COMPARE: return "NVME_NVM_CMD_COMPARE"; case NVME_CMD_WRITE_ZEROES: return "NVME_NVM_CMD_WRITE_ZEROES"; case NVME_CMD_DSM: return "NVME_NVM_CMD_DSM"; + case NVME_CMD_COPY: return "NVME_NVM_CMD_COPY"; case NVME_CMD_ZONE_MGMT_SEND: return "NVME_ZONED_CMD_MGMT_SEND"; case NVME_CMD_ZONE_MGMT_RECV: return "NVME_ZONED_CMD_MGMT_RECV"; case NVME_CMD_ZONE_APPEND: return "NVME_ZONED_CMD_ZONE_APPEND"; @@ -133,6 +154,7 @@ typedef struct NvmeCtrl { NvmeBus bus; BlockConf conf; + uint16_t cntlid; bool qs_created; uint32_t page_size; uint16_t page_bits; @@ -168,9 +190,19 @@ typedef struct NvmeCtrl { QTAILQ_HEAD(, NvmeAsyncEvent) aer_queue; int aer_queued; - uint8_t zasl; + uint32_t dmrsl; + + /* Namespace ID is started with 1 so bitmap should be 1-based */ +#define NVME_CHANGED_NSID_SIZE (NVME_MAX_NAMESPACES + 1) + DECLARE_BITMAP(changed_nsids, NVME_CHANGED_NSID_SIZE); + + NvmeSubsystem *subsys; NvmeNamespace namespace; + /* + * Attached namespaces to this controller. If subsys is not given, all + * namespaces in this list will always be attached. + */ NvmeNamespace *namespaces[NVME_MAX_NAMESPACES]; NvmeSQueue **sq; NvmeCQueue **cq; @@ -189,6 +221,29 @@ static inline NvmeNamespace *nvme_ns(NvmeCtrl *n, uint32_t nsid) return n->namespaces[nsid - 1]; } +static inline bool nvme_ns_is_attached(NvmeCtrl *n, NvmeNamespace *ns) +{ + int nsid; + + for (nsid = 1; nsid <= n->num_namespaces; nsid++) { + if (nvme_ns(n, nsid) == ns) { + return true; + } + } + + return false; +} + +static inline void nvme_ns_attach(NvmeCtrl *n, NvmeNamespace *ns) +{ + n->namespaces[nvme_nsid(ns) - 1] = ns; +} + +static inline void nvme_ns_detach(NvmeCtrl *n, NvmeNamespace *ns) +{ + n->namespaces[nvme_nsid(ns) - 1] = NULL; +} + static inline NvmeCQueue *nvme_cq(NvmeRequest *req) { NvmeSQueue *sq = req->sq; diff --git a/hw/block/trace-events b/hw/block/trace-events index d32475c398..ef06d2ea74 100644 --- a/hw/block/trace-events +++ b/hw/block/trace-events @@ -37,26 +37,36 @@ pci_nvme_dma_read(uint64_t prp1, uint64_t prp2) "DMA read, prp1=0x%"PRIx64" prp2 pci_nvme_map_addr(uint64_t addr, uint64_t len) "addr 0x%"PRIx64" len %"PRIu64"" pci_nvme_map_addr_cmb(uint64_t addr, uint64_t len) "addr 0x%"PRIx64" len %"PRIu64"" pci_nvme_map_prp(uint64_t trans_len, uint32_t len, uint64_t prp1, uint64_t prp2, int num_prps) "trans_len %"PRIu64" len %"PRIu32" prp1 0x%"PRIx64" prp2 0x%"PRIx64" num_prps %d" -pci_nvme_map_sgl(uint16_t cid, uint8_t typ, uint64_t len) "cid %"PRIu16" type 0x%"PRIx8" len %"PRIu64"" +pci_nvme_map_sgl(uint8_t typ, uint64_t len) "type 0x%"PRIx8" len %"PRIu64"" pci_nvme_io_cmd(uint16_t cid, uint32_t nsid, uint16_t sqid, uint8_t opcode, const char *opname) "cid %"PRIu16" nsid %"PRIu32" sqid %"PRIu16" opc 0x%"PRIx8" opname '%s'" pci_nvme_admin_cmd(uint16_t cid, uint16_t sqid, uint8_t opcode, const char *opname) "cid %"PRIu16" sqid %"PRIu16" opc 0x%"PRIx8" opname '%s'" +pci_nvme_flush(uint16_t cid, uint32_t nsid) "cid %"PRIu16" nsid %"PRIu32"" pci_nvme_read(uint16_t cid, uint32_t nsid, uint32_t nlb, uint64_t count, uint64_t lba) "cid %"PRIu16" nsid %"PRIu32" nlb %"PRIu32" count %"PRIu64" lba 0x%"PRIx64"" pci_nvme_write(uint16_t cid, const char *verb, uint32_t nsid, uint32_t nlb, uint64_t count, uint64_t lba) "cid %"PRIu16" opname '%s' nsid %"PRIu32" nlb %"PRIu32" count %"PRIu64" lba 0x%"PRIx64"" pci_nvme_rw_cb(uint16_t cid, const char *blkname) "cid %"PRIu16" blk '%s'" +pci_nvme_copy(uint16_t cid, uint32_t nsid, uint16_t nr, uint8_t format) "cid %"PRIu16" nsid %"PRIu32" nr %"PRIu16" format 0x%"PRIx8"" +pci_nvme_copy_source_range(uint64_t slba, uint32_t nlb) "slba 0x%"PRIx64" nlb %"PRIu32"" +pci_nvme_copy_in_complete(uint16_t cid) "cid %"PRIu16"" +pci_nvme_copy_cb(uint16_t cid) "cid %"PRIu16"" pci_nvme_block_status(int64_t offset, int64_t bytes, int64_t pnum, int ret, bool zeroed) "offset %"PRId64" bytes %"PRId64" pnum %"PRId64" ret 0x%x zeroed %d" pci_nvme_dsm(uint16_t cid, uint32_t nsid, uint32_t nr, uint32_t attr) "cid %"PRIu16" nsid %"PRIu32" nr %"PRIu32" attr 0x%"PRIx32"" pci_nvme_dsm_deallocate(uint16_t cid, uint32_t nsid, uint64_t slba, uint32_t nlb) "cid %"PRIu16" nsid %"PRIu32" slba %"PRIu64" nlb %"PRIu32"" +pci_nvme_dsm_single_range_limit_exceeded(uint32_t nlb, uint32_t dmrsl) "nlb %"PRIu32" dmrsl %"PRIu32"" pci_nvme_compare(uint16_t cid, uint32_t nsid, uint64_t slba, uint32_t nlb) "cid %"PRIu16" nsid %"PRIu32" slba 0x%"PRIx64" nlb %"PRIu32"" pci_nvme_compare_cb(uint16_t cid) "cid %"PRIu16"" pci_nvme_aio_discard_cb(uint16_t cid) "cid %"PRIu16"" +pci_nvme_aio_copy_in_cb(uint16_t cid) "cid %"PRIu16"" pci_nvme_aio_zone_reset_cb(uint16_t cid, uint64_t zslba) "cid %"PRIu16" zslba 0x%"PRIx64"" +pci_nvme_aio_flush_cb(uint16_t cid, const char *blkname) "cid %"PRIu16" blk '%s'" pci_nvme_create_sq(uint64_t addr, uint16_t sqid, uint16_t cqid, uint16_t qsize, uint16_t qflags) "create submission queue, addr=0x%"PRIx64", sqid=%"PRIu16", cqid=%"PRIu16", qsize=%"PRIu16", qflags=%"PRIu16"" pci_nvme_create_cq(uint64_t addr, uint16_t cqid, uint16_t vector, uint16_t size, uint16_t qflags, int ien) "create completion queue, addr=0x%"PRIx64", cqid=%"PRIu16", vector=%"PRIu16", qsize=%"PRIu16", qflags=%"PRIu16", ien=%d" pci_nvme_del_sq(uint16_t qid) "deleting submission queue sqid=%"PRIu16"" pci_nvme_del_cq(uint16_t cqid) "deleted completion queue, cqid=%"PRIu16"" +pci_nvme_identify(uint16_t cid, uint8_t cns, uint16_t ctrlid, uint8_t csi) "cid %"PRIu16" cns 0x%"PRIx8" ctrlid %"PRIu16" csi 0x%"PRIx8"" pci_nvme_identify_ctrl(void) "identify controller" pci_nvme_identify_ctrl_csi(uint8_t csi) "identify controller, csi=0x%"PRIx8"" pci_nvme_identify_ns(uint32_t ns) "nsid %"PRIu32"" +pci_nvme_identify_ns_attached_list(uint16_t cntid) "cntid=%"PRIu16"" pci_nvme_identify_ns_csi(uint32_t ns, uint8_t csi) "nsid=%"PRIu32", csi=0x%"PRIx8"" pci_nvme_identify_nslist(uint32_t ns) "nsid %"PRIu32"" pci_nvme_identify_nslist_csi(uint16_t ns, uint8_t csi) "nsid=%"PRIu16", csi=0x%"PRIx8"" @@ -75,6 +85,8 @@ pci_nvme_aer(uint16_t cid) "cid %"PRIu16"" pci_nvme_aer_aerl_exceeded(void) "aerl exceeded" pci_nvme_aer_masked(uint8_t type, uint8_t mask) "type 0x%"PRIx8" mask 0x%"PRIx8"" pci_nvme_aer_post_cqe(uint8_t typ, uint8_t info, uint8_t log_page) "type 0x%"PRIx8" info 0x%"PRIx8" lid 0x%"PRIx8"" +pci_nvme_ns_attachment(uint16_t cid, uint8_t sel) "cid %"PRIu16", sel=0x%"PRIx8"" +pci_nvme_ns_attachment_attach(uint16_t cntlid, uint32_t nsid) "cntlid=0x%"PRIx16", nsid=0x%"PRIx32"" pci_nvme_enqueue_event(uint8_t typ, uint8_t info, uint8_t log_page) "type 0x%"PRIx8" info 0x%"PRIx8" lid 0x%"PRIx8"" pci_nvme_enqueue_event_noqueue(int queued) "queued %d" pci_nvme_enqueue_event_masked(uint8_t typ) "type 0x%"PRIx8"" @@ -107,15 +119,17 @@ pci_nvme_clear_ns_close(uint32_t state, uint64_t slba) "zone state=%"PRIu32", sl pci_nvme_clear_ns_reset(uint32_t state, uint64_t slba) "zone state=%"PRIu32", slba=%"PRIu64" transitioned to Empty state" # nvme traces for error conditions -pci_nvme_err_mdts(uint16_t cid, size_t len) "cid %"PRIu16" len %zu" +pci_nvme_err_mdts(size_t len) "len %zu" +pci_nvme_err_zasl(size_t len) "len %zu" pci_nvme_err_req_status(uint16_t cid, uint32_t nsid, uint16_t status, uint8_t opc) "cid %"PRIu16" nsid %"PRIu32" status 0x%"PRIx16" opc 0x%"PRIx8"" pci_nvme_err_addr_read(uint64_t addr) "addr 0x%"PRIx64"" pci_nvme_err_addr_write(uint64_t addr) "addr 0x%"PRIx64"" pci_nvme_err_cfs(void) "controller fatal status" pci_nvme_err_aio(uint16_t cid, const char *errname, uint16_t status) "cid %"PRIu16" err '%s' status 0x%"PRIx16"" +pci_nvme_err_copy_invalid_format(uint8_t format) "format 0x%"PRIx8"" pci_nvme_err_invalid_sgld(uint16_t cid, uint8_t typ) "cid %"PRIu16" type 0x%"PRIx8"" pci_nvme_err_invalid_num_sgld(uint16_t cid, uint8_t typ) "cid %"PRIu16" type 0x%"PRIx8"" -pci_nvme_err_invalid_sgl_excess_length(uint16_t cid) "cid %"PRIu16"" +pci_nvme_err_invalid_sgl_excess_length(uint32_t residual) "residual %"PRIu32"" pci_nvme_err_invalid_dma(void) "PRP/SGL is too small for transfer size" pci_nvme_err_invalid_prplist_ent(uint64_t prplist) "PRP list entry is not page aligned: 0x%"PRIx64"" pci_nvme_err_invalid_prp2_align(uint64_t prp2) "PRP2 is not page aligned: 0x%"PRIx64"" @@ -136,7 +150,6 @@ pci_nvme_err_zone_boundary(uint64_t slba, uint32_t nlb, uint64_t zcap) "lba 0x%" pci_nvme_err_zone_invalid_write(uint64_t slba, uint64_t wp) "lba 0x%"PRIx64" wp 0x%"PRIx64"" pci_nvme_err_zone_write_not_ok(uint64_t slba, uint32_t nlb, uint16_t status) "slba=%"PRIu64", nlb=%"PRIu32", status=0x%"PRIx16"" pci_nvme_err_zone_read_not_ok(uint64_t slba, uint32_t nlb, uint16_t status) "slba=%"PRIu64", nlb=%"PRIu32", status=0x%"PRIx16"" -pci_nvme_err_append_too_large(uint64_t slba, uint32_t nlb, uint8_t zasl) "slba=%"PRIu64", nlb=%"PRIu32", zasl=%"PRIu8"" pci_nvme_err_insuff_active_res(uint32_t max_active) "max_active=%"PRIu32" zone limit exceeded" pci_nvme_err_insuff_open_res(uint32_t max_open) "max_open=%"PRIu32" zone limit exceeded" pci_nvme_err_zd_extension_map_error(uint32_t zone_idx) "can't map descriptor extension for zone_idx=%"PRIu32"" diff --git a/hw/block/vhost-user-blk.c b/hw/block/vhost-user-blk.c index da4fbf9084..b870a50e6b 100644 --- a/hw/block/vhost-user-blk.c +++ b/hw/block/vhost-user-blk.c @@ -54,6 +54,9 @@ static void vhost_user_blk_update_config(VirtIODevice *vdev, uint8_t *config) { VHostUserBlk *s = VHOST_USER_BLK(vdev); + /* Our num_queues overrides the device backend */ + virtio_stw_p(vdev, &s->blkcfg.num_queues, s->num_queues); + memcpy(config, &s->blkcfg, sizeof(struct virtio_blk_config)); } @@ -491,10 +494,6 @@ reconnect: goto reconnect; } - if (s->blkcfg.num_queues != s->num_queues) { - s->blkcfg.num_queues = s->num_queues; - } - return; virtio_err: diff --git a/hw/char/cadence_uart.c b/hw/char/cadence_uart.c index c603e14012..ceb677bc5a 100644 --- a/hw/char/cadence_uart.c +++ b/hw/char/cadence_uart.c @@ -519,7 +519,7 @@ static void cadence_uart_realize(DeviceState *dev, Error **errp) uart_event, NULL, s, NULL, true); } -static void cadence_uart_refclk_update(void *opaque) +static void cadence_uart_refclk_update(void *opaque, ClockEvent event) { CadenceUARTState *s = opaque; @@ -537,7 +537,7 @@ static void cadence_uart_init(Object *obj) sysbus_init_irq(sbd, &s->irq); s->refclk = qdev_init_clock_in(DEVICE(obj), "refclk", - cadence_uart_refclk_update, s); + cadence_uart_refclk_update, s, ClockUpdate); /* initialize the frequency in case the clock remains unconnected */ clock_set_hz(s->refclk, UART_DEFAULT_REF_CLK); diff --git a/hw/char/ibex_uart.c b/hw/char/ibex_uart.c index 89f1182c9b..edcaa30ade 100644 --- a/hw/char/ibex_uart.c +++ b/hw/char/ibex_uart.c @@ -396,7 +396,7 @@ static void ibex_uart_write(void *opaque, hwaddr addr, } } -static void ibex_uart_clk_update(void *opaque) +static void ibex_uart_clk_update(void *opaque, ClockEvent event) { IbexUartState *s = opaque; @@ -466,7 +466,7 @@ static void ibex_uart_init(Object *obj) IbexUartState *s = IBEX_UART(obj); s->f_clk = qdev_init_clock_in(DEVICE(obj), "f_clock", - ibex_uart_clk_update, s); + ibex_uart_clk_update, s, ClockUpdate); clock_set_hz(s->f_clk, IBEX_UART_CLOCK); sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->tx_watermark); diff --git a/hw/char/pl011.c b/hw/char/pl011.c index ea4a4e5235..c5621a195f 100644 --- a/hw/char/pl011.c +++ b/hw/char/pl011.c @@ -309,7 +309,7 @@ static void pl011_event(void *opaque, QEMUChrEvent event) pl011_put_fifo(opaque, 0x400); } -static void pl011_clock_update(void *opaque) +static void pl011_clock_update(void *opaque, ClockEvent event) { PL011State *s = PL011(opaque); @@ -378,7 +378,8 @@ static void pl011_init(Object *obj) sysbus_init_irq(sbd, &s->irq[i]); } - s->clk = qdev_init_clock_in(DEVICE(obj), "clk", pl011_clock_update, s); + s->clk = qdev_init_clock_in(DEVICE(obj), "clk", pl011_clock_update, s, + ClockUpdate); s->read_trigger = 1; s->ifl = 0x12; diff --git a/hw/core/clock.c b/hw/core/clock.c index 76b5f468b6..fc5a99683f 100644 --- a/hw/core/clock.c +++ b/hw/core/clock.c @@ -39,15 +39,17 @@ Clock *clock_new(Object *parent, const char *name) return clk; } -void clock_set_callback(Clock *clk, ClockCallback *cb, void *opaque) +void clock_set_callback(Clock *clk, ClockCallback *cb, void *opaque, + unsigned int events) { clk->callback = cb; clk->callback_opaque = opaque; + clk->callback_events = events; } void clock_clear_callback(Clock *clk) { - clock_set_callback(clk, NULL, NULL); + clock_set_callback(clk, NULL, NULL, 0); } bool clock_set(Clock *clk, uint64_t period) @@ -62,18 +64,32 @@ bool clock_set(Clock *clk, uint64_t period) return true; } +static void clock_call_callback(Clock *clk, ClockEvent event) +{ + /* + * Call the Clock's callback for this event, if it has one and + * is interested in this event. + */ + if (clk->callback && (clk->callback_events & event)) { + clk->callback(clk->callback_opaque, event); + } +} + static void clock_propagate_period(Clock *clk, bool call_callbacks) { Clock *child; QLIST_FOREACH(child, &clk->children, sibling) { if (child->period != clk->period) { + if (call_callbacks) { + clock_call_callback(child, ClockPreUpdate); + } child->period = clk->period; trace_clock_update(CLOCK_PATH(child), CLOCK_PATH(clk), CLOCK_PERIOD_TO_HZ(clk->period), call_callbacks); - if (call_callbacks && child->callback) { - child->callback(child->callback_opaque); + if (call_callbacks) { + clock_call_callback(child, ClockUpdate); } clock_propagate_period(child, call_callbacks); } diff --git a/hw/core/guest-loader.c b/hw/core/guest-loader.c new file mode 100644 index 0000000000..bde44e27b4 --- /dev/null +++ b/hw/core/guest-loader.c @@ -0,0 +1,145 @@ +/* + * Guest Loader + * + * Copyright (C) 2020 Linaro + * Written by Alex Bennée <alex.bennee@linaro.org> + * (based on the generic-loader by Li Guang <lig.fnst@cn.fujitsu.com>) + * + * 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. + */ + +/* + * Much like the generic-loader this is treated as a special device + * inside QEMU. However unlike the generic-loader this device is used + * to load guest images for hypervisors. As part of that process the + * hypervisor needs to have platform information passed to it by the + * lower levels of the stack (e.g. firmware/bootloader). If you boot + * the hypervisor directly you use the guest-loader to load the Dom0 + * or equivalent guest images in the right place in the same way a + * boot loader would. + * + * This is only relevant for full system emulation. + */ + +#include "qemu/osdep.h" +#include "hw/core/cpu.h" +#include "hw/sysbus.h" +#include "sysemu/dma.h" +#include "hw/loader.h" +#include "hw/qdev-properties.h" +#include "qapi/error.h" +#include "qemu/module.h" +#include "guest-loader.h" +#include "sysemu/device_tree.h" +#include "hw/boards.h" + +/* + * Insert some FDT nodes for the loaded blob. + */ +static void loader_insert_platform_data(GuestLoaderState *s, int size, + Error **errp) +{ + MachineState *machine = MACHINE(qdev_get_machine()); + void *fdt = machine->fdt; + g_autofree char *node = g_strdup_printf("/chosen/module@0x%08" PRIx64, + s->addr); + uint64_t reg_attr[2] = {cpu_to_be64(s->addr), cpu_to_be64(size)}; + + if (!fdt) { + error_setg(errp, "Cannot modify FDT fields if the machine has none"); + return; + } + + qemu_fdt_add_subnode(fdt, node); + qemu_fdt_setprop(fdt, node, "reg", ®_attr, sizeof(reg_attr)); + + if (s->kernel) { + const char *compat[2] = { "multiboot,module", "multiboot,kernel" }; + if (qemu_fdt_setprop_string_array(fdt, node, "compatible", + (char **) &compat, + ARRAY_SIZE(compat)) < 0) { + error_setg(errp, "couldn't set %s/compatible", node); + return; + } + if (s->args) { + if (qemu_fdt_setprop_string(fdt, node, "bootargs", s->args) < 0) { + error_setg(errp, "couldn't set %s/bootargs", node); + } + } + } else if (s->initrd) { + const char *compat[2] = { "multiboot,module", "multiboot,ramdisk" }; + if (qemu_fdt_setprop_string_array(fdt, node, "compatible", + (char **) &compat, + ARRAY_SIZE(compat)) < 0) { + error_setg(errp, "couldn't set %s/compatible", node); + return; + } + } +} + +static void guest_loader_realize(DeviceState *dev, Error **errp) +{ + GuestLoaderState *s = GUEST_LOADER(dev); + char *file = s->kernel ? s->kernel : s->initrd; + int size = 0; + + /* Perform some error checking on the user's options */ + if (s->kernel && s->initrd) { + error_setg(errp, "Cannot specify a kernel and initrd in same stanza"); + return; + } else if (!s->kernel && !s->initrd) { + error_setg(errp, "Need to specify a kernel or initrd image"); + return; + } else if (!s->addr) { + error_setg(errp, "Need to specify the address of guest blob"); + return; + } else if (s->args && !s->kernel) { + error_setg(errp, "Boot args only relevant to kernel blobs"); + } + + /* Default to the maximum size being the machine's ram size */ + size = load_image_targphys_as(file, s->addr, current_machine->ram_size, + NULL); + if (size < 0) { + error_setg(errp, "Cannot load specified image %s", file); + return; + } + + /* Now the image is loaded we need to update the platform data */ + loader_insert_platform_data(s, size, errp); +} + +static Property guest_loader_props[] = { + DEFINE_PROP_UINT64("addr", GuestLoaderState, addr, 0), + DEFINE_PROP_STRING("kernel", GuestLoaderState, kernel), + DEFINE_PROP_STRING("bootargs", GuestLoaderState, args), + DEFINE_PROP_STRING("initrd", GuestLoaderState, initrd), + DEFINE_PROP_END_OF_LIST(), +}; + +static void guest_loader_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = guest_loader_realize; + device_class_set_props(dc, guest_loader_props); + dc->desc = "Guest Loader"; + set_bit(DEVICE_CATEGORY_MISC, dc->categories); +} + +static TypeInfo guest_loader_info = { + .name = TYPE_GUEST_LOADER, + .parent = TYPE_DEVICE, + .instance_size = sizeof(GuestLoaderState), + .class_init = guest_loader_class_init, +}; + +static void guest_loader_register_type(void) +{ + type_register_static(&guest_loader_info); +} + +type_init(guest_loader_register_type) diff --git a/hw/core/guest-loader.h b/hw/core/guest-loader.h new file mode 100644 index 0000000000..07f4b4884b --- /dev/null +++ b/hw/core/guest-loader.h @@ -0,0 +1,34 @@ +/* + * Guest Loader + * + * Copyright (C) 2020 Linaro + * Written by Alex Bennée <alex.bennee@linaro.org> + * (based on the generic-loader by Li Guang <lig.fnst@cn.fujitsu.com>) + * + * 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. + */ + +#ifndef GUEST_LOADER_H +#define GUEST_LOADER_H + +#include "hw/qdev-core.h" +#include "qom/object.h" + +struct GuestLoaderState { + /* <private> */ + DeviceState parent_obj; + + /* <public> */ + uint64_t addr; + char *kernel; + char *args; + char *initrd; +}; + +#define TYPE_GUEST_LOADER "guest-loader" +OBJECT_DECLARE_SIMPLE_TYPE(GuestLoaderState, GUEST_LOADER) + +#endif diff --git a/hw/core/meson.build b/hw/core/meson.build index 032576f571..9cd72edf51 100644 --- a/hw/core/meson.build +++ b/hw/core/meson.build @@ -37,6 +37,8 @@ softmmu_ss.add(files( 'clock-vmstate.c', )) +softmmu_ss.add(when: 'CONFIG_TCG', if_true: files('guest-loader.c')) + specific_ss.add(when: 'CONFIG_SOFTMMU', if_true: files( 'machine-qmp-cmds.c', 'numa.c', diff --git a/hw/core/qdev-clock.c b/hw/core/qdev-clock.c index eb05f2a13c..117f4c6ea4 100644 --- a/hw/core/qdev-clock.c +++ b/hw/core/qdev-clock.c @@ -111,7 +111,8 @@ Clock *qdev_init_clock_out(DeviceState *dev, const char *name) } Clock *qdev_init_clock_in(DeviceState *dev, const char *name, - ClockCallback *callback, void *opaque) + ClockCallback *callback, void *opaque, + unsigned int events) { NamedClockList *ncl; @@ -120,7 +121,7 @@ Clock *qdev_init_clock_in(DeviceState *dev, const char *name, ncl = qdev_init_clocklist(dev, name, false, NULL); if (callback) { - clock_set_callback(ncl->clock, callback, opaque); + clock_set_callback(ncl->clock, callback, opaque, events); } return ncl->clock; } @@ -137,7 +138,8 @@ void qdev_init_clocks(DeviceState *dev, const ClockPortInitArray clocks) if (elem->is_output) { *clkp = qdev_init_clock_out(dev, elem->name); } else { - *clkp = qdev_init_clock_in(dev, elem->name, elem->callback, dev); + *clkp = qdev_init_clock_in(dev, elem->name, elem->callback, dev, + elem->callback_events); } } } diff --git a/hw/dma/Kconfig b/hw/dma/Kconfig index 5d6be1a7a7..98fbb1bb04 100644 --- a/hw/dma/Kconfig +++ b/hw/dma/Kconfig @@ -26,3 +26,7 @@ config STP2000 config SIFIVE_PDMA bool + +config XLNX_CSU_DMA + bool + select REGISTER diff --git a/hw/dma/meson.build b/hw/dma/meson.build index 47b4a7cb47..5c78a4e05f 100644 --- a/hw/dma/meson.build +++ b/hw/dma/meson.build @@ -14,3 +14,4 @@ softmmu_ss.add(when: 'CONFIG_OMAP', if_true: files('omap_dma.c', 'soc_dma.c')) softmmu_ss.add(when: 'CONFIG_PXA2XX', if_true: files('pxa2xx_dma.c')) softmmu_ss.add(when: 'CONFIG_RASPI', if_true: files('bcm2835_dma.c')) softmmu_ss.add(when: 'CONFIG_SIFIVE_PDMA', if_true: files('sifive_pdma.c')) +softmmu_ss.add(when: 'CONFIG_XLNX_CSU_DMA', if_true: files('xlnx_csu_dma.c')) diff --git a/hw/dma/xlnx_csu_dma.c b/hw/dma/xlnx_csu_dma.c new file mode 100644 index 0000000000..98324dadcd --- /dev/null +++ b/hw/dma/xlnx_csu_dma.c @@ -0,0 +1,745 @@ +/* + * Xilinx Platform CSU Stream DMA emulation + * + * This implementation is based on + * https://github.com/Xilinx/qemu/blob/master/hw/dma/csu_stream_dma.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 or + * (at your option) version 3 of the License. + * + * 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/log.h" +#include "qapi/error.h" +#include "hw/hw.h" +#include "hw/irq.h" +#include "hw/qdev-properties.h" +#include "hw/sysbus.h" +#include "migration/vmstate.h" +#include "sysemu/dma.h" +#include "hw/ptimer.h" +#include "hw/stream.h" +#include "hw/register.h" +#include "hw/dma/xlnx_csu_dma.h" + +/* + * Ref: UG1087 (v1.7) February 8, 2019 + * https://www.xilinx.com/html_docs/registers/ug1087/ug1087-zynq-ultrascale-registers.html + * CSUDMA Module section + */ +REG32(ADDR, 0x0) + FIELD(ADDR, ADDR, 2, 30) /* wo */ +REG32(SIZE, 0x4) + FIELD(SIZE, SIZE, 2, 27) /* wo */ + FIELD(SIZE, LAST_WORD, 0, 1) /* rw, only exists in SRC */ +REG32(STATUS, 0x8) + FIELD(STATUS, DONE_CNT, 13, 3) /* wtc */ + FIELD(STATUS, FIFO_LEVEL, 5, 8) /* ro */ + FIELD(STATUS, OUTSTANDING, 1, 4) /* ro */ + FIELD(STATUS, BUSY, 0, 1) /* ro */ +REG32(CTRL, 0xc) + FIELD(CTRL, FIFOTHRESH, 25, 7) /* rw, only exists in DST, reset 0x40 */ + FIELD(CTRL, APB_ERR_RESP, 24, 1) /* rw */ + FIELD(CTRL, ENDIANNESS, 23, 1) /* rw */ + FIELD(CTRL, AXI_BRST_TYPE, 22, 1) /* rw */ + FIELD(CTRL, TIMEOUT_VAL, 10, 12) /* rw, reset: 0xFFE */ + FIELD(CTRL, FIFO_THRESH, 2, 8) /* rw, reset: 0x80 */ + FIELD(CTRL, PAUSE_STRM, 1, 1) /* rw */ + FIELD(CTRL, PAUSE_MEM, 0, 1) /* rw */ +REG32(CRC, 0x10) +REG32(INT_STATUS, 0x14) + FIELD(INT_STATUS, FIFO_OVERFLOW, 7, 1) /* wtc */ + FIELD(INT_STATUS, INVALID_APB, 6, 1) /* wtc */ + FIELD(INT_STATUS, THRESH_HIT, 5, 1) /* wtc */ + FIELD(INT_STATUS, TIMEOUT_MEM, 4, 1) /* wtc */ + FIELD(INT_STATUS, TIMEOUT_STRM, 3, 1) /* wtc */ + FIELD(INT_STATUS, AXI_BRESP_ERR, 2, 1) /* wtc, SRC: AXI_RDERR */ + FIELD(INT_STATUS, DONE, 1, 1) /* wtc */ + FIELD(INT_STATUS, MEM_DONE, 0, 1) /* wtc */ +REG32(INT_ENABLE, 0x18) + FIELD(INT_ENABLE, FIFO_OVERFLOW, 7, 1) /* wtc */ + FIELD(INT_ENABLE, INVALID_APB, 6, 1) /* wtc */ + FIELD(INT_ENABLE, THRESH_HIT, 5, 1) /* wtc */ + FIELD(INT_ENABLE, TIMEOUT_MEM, 4, 1) /* wtc */ + FIELD(INT_ENABLE, TIMEOUT_STRM, 3, 1) /* wtc */ + FIELD(INT_ENABLE, AXI_BRESP_ERR, 2, 1) /* wtc, SRC: AXI_RDERR */ + FIELD(INT_ENABLE, DONE, 1, 1) /* wtc */ + FIELD(INT_ENABLE, MEM_DONE, 0, 1) /* wtc */ +REG32(INT_DISABLE, 0x1c) + FIELD(INT_DISABLE, FIFO_OVERFLOW, 7, 1) /* wtc */ + FIELD(INT_DISABLE, INVALID_APB, 6, 1) /* wtc */ + FIELD(INT_DISABLE, THRESH_HIT, 5, 1) /* wtc */ + FIELD(INT_DISABLE, TIMEOUT_MEM, 4, 1) /* wtc */ + FIELD(INT_DISABLE, TIMEOUT_STRM, 3, 1) /* wtc */ + FIELD(INT_DISABLE, AXI_BRESP_ERR, 2, 1) /* wtc, SRC: AXI_RDERR */ + FIELD(INT_DISABLE, DONE, 1, 1) /* wtc */ + FIELD(INT_DISABLE, MEM_DONE, 0, 1) /* wtc */ +REG32(INT_MASK, 0x20) + FIELD(INT_MASK, FIFO_OVERFLOW, 7, 1) /* ro, reset: 0x1 */ + FIELD(INT_MASK, INVALID_APB, 6, 1) /* ro, reset: 0x1 */ + FIELD(INT_MASK, THRESH_HIT, 5, 1) /* ro, reset: 0x1 */ + FIELD(INT_MASK, TIMEOUT_MEM, 4, 1) /* ro, reset: 0x1 */ + FIELD(INT_MASK, TIMEOUT_STRM, 3, 1) /* ro, reset: 0x1 */ + FIELD(INT_MASK, AXI_BRESP_ERR, 2, 1) /* ro, reset: 0x1, SRC: AXI_RDERR */ + FIELD(INT_MASK, DONE, 1, 1) /* ro, reset: 0x1 */ + FIELD(INT_MASK, MEM_DONE, 0, 1) /* ro, reset: 0x1 */ +REG32(CTRL2, 0x24) + FIELD(CTRL2, ARCACHE, 24, 3) /* rw */ + FIELD(CTRL2, ROUTE_BIT, 23, 1) /* rw */ + FIELD(CTRL2, TIMEOUT_EN, 22, 1) /* rw */ + FIELD(CTRL2, TIMEOUT_PRE, 4, 12) /* rw, reset: 0xFFF */ + FIELD(CTRL2, MAX_OUTS_CMDS, 0, 4) /* rw, reset: 0x8 */ +REG32(ADDR_MSB, 0x28) + FIELD(ADDR_MSB, ADDR_MSB, 0, 17) /* wo */ + +#define R_CTRL_TIMEOUT_VAL_RESET (0xFFE) +#define R_CTRL_FIFO_THRESH_RESET (0x80) +#define R_CTRL_FIFOTHRESH_RESET (0x40) + +#define R_CTRL2_TIMEOUT_PRE_RESET (0xFFF) +#define R_CTRL2_MAX_OUTS_CMDS_RESET (0x8) + +#define XLNX_CSU_DMA_ERR_DEBUG (0) +#define XLNX_CSU_DMA_INT_R_MASK (0xff) + +/* UG1807: Set the prescaler value for the timeout in clk (~2.5ns) cycles */ +#define XLNX_CSU_DMA_TIMER_FREQ (400 * 1000 * 1000) + +static bool xlnx_csu_dma_is_paused(XlnxCSUDMA *s) +{ + bool paused; + + paused = !!(s->regs[R_CTRL] & R_CTRL_PAUSE_STRM_MASK); + paused |= !!(s->regs[R_CTRL] & R_CTRL_PAUSE_MEM_MASK); + + return paused; +} + +static bool xlnx_csu_dma_get_eop(XlnxCSUDMA *s) +{ + return s->r_size_last_word; +} + +static bool xlnx_csu_dma_burst_is_fixed(XlnxCSUDMA *s) +{ + return !!(s->regs[R_CTRL] & R_CTRL_AXI_BRST_TYPE_MASK); +} + +static bool xlnx_csu_dma_timeout_enabled(XlnxCSUDMA *s) +{ + return !!(s->regs[R_CTRL2] & R_CTRL2_TIMEOUT_EN_MASK); +} + +static void xlnx_csu_dma_update_done_cnt(XlnxCSUDMA *s, int a) +{ + int cnt; + + /* Increase DONE_CNT */ + cnt = ARRAY_FIELD_EX32(s->regs, STATUS, DONE_CNT) + a; + ARRAY_FIELD_DP32(s->regs, STATUS, DONE_CNT, cnt); +} + +static void xlnx_csu_dma_data_process(XlnxCSUDMA *s, uint8_t *buf, uint32_t len) +{ + uint32_t bswap; + uint32_t i; + + bswap = s->regs[R_CTRL] & R_CTRL_ENDIANNESS_MASK; + if (s->is_dst && !bswap) { + /* Fast when ENDIANNESS cleared */ + return; + } + + for (i = 0; i < len; i += 4) { + uint8_t *b = &buf[i]; + union { + uint8_t u8[4]; + uint32_t u32; + } v = { + .u8 = { b[0], b[1], b[2], b[3] } + }; + + if (!s->is_dst) { + s->regs[R_CRC] += v.u32; + } + if (bswap) { + /* + * No point using bswap, we need to writeback + * into a potentially unaligned pointer. + */ + b[0] = v.u8[3]; + b[1] = v.u8[2]; + b[2] = v.u8[1]; + b[3] = v.u8[0]; + } + } +} + +static void xlnx_csu_dma_update_irq(XlnxCSUDMA *s) +{ + qemu_set_irq(s->irq, !!(s->regs[R_INT_STATUS] & ~s->regs[R_INT_MASK])); +} + +/* len is in bytes */ +static uint32_t xlnx_csu_dma_read(XlnxCSUDMA *s, uint8_t *buf, uint32_t len) +{ + hwaddr addr = (hwaddr)s->regs[R_ADDR_MSB] << 32 | s->regs[R_ADDR]; + MemTxResult result = MEMTX_OK; + + if (xlnx_csu_dma_burst_is_fixed(s)) { + uint32_t i; + + for (i = 0; i < len && (result == MEMTX_OK); i += s->width) { + uint32_t mlen = MIN(len - i, s->width); + + result = address_space_rw(s->dma_as, addr, s->attr, + buf + i, mlen, false); + } + } else { + result = address_space_rw(s->dma_as, addr, s->attr, buf, len, false); + } + + if (result == MEMTX_OK) { + xlnx_csu_dma_data_process(s, buf, len); + } else { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad address " TARGET_FMT_plx + " for mem read", __func__, addr); + s->regs[R_INT_STATUS] |= R_INT_STATUS_AXI_BRESP_ERR_MASK; + xlnx_csu_dma_update_irq(s); + } + return len; +} + +/* len is in bytes */ +static uint32_t xlnx_csu_dma_write(XlnxCSUDMA *s, uint8_t *buf, uint32_t len) +{ + hwaddr addr = (hwaddr)s->regs[R_ADDR_MSB] << 32 | s->regs[R_ADDR]; + MemTxResult result = MEMTX_OK; + + xlnx_csu_dma_data_process(s, buf, len); + if (xlnx_csu_dma_burst_is_fixed(s)) { + uint32_t i; + + for (i = 0; i < len && (result == MEMTX_OK); i += s->width) { + uint32_t mlen = MIN(len - i, s->width); + + result = address_space_rw(s->dma_as, addr, s->attr, + buf, mlen, true); + buf += mlen; + } + } else { + result = address_space_rw(s->dma_as, addr, s->attr, buf, len, true); + } + + if (result != MEMTX_OK) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad address " TARGET_FMT_plx + " for mem write", __func__, addr); + s->regs[R_INT_STATUS] |= R_INT_STATUS_AXI_BRESP_ERR_MASK; + xlnx_csu_dma_update_irq(s); + } + return len; +} + +static void xlnx_csu_dma_done(XlnxCSUDMA *s) +{ + s->regs[R_STATUS] &= ~R_STATUS_BUSY_MASK; + s->regs[R_INT_STATUS] |= R_INT_STATUS_DONE_MASK; + + if (!s->is_dst) { + s->regs[R_INT_STATUS] |= R_INT_STATUS_MEM_DONE_MASK; + } + + xlnx_csu_dma_update_done_cnt(s, 1); +} + +static uint32_t xlnx_csu_dma_advance(XlnxCSUDMA *s, uint32_t len) +{ + uint32_t size = s->regs[R_SIZE]; + hwaddr dst = (hwaddr)s->regs[R_ADDR_MSB] << 32 | s->regs[R_ADDR]; + + assert(len <= size); + + size -= len; + s->regs[R_SIZE] = size; + + if (!xlnx_csu_dma_burst_is_fixed(s)) { + dst += len; + s->regs[R_ADDR] = (uint32_t) dst; + s->regs[R_ADDR_MSB] = dst >> 32; + } + + if (size == 0) { + xlnx_csu_dma_done(s); + } + + return size; +} + +static void xlnx_csu_dma_src_notify(void *opaque) +{ + XlnxCSUDMA *s = XLNX_CSU_DMA(opaque); + unsigned char buf[4 * 1024]; + size_t rlen = 0; + + ptimer_transaction_begin(s->src_timer); + /* Stop the backpreassure timer */ + ptimer_stop(s->src_timer); + + while (s->regs[R_SIZE] && !xlnx_csu_dma_is_paused(s) && + stream_can_push(s->tx_dev, xlnx_csu_dma_src_notify, s)) { + uint32_t plen = MIN(s->regs[R_SIZE], sizeof buf); + bool eop = false; + + /* Did we fit it all? */ + if (s->regs[R_SIZE] == plen && xlnx_csu_dma_get_eop(s)) { + eop = true; + } + + /* DMA transfer */ + xlnx_csu_dma_read(s, buf, plen); + rlen = stream_push(s->tx_dev, buf, plen, eop); + xlnx_csu_dma_advance(s, rlen); + } + + if (xlnx_csu_dma_timeout_enabled(s) && s->regs[R_SIZE] && + !stream_can_push(s->tx_dev, xlnx_csu_dma_src_notify, s)) { + uint32_t timeout = ARRAY_FIELD_EX32(s->regs, CTRL, TIMEOUT_VAL); + uint32_t div = ARRAY_FIELD_EX32(s->regs, CTRL2, TIMEOUT_PRE) + 1; + uint32_t freq = XLNX_CSU_DMA_TIMER_FREQ; + + freq /= div; + ptimer_set_freq(s->src_timer, freq); + ptimer_set_count(s->src_timer, timeout); + ptimer_run(s->src_timer, 1); + } + + ptimer_transaction_commit(s->src_timer); + xlnx_csu_dma_update_irq(s); +} + +static uint64_t addr_pre_write(RegisterInfo *reg, uint64_t val) +{ + /* Address is word aligned */ + return val & R_ADDR_ADDR_MASK; +} + +static uint64_t size_pre_write(RegisterInfo *reg, uint64_t val) +{ + XlnxCSUDMA *s = XLNX_CSU_DMA(reg->opaque); + + if (s->regs[R_SIZE] != 0) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Starting DMA while already running.\n", __func__); + } + + if (!s->is_dst) { + s->r_size_last_word = !!(val & R_SIZE_LAST_WORD_MASK); + } + + /* Size is word aligned */ + return val & R_SIZE_SIZE_MASK; +} + +static uint64_t size_post_read(RegisterInfo *reg, uint64_t val) +{ + XlnxCSUDMA *s = XLNX_CSU_DMA(reg->opaque); + + return val | s->r_size_last_word; +} + +static void size_post_write(RegisterInfo *reg, uint64_t val) +{ + XlnxCSUDMA *s = XLNX_CSU_DMA(reg->opaque); + + s->regs[R_STATUS] |= R_STATUS_BUSY_MASK; + + /* + * Note that if SIZE is programmed to 0, and the DMA is started, + * the interrupts DONE and MEM_DONE will be asserted. + */ + if (s->regs[R_SIZE] == 0) { + xlnx_csu_dma_done(s); + xlnx_csu_dma_update_irq(s); + return; + } + + /* Set SIZE is considered the last step in transfer configuration */ + if (!s->is_dst) { + xlnx_csu_dma_src_notify(s); + } else { + if (s->notify) { + s->notify(s->notify_opaque); + } + } +} + +static uint64_t status_pre_write(RegisterInfo *reg, uint64_t val) +{ + return val & (R_STATUS_DONE_CNT_MASK | R_STATUS_BUSY_MASK); +} + +static void ctrl_post_write(RegisterInfo *reg, uint64_t val) +{ + XlnxCSUDMA *s = XLNX_CSU_DMA(reg->opaque); + + if (!s->is_dst) { + if (!xlnx_csu_dma_is_paused(s)) { + xlnx_csu_dma_src_notify(s); + } + } else { + if (!xlnx_csu_dma_is_paused(s) && s->notify) { + s->notify(s->notify_opaque); + } + } +} + +static uint64_t int_status_pre_write(RegisterInfo *reg, uint64_t val) +{ + XlnxCSUDMA *s = XLNX_CSU_DMA(reg->opaque); + + /* DMA counter decrements when flag 'DONE' is cleared */ + if ((val & s->regs[R_INT_STATUS] & R_INT_STATUS_DONE_MASK)) { + xlnx_csu_dma_update_done_cnt(s, -1); + } + + return s->regs[R_INT_STATUS] & ~val; +} + +static void int_status_post_write(RegisterInfo *reg, uint64_t val) +{ + XlnxCSUDMA *s = XLNX_CSU_DMA(reg->opaque); + + xlnx_csu_dma_update_irq(s); +} + +static uint64_t int_enable_pre_write(RegisterInfo *reg, uint64_t val) +{ + XlnxCSUDMA *s = XLNX_CSU_DMA(reg->opaque); + uint32_t v32 = val; + + /* + * R_INT_ENABLE doesn't have its own state. + * It is used to indirectly modify R_INT_MASK. + * + * 1: Enable this interrupt field (the mask bit will be cleared to 0) + * 0: No effect + */ + s->regs[R_INT_MASK] &= ~v32; + return 0; +} + +static void int_enable_post_write(RegisterInfo *reg, uint64_t val) +{ + XlnxCSUDMA *s = XLNX_CSU_DMA(reg->opaque); + + xlnx_csu_dma_update_irq(s); +} + +static uint64_t int_disable_pre_write(RegisterInfo *reg, uint64_t val) +{ + XlnxCSUDMA *s = XLNX_CSU_DMA(reg->opaque); + uint32_t v32 = val; + + /* + * R_INT_DISABLE doesn't have its own state. + * It is used to indirectly modify R_INT_MASK. + * + * 1: Disable this interrupt field (the mask bit will be set to 1) + * 0: No effect + */ + s->regs[R_INT_MASK] |= v32; + return 0; +} + +static void int_disable_post_write(RegisterInfo *reg, uint64_t val) +{ + XlnxCSUDMA *s = XLNX_CSU_DMA(reg->opaque); + + xlnx_csu_dma_update_irq(s); +} + +static uint64_t addr_msb_pre_write(RegisterInfo *reg, uint64_t val) +{ + return val & R_ADDR_MSB_ADDR_MSB_MASK; +} + +static const RegisterAccessInfo *xlnx_csu_dma_regs_info[] = { +#define DMACH_REGINFO(NAME, snd) \ + (const RegisterAccessInfo []) { \ + { \ + .name = #NAME "_ADDR", \ + .addr = A_ADDR, \ + .pre_write = addr_pre_write \ + }, { \ + .name = #NAME "_SIZE", \ + .addr = A_SIZE, \ + .pre_write = size_pre_write, \ + .post_write = size_post_write, \ + .post_read = size_post_read \ + }, { \ + .name = #NAME "_STATUS", \ + .addr = A_STATUS, \ + .pre_write = status_pre_write, \ + .w1c = R_STATUS_DONE_CNT_MASK, \ + .ro = (R_STATUS_BUSY_MASK \ + | R_STATUS_FIFO_LEVEL_MASK \ + | R_STATUS_OUTSTANDING_MASK) \ + }, { \ + .name = #NAME "_CTRL", \ + .addr = A_CTRL, \ + .post_write = ctrl_post_write, \ + .reset = ((R_CTRL_TIMEOUT_VAL_RESET << R_CTRL_TIMEOUT_VAL_SHIFT) \ + | (R_CTRL_FIFO_THRESH_RESET << R_CTRL_FIFO_THRESH_SHIFT)\ + | (snd ? 0 : R_CTRL_FIFOTHRESH_RESET \ + << R_CTRL_FIFOTHRESH_SHIFT)) \ + }, { \ + .name = #NAME "_CRC", \ + .addr = A_CRC, \ + }, { \ + .name = #NAME "_INT_STATUS", \ + .addr = A_INT_STATUS, \ + .pre_write = int_status_pre_write, \ + .post_write = int_status_post_write \ + }, { \ + .name = #NAME "_INT_ENABLE", \ + .addr = A_INT_ENABLE, \ + .pre_write = int_enable_pre_write, \ + .post_write = int_enable_post_write \ + }, { \ + .name = #NAME "_INT_DISABLE", \ + .addr = A_INT_DISABLE, \ + .pre_write = int_disable_pre_write, \ + .post_write = int_disable_post_write \ + }, { \ + .name = #NAME "_INT_MASK", \ + .addr = A_INT_MASK, \ + .ro = ~0, \ + .reset = XLNX_CSU_DMA_INT_R_MASK \ + }, { \ + .name = #NAME "_CTRL2", \ + .addr = A_CTRL2, \ + .reset = ((R_CTRL2_TIMEOUT_PRE_RESET \ + << R_CTRL2_TIMEOUT_PRE_SHIFT) \ + | (R_CTRL2_MAX_OUTS_CMDS_RESET \ + << R_CTRL2_MAX_OUTS_CMDS_SHIFT)) \ + }, { \ + .name = #NAME "_ADDR_MSB", \ + .addr = A_ADDR_MSB, \ + .pre_write = addr_msb_pre_write \ + } \ + } + + DMACH_REGINFO(DMA_SRC, true), + DMACH_REGINFO(DMA_DST, false) +}; + +static const MemoryRegionOps xlnx_csu_dma_ops = { + .read = register_read_memory, + .write = register_write_memory, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + } +}; + +static void xlnx_csu_dma_src_timeout_hit(void *opaque) +{ + XlnxCSUDMA *s = XLNX_CSU_DMA(opaque); + + /* Ignore if the timeout is masked */ + if (!xlnx_csu_dma_timeout_enabled(s)) { + return; + } + + s->regs[R_INT_STATUS] |= R_INT_STATUS_TIMEOUT_STRM_MASK; + xlnx_csu_dma_update_irq(s); +} + +static size_t xlnx_csu_dma_stream_push(StreamSink *obj, uint8_t *buf, + size_t len, bool eop) +{ + XlnxCSUDMA *s = XLNX_CSU_DMA(obj); + uint32_t size = s->regs[R_SIZE]; + uint32_t mlen = MIN(size, len) & (~3); /* Size is word aligned */ + + /* Be called when it's DST */ + assert(s->is_dst); + + if (size == 0 || len <= 0) { + return 0; + } + + if (len && (xlnx_csu_dma_is_paused(s) || mlen == 0)) { + qemu_log_mask(LOG_GUEST_ERROR, + "csu-dma: DST channel dropping %zd b of data.\n", len); + s->regs[R_INT_STATUS] |= R_INT_STATUS_FIFO_OVERFLOW_MASK; + return len; + } + + if (xlnx_csu_dma_write(s, buf, mlen) != mlen) { + return 0; + } + + xlnx_csu_dma_advance(s, mlen); + xlnx_csu_dma_update_irq(s); + + return mlen; +} + +static bool xlnx_csu_dma_stream_can_push(StreamSink *obj, + StreamCanPushNotifyFn notify, + void *notify_opaque) +{ + XlnxCSUDMA *s = XLNX_CSU_DMA(obj); + + if (s->regs[R_SIZE] != 0) { + return true; + } else { + s->notify = notify; + s->notify_opaque = notify_opaque; + return false; + } +} + +static void xlnx_csu_dma_reset(DeviceState *dev) +{ + XlnxCSUDMA *s = XLNX_CSU_DMA(dev); + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(s->regs_info); ++i) { + register_reset(&s->regs_info[i]); + } +} + +static void xlnx_csu_dma_realize(DeviceState *dev, Error **errp) +{ + XlnxCSUDMA *s = XLNX_CSU_DMA(dev); + RegisterInfoArray *reg_array; + + reg_array = + register_init_block32(dev, xlnx_csu_dma_regs_info[!!s->is_dst], + XLNX_CSU_DMA_R_MAX, + s->regs_info, s->regs, + &xlnx_csu_dma_ops, + XLNX_CSU_DMA_ERR_DEBUG, + XLNX_CSU_DMA_R_MAX * 4); + memory_region_add_subregion(&s->iomem, + 0x0, + ®_array->mem); + + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem); + sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq); + + if (!s->is_dst && !s->tx_dev) { + error_setg(errp, "zynqmp.csu-dma: Stream not connected"); + return; + } + + s->src_timer = ptimer_init(xlnx_csu_dma_src_timeout_hit, + s, PTIMER_POLICY_DEFAULT); + + if (s->dma_mr) { + s->dma_as = g_malloc0(sizeof(AddressSpace)); + address_space_init(s->dma_as, s->dma_mr, NULL); + } else { + s->dma_as = &address_space_memory; + } + + s->attr = MEMTXATTRS_UNSPECIFIED; + + s->r_size_last_word = 0; +} + +static const VMStateDescription vmstate_xlnx_csu_dma = { + .name = TYPE_XLNX_CSU_DMA, + .version_id = 0, + .minimum_version_id = 0, + .minimum_version_id_old = 0, + .fields = (VMStateField[]) { + VMSTATE_PTIMER(src_timer, XlnxCSUDMA), + VMSTATE_UINT16(width, XlnxCSUDMA), + VMSTATE_BOOL(is_dst, XlnxCSUDMA), + VMSTATE_BOOL(r_size_last_word, XlnxCSUDMA), + VMSTATE_UINT32_ARRAY(regs, XlnxCSUDMA, XLNX_CSU_DMA_R_MAX), + VMSTATE_END_OF_LIST(), + } +}; + +static Property xlnx_csu_dma_properties[] = { + /* + * Ref PG021, Stream Data Width: + * Data width in bits of the AXI S2MM AXI4-Stream Data bus. + * This value must be equal or less than the Memory Map Data Width. + * Valid values are 8, 16, 32, 64, 128, 512 and 1024. + * "dma-width" is the byte value of the "Stream Data Width". + */ + DEFINE_PROP_UINT16("dma-width", XlnxCSUDMA, width, 4), + /* + * The CSU DMA is a two-channel, simple DMA, allowing separate control of + * the SRC (read) channel and DST (write) channel. "is-dst" is used to mark + * which channel the device is connected to. + */ + DEFINE_PROP_BOOL("is-dst", XlnxCSUDMA, is_dst, true), + DEFINE_PROP_END_OF_LIST(), +}; + +static void xlnx_csu_dma_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + StreamSinkClass *ssc = STREAM_SINK_CLASS(klass); + + dc->reset = xlnx_csu_dma_reset; + dc->realize = xlnx_csu_dma_realize; + dc->vmsd = &vmstate_xlnx_csu_dma; + device_class_set_props(dc, xlnx_csu_dma_properties); + + ssc->push = xlnx_csu_dma_stream_push; + ssc->can_push = xlnx_csu_dma_stream_can_push; +} + +static void xlnx_csu_dma_init(Object *obj) +{ + XlnxCSUDMA *s = XLNX_CSU_DMA(obj); + + memory_region_init(&s->iomem, obj, TYPE_XLNX_CSU_DMA, + XLNX_CSU_DMA_R_MAX * 4); + + object_property_add_link(obj, "stream-connected-dma", TYPE_STREAM_SINK, + (Object **)&s->tx_dev, + qdev_prop_allow_set_link_before_realize, + OBJ_PROP_LINK_STRONG); + object_property_add_link(obj, "dma", TYPE_MEMORY_REGION, + (Object **)&s->dma_mr, + qdev_prop_allow_set_link_before_realize, + OBJ_PROP_LINK_STRONG); +} + +static const TypeInfo xlnx_csu_dma_info = { + .name = TYPE_XLNX_CSU_DMA, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(XlnxCSUDMA), + .class_init = xlnx_csu_dma_class_init, + .instance_init = xlnx_csu_dma_init, + .interfaces = (InterfaceInfo[]) { + { TYPE_STREAM_SINK }, + { } + } +}; + +static void xlnx_csu_dma_register_types(void) +{ + type_register_static(&xlnx_csu_dma_info); +} + +type_init(xlnx_csu_dma_register_types) diff --git a/hw/i386/pc.c b/hw/i386/pc.c index 8aa85dec54..410db9ef96 100644 --- a/hw/i386/pc.c +++ b/hw/i386/pc.c @@ -58,7 +58,6 @@ #include "sysemu/numa.h" #include "sysemu/kvm.h" #include "sysemu/xen.h" -#include "sysemu/qtest.h" #include "sysemu/reset.h" #include "sysemu/runstate.h" #include "kvm/kvm_i386.h" diff --git a/hw/meson.build b/hw/meson.build index e615d72d4d..8ba79b1a52 100644 --- a/hw/meson.build +++ b/hw/meson.build @@ -30,7 +30,6 @@ subdir('rdma') subdir('rtc') subdir('scsi') subdir('sd') -subdir('semihosting') subdir('smbios') subdir('ssi') subdir('timer') diff --git a/hw/mips/cps.c b/hw/mips/cps.c index 7a0d289efa..2b436700ce 100644 --- a/hw/mips/cps.c +++ b/hw/mips/cps.c @@ -39,7 +39,7 @@ static void mips_cps_init(Object *obj) SysBusDevice *sbd = SYS_BUS_DEVICE(obj); MIPSCPSState *s = MIPS_CPS(obj); - s->clock = qdev_init_clock_in(DEVICE(obj), "clk-in", NULL, NULL); + s->clock = qdev_init_clock_in(DEVICE(obj), "clk-in", NULL, NULL, 0); /* * Cover entire address space as there do not seem to be any * constraints for the base address of CPC and GIC. diff --git a/hw/mips/malta.c b/hw/mips/malta.c index 9afc0b427b..26e7b1bd9f 100644 --- a/hw/mips/malta.c +++ b/hw/mips/malta.c @@ -58,7 +58,7 @@ #include "qemu/error-report.h" #include "hw/misc/empty_slot.h" #include "sysemu/kvm.h" -#include "hw/semihosting/semihost.h" +#include "semihosting/semihost.h" #include "hw/mips/cps.h" #include "hw/qdev-clock.h" diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig index 19c216f3ef..5426b9b1a1 100644 --- a/hw/misc/Kconfig +++ b/hw/misc/Kconfig @@ -2,6 +2,15 @@ config APPLESMC bool depends on ISA_BUS +config ARMSSE_CPUID + bool + +config ARMSSE_MHU + bool + +config ARMSSE_CPU_PWRCTRL + bool + config MAX111X bool diff --git a/hw/misc/armsse-cpu-pwrctrl.c b/hw/misc/armsse-cpu-pwrctrl.c new file mode 100644 index 0000000000..42fc38879f --- /dev/null +++ b/hw/misc/armsse-cpu-pwrctrl.c @@ -0,0 +1,149 @@ +/* + * Arm SSE CPU PWRCTRL register block + * + * Copyright (c) 2021 Linaro Limited + * Written by Peter Maydell + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 or + * (at your option) any later version. + */ + +/* + * This is a model of the "CPU<N>_PWRCTRL block" which is part of the + * Arm Corstone SSE-300 Example Subsystem and documented in + * https://developer.arm.com/documentation/101773/0000 + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "trace.h" +#include "qapi/error.h" +#include "migration/vmstate.h" +#include "hw/sysbus.h" +#include "hw/registerfields.h" +#include "hw/misc/armsse-cpu-pwrctrl.h" + +REG32(CPUPWRCFG, 0x0) +REG32(PID4, 0xfd0) +REG32(PID5, 0xfd4) +REG32(PID6, 0xfd8) +REG32(PID7, 0xfdc) +REG32(PID0, 0xfe0) +REG32(PID1, 0xfe4) +REG32(PID2, 0xfe8) +REG32(PID3, 0xfec) +REG32(CID0, 0xff0) +REG32(CID1, 0xff4) +REG32(CID2, 0xff8) +REG32(CID3, 0xffc) + +/* PID/CID values */ +static const int cpu_pwrctrl_id[] = { + 0x04, 0x00, 0x00, 0x00, /* PID4..PID7 */ + 0x5a, 0xb8, 0x0b, 0x00, /* PID0..PID3 */ + 0x0d, 0xf0, 0x05, 0xb1, /* CID0..CID3 */ +}; + +static uint64_t pwrctrl_read(void *opaque, hwaddr offset, unsigned size) +{ + ARMSSECPUPwrCtrl *s = ARMSSE_CPU_PWRCTRL(opaque); + uint64_t r; + + switch (offset) { + case A_CPUPWRCFG: + r = s->cpupwrcfg; + break; + case A_PID4 ... A_CID3: + r = cpu_pwrctrl_id[(offset - A_PID4) / 4]; + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "SSE CPU_PWRCTRL read: bad offset %x\n", (int)offset); + r = 0; + break; + } + trace_armsse_cpu_pwrctrl_read(offset, r, size); + return r; +} + +static void pwrctrl_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + ARMSSECPUPwrCtrl *s = ARMSSE_CPU_PWRCTRL(opaque); + + trace_armsse_cpu_pwrctrl_write(offset, value, size); + + switch (offset) { + case A_CPUPWRCFG: + qemu_log_mask(LOG_UNIMP, + "SSE CPU_PWRCTRL: CPUPWRCFG unimplemented\n"); + s->cpupwrcfg = value; + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "SSE CPU_PWRCTRL write: bad offset 0x%x\n", (int)offset); + break; + } +} + +static const MemoryRegionOps pwrctrl_ops = { + .read = pwrctrl_read, + .write = pwrctrl_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl.min_access_size = 4, + .impl.max_access_size = 4, + .valid.min_access_size = 4, + .valid.max_access_size = 4, +}; + +static void pwrctrl_reset(DeviceState *dev) +{ + ARMSSECPUPwrCtrl *s = ARMSSE_CPU_PWRCTRL(dev); + + s->cpupwrcfg = 0; +} + +static const VMStateDescription pwrctrl_vmstate = { + .name = "armsse-cpu-pwrctrl", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(cpupwrcfg, ARMSSECPUPwrCtrl), + VMSTATE_END_OF_LIST() + }, +}; + +static void pwrctrl_init(Object *obj) +{ + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + ARMSSECPUPwrCtrl *s = ARMSSE_CPU_PWRCTRL(obj); + + memory_region_init_io(&s->iomem, obj, &pwrctrl_ops, + s, "armsse-cpu-pwrctrl", 0x1000); + sysbus_init_mmio(sbd, &s->iomem); +} + +static void pwrctrl_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->reset = pwrctrl_reset; + dc->vmsd = &pwrctrl_vmstate; +} + +static const TypeInfo pwrctrl_info = { + .name = TYPE_ARMSSE_CPU_PWRCTRL, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(ARMSSECPUPwrCtrl), + .instance_init = pwrctrl_init, + .class_init = pwrctrl_class_init, +}; + +static void pwrctrl_register_types(void) +{ + type_register_static(&pwrctrl_info); +} + +type_init(pwrctrl_register_types); diff --git a/hw/misc/aspeed_lpc.c b/hw/misc/aspeed_lpc.c new file mode 100644 index 0000000000..2dddb27c35 --- /dev/null +++ b/hw/misc/aspeed_lpc.c @@ -0,0 +1,486 @@ +/* + * ASPEED LPC Controller + * + * Copyright (C) 2017-2018 IBM Corp. + * + * This code is licensed under the GPL version 2 or later. See + * the COPYING file in the top-level directory. + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "qemu/error-report.h" +#include "hw/misc/aspeed_lpc.h" +#include "qapi/error.h" +#include "qapi/visitor.h" +#include "hw/irq.h" +#include "hw/qdev-properties.h" +#include "migration/vmstate.h" + +#define TO_REG(offset) ((offset) >> 2) + +#define HICR0 TO_REG(0x00) +#define HICR0_LPC3E BIT(7) +#define HICR0_LPC2E BIT(6) +#define HICR0_LPC1E BIT(5) +#define HICR1 TO_REG(0x04) +#define HICR2 TO_REG(0x08) +#define HICR2_IBFIE3 BIT(3) +#define HICR2_IBFIE2 BIT(2) +#define HICR2_IBFIE1 BIT(1) +#define HICR3 TO_REG(0x0C) +#define HICR4 TO_REG(0x10) +#define HICR4_KCSENBL BIT(2) +#define IDR1 TO_REG(0x24) +#define IDR2 TO_REG(0x28) +#define IDR3 TO_REG(0x2C) +#define ODR1 TO_REG(0x30) +#define ODR2 TO_REG(0x34) +#define ODR3 TO_REG(0x38) +#define STR1 TO_REG(0x3C) +#define STR_OBF BIT(0) +#define STR_IBF BIT(1) +#define STR_CMD_DATA BIT(3) +#define STR2 TO_REG(0x40) +#define STR3 TO_REG(0x44) +#define HICR5 TO_REG(0x80) +#define HICR6 TO_REG(0x84) +#define HICR7 TO_REG(0x88) +#define HICR8 TO_REG(0x8C) +#define HICRB TO_REG(0x100) +#define HICRB_IBFIE4 BIT(1) +#define HICRB_LPC4E BIT(0) +#define IDR4 TO_REG(0x114) +#define ODR4 TO_REG(0x118) +#define STR4 TO_REG(0x11C) + +enum aspeed_kcs_channel_id { + kcs_channel_1 = 0, + kcs_channel_2, + kcs_channel_3, + kcs_channel_4, +}; + +static const enum aspeed_lpc_subdevice aspeed_kcs_subdevice_map[] = { + [kcs_channel_1] = aspeed_lpc_kcs_1, + [kcs_channel_2] = aspeed_lpc_kcs_2, + [kcs_channel_3] = aspeed_lpc_kcs_3, + [kcs_channel_4] = aspeed_lpc_kcs_4, +}; + +struct aspeed_kcs_channel { + enum aspeed_kcs_channel_id id; + + int idr; + int odr; + int str; +}; + +static const struct aspeed_kcs_channel aspeed_kcs_channel_map[] = { + [kcs_channel_1] = { + .id = kcs_channel_1, + .idr = IDR1, + .odr = ODR1, + .str = STR1 + }, + + [kcs_channel_2] = { + .id = kcs_channel_2, + .idr = IDR2, + .odr = ODR2, + .str = STR2 + }, + + [kcs_channel_3] = { + .id = kcs_channel_3, + .idr = IDR3, + .odr = ODR3, + .str = STR3 + }, + + [kcs_channel_4] = { + .id = kcs_channel_4, + .idr = IDR4, + .odr = ODR4, + .str = STR4 + }, +}; + +struct aspeed_kcs_register_data { + const char *name; + int reg; + const struct aspeed_kcs_channel *chan; +}; + +static const struct aspeed_kcs_register_data aspeed_kcs_registers[] = { + { + .name = "idr1", + .reg = IDR1, + .chan = &aspeed_kcs_channel_map[kcs_channel_1], + }, + { + .name = "odr1", + .reg = ODR1, + .chan = &aspeed_kcs_channel_map[kcs_channel_1], + }, + { + .name = "str1", + .reg = STR1, + .chan = &aspeed_kcs_channel_map[kcs_channel_1], + }, + { + .name = "idr2", + .reg = IDR2, + .chan = &aspeed_kcs_channel_map[kcs_channel_2], + }, + { + .name = "odr2", + .reg = ODR2, + .chan = &aspeed_kcs_channel_map[kcs_channel_2], + }, + { + .name = "str2", + .reg = STR2, + .chan = &aspeed_kcs_channel_map[kcs_channel_2], + }, + { + .name = "idr3", + .reg = IDR3, + .chan = &aspeed_kcs_channel_map[kcs_channel_3], + }, + { + .name = "odr3", + .reg = ODR3, + .chan = &aspeed_kcs_channel_map[kcs_channel_3], + }, + { + .name = "str3", + .reg = STR3, + .chan = &aspeed_kcs_channel_map[kcs_channel_3], + }, + { + .name = "idr4", + .reg = IDR4, + .chan = &aspeed_kcs_channel_map[kcs_channel_4], + }, + { + .name = "odr4", + .reg = ODR4, + .chan = &aspeed_kcs_channel_map[kcs_channel_4], + }, + { + .name = "str4", + .reg = STR4, + .chan = &aspeed_kcs_channel_map[kcs_channel_4], + }, + { }, +}; + +static const struct aspeed_kcs_register_data * +aspeed_kcs_get_register_data_by_name(const char *name) +{ + const struct aspeed_kcs_register_data *pos = aspeed_kcs_registers; + + while (pos->name) { + if (!strcmp(pos->name, name)) { + return pos; + } + pos++; + } + + return NULL; +} + +static const struct aspeed_kcs_channel * +aspeed_kcs_get_channel_by_register(int reg) +{ + const struct aspeed_kcs_register_data *pos = aspeed_kcs_registers; + + while (pos->name) { + if (pos->reg == reg) { + return pos->chan; + } + pos++; + } + + return NULL; +} + +static void aspeed_kcs_get_register_property(Object *obj, + Visitor *v, + const char *name, + void *opaque, + Error **errp) +{ + const struct aspeed_kcs_register_data *data; + AspeedLPCState *s = ASPEED_LPC(obj); + uint32_t val; + + data = aspeed_kcs_get_register_data_by_name(name); + if (!data) { + return; + } + + if (!strncmp("odr", name, 3)) { + s->regs[data->chan->str] &= ~STR_OBF; + } + + val = s->regs[data->reg]; + + visit_type_uint32(v, name, &val, errp); +} + +static bool aspeed_kcs_channel_enabled(AspeedLPCState *s, + const struct aspeed_kcs_channel *channel) +{ + switch (channel->id) { + case kcs_channel_1: return s->regs[HICR0] & HICR0_LPC1E; + case kcs_channel_2: return s->regs[HICR0] & HICR0_LPC2E; + case kcs_channel_3: + return (s->regs[HICR0] & HICR0_LPC3E) && + (s->regs[HICR4] & HICR4_KCSENBL); + case kcs_channel_4: return s->regs[HICRB] & HICRB_LPC4E; + default: return false; + } +} + +static bool +aspeed_kcs_channel_ibf_irq_enabled(AspeedLPCState *s, + const struct aspeed_kcs_channel *channel) +{ + if (!aspeed_kcs_channel_enabled(s, channel)) { + return false; + } + + switch (channel->id) { + case kcs_channel_1: return s->regs[HICR2] & HICR2_IBFIE1; + case kcs_channel_2: return s->regs[HICR2] & HICR2_IBFIE2; + case kcs_channel_3: return s->regs[HICR2] & HICR2_IBFIE3; + case kcs_channel_4: return s->regs[HICRB] & HICRB_IBFIE4; + default: return false; + } +} + +static void aspeed_kcs_set_register_property(Object *obj, + Visitor *v, + const char *name, + void *opaque, + Error **errp) +{ + const struct aspeed_kcs_register_data *data; + AspeedLPCState *s = ASPEED_LPC(obj); + uint32_t val; + + data = aspeed_kcs_get_register_data_by_name(name); + if (!data) { + return; + } + + if (!visit_type_uint32(v, name, &val, errp)) { + return; + } + + if (strncmp("str", name, 3)) { + s->regs[data->reg] = val; + } + + if (!strncmp("idr", name, 3)) { + s->regs[data->chan->str] |= STR_IBF; + if (aspeed_kcs_channel_ibf_irq_enabled(s, data->chan)) { + enum aspeed_lpc_subdevice subdev; + + subdev = aspeed_kcs_subdevice_map[data->chan->id]; + qemu_irq_raise(s->subdevice_irqs[subdev]); + } + } +} + +static void aspeed_lpc_set_irq(void *opaque, int irq, int level) +{ + AspeedLPCState *s = (AspeedLPCState *)opaque; + + if (level) { + s->subdevice_irqs_pending |= BIT(irq); + } else { + s->subdevice_irqs_pending &= ~BIT(irq); + } + + qemu_set_irq(s->irq, !!s->subdevice_irqs_pending); +} + +static uint64_t aspeed_lpc_read(void *opaque, hwaddr offset, unsigned size) +{ + AspeedLPCState *s = ASPEED_LPC(opaque); + int reg = TO_REG(offset); + + if (reg >= ARRAY_SIZE(s->regs)) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Out-of-bounds read at offset 0x%" HWADDR_PRIx "\n", + __func__, offset); + return 0; + } + + switch (reg) { + case IDR1: + case IDR2: + case IDR3: + case IDR4: + { + const struct aspeed_kcs_channel *channel; + + channel = aspeed_kcs_get_channel_by_register(reg); + if (s->regs[channel->str] & STR_IBF) { + enum aspeed_lpc_subdevice subdev; + + subdev = aspeed_kcs_subdevice_map[channel->id]; + qemu_irq_lower(s->subdevice_irqs[subdev]); + } + + s->regs[channel->str] &= ~STR_IBF; + break; + } + default: + break; + } + + return s->regs[reg]; +} + +static void aspeed_lpc_write(void *opaque, hwaddr offset, uint64_t data, + unsigned int size) +{ + AspeedLPCState *s = ASPEED_LPC(opaque); + int reg = TO_REG(offset); + + if (reg >= ARRAY_SIZE(s->regs)) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Out-of-bounds write at offset 0x%" HWADDR_PRIx "\n", + __func__, offset); + return; + } + + + switch (reg) { + case ODR1: + case ODR2: + case ODR3: + case ODR4: + s->regs[aspeed_kcs_get_channel_by_register(reg)->str] |= STR_OBF; + break; + default: + break; + } + + s->regs[reg] = data; +} + +static const MemoryRegionOps aspeed_lpc_ops = { + .read = aspeed_lpc_read, + .write = aspeed_lpc_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + }, +}; + +static void aspeed_lpc_reset(DeviceState *dev) +{ + struct AspeedLPCState *s = ASPEED_LPC(dev); + + s->subdevice_irqs_pending = 0; + + memset(s->regs, 0, sizeof(s->regs)); + + s->regs[HICR7] = s->hicr7; +} + +static void aspeed_lpc_realize(DeviceState *dev, Error **errp) +{ + AspeedLPCState *s = ASPEED_LPC(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + + sysbus_init_irq(sbd, &s->irq); + sysbus_init_irq(sbd, &s->subdevice_irqs[aspeed_lpc_kcs_1]); + sysbus_init_irq(sbd, &s->subdevice_irqs[aspeed_lpc_kcs_2]); + sysbus_init_irq(sbd, &s->subdevice_irqs[aspeed_lpc_kcs_3]); + sysbus_init_irq(sbd, &s->subdevice_irqs[aspeed_lpc_kcs_4]); + sysbus_init_irq(sbd, &s->subdevice_irqs[aspeed_lpc_ibt]); + + memory_region_init_io(&s->iomem, OBJECT(s), &aspeed_lpc_ops, s, + TYPE_ASPEED_LPC, 0x1000); + + sysbus_init_mmio(sbd, &s->iomem); + + qdev_init_gpio_in(dev, aspeed_lpc_set_irq, ASPEED_LPC_NR_SUBDEVS); +} + +static void aspeed_lpc_init(Object *obj) +{ + object_property_add(obj, "idr1", "uint32", aspeed_kcs_get_register_property, + aspeed_kcs_set_register_property, NULL, NULL); + object_property_add(obj, "odr1", "uint32", aspeed_kcs_get_register_property, + aspeed_kcs_set_register_property, NULL, NULL); + object_property_add(obj, "str1", "uint32", aspeed_kcs_get_register_property, + aspeed_kcs_set_register_property, NULL, NULL); + object_property_add(obj, "idr2", "uint32", aspeed_kcs_get_register_property, + aspeed_kcs_set_register_property, NULL, NULL); + object_property_add(obj, "odr2", "uint32", aspeed_kcs_get_register_property, + aspeed_kcs_set_register_property, NULL, NULL); + object_property_add(obj, "str2", "uint32", aspeed_kcs_get_register_property, + aspeed_kcs_set_register_property, NULL, NULL); + object_property_add(obj, "idr3", "uint32", aspeed_kcs_get_register_property, + aspeed_kcs_set_register_property, NULL, NULL); + object_property_add(obj, "odr3", "uint32", aspeed_kcs_get_register_property, + aspeed_kcs_set_register_property, NULL, NULL); + object_property_add(obj, "str3", "uint32", aspeed_kcs_get_register_property, + aspeed_kcs_set_register_property, NULL, NULL); + object_property_add(obj, "idr4", "uint32", aspeed_kcs_get_register_property, + aspeed_kcs_set_register_property, NULL, NULL); + object_property_add(obj, "odr4", "uint32", aspeed_kcs_get_register_property, + aspeed_kcs_set_register_property, NULL, NULL); + object_property_add(obj, "str4", "uint32", aspeed_kcs_get_register_property, + aspeed_kcs_set_register_property, NULL, NULL); +} + +static const VMStateDescription vmstate_aspeed_lpc = { + .name = TYPE_ASPEED_LPC, + .version_id = 2, + .minimum_version_id = 2, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(regs, AspeedLPCState, ASPEED_LPC_NR_REGS), + VMSTATE_UINT32(subdevice_irqs_pending, AspeedLPCState), + VMSTATE_END_OF_LIST(), + } +}; + +static Property aspeed_lpc_properties[] = { + DEFINE_PROP_UINT32("hicr7", AspeedLPCState, hicr7, 0), + DEFINE_PROP_END_OF_LIST(), +}; + +static void aspeed_lpc_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = aspeed_lpc_realize; + dc->reset = aspeed_lpc_reset; + dc->desc = "Aspeed LPC Controller", + dc->vmsd = &vmstate_aspeed_lpc; + device_class_set_props(dc, aspeed_lpc_properties); +} + +static const TypeInfo aspeed_lpc_info = { + .name = TYPE_ASPEED_LPC, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(AspeedLPCState), + .class_init = aspeed_lpc_class_init, + .instance_init = aspeed_lpc_init, +}; + +static void aspeed_lpc_register_types(void) +{ + type_register_static(&aspeed_lpc_info); +} + +type_init(aspeed_lpc_register_types); diff --git a/hw/misc/bcm2835_cprman.c b/hw/misc/bcm2835_cprman.c index 7e415a017c..75e6c574d4 100644 --- a/hw/misc/bcm2835_cprman.c +++ b/hw/misc/bcm2835_cprman.c @@ -107,7 +107,7 @@ static void pll_update(CprmanPllState *pll) clock_update_hz(pll->out, freq); } -static void pll_xosc_update(void *opaque) +static void pll_xosc_update(void *opaque, ClockEvent event) { pll_update(CPRMAN_PLL(opaque)); } @@ -116,7 +116,8 @@ static void pll_init(Object *obj) { CprmanPllState *s = CPRMAN_PLL(obj); - s->xosc_in = qdev_init_clock_in(DEVICE(s), "xosc-in", pll_xosc_update, s); + s->xosc_in = qdev_init_clock_in(DEVICE(s), "xosc-in", pll_xosc_update, + s, ClockUpdate); s->out = qdev_init_clock_out(DEVICE(s), "out"); } @@ -209,7 +210,7 @@ static void pll_update_all_channels(BCM2835CprmanState *s, } } -static void pll_channel_pll_in_update(void *opaque) +static void pll_channel_pll_in_update(void *opaque, ClockEvent event) { pll_channel_update(CPRMAN_PLL_CHANNEL(opaque)); } @@ -219,7 +220,8 @@ static void pll_channel_init(Object *obj) CprmanPllChannelState *s = CPRMAN_PLL_CHANNEL(obj); s->pll_in = qdev_init_clock_in(DEVICE(s), "pll-in", - pll_channel_pll_in_update, s); + pll_channel_pll_in_update, s, + ClockUpdate); s->out = qdev_init_clock_out(DEVICE(s), "out"); } @@ -303,7 +305,7 @@ static void clock_mux_update(CprmanClockMuxState *mux) clock_update_hz(mux->out, freq); } -static void clock_mux_src_update(void *opaque) +static void clock_mux_src_update(void *opaque, ClockEvent event) { CprmanClockMuxState **backref = opaque; CprmanClockMuxState *s = *backref; @@ -335,7 +337,8 @@ static void clock_mux_init(Object *obj) s->backref[i] = s; s->srcs[i] = qdev_init_clock_in(DEVICE(s), name, clock_mux_src_update, - &s->backref[i]); + &s->backref[i], + ClockUpdate); g_free(name); } @@ -380,7 +383,7 @@ static void dsi0hsck_mux_update(CprmanDsi0HsckMuxState *s) clock_update(s->out, clock_get(src)); } -static void dsi0hsck_mux_in_update(void *opaque) +static void dsi0hsck_mux_in_update(void *opaque, ClockEvent event) { dsi0hsck_mux_update(CPRMAN_DSI0HSCK_MUX(opaque)); } @@ -390,8 +393,10 @@ static void dsi0hsck_mux_init(Object *obj) CprmanDsi0HsckMuxState *s = CPRMAN_DSI0HSCK_MUX(obj); DeviceState *dev = DEVICE(obj); - s->plla_in = qdev_init_clock_in(dev, "plla-in", dsi0hsck_mux_in_update, s); - s->plld_in = qdev_init_clock_in(dev, "plld-in", dsi0hsck_mux_in_update, s); + s->plla_in = qdev_init_clock_in(dev, "plla-in", dsi0hsck_mux_in_update, + s, ClockUpdate); + s->plld_in = qdev_init_clock_in(dev, "plld-in", dsi0hsck_mux_in_update, + s, ClockUpdate); s->out = qdev_init_clock_out(DEVICE(s), "out"); } diff --git a/hw/misc/iotkit-secctl.c b/hw/misc/iotkit-secctl.c index 9fdb82056a..7b41cfa8fc 100644 --- a/hw/misc/iotkit-secctl.c +++ b/hw/misc/iotkit-secctl.c @@ -19,6 +19,8 @@ #include "hw/registerfields.h" #include "hw/irq.h" #include "hw/misc/iotkit-secctl.h" +#include "hw/arm/armsse-version.h" +#include "hw/qdev-properties.h" /* Registers in the secure privilege control block */ REG32(SECRESPCFG, 0x10) @@ -95,6 +97,19 @@ static const uint8_t iotkit_secctl_ns_idregs[] = { 0x0d, 0xf0, 0x05, 0xb1, }; +static const uint8_t iotkit_secctl_s_sse300_idregs[] = { + 0x04, 0x00, 0x00, 0x00, + 0x52, 0xb8, 0x2b, 0x00, + 0x0d, 0xf0, 0x05, 0xb1, +}; + +static const uint8_t iotkit_secctl_ns_sse300_idregs[] = { + 0x04, 0x00, 0x00, 0x00, + 0x53, 0xb8, 0x2b, 0x00, + 0x0d, 0xf0, 0x05, 0xb1, +}; + + /* The register sets for the various PPCs (AHB internal, APB internal, * AHB expansion, APB expansion) are all set up so that they are * in 16-aligned blocks so offsets 0xN0, 0xN4, 0xN8, 0xNC are PPCs @@ -213,7 +228,14 @@ static MemTxResult iotkit_secctl_s_read(void *opaque, hwaddr addr, case A_CID1: case A_CID2: case A_CID3: - r = iotkit_secctl_s_idregs[(offset - A_PID4) / 4]; + switch (s->sse_version) { + case ARMSSE_SSE300: + r = iotkit_secctl_s_sse300_idregs[(offset - A_PID4) / 4]; + break; + default: + r = iotkit_secctl_s_idregs[(offset - A_PID4) / 4]; + break; + } break; case A_SECPPCINTCLR: case A_SECMSCINTCLR: @@ -473,7 +495,14 @@ static MemTxResult iotkit_secctl_ns_read(void *opaque, hwaddr addr, case A_CID1: case A_CID2: case A_CID3: - r = iotkit_secctl_ns_idregs[(offset - A_PID4) / 4]; + switch (s->sse_version) { + case ARMSSE_SSE300: + r = iotkit_secctl_ns_sse300_idregs[(offset - A_PID4) / 4]; + break; + default: + r = iotkit_secctl_ns_idregs[(offset - A_PID4) / 4]; + break; + } break; default: qemu_log_mask(LOG_GUEST_ERROR, @@ -710,6 +739,16 @@ static void iotkit_secctl_init(Object *obj) sysbus_init_mmio(sbd, &s->ns_regs); } +static void iotkit_secctl_realize(DeviceState *dev, Error **errp) +{ + IoTKitSecCtl *s = IOTKIT_SECCTL(dev); + + if (!armsse_version_valid(s->sse_version)) { + error_setg(errp, "invalid sse-version value %d", s->sse_version); + return; + } +} + static const VMStateDescription iotkit_secctl_ppc_vmstate = { .name = "iotkit-secctl-ppc", .version_id = 1, @@ -775,12 +814,19 @@ static const VMStateDescription iotkit_secctl_vmstate = { }, }; +static Property iotkit_secctl_props[] = { + DEFINE_PROP_UINT32("sse-version", IoTKitSecCtl, sse_version, 0), + DEFINE_PROP_END_OF_LIST() +}; + static void iotkit_secctl_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); dc->vmsd = &iotkit_secctl_vmstate; dc->reset = iotkit_secctl_reset; + dc->realize = iotkit_secctl_realize; + device_class_set_props(dc, iotkit_secctl_props); } static const TypeInfo iotkit_secctl_info = { diff --git a/hw/misc/iotkit-sysctl.c b/hw/misc/iotkit-sysctl.c index 222511c4b0..9ee8fe8495 100644 --- a/hw/misc/iotkit-sysctl.c +++ b/hw/misc/iotkit-sysctl.c @@ -28,6 +28,7 @@ #include "hw/registerfields.h" #include "hw/misc/iotkit-sysctl.h" #include "hw/qdev-properties.h" +#include "hw/arm/armsse-version.h" #include "target/arm/arm-powerctl.h" #include "target/arm/cpu.h" @@ -44,16 +45,22 @@ REG32(SWRESET, 0x108) FIELD(SWRESET, SWRESETREQ, 9, 1) REG32(GRETREG, 0x10c) REG32(INITSVTOR0, 0x110) + FIELD(INITSVTOR0, LOCK, 0, 1) + FIELD(INITSVTOR0, VTOR, 7, 25) REG32(INITSVTOR1, 0x114) REG32(CPUWAIT, 0x118) REG32(NMI_ENABLE, 0x11c) /* BUSWAIT in IoTKit */ REG32(WICCTRL, 0x120) REG32(EWCTRL, 0x124) +REG32(PWRCTRL, 0x1fc) + FIELD(PWRCTRL, PPU_ACCESS_UNLOCK, 0, 1) + FIELD(PWRCTRL, PPU_ACCESS_FILTER, 1, 1) REG32(PDCM_PD_SYS_SENSE, 0x200) +REG32(PDCM_PD_CPU0_SENSE, 0x204) REG32(PDCM_PD_SRAM0_SENSE, 0x20c) REG32(PDCM_PD_SRAM1_SENSE, 0x210) -REG32(PDCM_PD_SRAM2_SENSE, 0x214) -REG32(PDCM_PD_SRAM3_SENSE, 0x218) +REG32(PDCM_PD_SRAM2_SENSE, 0x214) /* PDCM_PD_VMR0_SENSE on SSE300 */ +REG32(PDCM_PD_SRAM3_SENSE, 0x218) /* PDCM_PD_VMR1_SENSE on SSE300 */ REG32(PID4, 0xfd0) REG32(PID5, 0xfd4) REG32(PID6, 0xfd8) @@ -68,12 +75,19 @@ REG32(CID2, 0xff8) REG32(CID3, 0xffc) /* PID/CID values */ -static const int sysctl_id[] = { +static const int iotkit_sysctl_id[] = { 0x04, 0x00, 0x00, 0x00, /* PID4..PID7 */ 0x54, 0xb8, 0x0b, 0x00, /* PID0..PID3 */ 0x0d, 0xf0, 0x05, 0xb1, /* CID0..CID3 */ }; +/* Also used by the SSE300 */ +static const int sse200_sysctl_id[] = { + 0x04, 0x00, 0x00, 0x00, /* PID4..PID7 */ + 0x54, 0xb8, 0x1b, 0x00, /* PID0..PID3 */ + 0x0d, 0xf0, 0x05, 0xb1, /* CID0..CID3 */ +}; + /* * Set the initial secure vector table offset address for the core. * This will take effect when the CPU next resets. @@ -100,28 +114,52 @@ static uint64_t iotkit_sysctl_read(void *opaque, hwaddr offset, r = s->secure_debug; break; case A_SCSECCTRL: - if (!s->is_sse200) { + switch (s->sse_version) { + case ARMSSE_IOTKIT: goto bad_offset; + case ARMSSE_SSE200: + case ARMSSE_SSE300: + r = s->scsecctrl; + break; + default: + g_assert_not_reached(); } - r = s->scsecctrl; break; case A_FCLK_DIV: - if (!s->is_sse200) { + switch (s->sse_version) { + case ARMSSE_IOTKIT: goto bad_offset; + case ARMSSE_SSE200: + case ARMSSE_SSE300: + r = s->fclk_div; + break; + default: + g_assert_not_reached(); } - r = s->fclk_div; break; case A_SYSCLK_DIV: - if (!s->is_sse200) { + switch (s->sse_version) { + case ARMSSE_IOTKIT: goto bad_offset; + case ARMSSE_SSE200: + case ARMSSE_SSE300: + r = s->sysclk_div; + break; + default: + g_assert_not_reached(); } - r = s->sysclk_div; break; case A_CLOCK_FORCE: - if (!s->is_sse200) { + switch (s->sse_version) { + case ARMSSE_IOTKIT: goto bad_offset; + case ARMSSE_SSE200: + case ARMSSE_SSE300: + r = s->clock_force; + break; + default: + g_assert_not_reached(); } - r = s->clock_force; break; case A_RESET_SYNDROME: r = s->reset_syndrome; @@ -136,63 +174,178 @@ static uint64_t iotkit_sysctl_read(void *opaque, hwaddr offset, r = s->initsvtor0; break; case A_INITSVTOR1: - if (!s->is_sse200) { + switch (s->sse_version) { + case ARMSSE_IOTKIT: + goto bad_offset; + case ARMSSE_SSE200: + r = s->initsvtor1; + break; + case ARMSSE_SSE300: goto bad_offset; + default: + g_assert_not_reached(); } - r = s->initsvtor1; break; case A_CPUWAIT: - r = s->cpuwait; + switch (s->sse_version) { + case ARMSSE_IOTKIT: + case ARMSSE_SSE200: + r = s->cpuwait; + break; + case ARMSSE_SSE300: + /* In SSE300 this is reserved (for INITSVTOR2) */ + goto bad_offset; + default: + g_assert_not_reached(); + } break; case A_NMI_ENABLE: - /* In IoTKit this is named BUSWAIT but is marked reserved, R/O, zero */ - if (!s->is_sse200) { + switch (s->sse_version) { + case ARMSSE_IOTKIT: + /* In IoTKit this is named BUSWAIT but marked reserved, R/O, zero */ r = 0; break; + case ARMSSE_SSE200: + r = s->nmi_enable; + break; + case ARMSSE_SSE300: + /* In SSE300 this is reserved (for INITSVTOR3) */ + goto bad_offset; + default: + g_assert_not_reached(); } - r = s->nmi_enable; break; case A_WICCTRL: - r = s->wicctrl; + switch (s->sse_version) { + case ARMSSE_IOTKIT: + case ARMSSE_SSE200: + r = s->wicctrl; + break; + case ARMSSE_SSE300: + /* In SSE300 this offset is CPUWAIT */ + r = s->cpuwait; + break; + default: + g_assert_not_reached(); + } break; case A_EWCTRL: - if (!s->is_sse200) { + switch (s->sse_version) { + case ARMSSE_IOTKIT: goto bad_offset; + case ARMSSE_SSE200: + r = s->ewctrl; + break; + case ARMSSE_SSE300: + /* In SSE300 this offset is is NMI_ENABLE */ + r = s->nmi_enable; + break; + default: + g_assert_not_reached(); + } + break; + case A_PWRCTRL: + switch (s->sse_version) { + case ARMSSE_IOTKIT: + case ARMSSE_SSE200: + goto bad_offset; + case ARMSSE_SSE300: + r = s->pwrctrl; + break; + default: + g_assert_not_reached(); } - r = s->ewctrl; break; case A_PDCM_PD_SYS_SENSE: - if (!s->is_sse200) { + switch (s->sse_version) { + case ARMSSE_IOTKIT: goto bad_offset; + case ARMSSE_SSE200: + case ARMSSE_SSE300: + r = s->pdcm_pd_sys_sense; + break; + default: + g_assert_not_reached(); + } + break; + case A_PDCM_PD_CPU0_SENSE: + switch (s->sse_version) { + case ARMSSE_IOTKIT: + case ARMSSE_SSE200: + goto bad_offset; + case ARMSSE_SSE300: + r = s->pdcm_pd_cpu0_sense; + break; + default: + g_assert_not_reached(); } - r = s->pdcm_pd_sys_sense; break; case A_PDCM_PD_SRAM0_SENSE: - if (!s->is_sse200) { + switch (s->sse_version) { + case ARMSSE_IOTKIT: goto bad_offset; + case ARMSSE_SSE200: + r = s->pdcm_pd_sram0_sense; + break; + case ARMSSE_SSE300: + goto bad_offset; + default: + g_assert_not_reached(); } - r = s->pdcm_pd_sram0_sense; break; case A_PDCM_PD_SRAM1_SENSE: - if (!s->is_sse200) { + switch (s->sse_version) { + case ARMSSE_IOTKIT: + goto bad_offset; + case ARMSSE_SSE200: + r = s->pdcm_pd_sram1_sense; + break; + case ARMSSE_SSE300: goto bad_offset; + default: + g_assert_not_reached(); } - r = s->pdcm_pd_sram1_sense; break; case A_PDCM_PD_SRAM2_SENSE: - if (!s->is_sse200) { + switch (s->sse_version) { + case ARMSSE_IOTKIT: goto bad_offset; + case ARMSSE_SSE200: + r = s->pdcm_pd_sram2_sense; + break; + case ARMSSE_SSE300: + r = s->pdcm_pd_vmr0_sense; + break; + default: + g_assert_not_reached(); } - r = s->pdcm_pd_sram2_sense; break; case A_PDCM_PD_SRAM3_SENSE: - if (!s->is_sse200) { + switch (s->sse_version) { + case ARMSSE_IOTKIT: goto bad_offset; + case ARMSSE_SSE200: + r = s->pdcm_pd_sram3_sense; + break; + case ARMSSE_SSE300: + r = s->pdcm_pd_vmr1_sense; + break; + default: + g_assert_not_reached(); } - r = s->pdcm_pd_sram3_sense; break; case A_PID4 ... A_CID3: - r = sysctl_id[(offset - A_PID4) / 4]; + switch (s->sse_version) { + case ARMSSE_IOTKIT: + r = iotkit_sysctl_id[(offset - A_PID4) / 4]; + break; + case ARMSSE_SSE200: + case ARMSSE_SSE300: + r = sse200_sysctl_id[(offset - A_PID4) / 4]; + break; + default: + g_assert_not_reached(); + } break; case A_SECDBGSET: case A_SECDBGCLR: @@ -213,6 +366,21 @@ static uint64_t iotkit_sysctl_read(void *opaque, hwaddr offset, return r; } +static void cpuwait_write(IoTKitSysCtl *s, uint32_t value) +{ + int num_cpus = (s->sse_version == ARMSSE_SSE300) ? 1 : 2; + int i; + + for (i = 0; i < num_cpus; i++) { + uint32_t mask = 1 << i; + if ((s->cpuwait & mask) && !(value & mask)) { + /* Powering up CPU 0 */ + arm_set_cpu_on_and_reset(i); + } + } + s->cpuwait = value; +} + static void iotkit_sysctl_write(void *opaque, hwaddr offset, uint64_t value, unsigned size) { @@ -249,23 +417,53 @@ static void iotkit_sysctl_write(void *opaque, hwaddr offset, s->gretreg = value; break; case A_INITSVTOR0: - s->initsvtor0 = value; - set_init_vtor(0, s->initsvtor0); + switch (s->sse_version) { + case ARMSSE_SSE300: + /* SSE300 has a LOCK bit which prevents further writes when set */ + if (s->initsvtor0 & R_INITSVTOR0_LOCK_MASK) { + qemu_log_mask(LOG_GUEST_ERROR, + "IoTKit INITSVTOR0 write when register locked\n"); + break; + } + s->initsvtor0 = value; + set_init_vtor(0, s->initsvtor0 & R_INITSVTOR0_VTOR_MASK); + break; + case ARMSSE_IOTKIT: + case ARMSSE_SSE200: + s->initsvtor0 = value; + set_init_vtor(0, s->initsvtor0); + break; + default: + g_assert_not_reached(); + } break; case A_CPUWAIT: - if ((s->cpuwait & 1) && !(value & 1)) { - /* Powering up CPU 0 */ - arm_set_cpu_on_and_reset(0); - } - if ((s->cpuwait & 2) && !(value & 2)) { - /* Powering up CPU 1 */ - arm_set_cpu_on_and_reset(1); + switch (s->sse_version) { + case ARMSSE_IOTKIT: + case ARMSSE_SSE200: + cpuwait_write(s, value); + break; + case ARMSSE_SSE300: + /* In SSE300 this is reserved (for INITSVTOR2) */ + goto bad_offset; + default: + g_assert_not_reached(); } - s->cpuwait = value; break; case A_WICCTRL: - qemu_log_mask(LOG_UNIMP, "IoTKit SysCtl WICCTRL unimplemented\n"); - s->wicctrl = value; + switch (s->sse_version) { + case ARMSSE_IOTKIT: + case ARMSSE_SSE200: + qemu_log_mask(LOG_UNIMP, "IoTKit SysCtl WICCTRL unimplemented\n"); + s->wicctrl = value; + break; + case ARMSSE_SSE300: + /* In SSE300 this offset is CPUWAIT */ + cpuwait_write(s, value); + break; + default: + g_assert_not_reached(); + } break; case A_SECDBGSET: /* write-1-to-set */ @@ -283,94 +481,214 @@ static void iotkit_sysctl_write(void *opaque, hwaddr offset, } break; case A_SCSECCTRL: - if (!s->is_sse200) { + switch (s->sse_version) { + case ARMSSE_IOTKIT: goto bad_offset; + case ARMSSE_SSE200: + case ARMSSE_SSE300: + qemu_log_mask(LOG_UNIMP, "IoTKit SysCtl SCSECCTRL unimplemented\n"); + s->scsecctrl = value; + break; + default: + g_assert_not_reached(); } - qemu_log_mask(LOG_UNIMP, "IoTKit SysCtl SCSECCTRL unimplemented\n"); - s->scsecctrl = value; break; case A_FCLK_DIV: - if (!s->is_sse200) { + switch (s->sse_version) { + case ARMSSE_IOTKIT: goto bad_offset; + case ARMSSE_SSE200: + case ARMSSE_SSE300: + qemu_log_mask(LOG_UNIMP, "IoTKit SysCtl FCLK_DIV unimplemented\n"); + s->fclk_div = value; + break; + default: + g_assert_not_reached(); } - qemu_log_mask(LOG_UNIMP, "IoTKit SysCtl FCLK_DIV unimplemented\n"); - s->fclk_div = value; break; case A_SYSCLK_DIV: - if (!s->is_sse200) { + switch (s->sse_version) { + case ARMSSE_IOTKIT: goto bad_offset; + case ARMSSE_SSE200: + case ARMSSE_SSE300: + qemu_log_mask(LOG_UNIMP, "IoTKit SysCtl SYSCLK_DIV unimplemented\n"); + s->sysclk_div = value; + break; + default: + g_assert_not_reached(); } - qemu_log_mask(LOG_UNIMP, "IoTKit SysCtl SYSCLK_DIV unimplemented\n"); - s->sysclk_div = value; break; case A_CLOCK_FORCE: - if (!s->is_sse200) { + switch (s->sse_version) { + case ARMSSE_IOTKIT: goto bad_offset; + case ARMSSE_SSE200: + case ARMSSE_SSE300: + qemu_log_mask(LOG_UNIMP, "IoTKit SysCtl CLOCK_FORCE unimplemented\n"); + s->clock_force = value; + break; + default: + g_assert_not_reached(); } - qemu_log_mask(LOG_UNIMP, "IoTKit SysCtl CLOCK_FORCE unimplemented\n"); - s->clock_force = value; break; case A_INITSVTOR1: - if (!s->is_sse200) { + switch (s->sse_version) { + case ARMSSE_IOTKIT: goto bad_offset; + case ARMSSE_SSE200: + s->initsvtor1 = value; + set_init_vtor(1, s->initsvtor1); + break; + case ARMSSE_SSE300: + goto bad_offset; + default: + g_assert_not_reached(); } - s->initsvtor1 = value; - set_init_vtor(1, s->initsvtor1); break; case A_EWCTRL: - if (!s->is_sse200) { + switch (s->sse_version) { + case ARMSSE_IOTKIT: goto bad_offset; + case ARMSSE_SSE200: + qemu_log_mask(LOG_UNIMP, "IoTKit SysCtl EWCTRL unimplemented\n"); + s->ewctrl = value; + break; + case ARMSSE_SSE300: + /* In SSE300 this offset is is NMI_ENABLE */ + qemu_log_mask(LOG_UNIMP, "IoTKit SysCtl NMI_ENABLE unimplemented\n"); + s->nmi_enable = value; + break; + default: + g_assert_not_reached(); + } + break; + case A_PWRCTRL: + switch (s->sse_version) { + case ARMSSE_IOTKIT: + case ARMSSE_SSE200: + goto bad_offset; + case ARMSSE_SSE300: + if (!(s->pwrctrl & R_PWRCTRL_PPU_ACCESS_UNLOCK_MASK)) { + qemu_log_mask(LOG_GUEST_ERROR, + "IoTKit PWRCTRL write when register locked\n"); + break; + } + s->pwrctrl = value; + break; + default: + g_assert_not_reached(); } - qemu_log_mask(LOG_UNIMP, "IoTKit SysCtl EWCTRL unimplemented\n"); - s->ewctrl = value; break; case A_PDCM_PD_SYS_SENSE: - if (!s->is_sse200) { + switch (s->sse_version) { + case ARMSSE_IOTKIT: + goto bad_offset; + case ARMSSE_SSE200: + case ARMSSE_SSE300: + qemu_log_mask(LOG_UNIMP, + "IoTKit SysCtl PDCM_PD_SYS_SENSE unimplemented\n"); + s->pdcm_pd_sys_sense = value; + break; + default: + g_assert_not_reached(); + } + break; + case A_PDCM_PD_CPU0_SENSE: + switch (s->sse_version) { + case ARMSSE_IOTKIT: + case ARMSSE_SSE200: goto bad_offset; + case ARMSSE_SSE300: + qemu_log_mask(LOG_UNIMP, + "IoTKit SysCtl PDCM_PD_CPU0_SENSE unimplemented\n"); + s->pdcm_pd_cpu0_sense = value; + break; + default: + g_assert_not_reached(); } - qemu_log_mask(LOG_UNIMP, - "IoTKit SysCtl PDCM_PD_SYS_SENSE unimplemented\n"); - s->pdcm_pd_sys_sense = value; break; case A_PDCM_PD_SRAM0_SENSE: - if (!s->is_sse200) { + switch (s->sse_version) { + case ARMSSE_IOTKIT: + goto bad_offset; + case ARMSSE_SSE200: + qemu_log_mask(LOG_UNIMP, + "IoTKit SysCtl PDCM_PD_SRAM0_SENSE unimplemented\n"); + s->pdcm_pd_sram0_sense = value; + break; + case ARMSSE_SSE300: goto bad_offset; + default: + g_assert_not_reached(); } - qemu_log_mask(LOG_UNIMP, - "IoTKit SysCtl PDCM_PD_SRAM0_SENSE unimplemented\n"); - s->pdcm_pd_sram0_sense = value; break; case A_PDCM_PD_SRAM1_SENSE: - if (!s->is_sse200) { + switch (s->sse_version) { + case ARMSSE_IOTKIT: + goto bad_offset; + case ARMSSE_SSE200: + qemu_log_mask(LOG_UNIMP, + "IoTKit SysCtl PDCM_PD_SRAM1_SENSE unimplemented\n"); + s->pdcm_pd_sram1_sense = value; + break; + case ARMSSE_SSE300: goto bad_offset; + default: + g_assert_not_reached(); } - qemu_log_mask(LOG_UNIMP, - "IoTKit SysCtl PDCM_PD_SRAM1_SENSE unimplemented\n"); - s->pdcm_pd_sram1_sense = value; break; case A_PDCM_PD_SRAM2_SENSE: - if (!s->is_sse200) { + switch (s->sse_version) { + case ARMSSE_IOTKIT: goto bad_offset; + case ARMSSE_SSE200: + qemu_log_mask(LOG_UNIMP, + "IoTKit SysCtl PDCM_PD_SRAM2_SENSE unimplemented\n"); + s->pdcm_pd_sram2_sense = value; + break; + case ARMSSE_SSE300: + qemu_log_mask(LOG_UNIMP, + "IoTKit SysCtl PDCM_PD_VMR0_SENSE unimplemented\n"); + s->pdcm_pd_vmr0_sense = value; + break; + default: + g_assert_not_reached(); } - qemu_log_mask(LOG_UNIMP, - "IoTKit SysCtl PDCM_PD_SRAM2_SENSE unimplemented\n"); - s->pdcm_pd_sram2_sense = value; break; case A_PDCM_PD_SRAM3_SENSE: - if (!s->is_sse200) { + switch (s->sse_version) { + case ARMSSE_IOTKIT: goto bad_offset; + case ARMSSE_SSE200: + qemu_log_mask(LOG_UNIMP, + "IoTKit SysCtl PDCM_PD_SRAM3_SENSE unimplemented\n"); + s->pdcm_pd_sram3_sense = value; + break; + case ARMSSE_SSE300: + qemu_log_mask(LOG_UNIMP, + "IoTKit SysCtl PDCM_PD_VMR1_SENSE unimplemented\n"); + s->pdcm_pd_vmr1_sense = value; + break; + default: + g_assert_not_reached(); } - qemu_log_mask(LOG_UNIMP, - "IoTKit SysCtl PDCM_PD_SRAM3_SENSE unimplemented\n"); - s->pdcm_pd_sram3_sense = value; break; case A_NMI_ENABLE: /* In IoTKit this is BUSWAIT: reserved, R/O, zero */ - if (!s->is_sse200) { + switch (s->sse_version) { + case ARMSSE_IOTKIT: goto ro_offset; + case ARMSSE_SSE200: + qemu_log_mask(LOG_UNIMP, "IoTKit SysCtl NMI_ENABLE unimplemented\n"); + s->nmi_enable = value; + break; + case ARMSSE_SSE300: + /* In SSE300 this is reserved (for INITSVTOR3) */ + goto bad_offset; + default: + g_assert_not_reached(); } - qemu_log_mask(LOG_UNIMP, "IoTKit SysCtl NMI_ENABLE unimplemented\n"); - s->nmi_enable = value; break; case A_SECDBGSTAT: case A_PID4 ... A_CID3: @@ -417,11 +735,15 @@ static void iotkit_sysctl_reset(DeviceState *dev) s->clock_force = 0; s->nmi_enable = 0; s->ewctrl = 0; + s->pwrctrl = 0x3; s->pdcm_pd_sys_sense = 0x7f; s->pdcm_pd_sram0_sense = 0; s->pdcm_pd_sram1_sense = 0; s->pdcm_pd_sram2_sense = 0; s->pdcm_pd_sram3_sense = 0; + s->pdcm_pd_cpu0_sense = 0; + s->pdcm_pd_vmr0_sense = 0; + s->pdcm_pd_vmr1_sense = 0; } static void iotkit_sysctl_init(Object *obj) @@ -438,17 +760,38 @@ static void iotkit_sysctl_realize(DeviceState *dev, Error **errp) { IoTKitSysCtl *s = IOTKIT_SYSCTL(dev); - /* The top 4 bits of the SYS_VERSION register tell us if we're an SSE-200 */ - if (extract32(s->sys_version, 28, 4) == 2) { - s->is_sse200 = true; + if (!armsse_version_valid(s->sse_version)) { + error_setg(errp, "invalid sse-version value %d", s->sse_version); + return; } } +static bool sse300_needed(void *opaque) +{ + IoTKitSysCtl *s = IOTKIT_SYSCTL(opaque); + + return s->sse_version == ARMSSE_SSE300; +} + +static const VMStateDescription iotkit_sysctl_sse300_vmstate = { + .name = "iotkit-sysctl/sse-300", + .version_id = 1, + .minimum_version_id = 1, + .needed = sse300_needed, + .fields = (VMStateField[]) { + VMSTATE_UINT32(pwrctrl, IoTKitSysCtl), + VMSTATE_UINT32(pdcm_pd_cpu0_sense, IoTKitSysCtl), + VMSTATE_UINT32(pdcm_pd_vmr0_sense, IoTKitSysCtl), + VMSTATE_UINT32(pdcm_pd_vmr1_sense, IoTKitSysCtl), + VMSTATE_END_OF_LIST() + } +}; + static bool sse200_needed(void *opaque) { IoTKitSysCtl *s = IOTKIT_SYSCTL(opaque); - return s->is_sse200; + return s->sse_version != ARMSSE_IOTKIT; } static const VMStateDescription iotkit_sysctl_sse200_vmstate = { @@ -488,12 +831,13 @@ static const VMStateDescription iotkit_sysctl_vmstate = { }, .subsections = (const VMStateDescription*[]) { &iotkit_sysctl_sse200_vmstate, + &iotkit_sysctl_sse300_vmstate, NULL } }; static Property iotkit_sysctl_props[] = { - DEFINE_PROP_UINT32("SYS_VERSION", IoTKitSysCtl, sys_version, 0), + DEFINE_PROP_UINT32("sse-version", IoTKitSysCtl, sse_version, 0), DEFINE_PROP_UINT32("CPUWAIT_RST", IoTKitSysCtl, cpuwait_rst, 0), DEFINE_PROP_UINT32("INITSVTOR0_RST", IoTKitSysCtl, initsvtor0_rst, 0x10000000), diff --git a/hw/misc/iotkit-sysinfo.c b/hw/misc/iotkit-sysinfo.c index 52e70053df..aaa9305b2e 100644 --- a/hw/misc/iotkit-sysinfo.c +++ b/hw/misc/iotkit-sysinfo.c @@ -26,9 +26,12 @@ #include "hw/registerfields.h" #include "hw/misc/iotkit-sysinfo.h" #include "hw/qdev-properties.h" +#include "hw/arm/armsse-version.h" REG32(SYS_VERSION, 0x0) REG32(SYS_CONFIG, 0x4) +REG32(SYS_CONFIG1, 0x8) +REG32(IIDR, 0xfc8) REG32(PID4, 0xfd0) REG32(PID5, 0xfd4) REG32(PID6, 0xfd8) @@ -49,6 +52,12 @@ static const int sysinfo_id[] = { 0x0d, 0xf0, 0x05, 0xb1, /* CID0..CID3 */ }; +static const int sysinfo_sse300_id[] = { + 0x04, 0x00, 0x00, 0x00, /* PID4..PID7 */ + 0x58, 0xb8, 0x1b, 0x00, /* PID0..PID3 */ + 0x0d, 0xf0, 0x05, 0xb1, /* CID0..CID3 */ +}; + static uint64_t iotkit_sysinfo_read(void *opaque, hwaddr offset, unsigned size) { @@ -63,10 +72,36 @@ static uint64_t iotkit_sysinfo_read(void *opaque, hwaddr offset, case A_SYS_CONFIG: r = s->sys_config; break; + case A_SYS_CONFIG1: + switch (s->sse_version) { + case ARMSSE_SSE300: + return 0; + break; + default: + goto bad_read; + } + break; + case A_IIDR: + switch (s->sse_version) { + case ARMSSE_SSE300: + return s->iidr; + break; + default: + goto bad_read; + } + break; case A_PID4 ... A_CID3: - r = sysinfo_id[(offset - A_PID4) / 4]; + switch (s->sse_version) { + case ARMSSE_SSE300: + r = sysinfo_sse300_id[(offset - A_PID4) / 4]; + break; + default: + r = sysinfo_id[(offset - A_PID4) / 4]; + break; + } break; default: + bad_read: qemu_log_mask(LOG_GUEST_ERROR, "IoTKit SysInfo read: bad offset %x\n", (int)offset); r = 0; @@ -99,6 +134,8 @@ static const MemoryRegionOps iotkit_sysinfo_ops = { static Property iotkit_sysinfo_props[] = { DEFINE_PROP_UINT32("SYS_VERSION", IoTKitSysInfo, sys_version, 0), DEFINE_PROP_UINT32("SYS_CONFIG", IoTKitSysInfo, sys_config, 0), + DEFINE_PROP_UINT32("sse-version", IoTKitSysInfo, sse_version, 0), + DEFINE_PROP_UINT32("IIDR", IoTKitSysInfo, iidr, 0), DEFINE_PROP_END_OF_LIST() }; @@ -112,6 +149,16 @@ static void iotkit_sysinfo_init(Object *obj) sysbus_init_mmio(sbd, &s->iomem); } +static void iotkit_sysinfo_realize(DeviceState *dev, Error **errp) +{ + IoTKitSysInfo *s = IOTKIT_SYSINFO(dev); + + if (!armsse_version_valid(s->sse_version)) { + error_setg(errp, "invalid sse-version value %d", s->sse_version); + return; + } +} + static void iotkit_sysinfo_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); @@ -120,7 +167,7 @@ static void iotkit_sysinfo_class_init(ObjectClass *klass, void *data) * This device has no guest-modifiable state and so it * does not need a reset function or VMState. */ - + dc->realize = iotkit_sysinfo_realize; device_class_set_props(dc, iotkit_sysinfo_props); } diff --git a/hw/misc/ivshmem.c b/hw/misc/ivshmem.c index 603e992a7f..a1fa4878be 100644 --- a/hw/misc/ivshmem.c +++ b/hw/misc/ivshmem.c @@ -35,7 +35,6 @@ #include "qom/object_interfaces.h" #include "chardev/char-fe.h" #include "sysemu/hostmem.h" -#include "sysemu/qtest.h" #include "qapi/visitor.h" #include "hw/misc/ivshmem.h" diff --git a/hw/misc/meson.build b/hw/misc/meson.build index 629283957f..00356cf12e 100644 --- a/hw/misc/meson.build +++ b/hw/misc/meson.build @@ -96,13 +96,19 @@ softmmu_ss.add(when: 'CONFIG_TZ_MSC', if_true: files('tz-msc.c')) softmmu_ss.add(when: 'CONFIG_TZ_PPC', if_true: files('tz-ppc.c')) softmmu_ss.add(when: 'CONFIG_IOTKIT_SECCTL', if_true: files('iotkit-secctl.c')) softmmu_ss.add(when: 'CONFIG_IOTKIT_SYSINFO', if_true: files('iotkit-sysinfo.c')) +softmmu_ss.add(when: 'CONFIG_ARMSSE_CPU_PWRCTRL', if_true: files('armsse-cpu-pwrctrl.c')) softmmu_ss.add(when: 'CONFIG_ARMSSE_CPUID', if_true: files('armsse-cpuid.c')) softmmu_ss.add(when: 'CONFIG_ARMSSE_MHU', if_true: files('armsse-mhu.c')) softmmu_ss.add(when: 'CONFIG_PVPANIC_ISA', if_true: files('pvpanic-isa.c')) softmmu_ss.add(when: 'CONFIG_PVPANIC_PCI', if_true: files('pvpanic-pci.c')) softmmu_ss.add(when: 'CONFIG_AUX', if_true: files('auxbus.c')) -softmmu_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files('aspeed_scu.c', 'aspeed_sdmc.c', 'aspeed_xdma.c')) +softmmu_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files( + 'aspeed_lpc.c', + 'aspeed_scu.c', + 'aspeed_sdmc.c', + 'aspeed_xdma.c')) + softmmu_ss.add(when: 'CONFIG_MSF2', if_true: files('msf2-sysreg.c')) softmmu_ss.add(when: 'CONFIG_NRF51_SOC', if_true: files('nrf51_rng.c')) diff --git a/hw/misc/mps2-fpgaio.c b/hw/misc/mps2-fpgaio.c index f3db88ddcc..07b8cbdad2 100644 --- a/hw/misc/mps2-fpgaio.c +++ b/hw/misc/mps2-fpgaio.c @@ -29,6 +29,7 @@ #include "qemu/timer.h" REG32(LED0, 0) +REG32(DBGCTRL, 4) REG32(BUTTON, 8) REG32(CLK1HZ, 0x10) REG32(CLK100HZ, 0x14) @@ -129,6 +130,12 @@ static uint64_t mps2_fpgaio_read(void *opaque, hwaddr offset, unsigned size) case A_LED0: r = s->led0; break; + case A_DBGCTRL: + if (!s->has_dbgctrl) { + goto bad_offset; + } + r = s->dbgctrl; + break; case A_BUTTON: /* User-pressable board buttons. We don't model that, so just return * zeroes. @@ -195,6 +202,14 @@ static void mps2_fpgaio_write(void *opaque, hwaddr offset, uint64_t value, } } break; + case A_DBGCTRL: + if (!s->has_dbgctrl) { + goto bad_offset; + } + qemu_log_mask(LOG_UNIMP, + "MPS2 FPGAIO: DBGCTRL unimplemented\n"); + s->dbgctrl = value; + break; case A_PRESCALE: resync_counter(s); s->prescale = value; @@ -225,6 +240,7 @@ static void mps2_fpgaio_write(void *opaque, hwaddr offset, uint64_t value, s->pscntr = value; break; default: + bad_offset: qemu_log_mask(LOG_GUEST_ERROR, "MPS2 FPGAIO write: bad offset 0x%x\n", (int) offset); break; @@ -285,41 +301,22 @@ static void mps2_fpgaio_realize(DeviceState *dev, Error **errp) } } -static bool mps2_fpgaio_counters_needed(void *opaque) -{ - /* Currently vmstate.c insists all subsections have a 'needed' function */ - return true; -} - -static const VMStateDescription mps2_fpgaio_counters_vmstate = { - .name = "mps2-fpgaio/counters", - .version_id = 2, - .minimum_version_id = 2, - .needed = mps2_fpgaio_counters_needed, - .fields = (VMStateField[]) { - VMSTATE_INT64(clk1hz_tick_offset, MPS2FPGAIO), - VMSTATE_INT64(clk100hz_tick_offset, MPS2FPGAIO), - VMSTATE_UINT32(counter, MPS2FPGAIO), - VMSTATE_UINT32(pscntr, MPS2FPGAIO), - VMSTATE_INT64(pscntr_sync_ticks, MPS2FPGAIO), - VMSTATE_END_OF_LIST() - } -}; - static const VMStateDescription mps2_fpgaio_vmstate = { .name = "mps2-fpgaio", - .version_id = 1, - .minimum_version_id = 1, + .version_id = 3, + .minimum_version_id = 3, .fields = (VMStateField[]) { VMSTATE_UINT32(led0, MPS2FPGAIO), VMSTATE_UINT32(prescale, MPS2FPGAIO), VMSTATE_UINT32(misc, MPS2FPGAIO), + VMSTATE_UINT32(dbgctrl, MPS2FPGAIO), + VMSTATE_INT64(clk1hz_tick_offset, MPS2FPGAIO), + VMSTATE_INT64(clk100hz_tick_offset, MPS2FPGAIO), + VMSTATE_UINT32(counter, MPS2FPGAIO), + VMSTATE_UINT32(pscntr, MPS2FPGAIO), + VMSTATE_INT64(pscntr_sync_ticks, MPS2FPGAIO), VMSTATE_END_OF_LIST() }, - .subsections = (const VMStateDescription*[]) { - &mps2_fpgaio_counters_vmstate, - NULL - } }; static Property mps2_fpgaio_properties[] = { @@ -328,6 +325,7 @@ static Property mps2_fpgaio_properties[] = { /* Number of LEDs controlled by LED0 register */ DEFINE_PROP_UINT32("num-leds", MPS2FPGAIO, num_leds, 2), DEFINE_PROP_BOOL("has-switches", MPS2FPGAIO, has_switches, false), + DEFINE_PROP_BOOL("has-dbgctrl", MPS2FPGAIO, has_dbgctrl, false), DEFINE_PROP_END_OF_LIST(), }; diff --git a/hw/misc/mps2-scc.c b/hw/misc/mps2-scc.c index 140a4b9ceb..c56aca86ad 100644 --- a/hw/misc/mps2-scc.c +++ b/hw/misc/mps2-scc.c @@ -110,14 +110,14 @@ static uint64_t mps2_scc_read(void *opaque, hwaddr offset, unsigned size) r = s->cfg1; break; case A_CFG2: - if (scc_partno(s) != 0x524) { + if (scc_partno(s) != 0x524 && scc_partno(s) != 0x547) { /* CFG2 reserved on other boards */ goto bad_offset; } r = s->cfg2; break; case A_CFG3: - if (scc_partno(s) == 0x524) { + if (scc_partno(s) == 0x524 && scc_partno(s) == 0x547) { /* CFG3 reserved on AN524 */ goto bad_offset; } @@ -130,7 +130,7 @@ static uint64_t mps2_scc_read(void *opaque, hwaddr offset, unsigned size) r = s->cfg4; break; case A_CFG5: - if (scc_partno(s) != 0x524) { + if (scc_partno(s) != 0x524 && scc_partno(s) != 0x547) { /* CFG5 reserved on other boards */ goto bad_offset; } @@ -185,7 +185,10 @@ static void mps2_scc_write(void *opaque, hwaddr offset, uint64_t value, switch (offset) { case A_CFG0: - /* TODO on some boards bit 0 controls RAM remapping */ + /* + * TODO on some boards bit 0 controls RAM remapping; + * on others bit 1 is CPU_WAIT. + */ s->cfg0 = value; break; case A_CFG1: @@ -195,7 +198,7 @@ static void mps2_scc_write(void *opaque, hwaddr offset, uint64_t value, } break; case A_CFG2: - if (scc_partno(s) != 0x524) { + if (scc_partno(s) != 0x524 && scc_partno(s) != 0x547) { /* CFG2 reserved on other boards */ goto bad_offset; } @@ -203,7 +206,7 @@ static void mps2_scc_write(void *opaque, hwaddr offset, uint64_t value, s->cfg2 = value; break; case A_CFG5: - if (scc_partno(s) != 0x524) { + if (scc_partno(s) != 0x524 && scc_partno(s) != 0x547) { /* CFG5 reserved on other boards */ goto bad_offset; } diff --git a/hw/misc/npcm7xx_clk.c b/hw/misc/npcm7xx_clk.c index 0bcae9ce95..a1ee67dc9a 100644 --- a/hw/misc/npcm7xx_clk.c +++ b/hw/misc/npcm7xx_clk.c @@ -586,15 +586,26 @@ static const DividerInitInfo divider_init_info_list[] = { }, }; +static void npcm7xx_clk_update_pll_cb(void *opaque, ClockEvent event) +{ + npcm7xx_clk_update_pll(opaque); +} + static void npcm7xx_clk_pll_init(Object *obj) { NPCM7xxClockPLLState *pll = NPCM7XX_CLOCK_PLL(obj); pll->clock_in = qdev_init_clock_in(DEVICE(pll), "clock-in", - npcm7xx_clk_update_pll, pll); + npcm7xx_clk_update_pll_cb, pll, + ClockUpdate); pll->clock_out = qdev_init_clock_out(DEVICE(pll), "clock-out"); } +static void npcm7xx_clk_update_sel_cb(void *opaque, ClockEvent event) +{ + npcm7xx_clk_update_sel(opaque); +} + static void npcm7xx_clk_sel_init(Object *obj) { int i; @@ -603,16 +614,23 @@ static void npcm7xx_clk_sel_init(Object *obj) for (i = 0; i < NPCM7XX_CLK_SEL_MAX_INPUT; ++i) { sel->clock_in[i] = qdev_init_clock_in(DEVICE(sel), g_strdup_printf("clock-in[%d]", i), - npcm7xx_clk_update_sel, sel); + npcm7xx_clk_update_sel_cb, sel, ClockUpdate); } sel->clock_out = qdev_init_clock_out(DEVICE(sel), "clock-out"); } + +static void npcm7xx_clk_update_divider_cb(void *opaque, ClockEvent event) +{ + npcm7xx_clk_update_divider(opaque); +} + static void npcm7xx_clk_divider_init(Object *obj) { NPCM7xxClockDividerState *div = NPCM7XX_CLOCK_DIVIDER(obj); div->clock_in = qdev_init_clock_in(DEVICE(div), "clock-in", - npcm7xx_clk_update_divider, div); + npcm7xx_clk_update_divider_cb, + div, ClockUpdate); div->clock_out = qdev_init_clock_out(DEVICE(div), "clock-out"); } @@ -875,7 +893,7 @@ static void npcm7xx_clk_init_clock_hierarchy(NPCM7xxCLKState *s) { int i; - s->clkref = qdev_init_clock_in(DEVICE(s), "clkref", NULL, NULL); + s->clkref = qdev_init_clock_in(DEVICE(s), "clkref", NULL, NULL, 0); /* First pass: init all converter modules */ QEMU_BUILD_BUG_ON(ARRAY_SIZE(pll_init_info_list) != NPCM7XX_CLOCK_NR_PLLS); diff --git a/hw/misc/npcm7xx_pwm.c b/hw/misc/npcm7xx_pwm.c index dabcb6c0f9..ce192bb274 100644 --- a/hw/misc/npcm7xx_pwm.c +++ b/hw/misc/npcm7xx_pwm.c @@ -493,7 +493,7 @@ static void npcm7xx_pwm_init(Object *obj) memory_region_init_io(&s->iomem, obj, &npcm7xx_pwm_ops, s, TYPE_NPCM7XX_PWM, 4 * KiB); sysbus_init_mmio(sbd, &s->iomem); - s->clock = qdev_init_clock_in(DEVICE(s), "clock", NULL, NULL); + s->clock = qdev_init_clock_in(DEVICE(s), "clock", NULL, NULL, 0); for (i = 0; i < NPCM7XX_PWM_PER_MODULE; ++i) { object_property_add_uint32_ptr(obj, "freq[*]", diff --git a/hw/misc/trace-events b/hw/misc/trace-events index ef0a4de39d..cae005549e 100644 --- a/hw/misc/trace-events +++ b/hw/misc/trace-events @@ -186,6 +186,10 @@ iotkit_sysctl_read(uint64_t offset, uint64_t data, unsigned size) "IoTKit SysCtl iotkit_sysctl_write(uint64_t offset, uint64_t data, unsigned size) "IoTKit SysCtl write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u" iotkit_sysctl_reset(void) "IoTKit SysCtl: reset" +# armsse-cpu-pwrctrl.c +armsse_cpu_pwrctrl_read(uint64_t offset, uint64_t data, unsigned size) "SSE-300 CPU_PWRCTRL read: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u" +armsse_cpu_pwrctrl_write(uint64_t offset, uint64_t data, unsigned size) "SSE-300 CPU_PWRCTRL write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u" + # armsse-cpuid.c armsse_cpuid_read(uint64_t offset, uint64_t data, unsigned size) "SSE-200 CPU_IDENTITY read: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u" armsse_cpuid_write(uint64_t offset, uint64_t data, unsigned size) "SSE-200 CPU_IDENTITY write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u" diff --git a/hw/misc/zynq_slcr.c b/hw/misc/zynq_slcr.c index 66504a9d3a..c66d7db177 100644 --- a/hw/misc/zynq_slcr.c +++ b/hw/misc/zynq_slcr.c @@ -307,9 +307,10 @@ static void zynq_slcr_propagate_clocks(ZynqSLCRState *s) clock_propagate(s->uart1_ref_clk); } -static void zynq_slcr_ps_clk_callback(void *opaque) +static void zynq_slcr_ps_clk_callback(void *opaque, ClockEvent event) { ZynqSLCRState *s = (ZynqSLCRState *) opaque; + zynq_slcr_compute_clocks(s); zynq_slcr_propagate_clocks(s); } @@ -576,7 +577,7 @@ static const MemoryRegionOps slcr_ops = { }; static const ClockPortInitArray zynq_slcr_clocks = { - QDEV_CLOCK_IN(ZynqSLCRState, ps_clk, zynq_slcr_ps_clk_callback), + QDEV_CLOCK_IN(ZynqSLCRState, ps_clk, zynq_slcr_ps_clk_callback, ClockUpdate), QDEV_CLOCK_OUT(ZynqSLCRState, uart0_ref_clk), QDEV_CLOCK_OUT(ZynqSLCRState, uart1_ref_clk), QDEV_CLOCK_END diff --git a/hw/ppc/ppc440_bamboo.c b/hw/ppc/ppc440_bamboo.c index b156bcb999..b7539aa721 100644 --- a/hw/ppc/ppc440_bamboo.c +++ b/hw/ppc/ppc440_bamboo.c @@ -30,7 +30,6 @@ #include "hw/ppc/ppc.h" #include "ppc405.h" #include "sysemu/sysemu.h" -#include "sysemu/qtest.h" #include "sysemu/reset.h" #include "hw/sysbus.h" #include "hw/intc/ppc-uic.h" diff --git a/hw/ppc/prep.c b/hw/ppc/prep.c index 7e72f6e4a9..f1b1efdcef 100644 --- a/hw/ppc/prep.c +++ b/hw/ppc/prep.c @@ -45,7 +45,6 @@ #include "hw/qdev-properties.h" #include "sysemu/arch_init.h" #include "sysemu/kvm.h" -#include "sysemu/qtest.h" #include "sysemu/reset.h" #include "exec/address-spaces.h" #include "trace.h" diff --git a/hw/ppc/sam460ex.c b/hw/ppc/sam460ex.c index e459b43065..0c6baf77e8 100644 --- a/hw/ppc/sam460ex.c +++ b/hw/ppc/sam460ex.c @@ -30,7 +30,6 @@ #include "ppc405.h" #include "hw/block/flash.h" #include "sysemu/sysemu.h" -#include "sysemu/qtest.h" #include "sysemu/reset.h" #include "hw/sysbus.h" #include "hw/char/serial.h" diff --git a/hw/ppc/spapr_caps.c b/hw/ppc/spapr_caps.c index 9341e9782a..9ea7ddd1e9 100644 --- a/hw/ppc/spapr_caps.c +++ b/hw/ppc/spapr_caps.c @@ -33,7 +33,6 @@ #include "cpu-models.h" #include "kvm_ppc.h" #include "migration/vmstate.h" -#include "sysemu/qtest.h" #include "sysemu/tcg.h" #include "hw/ppc/spapr.h" diff --git a/hw/ppc/spapr_pci_vfio.c b/hw/ppc/spapr_pci_vfio.c index ecb34aaade..e0547b1740 100644 --- a/hw/ppc/spapr_pci_vfio.c +++ b/hw/ppc/spapr_pci_vfio.c @@ -25,7 +25,6 @@ #include "hw/pci/msix.h" #include "hw/vfio/vfio.h" #include "qemu/error-report.h" -#include "sysemu/qtest.h" bool spapr_phb_eeh_available(SpaprPhbState *sphb) { diff --git a/hw/ppc/spapr_vio.c b/hw/ppc/spapr_vio.c index 3cc9421526..ef06e0362c 100644 --- a/hw/ppc/spapr_vio.c +++ b/hw/ppc/spapr_vio.c @@ -31,7 +31,6 @@ #include "sysemu/device_tree.h" #include "kvm_ppc.h" #include "migration/vmstate.h" -#include "sysemu/qtest.h" #include "hw/ppc/spapr.h" #include "hw/ppc/spapr_vio.h" diff --git a/hw/ppc/virtex_ml507.c b/hw/ppc/virtex_ml507.c index b26ff17767..cb421570da 100644 --- a/hw/ppc/virtex_ml507.c +++ b/hw/ppc/virtex_ml507.c @@ -31,7 +31,6 @@ #include "hw/char/serial.h" #include "hw/block/flash.h" #include "sysemu/sysemu.h" -#include "sysemu/qtest.h" #include "sysemu/reset.h" #include "hw/boards.h" #include "sysemu/device_tree.h" diff --git a/hw/riscv/spike.c b/hw/riscv/spike.c index ed4ca9808e..ec7cb2f707 100644 --- a/hw/riscv/spike.c +++ b/hw/riscv/spike.c @@ -40,7 +40,6 @@ #include "chardev/char.h" #include "sysemu/arch_init.h" #include "sysemu/device_tree.h" -#include "sysemu/qtest.h" #include "sysemu/sysemu.h" static const MemMapEntry spike_memmap[] = { diff --git a/hw/riscv/virt.c b/hw/riscv/virt.c index 4f0c2fbca0..0b39101a5e 100644 --- a/hw/riscv/virt.c +++ b/hw/riscv/virt.c @@ -195,14 +195,14 @@ static void create_fdt(RISCVVirtState *s, const MemMapEntry *memmap, hwaddr flashbase = virt_memmap[VIRT_FLASH].base; if (mc->dtb) { - fdt = s->fdt = load_device_tree(mc->dtb, &s->fdt_size); + fdt = mc->fdt = load_device_tree(mc->dtb, &s->fdt_size); if (!fdt) { error_report("load_device_tree() failed"); exit(1); } goto update_bootargs; } else { - fdt = s->fdt = create_device_tree(&s->fdt_size); + fdt = mc->fdt = create_device_tree(&s->fdt_size); if (!fdt) { error_report("create_device_tree() failed"); exit(1); @@ -444,12 +444,12 @@ static void create_fdt(RISCVVirtState *s, const MemMapEntry *memmap, g_free(name); name = g_strdup_printf("/soc/flash@%" PRIx64, flashbase); - qemu_fdt_add_subnode(s->fdt, name); - qemu_fdt_setprop_string(s->fdt, name, "compatible", "cfi-flash"); - qemu_fdt_setprop_sized_cells(s->fdt, name, "reg", + qemu_fdt_add_subnode(mc->fdt, name); + qemu_fdt_setprop_string(mc->fdt, name, "compatible", "cfi-flash"); + qemu_fdt_setprop_sized_cells(mc->fdt, name, "reg", 2, flashbase, 2, flashsize, 2, flashbase + flashsize, 2, flashsize); - qemu_fdt_setprop_cell(s->fdt, name, "bank-width", 4); + qemu_fdt_setprop_cell(mc->fdt, name, "bank-width", 4); g_free(name); update_bootargs: @@ -667,9 +667,9 @@ static void virt_machine_init(MachineState *machine) hwaddr end = riscv_load_initrd(machine->initrd_filename, machine->ram_size, kernel_entry, &start); - qemu_fdt_setprop_cell(s->fdt, "/chosen", + qemu_fdt_setprop_cell(machine->fdt, "/chosen", "linux,initrd-start", start); - qemu_fdt_setprop_cell(s->fdt, "/chosen", "linux,initrd-end", + qemu_fdt_setprop_cell(machine->fdt, "/chosen", "linux,initrd-end", end); } } else { @@ -690,12 +690,12 @@ static void virt_machine_init(MachineState *machine) /* Compute the fdt load address in dram */ fdt_load_addr = riscv_load_fdt(memmap[VIRT_DRAM].base, - machine->ram_size, s->fdt); + machine->ram_size, machine->fdt); /* load the reset vector */ riscv_setup_rom_reset_vec(machine, &s->soc[0], start_addr, virt_memmap[VIRT_MROM].base, virt_memmap[VIRT_MROM].size, kernel_entry, - fdt_load_addr, s->fdt); + fdt_load_addr, machine->fdt); /* SiFive Test MMIO device */ sifive_test_create(memmap[VIRT_TEST].base); diff --git a/hw/rx/rx62n.c b/hw/rx/rx62n.c index 17ec73fc7b..9c34ce14de 100644 --- a/hw/rx/rx62n.c +++ b/hw/rx/rx62n.c @@ -29,7 +29,6 @@ #include "hw/sysbus.h" #include "hw/qdev-properties.h" #include "sysemu/sysemu.h" -#include "sysemu/qtest.h" #include "cpu.h" #include "qom/object.h" diff --git a/hw/semihosting/Kconfig b/hw/semihosting/Kconfig deleted file mode 100644 index eaf3a20ef5..0000000000 --- a/hw/semihosting/Kconfig +++ /dev/null @@ -1,7 +0,0 @@ - -config SEMIHOSTING - bool - -config ARM_COMPATIBLE_SEMIHOSTING - bool - select SEMIHOSTING diff --git a/hw/semihosting/arm-compat-semi.c b/hw/semihosting/arm-compat-semi.c deleted file mode 100644 index 23c6e3edcb..0000000000 --- a/hw/semihosting/arm-compat-semi.c +++ /dev/null @@ -1,1306 +0,0 @@ -/* - * Semihosting support for systems modeled on the Arm "Angel" - * semihosting syscalls design. This includes Arm and RISC-V processors - * - * Copyright (c) 2005, 2007 CodeSourcery. - * Copyright (c) 2019 Linaro - * Written by Paul Brook. - * - * Copyright © 2020 by Keith Packard <keithp@keithp.com> - * Adapted for systems other than ARM, including RISC-V, by Keith Packard - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * 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/>. - * - * ARM Semihosting is documented in: - * Semihosting for AArch32 and AArch64 Release 2.0 - * https://static.docs.arm.com/100863/0200/semihosting.pdf - * - * RISC-V Semihosting is documented in: - * RISC-V Semihosting - * https://github.com/riscv/riscv-semihosting-spec/blob/main/riscv-semihosting-spec.adoc - */ - -#include "qemu/osdep.h" - -#include "cpu.h" -#include "hw/semihosting/semihost.h" -#include "hw/semihosting/console.h" -#include "hw/semihosting/common-semi.h" -#include "qemu/log.h" -#include "qemu/timer.h" -#ifdef CONFIG_USER_ONLY -#include "qemu.h" - -#define COMMON_SEMI_HEAP_SIZE (128 * 1024 * 1024) -#else -#include "exec/gdbstub.h" -#include "qemu/cutils.h" -#ifdef TARGET_ARM -#include "hw/arm/boot.h" -#endif -#include "hw/boards.h" -#endif - -#define TARGET_SYS_OPEN 0x01 -#define TARGET_SYS_CLOSE 0x02 -#define TARGET_SYS_WRITEC 0x03 -#define TARGET_SYS_WRITE0 0x04 -#define TARGET_SYS_WRITE 0x05 -#define TARGET_SYS_READ 0x06 -#define TARGET_SYS_READC 0x07 -#define TARGET_SYS_ISERROR 0x08 -#define TARGET_SYS_ISTTY 0x09 -#define TARGET_SYS_SEEK 0x0a -#define TARGET_SYS_FLEN 0x0c -#define TARGET_SYS_TMPNAM 0x0d -#define TARGET_SYS_REMOVE 0x0e -#define TARGET_SYS_RENAME 0x0f -#define TARGET_SYS_CLOCK 0x10 -#define TARGET_SYS_TIME 0x11 -#define TARGET_SYS_SYSTEM 0x12 -#define TARGET_SYS_ERRNO 0x13 -#define TARGET_SYS_GET_CMDLINE 0x15 -#define TARGET_SYS_HEAPINFO 0x16 -#define TARGET_SYS_EXIT 0x18 -#define TARGET_SYS_SYNCCACHE 0x19 -#define TARGET_SYS_EXIT_EXTENDED 0x20 -#define TARGET_SYS_ELAPSED 0x30 -#define TARGET_SYS_TICKFREQ 0x31 - -/* ADP_Stopped_ApplicationExit is used for exit(0), - * anything else is implemented as exit(1) */ -#define ADP_Stopped_ApplicationExit (0x20026) - -#ifndef O_BINARY -#define O_BINARY 0 -#endif - -#define GDB_O_RDONLY 0x000 -#define GDB_O_WRONLY 0x001 -#define GDB_O_RDWR 0x002 -#define GDB_O_APPEND 0x008 -#define GDB_O_CREAT 0x200 -#define GDB_O_TRUNC 0x400 -#define GDB_O_BINARY 0 - -static int gdb_open_modeflags[12] = { - GDB_O_RDONLY, - GDB_O_RDONLY | GDB_O_BINARY, - GDB_O_RDWR, - GDB_O_RDWR | GDB_O_BINARY, - GDB_O_WRONLY | GDB_O_CREAT | GDB_O_TRUNC, - GDB_O_WRONLY | GDB_O_CREAT | GDB_O_TRUNC | GDB_O_BINARY, - GDB_O_RDWR | GDB_O_CREAT | GDB_O_TRUNC, - GDB_O_RDWR | GDB_O_CREAT | GDB_O_TRUNC | GDB_O_BINARY, - GDB_O_WRONLY | GDB_O_CREAT | GDB_O_APPEND, - GDB_O_WRONLY | GDB_O_CREAT | GDB_O_APPEND | GDB_O_BINARY, - GDB_O_RDWR | GDB_O_CREAT | GDB_O_APPEND, - GDB_O_RDWR | GDB_O_CREAT | GDB_O_APPEND | GDB_O_BINARY -}; - -static int open_modeflags[12] = { - O_RDONLY, - O_RDONLY | O_BINARY, - O_RDWR, - O_RDWR | O_BINARY, - O_WRONLY | O_CREAT | O_TRUNC, - O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, - O_RDWR | O_CREAT | O_TRUNC, - O_RDWR | O_CREAT | O_TRUNC | O_BINARY, - O_WRONLY | O_CREAT | O_APPEND, - O_WRONLY | O_CREAT | O_APPEND | O_BINARY, - O_RDWR | O_CREAT | O_APPEND, - O_RDWR | O_CREAT | O_APPEND | O_BINARY -}; - -typedef enum GuestFDType { - GuestFDUnused = 0, - GuestFDHost = 1, - GuestFDGDB = 2, - GuestFDFeatureFile = 3, -} GuestFDType; - -/* - * Guest file descriptors are integer indexes into an array of - * these structures (we will dynamically resize as necessary). - */ -typedef struct GuestFD { - GuestFDType type; - union { - int hostfd; - target_ulong featurefile_offset; - }; -} GuestFD; - -static GArray *guestfd_array; - -#ifndef CONFIG_USER_ONLY -#include "exec/address-spaces.h" -/* - * Find the base of a RAM region containing the specified address - */ -static inline hwaddr -common_semi_find_region_base(hwaddr addr) -{ - MemoryRegion *subregion; - - /* - * Find the chunk of R/W memory containing the address. This is - * used for the SYS_HEAPINFO semihosting call, which should - * probably be using information from the loaded application. - */ - QTAILQ_FOREACH(subregion, &get_system_memory()->subregions, - subregions_link) { - if (subregion->ram && !subregion->readonly) { - Int128 top128 = int128_add(int128_make64(subregion->addr), - subregion->size); - Int128 addr128 = int128_make64(addr); - if (subregion->addr <= addr && int128_lt(addr128, top128)) { - return subregion->addr; - } - } - } - return 0; -} -#endif - -#ifdef TARGET_ARM -static inline target_ulong -common_semi_arg(CPUState *cs, int argno) -{ - ARMCPU *cpu = ARM_CPU(cs); - CPUARMState *env = &cpu->env; - if (is_a64(env)) { - return env->xregs[argno]; - } else { - return env->regs[argno]; - } -} - -static inline void -common_semi_set_ret(CPUState *cs, target_ulong ret) -{ - ARMCPU *cpu = ARM_CPU(cs); - CPUARMState *env = &cpu->env; - if (is_a64(env)) { - env->xregs[0] = ret; - } else { - env->regs[0] = ret; - } -} - -static inline bool -common_semi_sys_exit_extended(CPUState *cs, int nr) -{ - return (nr == TARGET_SYS_EXIT_EXTENDED || is_a64(cs->env_ptr)); -} - -#ifndef CONFIG_USER_ONLY -#include "hw/arm/boot.h" -static inline target_ulong -common_semi_rambase(CPUState *cs) -{ - CPUArchState *env = cs->env_ptr; - const struct arm_boot_info *info = env->boot_info; - target_ulong sp; - - if (info) { - return info->loader_start; - } - - if (is_a64(env)) { - sp = env->xregs[31]; - } else { - sp = env->regs[13]; - } - return common_semi_find_region_base(sp); -} -#endif - -#endif /* TARGET_ARM */ - -#ifdef TARGET_RISCV -static inline target_ulong -common_semi_arg(CPUState *cs, int argno) -{ - RISCVCPU *cpu = RISCV_CPU(cs); - CPURISCVState *env = &cpu->env; - return env->gpr[xA0 + argno]; -} - -static inline void -common_semi_set_ret(CPUState *cs, target_ulong ret) -{ - RISCVCPU *cpu = RISCV_CPU(cs); - CPURISCVState *env = &cpu->env; - env->gpr[xA0] = ret; -} - -static inline bool -common_semi_sys_exit_extended(CPUState *cs, int nr) -{ - return (nr == TARGET_SYS_EXIT_EXTENDED || sizeof(target_ulong) == 8); -} - -#ifndef CONFIG_USER_ONLY - -static inline target_ulong -common_semi_rambase(CPUState *cs) -{ - RISCVCPU *cpu = RISCV_CPU(cs); - CPURISCVState *env = &cpu->env; - return common_semi_find_region_base(env->gpr[xSP]); -} -#endif - -#endif - -/* - * Allocate a new guest file descriptor and return it; if we - * couldn't allocate a new fd then return -1. - * This is a fairly simplistic implementation because we don't - * expect that most semihosting guest programs will make very - * heavy use of opening and closing fds. - */ -static int alloc_guestfd(void) -{ - guint i; - - if (!guestfd_array) { - /* New entries zero-initialized, i.e. type GuestFDUnused */ - guestfd_array = g_array_new(FALSE, TRUE, sizeof(GuestFD)); - } - - /* SYS_OPEN should return nonzero handle on success. Start guestfd from 1 */ - for (i = 1; i < guestfd_array->len; i++) { - GuestFD *gf = &g_array_index(guestfd_array, GuestFD, i); - - if (gf->type == GuestFDUnused) { - return i; - } - } - - /* All elements already in use: expand the array */ - g_array_set_size(guestfd_array, i + 1); - return i; -} - -/* - * Look up the guestfd in the data structure; return NULL - * for out of bounds, but don't check whether the slot is unused. - * This is used internally by the other guestfd functions. - */ -static GuestFD *do_get_guestfd(int guestfd) -{ - if (!guestfd_array) { - return NULL; - } - - if (guestfd <= 0 || guestfd >= guestfd_array->len) { - return NULL; - } - - return &g_array_index(guestfd_array, GuestFD, guestfd); -} - -/* - * Associate the specified guest fd (which must have been - * allocated via alloc_fd() and not previously used) with - * the specified host/gdb fd. - */ -static void associate_guestfd(int guestfd, int hostfd) -{ - GuestFD *gf = do_get_guestfd(guestfd); - - assert(gf); - gf->type = use_gdb_syscalls() ? GuestFDGDB : GuestFDHost; - gf->hostfd = hostfd; -} - -/* - * Deallocate the specified guest file descriptor. This doesn't - * close the host fd, it merely undoes the work of alloc_fd(). - */ -static void dealloc_guestfd(int guestfd) -{ - GuestFD *gf = do_get_guestfd(guestfd); - - assert(gf); - gf->type = GuestFDUnused; -} - -/* - * Given a guest file descriptor, get the associated struct. - * If the fd is not valid, return NULL. This is the function - * used by the various semihosting calls to validate a handle - * from the guest. - * Note: calling alloc_guestfd() or dealloc_guestfd() will - * invalidate any GuestFD* obtained by calling this function. - */ -static GuestFD *get_guestfd(int guestfd) -{ - GuestFD *gf = do_get_guestfd(guestfd); - - if (!gf || gf->type == GuestFDUnused) { - return NULL; - } - return gf; -} - -/* - * The semihosting API has no concept of its errno being thread-safe, - * as the API design predates SMP CPUs and was intended as a simple - * real-hardware set of debug functionality. For QEMU, we make the - * errno be per-thread in linux-user mode; in softmmu it is a simple - * global, and we assume that the guest takes care of avoiding any races. - */ -#ifndef CONFIG_USER_ONLY -static target_ulong syscall_err; - -#include "exec/softmmu-semi.h" -#endif - -static inline uint32_t set_swi_errno(CPUState *cs, uint32_t code) -{ - if (code == (uint32_t)-1) { -#ifdef CONFIG_USER_ONLY - TaskState *ts = cs->opaque; - - ts->swi_errno = errno; -#else - syscall_err = errno; -#endif - } - return code; -} - -static inline uint32_t get_swi_errno(CPUState *cs) -{ -#ifdef CONFIG_USER_ONLY - TaskState *ts = cs->opaque; - - return ts->swi_errno; -#else - return syscall_err; -#endif -} - -static target_ulong common_semi_syscall_len; - -static void common_semi_cb(CPUState *cs, target_ulong ret, target_ulong err) -{ - target_ulong reg0 = common_semi_arg(cs, 0); - - if (ret == (target_ulong)-1) { - errno = err; - set_swi_errno(cs, -1); - reg0 = ret; - } else { - /* Fixup syscalls that use nonstardard return conventions. */ - switch (reg0) { - case TARGET_SYS_WRITE: - case TARGET_SYS_READ: - reg0 = common_semi_syscall_len - ret; - break; - case TARGET_SYS_SEEK: - reg0 = 0; - break; - default: - reg0 = ret; - break; - } - } - common_semi_set_ret(cs, reg0); -} - -static target_ulong common_semi_flen_buf(CPUState *cs) -{ - target_ulong sp; -#ifdef TARGET_ARM - /* Return an address in target memory of 64 bytes where the remote - * gdb should write its stat struct. (The format of this structure - * is defined by GDB's remote protocol and is not target-specific.) - * We put this on the guest's stack just below SP. - */ - ARMCPU *cpu = ARM_CPU(cs); - CPUARMState *env = &cpu->env; - - if (is_a64(env)) { - sp = env->xregs[31]; - } else { - sp = env->regs[13]; - } -#endif -#ifdef TARGET_RISCV - RISCVCPU *cpu = RISCV_CPU(cs); - CPURISCVState *env = &cpu->env; - - sp = env->gpr[xSP]; -#endif - - return sp - 64; -} - -static void -common_semi_flen_cb(CPUState *cs, target_ulong ret, target_ulong err) -{ - /* The size is always stored in big-endian order, extract - the value. We assume the size always fit in 32 bits. */ - uint32_t size; - cpu_memory_rw_debug(cs, common_semi_flen_buf(cs) + 32, - (uint8_t *)&size, 4, 0); - size = be32_to_cpu(size); - common_semi_set_ret(cs, size); - errno = err; - set_swi_errno(cs, -1); -} - -static int common_semi_open_guestfd; - -static void -common_semi_open_cb(CPUState *cs, target_ulong ret, target_ulong err) -{ - if (ret == (target_ulong)-1) { - errno = err; - set_swi_errno(cs, -1); - dealloc_guestfd(common_semi_open_guestfd); - } else { - associate_guestfd(common_semi_open_guestfd, ret); - ret = common_semi_open_guestfd; - } - common_semi_set_ret(cs, ret); -} - -static target_ulong -common_semi_gdb_syscall(CPUState *cs, gdb_syscall_complete_cb cb, - const char *fmt, ...) -{ - va_list va; - - va_start(va, fmt); - gdb_do_syscallv(cb, fmt, va); - va_end(va); - - /* - * FIXME: in softmmu mode, the gdbstub will schedule our callback - * to occur, but will not actually call it to complete the syscall - * until after this function has returned and we are back in the - * CPU main loop. Therefore callers to this function must not - * do anything with its return value, because it is not necessarily - * the result of the syscall, but could just be the old value of X0. - * The only thing safe to do with this is that the callers of - * do_common_semihosting() will write it straight back into X0. - * (In linux-user mode, the callback will have happened before - * gdb_do_syscallv() returns.) - * - * We should tidy this up so neither this function nor - * do_common_semihosting() return a value, so the mistake of - * doing something with the return value is not possible to make. - */ - - return common_semi_arg(cs, 0); -} - -/* - * Types for functions implementing various semihosting calls - * for specific types of guest file descriptor. These must all - * do the work and return the required return value for the guest, - * setting the guest errno if appropriate. - */ -typedef uint32_t sys_closefn(CPUState *cs, GuestFD *gf); -typedef uint32_t sys_writefn(CPUState *cs, GuestFD *gf, - target_ulong buf, uint32_t len); -typedef uint32_t sys_readfn(CPUState *cs, GuestFD *gf, - target_ulong buf, uint32_t len); -typedef uint32_t sys_isattyfn(CPUState *cs, GuestFD *gf); -typedef uint32_t sys_seekfn(CPUState *cs, GuestFD *gf, - target_ulong offset); -typedef uint32_t sys_flenfn(CPUState *cs, GuestFD *gf); - -static uint32_t host_closefn(CPUState *cs, GuestFD *gf) -{ - /* - * Only close the underlying host fd if it's one we opened on behalf - * of the guest in SYS_OPEN. - */ - if (gf->hostfd == STDIN_FILENO || - gf->hostfd == STDOUT_FILENO || - gf->hostfd == STDERR_FILENO) { - return 0; - } - return set_swi_errno(cs, close(gf->hostfd)); -} - -static uint32_t host_writefn(CPUState *cs, GuestFD *gf, - target_ulong buf, uint32_t len) -{ - CPUArchState *env = cs->env_ptr; - uint32_t ret; - char *s = lock_user(VERIFY_READ, buf, len, 1); - (void) env; /* Used in arm softmmu lock_user implicitly */ - if (!s) { - /* Return bytes not written on error */ - return len; - } - ret = set_swi_errno(cs, write(gf->hostfd, s, len)); - unlock_user(s, buf, 0); - if (ret == (uint32_t)-1) { - ret = 0; - } - /* Return bytes not written */ - return len - ret; -} - -static uint32_t host_readfn(CPUState *cs, GuestFD *gf, - target_ulong buf, uint32_t len) -{ - CPUArchState *env = cs->env_ptr; - uint32_t ret; - char *s = lock_user(VERIFY_WRITE, buf, len, 0); - (void) env; /* Used in arm softmmu lock_user implicitly */ - if (!s) { - /* return bytes not read */ - return len; - } - do { - ret = set_swi_errno(cs, read(gf->hostfd, s, len)); - } while (ret == -1 && errno == EINTR); - unlock_user(s, buf, len); - if (ret == (uint32_t)-1) { - ret = 0; - } - /* Return bytes not read */ - return len - ret; -} - -static uint32_t host_isattyfn(CPUState *cs, GuestFD *gf) -{ - return isatty(gf->hostfd); -} - -static uint32_t host_seekfn(CPUState *cs, GuestFD *gf, target_ulong offset) -{ - uint32_t ret = set_swi_errno(cs, lseek(gf->hostfd, offset, SEEK_SET)); - if (ret == (uint32_t)-1) { - return -1; - } - return 0; -} - -static uint32_t host_flenfn(CPUState *cs, GuestFD *gf) -{ - struct stat buf; - uint32_t ret = set_swi_errno(cs, fstat(gf->hostfd, &buf)); - if (ret == (uint32_t)-1) { - return -1; - } - return buf.st_size; -} - -static uint32_t gdb_closefn(CPUState *cs, GuestFD *gf) -{ - return common_semi_gdb_syscall(cs, common_semi_cb, "close,%x", gf->hostfd); -} - -static uint32_t gdb_writefn(CPUState *cs, GuestFD *gf, - target_ulong buf, uint32_t len) -{ - common_semi_syscall_len = len; - return common_semi_gdb_syscall(cs, common_semi_cb, "write,%x,%x,%x", - gf->hostfd, buf, len); -} - -static uint32_t gdb_readfn(CPUState *cs, GuestFD *gf, - target_ulong buf, uint32_t len) -{ - common_semi_syscall_len = len; - return common_semi_gdb_syscall(cs, common_semi_cb, "read,%x,%x,%x", - gf->hostfd, buf, len); -} - -static uint32_t gdb_isattyfn(CPUState *cs, GuestFD *gf) -{ - return common_semi_gdb_syscall(cs, common_semi_cb, "isatty,%x", gf->hostfd); -} - -static uint32_t gdb_seekfn(CPUState *cs, GuestFD *gf, target_ulong offset) -{ - return common_semi_gdb_syscall(cs, common_semi_cb, "lseek,%x,%x,0", - gf->hostfd, offset); -} - -static uint32_t gdb_flenfn(CPUState *cs, GuestFD *gf) -{ - return common_semi_gdb_syscall(cs, common_semi_flen_cb, "fstat,%x,%x", - gf->hostfd, common_semi_flen_buf(cs)); -} - -#define SHFB_MAGIC_0 0x53 -#define SHFB_MAGIC_1 0x48 -#define SHFB_MAGIC_2 0x46 -#define SHFB_MAGIC_3 0x42 - -/* Feature bits reportable in feature byte 0 */ -#define SH_EXT_EXIT_EXTENDED (1 << 0) -#define SH_EXT_STDOUT_STDERR (1 << 1) - -static const uint8_t featurefile_data[] = { - SHFB_MAGIC_0, - SHFB_MAGIC_1, - SHFB_MAGIC_2, - SHFB_MAGIC_3, - SH_EXT_EXIT_EXTENDED | SH_EXT_STDOUT_STDERR, /* Feature byte 0 */ -}; - -static void init_featurefile_guestfd(int guestfd) -{ - GuestFD *gf = do_get_guestfd(guestfd); - - assert(gf); - gf->type = GuestFDFeatureFile; - gf->featurefile_offset = 0; -} - -static uint32_t featurefile_closefn(CPUState *cs, GuestFD *gf) -{ - /* Nothing to do */ - return 0; -} - -static uint32_t featurefile_writefn(CPUState *cs, GuestFD *gf, - target_ulong buf, uint32_t len) -{ - /* This fd can never be open for writing */ - - errno = EBADF; - return set_swi_errno(cs, -1); -} - -static uint32_t featurefile_readfn(CPUState *cs, GuestFD *gf, - target_ulong buf, uint32_t len) -{ - CPUArchState *env = cs->env_ptr; - uint32_t i; - char *s; - - (void) env; /* Used in arm softmmu lock_user implicitly */ - s = lock_user(VERIFY_WRITE, buf, len, 0); - if (!s) { - return len; - } - - for (i = 0; i < len; i++) { - if (gf->featurefile_offset >= sizeof(featurefile_data)) { - break; - } - s[i] = featurefile_data[gf->featurefile_offset]; - gf->featurefile_offset++; - } - - unlock_user(s, buf, len); - - /* Return number of bytes not read */ - return len - i; -} - -static uint32_t featurefile_isattyfn(CPUState *cs, GuestFD *gf) -{ - return 0; -} - -static uint32_t featurefile_seekfn(CPUState *cs, GuestFD *gf, - target_ulong offset) -{ - gf->featurefile_offset = offset; - return 0; -} - -static uint32_t featurefile_flenfn(CPUState *cs, GuestFD *gf) -{ - return sizeof(featurefile_data); -} - -typedef struct GuestFDFunctions { - sys_closefn *closefn; - sys_writefn *writefn; - sys_readfn *readfn; - sys_isattyfn *isattyfn; - sys_seekfn *seekfn; - sys_flenfn *flenfn; -} GuestFDFunctions; - -static const GuestFDFunctions guestfd_fns[] = { - [GuestFDHost] = { - .closefn = host_closefn, - .writefn = host_writefn, - .readfn = host_readfn, - .isattyfn = host_isattyfn, - .seekfn = host_seekfn, - .flenfn = host_flenfn, - }, - [GuestFDGDB] = { - .closefn = gdb_closefn, - .writefn = gdb_writefn, - .readfn = gdb_readfn, - .isattyfn = gdb_isattyfn, - .seekfn = gdb_seekfn, - .flenfn = gdb_flenfn, - }, - [GuestFDFeatureFile] = { - .closefn = featurefile_closefn, - .writefn = featurefile_writefn, - .readfn = featurefile_readfn, - .isattyfn = featurefile_isattyfn, - .seekfn = featurefile_seekfn, - .flenfn = featurefile_flenfn, - }, -}; - -/* Read the input value from the argument block; fail the semihosting - * call if the memory read fails. - */ -#ifdef TARGET_ARM -#define GET_ARG(n) do { \ - if (is_a64(env)) { \ - if (get_user_u64(arg ## n, args + (n) * 8)) { \ - errno = EFAULT; \ - return set_swi_errno(cs, -1); \ - } \ - } else { \ - if (get_user_u32(arg ## n, args + (n) * 4)) { \ - errno = EFAULT; \ - return set_swi_errno(cs, -1); \ - } \ - } \ -} while (0) - -#define SET_ARG(n, val) \ - (is_a64(env) ? \ - put_user_u64(val, args + (n) * 8) : \ - put_user_u32(val, args + (n) * 4)) -#endif - -#ifdef TARGET_RISCV - -/* - * get_user_ual is defined as get_user_u32 in softmmu-semi.h, - * we need a macro that fetches a target_ulong - */ -#define get_user_utl(arg, p) \ - ((sizeof(target_ulong) == 8) ? \ - get_user_u64(arg, p) : \ - get_user_u32(arg, p)) - -/* - * put_user_ual is defined as put_user_u32 in softmmu-semi.h, - * we need a macro that stores a target_ulong - */ -#define put_user_utl(arg, p) \ - ((sizeof(target_ulong) == 8) ? \ - put_user_u64(arg, p) : \ - put_user_u32(arg, p)) - -#define GET_ARG(n) do { \ - if (get_user_utl(arg ## n, args + (n) * sizeof(target_ulong))) { \ - errno = EFAULT; \ - return set_swi_errno(cs, -1); \ - } \ - } while (0) - -#define SET_ARG(n, val) \ - put_user_utl(val, args + (n) * sizeof(target_ulong)) -#endif - -/* - * Do a semihosting call. - * - * The specification always says that the "return register" either - * returns a specific value or is corrupted, so we don't need to - * report to our caller whether we are returning a value or trying to - * leave the register unchanged. We use 0xdeadbeef as the return value - * when there isn't a defined return value for the call. - */ -target_ulong do_common_semihosting(CPUState *cs) -{ - CPUArchState *env = cs->env_ptr; - target_ulong args; - target_ulong arg0, arg1, arg2, arg3; - target_ulong ul_ret; - char * s; - int nr; - uint32_t ret; - uint32_t len; - GuestFD *gf; - int64_t elapsed; - - (void) env; /* Used implicitly by arm lock_user macro */ - nr = common_semi_arg(cs, 0) & 0xffffffffU; - args = common_semi_arg(cs, 1); - - switch (nr) { - case TARGET_SYS_OPEN: - { - int guestfd; - - GET_ARG(0); - GET_ARG(1); - GET_ARG(2); - s = lock_user_string(arg0); - if (!s) { - errno = EFAULT; - return set_swi_errno(cs, -1); - } - if (arg1 >= 12) { - unlock_user(s, arg0, 0); - errno = EINVAL; - return set_swi_errno(cs, -1); - } - - guestfd = alloc_guestfd(); - if (guestfd < 0) { - unlock_user(s, arg0, 0); - errno = EMFILE; - return set_swi_errno(cs, -1); - } - - if (strcmp(s, ":tt") == 0) { - int result_fileno; - - /* - * We implement SH_EXT_STDOUT_STDERR, so: - * open for read == stdin - * open for write == stdout - * open for append == stderr - */ - if (arg1 < 4) { - result_fileno = STDIN_FILENO; - } else if (arg1 < 8) { - result_fileno = STDOUT_FILENO; - } else { - result_fileno = STDERR_FILENO; - } - associate_guestfd(guestfd, result_fileno); - unlock_user(s, arg0, 0); - return guestfd; - } - if (strcmp(s, ":semihosting-features") == 0) { - unlock_user(s, arg0, 0); - /* We must fail opens for modes other than 0 ('r') or 1 ('rb') */ - if (arg1 != 0 && arg1 != 1) { - dealloc_guestfd(guestfd); - errno = EACCES; - return set_swi_errno(cs, -1); - } - init_featurefile_guestfd(guestfd); - return guestfd; - } - - if (use_gdb_syscalls()) { - common_semi_open_guestfd = guestfd; - ret = common_semi_gdb_syscall(cs, common_semi_open_cb, - "open,%s,%x,1a4", arg0, (int)arg2 + 1, - gdb_open_modeflags[arg1]); - } else { - ret = set_swi_errno(cs, open(s, open_modeflags[arg1], 0644)); - if (ret == (uint32_t)-1) { - dealloc_guestfd(guestfd); - } else { - associate_guestfd(guestfd, ret); - ret = guestfd; - } - } - unlock_user(s, arg0, 0); - return ret; - } - case TARGET_SYS_CLOSE: - GET_ARG(0); - - gf = get_guestfd(arg0); - if (!gf) { - errno = EBADF; - return set_swi_errno(cs, -1); - } - - ret = guestfd_fns[gf->type].closefn(cs, gf); - dealloc_guestfd(arg0); - return ret; - case TARGET_SYS_WRITEC: - qemu_semihosting_console_outc(cs->env_ptr, args); - return 0xdeadbeef; - case TARGET_SYS_WRITE0: - return qemu_semihosting_console_outs(cs->env_ptr, args); - case TARGET_SYS_WRITE: - GET_ARG(0); - GET_ARG(1); - GET_ARG(2); - len = arg2; - - gf = get_guestfd(arg0); - if (!gf) { - errno = EBADF; - return set_swi_errno(cs, -1); - } - - return guestfd_fns[gf->type].writefn(cs, gf, arg1, len); - case TARGET_SYS_READ: - GET_ARG(0); - GET_ARG(1); - GET_ARG(2); - len = arg2; - - gf = get_guestfd(arg0); - if (!gf) { - errno = EBADF; - return set_swi_errno(cs, -1); - } - - return guestfd_fns[gf->type].readfn(cs, gf, arg1, len); - case TARGET_SYS_READC: - return qemu_semihosting_console_inc(cs->env_ptr); - case TARGET_SYS_ISERROR: - GET_ARG(0); - return (target_long) arg0 < 0 ? 1 : 0; - case TARGET_SYS_ISTTY: - GET_ARG(0); - - gf = get_guestfd(arg0); - if (!gf) { - errno = EBADF; - return set_swi_errno(cs, -1); - } - - return guestfd_fns[gf->type].isattyfn(cs, gf); - case TARGET_SYS_SEEK: - GET_ARG(0); - GET_ARG(1); - - gf = get_guestfd(arg0); - if (!gf) { - errno = EBADF; - return set_swi_errno(cs, -1); - } - - return guestfd_fns[gf->type].seekfn(cs, gf, arg1); - case TARGET_SYS_FLEN: - GET_ARG(0); - - gf = get_guestfd(arg0); - if (!gf) { - errno = EBADF; - return set_swi_errno(cs, -1); - } - - return guestfd_fns[gf->type].flenfn(cs, gf); - case TARGET_SYS_TMPNAM: - GET_ARG(0); - GET_ARG(1); - GET_ARG(2); - if (asprintf(&s, "/tmp/qemu-%x%02x", getpid(), - (int) (arg1 & 0xff)) < 0) { - return -1; - } - ul_ret = (target_ulong) -1; - - /* Make sure there's enough space in the buffer */ - if (strlen(s) < arg2) { - char *output = lock_user(VERIFY_WRITE, arg0, arg2, 0); - strcpy(output, s); - unlock_user(output, arg0, arg2); - ul_ret = 0; - } - free(s); - return ul_ret; - case TARGET_SYS_REMOVE: - GET_ARG(0); - GET_ARG(1); - if (use_gdb_syscalls()) { - ret = common_semi_gdb_syscall(cs, common_semi_cb, "unlink,%s", - arg0, (int)arg1 + 1); - } else { - s = lock_user_string(arg0); - if (!s) { - errno = EFAULT; - return set_swi_errno(cs, -1); - } - ret = set_swi_errno(cs, remove(s)); - unlock_user(s, arg0, 0); - } - return ret; - case TARGET_SYS_RENAME: - GET_ARG(0); - GET_ARG(1); - GET_ARG(2); - GET_ARG(3); - if (use_gdb_syscalls()) { - return common_semi_gdb_syscall(cs, common_semi_cb, "rename,%s,%s", - arg0, (int)arg1 + 1, arg2, - (int)arg3 + 1); - } else { - char *s2; - s = lock_user_string(arg0); - s2 = lock_user_string(arg2); - if (!s || !s2) { - errno = EFAULT; - ret = set_swi_errno(cs, -1); - } else { - ret = set_swi_errno(cs, rename(s, s2)); - } - if (s2) - unlock_user(s2, arg2, 0); - if (s) - unlock_user(s, arg0, 0); - return ret; - } - case TARGET_SYS_CLOCK: - return clock() / (CLOCKS_PER_SEC / 100); - case TARGET_SYS_TIME: - return set_swi_errno(cs, time(NULL)); - case TARGET_SYS_SYSTEM: - GET_ARG(0); - GET_ARG(1); - if (use_gdb_syscalls()) { - return common_semi_gdb_syscall(cs, common_semi_cb, "system,%s", - arg0, (int)arg1 + 1); - } else { - s = lock_user_string(arg0); - if (!s) { - errno = EFAULT; - return set_swi_errno(cs, -1); - } - ret = set_swi_errno(cs, system(s)); - unlock_user(s, arg0, 0); - return ret; - } - case TARGET_SYS_ERRNO: - return get_swi_errno(cs); - case TARGET_SYS_GET_CMDLINE: - { - /* Build a command-line from the original argv. - * - * The inputs are: - * * arg0, pointer to a buffer of at least the size - * specified in arg1. - * * arg1, size of the buffer pointed to by arg0 in - * bytes. - * - * The outputs are: - * * arg0, pointer to null-terminated string of the - * command line. - * * arg1, length of the string pointed to by arg0. - */ - - char *output_buffer; - size_t input_size; - size_t output_size; - int status = 0; -#if !defined(CONFIG_USER_ONLY) - const char *cmdline; -#else - TaskState *ts = cs->opaque; -#endif - GET_ARG(0); - GET_ARG(1); - input_size = arg1; - /* Compute the size of the output string. */ -#if !defined(CONFIG_USER_ONLY) - cmdline = semihosting_get_cmdline(); - if (cmdline == NULL) { - cmdline = ""; /* Default to an empty line. */ - } - output_size = strlen(cmdline) + 1; /* Count terminating 0. */ -#else - unsigned int i; - - output_size = ts->info->arg_end - ts->info->arg_start; - if (!output_size) { - /* - * We special-case the "empty command line" case (argc==0). - * Just provide the terminating 0. - */ - output_size = 1; - } -#endif - - if (output_size > input_size) { - /* Not enough space to store command-line arguments. */ - errno = E2BIG; - return set_swi_errno(cs, -1); - } - - /* Adjust the command-line length. */ - if (SET_ARG(1, output_size - 1)) { - /* Couldn't write back to argument block */ - errno = EFAULT; - return set_swi_errno(cs, -1); - } - - /* Lock the buffer on the ARM side. */ - output_buffer = lock_user(VERIFY_WRITE, arg0, output_size, 0); - if (!output_buffer) { - errno = EFAULT; - return set_swi_errno(cs, -1); - } - - /* Copy the command-line arguments. */ -#if !defined(CONFIG_USER_ONLY) - pstrcpy(output_buffer, output_size, cmdline); -#else - if (output_size == 1) { - /* Empty command-line. */ - output_buffer[0] = '\0'; - goto out; - } - - if (copy_from_user(output_buffer, ts->info->arg_start, - output_size)) { - errno = EFAULT; - status = set_swi_errno(cs, -1); - goto out; - } - - /* Separate arguments by white spaces. */ - for (i = 0; i < output_size - 1; i++) { - if (output_buffer[i] == 0) { - output_buffer[i] = ' '; - } - } - out: -#endif - /* Unlock the buffer on the ARM side. */ - unlock_user(output_buffer, arg0, output_size); - - return status; - } - case TARGET_SYS_HEAPINFO: - { - target_ulong retvals[4]; - target_ulong limit; - int i; -#ifdef CONFIG_USER_ONLY - TaskState *ts = cs->opaque; -#else - target_ulong rambase = common_semi_rambase(cs); -#endif - - GET_ARG(0); - -#ifdef CONFIG_USER_ONLY - /* - * Some C libraries assume the heap immediately follows .bss, so - * allocate it using sbrk. - */ - if (!ts->heap_limit) { - abi_ulong ret; - - ts->heap_base = do_brk(0); - limit = ts->heap_base + COMMON_SEMI_HEAP_SIZE; - /* Try a big heap, and reduce the size if that fails. */ - for (;;) { - ret = do_brk(limit); - if (ret >= limit) { - break; - } - limit = (ts->heap_base >> 1) + (limit >> 1); - } - ts->heap_limit = limit; - } - - retvals[0] = ts->heap_base; - retvals[1] = ts->heap_limit; - retvals[2] = ts->stack_base; - retvals[3] = 0; /* Stack limit. */ -#else - limit = current_machine->ram_size; - /* TODO: Make this use the limit of the loaded application. */ - retvals[0] = rambase + limit / 2; - retvals[1] = rambase + limit; - retvals[2] = rambase + limit; /* Stack base */ - retvals[3] = rambase; /* Stack limit. */ -#endif - - for (i = 0; i < ARRAY_SIZE(retvals); i++) { - bool fail; - - fail = SET_ARG(i, retvals[i]); - - if (fail) { - /* Couldn't write back to argument block */ - errno = EFAULT; - return set_swi_errno(cs, -1); - } - } - return 0; - } - case TARGET_SYS_EXIT: - case TARGET_SYS_EXIT_EXTENDED: - if (common_semi_sys_exit_extended(cs, nr)) { - /* - * The A64 version of SYS_EXIT takes a parameter block, - * so the application-exit type can return a subcode which - * is the exit status code from the application. - * SYS_EXIT_EXTENDED is an a new-in-v2.0 optional function - * which allows A32/T32 guests to also provide a status code. - */ - GET_ARG(0); - GET_ARG(1); - - if (arg0 == ADP_Stopped_ApplicationExit) { - ret = arg1; - } else { - ret = 1; - } - } else { - /* - * The A32/T32 version of SYS_EXIT specifies only - * Stopped_ApplicationExit as normal exit, but does not - * allow the guest to specify the exit status code. - * Everything else is considered an error. - */ - ret = (args == ADP_Stopped_ApplicationExit) ? 0 : 1; - } - gdb_exit(ret); - exit(ret); - case TARGET_SYS_ELAPSED: - elapsed = get_clock() - clock_start; - if (sizeof(target_ulong) == 8) { - SET_ARG(0, elapsed); - } else { - SET_ARG(0, (uint32_t) elapsed); - SET_ARG(1, (uint32_t) (elapsed >> 32)); - } - return 0; - case TARGET_SYS_TICKFREQ: - /* qemu always uses nsec */ - return 1000000000; - case TARGET_SYS_SYNCCACHE: - /* - * Clean the D-cache and invalidate the I-cache for the specified - * virtual address range. This is a nop for us since we don't - * implement caches. This is only present on A64. - */ -#ifdef TARGET_ARM - if (is_a64(cs->env_ptr)) { - return 0; - } -#endif -#ifdef TARGET_RISCV - return 0; -#endif - /* fall through -- invalid for A32/T32 */ - default: - fprintf(stderr, "qemu: Unsupported SemiHosting SWI 0x%02x\n", nr); - cpu_dump_state(cs, stderr, 0); - abort(); - } -} diff --git a/hw/semihosting/common-semi.h b/hw/semihosting/common-semi.h deleted file mode 100644 index 0bfab1c669..0000000000 --- a/hw/semihosting/common-semi.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Semihosting support for systems modeled on the Arm "Angel" - * semihosting syscalls design. This includes Arm and RISC-V processors - * - * Copyright (c) 2005, 2007 CodeSourcery. - * Copyright (c) 2019 Linaro - * Written by Paul Brook. - * - * Copyright © 2020 by Keith Packard <keithp@keithp.com> - * Adapted for systems other than ARM, including RISC-V, by Keith Packard - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * 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/>. - * - * ARM Semihosting is documented in: - * Semihosting for AArch32 and AArch64 Release 2.0 - * https://static.docs.arm.com/100863/0200/semihosting.pdf - * - * RISC-V Semihosting is documented in: - * RISC-V Semihosting - * https://github.com/riscv/riscv-semihosting-spec/blob/main/riscv-semihosting-spec.adoc - */ - -#ifndef COMMON_SEMI_H -#define COMMON_SEMI_H - -target_ulong do_common_semihosting(CPUState *cs); - -#endif /* COMMON_SEMI_H */ diff --git a/hw/semihosting/config.c b/hw/semihosting/config.c deleted file mode 100644 index 9807f10cb0..0000000000 --- a/hw/semihosting/config.c +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Semihosting configuration - * - * Copyright (c) 2015 Imagination Technologies - * Copyright (c) 2019 Linaro Ltd - * - * This controls the configuration of semihosting for all guest - * targets that support it. Architecture specific handling is handled - * in target/HW/HW-semi.c - * - * Semihosting is sightly strange in that it is also supported by some - * linux-user targets. However in that use case no configuration of - * the outputs and command lines is supported. - * - * The config module is common to all softmmu targets however as vl.c - * needs to link against the helpers. - * - * SPDX-License-Identifier: GPL-2.0-or-later - */ - -#include "qemu/osdep.h" -#include "qemu/option.h" -#include "qemu/config-file.h" -#include "qemu/error-report.h" -#include "hw/semihosting/semihost.h" -#include "chardev/char.h" -#include "sysemu/sysemu.h" - -QemuOptsList qemu_semihosting_config_opts = { - .name = "semihosting-config", - .implied_opt_name = "enable", - .head = QTAILQ_HEAD_INITIALIZER(qemu_semihosting_config_opts.head), - .desc = { - { - .name = "enable", - .type = QEMU_OPT_BOOL, - }, { - .name = "target", - .type = QEMU_OPT_STRING, - }, { - .name = "chardev", - .type = QEMU_OPT_STRING, - }, { - .name = "arg", - .type = QEMU_OPT_STRING, - }, - { /* end of list */ } - }, -}; - -typedef struct SemihostingConfig { - bool enabled; - SemihostingTarget target; - Chardev *chardev; - const char **argv; - int argc; - const char *cmdline; /* concatenated argv */ -} SemihostingConfig; - -static SemihostingConfig semihosting; -static const char *semihost_chardev; - -bool semihosting_enabled(void) -{ - return semihosting.enabled; -} - -SemihostingTarget semihosting_get_target(void) -{ - return semihosting.target; -} - -const char *semihosting_get_arg(int i) -{ - if (i >= semihosting.argc) { - return NULL; - } - return semihosting.argv[i]; -} - -int semihosting_get_argc(void) -{ - return semihosting.argc; -} - -const char *semihosting_get_cmdline(void) -{ - if (semihosting.cmdline == NULL && semihosting.argc > 0) { - semihosting.cmdline = g_strjoinv(" ", (gchar **)semihosting.argv); - } - return semihosting.cmdline; -} - -static int add_semihosting_arg(void *opaque, - const char *name, const char *val, - Error **errp) -{ - SemihostingConfig *s = opaque; - if (strcmp(name, "arg") == 0) { - s->argc++; - /* one extra element as g_strjoinv() expects NULL-terminated array */ - s->argv = g_realloc(s->argv, (s->argc + 1) * sizeof(void *)); - s->argv[s->argc - 1] = val; - s->argv[s->argc] = NULL; - } - return 0; -} - -/* Use strings passed via -kernel/-append to initialize semihosting.argv[] */ -void semihosting_arg_fallback(const char *file, const char *cmd) -{ - char *cmd_token; - - /* argv[0] */ - add_semihosting_arg(&semihosting, "arg", file, NULL); - - /* split -append and initialize argv[1..n] */ - cmd_token = strtok(g_strdup(cmd), " "); - while (cmd_token) { - add_semihosting_arg(&semihosting, "arg", cmd_token, NULL); - cmd_token = strtok(NULL, " "); - } -} - -Chardev *semihosting_get_chardev(void) -{ - return semihosting.chardev; -} - -void qemu_semihosting_enable(void) -{ - semihosting.enabled = true; - semihosting.target = SEMIHOSTING_TARGET_AUTO; -} - -int qemu_semihosting_config_options(const char *optarg) -{ - QemuOptsList *opt_list = qemu_find_opts("semihosting-config"); - QemuOpts *opts = qemu_opts_parse_noisily(opt_list, optarg, false); - - semihosting.enabled = true; - - if (opts != NULL) { - semihosting.enabled = qemu_opt_get_bool(opts, "enable", - true); - const char *target = qemu_opt_get(opts, "target"); - /* setup of chardev is deferred until they are initialised */ - semihost_chardev = qemu_opt_get(opts, "chardev"); - if (target != NULL) { - if (strcmp("native", target) == 0) { - semihosting.target = SEMIHOSTING_TARGET_NATIVE; - } else if (strcmp("gdb", target) == 0) { - semihosting.target = SEMIHOSTING_TARGET_GDB; - } else if (strcmp("auto", target) == 0) { - semihosting.target = SEMIHOSTING_TARGET_AUTO; - } else { - error_report("unsupported semihosting-config %s", - optarg); - return 1; - } - } else { - semihosting.target = SEMIHOSTING_TARGET_AUTO; - } - /* Set semihosting argument count and vector */ - qemu_opt_foreach(opts, add_semihosting_arg, - &semihosting, NULL); - } else { - error_report("unsupported semihosting-config %s", optarg); - return 1; - } - - return 0; -} - -void qemu_semihosting_connect_chardevs(void) -{ - /* We had to defer this until chardevs were created */ - if (semihost_chardev) { - Chardev *chr = qemu_chr_find(semihost_chardev); - if (chr == NULL) { - error_report("semihosting chardev '%s' not found", - semihost_chardev); - exit(1); - } - semihosting.chardev = chr; - } -} diff --git a/hw/semihosting/console.c b/hw/semihosting/console.c deleted file mode 100644 index 9b4fee9260..0000000000 --- a/hw/semihosting/console.c +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Semihosting Console Support - * - * Copyright (c) 2015 Imagination Technologies - * Copyright (c) 2019 Linaro Ltd - * - * This provides support for outputting to a semihosting console. - * - * While most semihosting implementations support reading and writing - * to arbitrary file descriptors we treat the console as something - * specifically for debugging interaction. This means messages can be - * re-directed to gdb (if currently being used to debug) or even - * re-directed elsewhere. - * - * SPDX-License-Identifier: GPL-2.0-or-later - */ - -#include "qemu/osdep.h" -#include "cpu.h" -#include "hw/semihosting/semihost.h" -#include "hw/semihosting/console.h" -#include "exec/gdbstub.h" -#include "exec/exec-all.h" -#include "qemu/log.h" -#include "chardev/char.h" -#include "chardev/char-fe.h" -#include "sysemu/sysemu.h" -#include "qemu/main-loop.h" -#include "qapi/error.h" -#include "qemu/fifo8.h" - -int qemu_semihosting_log_out(const char *s, int len) -{ - Chardev *chardev = semihosting_get_chardev(); - if (chardev) { - return qemu_chr_write_all(chardev, (uint8_t *) s, len); - } else { - return write(STDERR_FILENO, s, len); - } -} - -/* - * A re-implementation of lock_user_string that we can use locally - * instead of relying on softmmu-semi. Hopefully we can deprecate that - * in time. Copy string until we find a 0 or address error. - */ -static GString *copy_user_string(CPUArchState *env, target_ulong addr) -{ - CPUState *cpu = env_cpu(env); - GString *s = g_string_sized_new(128); - uint8_t c; - - do { - if (cpu_memory_rw_debug(cpu, addr++, &c, 1, 0) == 0) { - if (c) { - s = g_string_append_c(s, c); - } - } else { - qemu_log_mask(LOG_GUEST_ERROR, - "%s: passed inaccessible address " TARGET_FMT_lx, - __func__, addr); - break; - } - } while (c!=0); - - return s; -} - -static void semihosting_cb(CPUState *cs, target_ulong ret, target_ulong err) -{ - if (ret == (target_ulong) -1) { - qemu_log("%s: gdb console output failed ("TARGET_FMT_ld")", - __func__, err); - } -} - -int qemu_semihosting_console_outs(CPUArchState *env, target_ulong addr) -{ - GString *s = copy_user_string(env, addr); - int out = s->len; - - if (use_gdb_syscalls()) { - gdb_do_syscall(semihosting_cb, "write,2,%x,%x", addr, s->len); - } else { - out = qemu_semihosting_log_out(s->str, s->len); - } - - g_string_free(s, true); - return out; -} - -void qemu_semihosting_console_outc(CPUArchState *env, target_ulong addr) -{ - CPUState *cpu = env_cpu(env); - uint8_t c; - - if (cpu_memory_rw_debug(cpu, addr, &c, 1, 0) == 0) { - if (use_gdb_syscalls()) { - gdb_do_syscall(semihosting_cb, "write,2,%x,%x", addr, 1); - } else { - qemu_semihosting_log_out((const char *) &c, 1); - } - } else { - qemu_log_mask(LOG_GUEST_ERROR, - "%s: passed inaccessible address " TARGET_FMT_lx, - __func__, addr); - } -} - -#define FIFO_SIZE 1024 - -/* Access to this structure is protected by the BQL */ -typedef struct SemihostingConsole { - CharBackend backend; - GSList *sleeping_cpus; - bool got; - Fifo8 fifo; -} SemihostingConsole; - -static SemihostingConsole console; - -static int console_can_read(void *opaque) -{ - SemihostingConsole *c = opaque; - int ret; - g_assert(qemu_mutex_iothread_locked()); - ret = (int) fifo8_num_free(&c->fifo); - return ret; -} - -static void console_wake_up(gpointer data, gpointer user_data) -{ - CPUState *cs = (CPUState *) data; - /* cpu_handle_halt won't know we have work so just unbung here */ - cs->halted = 0; - qemu_cpu_kick(cs); -} - -static void console_read(void *opaque, const uint8_t *buf, int size) -{ - SemihostingConsole *c = opaque; - g_assert(qemu_mutex_iothread_locked()); - while (size-- && !fifo8_is_full(&c->fifo)) { - fifo8_push(&c->fifo, *buf++); - } - g_slist_foreach(c->sleeping_cpus, console_wake_up, NULL); - c->sleeping_cpus = NULL; -} - -target_ulong qemu_semihosting_console_inc(CPUArchState *env) -{ - uint8_t ch; - SemihostingConsole *c = &console; - g_assert(qemu_mutex_iothread_locked()); - g_assert(current_cpu); - if (fifo8_is_empty(&c->fifo)) { - c->sleeping_cpus = g_slist_prepend(c->sleeping_cpus, current_cpu); - current_cpu->halted = 1; - current_cpu->exception_index = EXCP_HALTED; - cpu_loop_exit(current_cpu); - /* never returns */ - } - ch = fifo8_pop(&c->fifo); - return (target_ulong) ch; -} - -void qemu_semihosting_console_init(void) -{ - Chardev *chr = semihosting_get_chardev(); - - if (chr) { - fifo8_create(&console.fifo, FIFO_SIZE); - qemu_chr_fe_init(&console.backend, chr, &error_abort); - qemu_chr_fe_set_handlers(&console.backend, - console_can_read, - console_read, - NULL, NULL, &console, - NULL, true); - } -} diff --git a/hw/semihosting/meson.build b/hw/semihosting/meson.build deleted file mode 100644 index ea8090abe3..0000000000 --- a/hw/semihosting/meson.build +++ /dev/null @@ -1,7 +0,0 @@ -specific_ss.add(when: 'CONFIG_SEMIHOSTING', if_true: files( - 'config.c', - 'console.c', -)) - -specific_ss.add(when: ['CONFIG_ARM_COMPATIBLE_SEMIHOSTING'], - if_true: files('arm-compat-semi.c')) diff --git a/hw/ssi/xilinx_spips.c b/hw/ssi/xilinx_spips.c index a897034601..1e9dba2039 100644 --- a/hw/ssi/xilinx_spips.c +++ b/hw/ssi/xilinx_spips.c @@ -176,7 +176,8 @@ FIELD(GQSPI_FIFO_CTRL, GENERIC_FIFO_RESET, 0, 1) #define R_GQSPI_GFIFO_THRESH (0x150 / 4) #define R_GQSPI_DATA_STS (0x15c / 4) -/* We use the snapshot register to hold the core state for the currently +/* + * We use the snapshot register to hold the core state for the currently * or most recently executed command. So the generic fifo format is defined * for the snapshot register */ @@ -194,13 +195,6 @@ #define R_GQSPI_MOD_ID (0x1fc / 4) #define R_GQSPI_MOD_ID_RESET (0x10a0000) -#define R_QSPIDMA_DST_CTRL (0x80c / 4) -#define R_QSPIDMA_DST_CTRL_RESET (0x803ffa00) -#define R_QSPIDMA_DST_I_MASK (0x820 / 4) -#define R_QSPIDMA_DST_I_MASK_RESET (0xfe) -#define R_QSPIDMA_DST_CTRL2 (0x824 / 4) -#define R_QSPIDMA_DST_CTRL2_RESET (0x081bfff8) - /* size of TXRX FIFOs */ #define RXFF_A (128) #define TXFF_A (128) @@ -416,15 +410,13 @@ static void xlnx_zynqmp_qspips_reset(DeviceState *d) s->regs[R_GQSPI_GPIO] = 1; s->regs[R_GQSPI_LPBK_DLY_ADJ] = R_GQSPI_LPBK_DLY_ADJ_RESET; s->regs[R_GQSPI_MOD_ID] = R_GQSPI_MOD_ID_RESET; - s->regs[R_QSPIDMA_DST_CTRL] = R_QSPIDMA_DST_CTRL_RESET; - s->regs[R_QSPIDMA_DST_I_MASK] = R_QSPIDMA_DST_I_MASK_RESET; - s->regs[R_QSPIDMA_DST_CTRL2] = R_QSPIDMA_DST_CTRL2_RESET; s->man_start_com_g = false; s->gqspi_irqline = 0; xlnx_zynqmp_qspips_update_ixr(s); } -/* N way (num) in place bit striper. Lay out row wise bits (MSB to LSB) +/* + * N way (num) in place bit striper. Lay out row wise bits (MSB to LSB) * column wise (from element 0 to N-1). num is the length of x, and dir * reverses the direction of the transform. Best illustrated by example: * Each digit in the below array is a single bit (num == 3): @@ -637,8 +629,10 @@ static void xilinx_spips_flush_txfifo(XilinxSPIPS *s) tx_rx[i] = tx; } } else { - /* Extract a dummy byte and generate dummy cycles according to the - * link state */ + /* + * Extract a dummy byte and generate dummy cycles according to the + * link state + */ tx = fifo8_pop(&s->tx_fifo); dummy_cycles = 8 / s->link_state; } @@ -721,8 +715,9 @@ static void xilinx_spips_flush_txfifo(XilinxSPIPS *s) } break; case (SNOOP_ADDR): - /* Address has been transmitted, transmit dummy cycles now if - * needed */ + /* + * Address has been transmitted, transmit dummy cycles now if needed + */ if (s->cmd_dummies < 0) { s->snoop_state = SNOOP_NONE; } else { @@ -876,7 +871,7 @@ static void xlnx_zynqmp_qspips_notify(void *opaque) } static uint64_t xilinx_spips_read(void *opaque, hwaddr addr, - unsigned size) + unsigned size) { XilinxSPIPS *s = opaque; uint32_t mask = ~0; @@ -970,7 +965,7 @@ static uint64_t xlnx_zynqmp_qspips_read(void *opaque, } static void xilinx_spips_write(void *opaque, hwaddr addr, - uint64_t value, unsigned size) + uint64_t value, unsigned size) { int mask = ~0; XilinxSPIPS *s = opaque; @@ -1072,7 +1067,7 @@ static void xilinx_qspips_write(void *opaque, hwaddr addr, } static void xlnx_zynqmp_qspips_write(void *opaque, hwaddr addr, - uint64_t value, unsigned size) + uint64_t value, unsigned size) { XlnxZynqMPQSPIPS *s = XLNX_ZYNQMP_QSPIPS(opaque); uint32_t reg = addr / 4; diff --git a/hw/timer/Kconfig b/hw/timer/Kconfig index 18936ef55b..bac2511715 100644 --- a/hw/timer/Kconfig +++ b/hw/timer/Kconfig @@ -46,5 +46,11 @@ config RENESAS_TMR config RENESAS_CMT bool +config SSE_COUNTER + bool + +config SSE_TIMER + bool + config AVR_TIMER16 bool diff --git a/hw/timer/cmsdk-apb-dualtimer.c b/hw/timer/cmsdk-apb-dualtimer.c index ef49f5852d..d4a509c798 100644 --- a/hw/timer/cmsdk-apb-dualtimer.c +++ b/hw/timer/cmsdk-apb-dualtimer.c @@ -449,7 +449,7 @@ static void cmsdk_apb_dualtimer_reset(DeviceState *dev) s->timeritop = 0; } -static void cmsdk_apb_dualtimer_clk_update(void *opaque) +static void cmsdk_apb_dualtimer_clk_update(void *opaque, ClockEvent event) { CMSDKAPBDualTimer *s = CMSDK_APB_DUALTIMER(opaque); int i; @@ -478,7 +478,8 @@ static void cmsdk_apb_dualtimer_init(Object *obj) sysbus_init_irq(sbd, &s->timermod[i].timerint); } s->timclk = qdev_init_clock_in(DEVICE(s), "TIMCLK", - cmsdk_apb_dualtimer_clk_update, s); + cmsdk_apb_dualtimer_clk_update, s, + ClockUpdate); } static void cmsdk_apb_dualtimer_realize(DeviceState *dev, Error **errp) diff --git a/hw/timer/cmsdk-apb-timer.c b/hw/timer/cmsdk-apb-timer.c index ee51ce3369..68aa1a7636 100644 --- a/hw/timer/cmsdk-apb-timer.c +++ b/hw/timer/cmsdk-apb-timer.c @@ -204,7 +204,7 @@ static void cmsdk_apb_timer_reset(DeviceState *dev) ptimer_transaction_commit(s->timer); } -static void cmsdk_apb_timer_clk_update(void *opaque) +static void cmsdk_apb_timer_clk_update(void *opaque, ClockEvent event) { CMSDKAPBTimer *s = CMSDK_APB_TIMER(opaque); @@ -223,7 +223,7 @@ static void cmsdk_apb_timer_init(Object *obj) sysbus_init_mmio(sbd, &s->iomem); sysbus_init_irq(sbd, &s->timerint); s->pclk = qdev_init_clock_in(DEVICE(s), "pclk", - cmsdk_apb_timer_clk_update, s); + cmsdk_apb_timer_clk_update, s, ClockUpdate); } static void cmsdk_apb_timer_realize(DeviceState *dev, Error **errp) diff --git a/hw/timer/meson.build b/hw/timer/meson.build index a2372629f0..598d058506 100644 --- a/hw/timer/meson.build +++ b/hw/timer/meson.build @@ -32,6 +32,8 @@ softmmu_ss.add(when: 'CONFIG_PXA2XX', if_true: files('pxa2xx_timer.c')) softmmu_ss.add(when: 'CONFIG_RASPI', if_true: files('bcm2835_systmr.c')) softmmu_ss.add(when: 'CONFIG_SH_TIMER', if_true: files('sh_timer.c')) softmmu_ss.add(when: 'CONFIG_SLAVIO', if_true: files('slavio_timer.c')) +softmmu_ss.add(when: 'CONFIG_SSE_COUNTER', if_true: files('sse-counter.c')) +softmmu_ss.add(when: 'CONFIG_SSE_TIMER', if_true: files('sse-timer.c')) softmmu_ss.add(when: 'CONFIG_STM32F2XX_TIMER', if_true: files('stm32f2xx_timer.c')) softmmu_ss.add(when: 'CONFIG_XILINX', if_true: files('xilinx_timer.c')) diff --git a/hw/timer/npcm7xx_timer.c b/hw/timer/npcm7xx_timer.c index 36e2c07db2..32f5e021f8 100644 --- a/hw/timer/npcm7xx_timer.c +++ b/hw/timer/npcm7xx_timer.c @@ -138,8 +138,8 @@ static int64_t npcm7xx_timer_count_to_ns(NPCM7xxTimer *t, uint32_t count) /* Convert a time interval in nanoseconds to a timer cycle count. */ static uint32_t npcm7xx_timer_ns_to_count(NPCM7xxTimer *t, int64_t ns) { - return ns / clock_ticks_to_ns(t->ctrl->clock, - npcm7xx_tcsr_prescaler(t->tcsr)); + return clock_ns_to_ticks(t->ctrl->clock, ns) / + npcm7xx_tcsr_prescaler(t->tcsr); } static uint32_t npcm7xx_watchdog_timer_prescaler(const NPCM7xxWatchdogTimer *t) @@ -627,7 +627,7 @@ static void npcm7xx_timer_init(Object *obj) sysbus_init_mmio(sbd, &s->iomem); qdev_init_gpio_out_named(dev, &w->reset_signal, NPCM7XX_WATCHDOG_RESET_GPIO_OUT, 1); - s->clock = qdev_init_clock_in(dev, "clock", NULL, NULL); + s->clock = qdev_init_clock_in(dev, "clock", NULL, NULL, 0); } static const VMStateDescription vmstate_npcm7xx_base_timer = { diff --git a/hw/timer/renesas_tmr.c b/hw/timer/renesas_tmr.c index e03a8155b2..eed39917fe 100644 --- a/hw/timer/renesas_tmr.c +++ b/hw/timer/renesas_tmr.c @@ -46,8 +46,10 @@ REG8(TCCR, 10) FIELD(TCCR, CSS, 3, 2) FIELD(TCCR, TMRIS, 7, 1) -#define INTERNAL 0x01 -#define CASCADING 0x03 +#define CSS_EXTERNAL 0x00 +#define CSS_INTERNAL 0x01 +#define CSS_INVALID 0x02 +#define CSS_CASCADING 0x03 #define CCLR_A 0x01 #define CCLR_B 0x02 @@ -72,7 +74,7 @@ static void update_events(RTMRState *tmr, int ch) /* event not happened */ return ; } - if (FIELD_EX8(tmr->tccr[0], TCCR, CSS) == CASCADING) { + if (FIELD_EX8(tmr->tccr[0], TCCR, CSS) == CSS_CASCADING) { /* cascading mode */ if (ch == 1) { tmr->next[ch] = none; @@ -130,23 +132,32 @@ static uint16_t read_tcnt(RTMRState *tmr, unsigned size, int ch) if (delta > 0) { tmr->tick = now; - if (FIELD_EX8(tmr->tccr[1], TCCR, CSS) == INTERNAL) { + switch (FIELD_EX8(tmr->tccr[1], TCCR, CSS)) { + case CSS_INTERNAL: /* timer1 count update */ elapsed = elapsed_time(tmr, 1, delta); if (elapsed >= 0x100) { ovf = elapsed >> 8; } tcnt[1] = tmr->tcnt[1] + (elapsed & 0xff); + break; + case CSS_INVALID: /* guest error to have set this */ + case CSS_EXTERNAL: /* QEMU doesn't implement these */ + case CSS_CASCADING: + tcnt[1] = tmr->tcnt[1]; + break; } switch (FIELD_EX8(tmr->tccr[0], TCCR, CSS)) { - case INTERNAL: + case CSS_INTERNAL: elapsed = elapsed_time(tmr, 0, delta); tcnt[0] = tmr->tcnt[0] + elapsed; break; - case CASCADING: - if (ovf > 0) { - tcnt[0] = tmr->tcnt[0] + ovf; - } + case CSS_CASCADING: + tcnt[0] = tmr->tcnt[0] + ovf; + break; + case CSS_INVALID: /* guest error to have set this */ + case CSS_EXTERNAL: /* QEMU doesn't implement this */ + tcnt[0] = tmr->tcnt[0]; break; } } else { @@ -330,7 +341,7 @@ static uint16_t issue_event(RTMRState *tmr, int ch, int sz, qemu_irq_pulse(tmr->cmia[ch]); } if (sz == 8 && ch == 0 && - FIELD_EX8(tmr->tccr[1], TCCR, CSS) == CASCADING) { + FIELD_EX8(tmr->tccr[1], TCCR, CSS) == CSS_CASCADING) { tmr->tcnt[1]++; timer_events(tmr, 1); } @@ -362,7 +373,7 @@ static void timer_events(RTMRState *tmr, int ch) uint16_t tcnt; tmr->tcnt[ch] = read_tcnt(tmr, 1, ch); - if (FIELD_EX8(tmr->tccr[0], TCCR, CSS) != CASCADING) { + if (FIELD_EX8(tmr->tccr[0], TCCR, CSS) != CSS_CASCADING) { tmr->tcnt[ch] = issue_event(tmr, ch, 8, tmr->tcnt[ch], tmr->tcora[ch], diff --git a/hw/timer/sse-counter.c b/hw/timer/sse-counter.c new file mode 100644 index 0000000000..0384051f15 --- /dev/null +++ b/hw/timer/sse-counter.c @@ -0,0 +1,474 @@ +/* + * Arm SSE Subsystem System Counter + * + * Copyright (c) 2020 Linaro Limited + * Written by Peter Maydell + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 or + * (at your option) any later version. + */ + +/* + * This is a model of the "System counter" which is documented in + * the Arm SSE-123 Example Subsystem Technical Reference Manual: + * https://developer.arm.com/documentation/101370/latest/ + * + * The system counter is a non-stop 64-bit up-counter. It provides + * this count value to other devices like the SSE system timer, + * which are driven by this system timestamp rather than directly + * from a clock. Internally to the counter the count is actually + * 88-bit precision (64.24 fixed point), with a programmable scale factor. + * + * The hardware has the optional feature that it supports dynamic + * clock switching, where two clock inputs are connected, and which + * one is used is selected via a CLKSEL input signal. Since the + * users of this device in QEMU don't use this feature, we only model + * the HWCLKSW=0 configuration. + */ +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "qemu/timer.h" +#include "qapi/error.h" +#include "trace.h" +#include "hw/timer/sse-counter.h" +#include "hw/sysbus.h" +#include "hw/irq.h" +#include "hw/registerfields.h" +#include "hw/clock.h" +#include "hw/qdev-clock.h" +#include "migration/vmstate.h" + +/* Registers in the control frame */ +REG32(CNTCR, 0x0) + FIELD(CNTCR, EN, 0, 1) + FIELD(CNTCR, HDBG, 1, 1) + FIELD(CNTCR, SCEN, 2, 1) + FIELD(CNTCR, INTRMASK, 3, 1) + FIELD(CNTCR, PSLVERRDIS, 4, 1) + FIELD(CNTCR, INTRCLR, 5, 1) +/* + * Although CNTCR defines interrupt-related bits, the counter doesn't + * appear to actually have an interrupt output. So INTRCLR is + * effectively a RAZ/WI bit, as are the reserved bits [31:6]. + */ +#define CNTCR_VALID_MASK (R_CNTCR_EN_MASK | R_CNTCR_HDBG_MASK | \ + R_CNTCR_SCEN_MASK | R_CNTCR_INTRMASK_MASK | \ + R_CNTCR_PSLVERRDIS_MASK) +REG32(CNTSR, 0x4) +REG32(CNTCV_LO, 0x8) +REG32(CNTCV_HI, 0xc) +REG32(CNTSCR, 0x10) /* Aliased with CNTSCR0 */ +REG32(CNTID, 0x1c) + FIELD(CNTID, CNTSC, 0, 4) + FIELD(CNTID, CNTCS, 16, 1) + FIELD(CNTID, CNTSELCLK, 17, 2) + FIELD(CNTID, CNTSCR_OVR, 19, 1) +REG32(CNTSCR0, 0xd0) +REG32(CNTSCR1, 0xd4) + +/* Registers in the status frame */ +REG32(STATUS_CNTCV_LO, 0x0) +REG32(STATUS_CNTCV_HI, 0x4) + +/* Standard ID registers, present in both frames */ +REG32(PID4, 0xFD0) +REG32(PID5, 0xFD4) +REG32(PID6, 0xFD8) +REG32(PID7, 0xFDC) +REG32(PID0, 0xFE0) +REG32(PID1, 0xFE4) +REG32(PID2, 0xFE8) +REG32(PID3, 0xFEC) +REG32(CID0, 0xFF0) +REG32(CID1, 0xFF4) +REG32(CID2, 0xFF8) +REG32(CID3, 0xFFC) + +/* PID/CID values */ +static const int control_id[] = { + 0x04, 0x00, 0x00, 0x00, /* PID4..PID7 */ + 0xba, 0xb0, 0x0b, 0x00, /* PID0..PID3 */ + 0x0d, 0xf0, 0x05, 0xb1, /* CID0..CID3 */ +}; + +static const int status_id[] = { + 0x04, 0x00, 0x00, 0x00, /* PID4..PID7 */ + 0xbb, 0xb0, 0x0b, 0x00, /* PID0..PID3 */ + 0x0d, 0xf0, 0x05, 0xb1, /* CID0..CID3 */ +}; + +static void sse_counter_notify_users(SSECounter *s) +{ + /* + * Notify users of the count timestamp that they may + * need to recalculate. + */ + notifier_list_notify(&s->notifier_list, NULL); +} + +static bool sse_counter_enabled(SSECounter *s) +{ + return (s->cntcr & R_CNTCR_EN_MASK) != 0; +} + +uint64_t sse_counter_tick_to_time(SSECounter *s, uint64_t tick) +{ + if (!sse_counter_enabled(s)) { + return UINT64_MAX; + } + + tick -= s->ticks_then; + + if (s->cntcr & R_CNTCR_SCEN_MASK) { + /* Adjust the tick count to account for the scale factor */ + tick = muldiv64(tick, 0x01000000, s->cntscr0); + } + + return s->ns_then + clock_ticks_to_ns(s->clk, tick); +} + +void sse_counter_register_consumer(SSECounter *s, Notifier *notifier) +{ + /* + * For the moment we assume that both we and the devices + * which consume us last for the life of the simulation, + * and so there is no mechanism for removing a notifier. + */ + notifier_list_add(&s->notifier_list, notifier); +} + +uint64_t sse_counter_for_timestamp(SSECounter *s, uint64_t now) +{ + /* Return the CNTCV value for a particular timestamp (clock ns value). */ + uint64_t ticks; + + if (!sse_counter_enabled(s)) { + /* Counter is disabled and does not increment */ + return s->ticks_then; + } + + ticks = clock_ns_to_ticks(s->clk, now - s->ns_then); + if (s->cntcr & R_CNTCR_SCEN_MASK) { + /* + * Scaling is enabled. The CNTSCR value is the amount added to + * the underlying 88-bit counter for every tick of the + * underlying clock; CNTCV is the top 64 bits of that full + * 88-bit value. Multiplying the tick count by CNTSCR tells us + * how much the full 88-bit counter has moved on; we then + * divide that by 0x01000000 to find out how much the 64-bit + * visible portion has advanced. muldiv64() gives us the + * necessary at-least-88-bit precision for the intermediate + * result. + */ + ticks = muldiv64(ticks, s->cntscr0, 0x01000000); + } + return s->ticks_then + ticks; +} + +static uint64_t sse_cntcv(SSECounter *s) +{ + /* Return the CNTCV value for the current time */ + return sse_counter_for_timestamp(s, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL)); +} + +static void sse_write_cntcv(SSECounter *s, uint32_t value, unsigned startbit) +{ + /* + * Write one 32-bit half of the counter value; startbit is the + * bit position of this half in the 64-bit word, either 0 or 32. + */ + uint64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + uint64_t cntcv = sse_counter_for_timestamp(s, now); + + cntcv = deposit64(cntcv, startbit, 32, value); + s->ticks_then = cntcv; + s->ns_then = now; + sse_counter_notify_users(s); +} + +static uint64_t sse_counter_control_read(void *opaque, hwaddr offset, + unsigned size) +{ + SSECounter *s = SSE_COUNTER(opaque); + uint64_t r; + + switch (offset) { + case A_CNTCR: + r = s->cntcr; + break; + case A_CNTSR: + /* + * The only bit here is DBGH, indicating that the counter has been + * halted via the Halt-on-Debug signal. We don't implement halting + * debug, so the whole register always reads as zero. + */ + r = 0; + break; + case A_CNTCV_LO: + r = extract64(sse_cntcv(s), 0, 32); + break; + case A_CNTCV_HI: + r = extract64(sse_cntcv(s), 32, 32); + break; + case A_CNTID: + /* + * For our implementation: + * - CNTSCR can only be written when CNTCR.EN == 0 + * - HWCLKSW=0, so selected clock is always CLK0 + * - counter scaling is implemented + */ + r = (1 << R_CNTID_CNTSELCLK_SHIFT) | (1 << R_CNTID_CNTSC_SHIFT); + break; + case A_CNTSCR: + case A_CNTSCR0: + r = s->cntscr0; + break; + case A_CNTSCR1: + /* If HWCLKSW == 0, CNTSCR1 is RAZ/WI */ + r = 0; + break; + case A_PID4 ... A_CID3: + r = control_id[(offset - A_PID4) / 4]; + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "SSE System Counter control frame read: bad offset 0x%x", + (unsigned)offset); + r = 0; + break; + } + + trace_sse_counter_control_read(offset, r, size); + return r; +} + +static void sse_counter_control_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + SSECounter *s = SSE_COUNTER(opaque); + + trace_sse_counter_control_write(offset, value, size); + + switch (offset) { + case A_CNTCR: + /* + * Although CNTCR defines interrupt-related bits, the counter doesn't + * appear to actually have an interrupt output. So INTRCLR is + * effectively a RAZ/WI bit, as are the reserved bits [31:6]. + * The documentation does not explicitly say so, but we assume + * that changing the scale factor while the counter is enabled + * by toggling CNTCR.SCEN has the same behaviour (making the counter + * value UNKNOWN) as changing it by writing to CNTSCR, and so we + * don't need to try to recalculate for that case. + */ + value &= CNTCR_VALID_MASK; + if ((value ^ s->cntcr) & R_CNTCR_EN_MASK) { + /* + * Whether the counter is being enabled or disabled, the + * required action is the same: sync the (ns_then, ticks_then) + * tuple. + */ + uint64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + s->ticks_then = sse_counter_for_timestamp(s, now); + s->ns_then = now; + sse_counter_notify_users(s); + } + s->cntcr = value; + break; + case A_CNTCV_LO: + sse_write_cntcv(s, value, 0); + break; + case A_CNTCV_HI: + sse_write_cntcv(s, value, 32); + break; + case A_CNTSCR: + case A_CNTSCR0: + /* + * If the scale registers are changed when the counter is enabled, + * the count value becomes UNKNOWN. So we don't try to recalculate + * anything here but only do it on a write to CNTCR.EN. + */ + s->cntscr0 = value; + break; + case A_CNTSCR1: + /* If HWCLKSW == 0, CNTSCR1 is RAZ/WI */ + break; + case A_CNTSR: + case A_CNTID: + case A_PID4 ... A_CID3: + qemu_log_mask(LOG_GUEST_ERROR, + "SSE System Counter control frame: write to RO offset 0x%x\n", + (unsigned)offset); + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "SSE System Counter control frame: write to bad offset 0x%x\n", + (unsigned)offset); + break; + } +} + +static uint64_t sse_counter_status_read(void *opaque, hwaddr offset, + unsigned size) +{ + SSECounter *s = SSE_COUNTER(opaque); + uint64_t r; + + switch (offset) { + case A_STATUS_CNTCV_LO: + r = extract64(sse_cntcv(s), 0, 32); + break; + case A_STATUS_CNTCV_HI: + r = extract64(sse_cntcv(s), 32, 32); + break; + case A_PID4 ... A_CID3: + r = status_id[(offset - A_PID4) / 4]; + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "SSE System Counter status frame read: bad offset 0x%x", + (unsigned)offset); + r = 0; + break; + } + + trace_sse_counter_status_read(offset, r, size); + return r; +} + +static void sse_counter_status_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + trace_sse_counter_status_write(offset, value, size); + + switch (offset) { + case A_STATUS_CNTCV_LO: + case A_STATUS_CNTCV_HI: + case A_PID4 ... A_CID3: + qemu_log_mask(LOG_GUEST_ERROR, + "SSE System Counter status frame: write to RO offset 0x%x\n", + (unsigned)offset); + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "SSE System Counter status frame: write to bad offset 0x%x\n", + (unsigned)offset); + break; + } +} + +static const MemoryRegionOps sse_counter_control_ops = { + .read = sse_counter_control_read, + .write = sse_counter_control_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid.min_access_size = 4, + .valid.max_access_size = 4, +}; + +static const MemoryRegionOps sse_counter_status_ops = { + .read = sse_counter_status_read, + .write = sse_counter_status_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid.min_access_size = 4, + .valid.max_access_size = 4, +}; + +static void sse_counter_reset(DeviceState *dev) +{ + SSECounter *s = SSE_COUNTER(dev); + + trace_sse_counter_reset(); + + s->cntcr = 0; + s->cntscr0 = 0x01000000; + s->ns_then = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + s->ticks_then = 0; +} + +static void sse_clk_callback(void *opaque, ClockEvent event) +{ + SSECounter *s = SSE_COUNTER(opaque); + uint64_t now; + + switch (event) { + case ClockPreUpdate: + /* + * Before the clock period updates, set (ticks_then, ns_then) + * to the current time and tick count (as calculated with + * the old clock period). + */ + if (sse_counter_enabled(s)) { + now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + s->ticks_then = sse_counter_for_timestamp(s, now); + s->ns_then = now; + } + break; + case ClockUpdate: + sse_counter_notify_users(s); + break; + default: + break; + } +} + +static void sse_counter_init(Object *obj) +{ + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + SSECounter *s = SSE_COUNTER(obj); + + notifier_list_init(&s->notifier_list); + + s->clk = qdev_init_clock_in(DEVICE(obj), "CLK", sse_clk_callback, s, + ClockPreUpdate | ClockUpdate); + memory_region_init_io(&s->control_mr, obj, &sse_counter_control_ops, + s, "sse-counter-control", 0x1000); + memory_region_init_io(&s->status_mr, obj, &sse_counter_status_ops, + s, "sse-counter-status", 0x1000); + sysbus_init_mmio(sbd, &s->control_mr); + sysbus_init_mmio(sbd, &s->status_mr); +} + +static void sse_counter_realize(DeviceState *dev, Error **errp) +{ + SSECounter *s = SSE_COUNTER(dev); + + if (!clock_has_source(s->clk)) { + error_setg(errp, "SSE system counter: CLK must be connected"); + return; + } +} + +static const VMStateDescription sse_counter_vmstate = { + .name = "sse-counter", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_CLOCK(clk, SSECounter), + VMSTATE_END_OF_LIST() + } +}; + +static void sse_counter_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = sse_counter_realize; + dc->vmsd = &sse_counter_vmstate; + dc->reset = sse_counter_reset; +} + +static const TypeInfo sse_counter_info = { + .name = TYPE_SSE_COUNTER, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(SSECounter), + .instance_init = sse_counter_init, + .class_init = sse_counter_class_init, +}; + +static void sse_counter_register_types(void) +{ + type_register_static(&sse_counter_info); +} + +type_init(sse_counter_register_types); diff --git a/hw/timer/sse-timer.c b/hw/timer/sse-timer.c new file mode 100644 index 0000000000..8dbe6ac651 --- /dev/null +++ b/hw/timer/sse-timer.c @@ -0,0 +1,470 @@ +/* + * Arm SSE Subsystem System Timer + * + * Copyright (c) 2020 Linaro Limited + * Written by Peter Maydell + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 or + * (at your option) any later version. + */ + +/* + * This is a model of the "System timer" which is documented in + * the Arm SSE-123 Example Subsystem Technical Reference Manual: + * https://developer.arm.com/documentation/101370/latest/ + * + * The timer is based around a simple 64-bit incrementing counter + * (readable from CNTPCT_HI/LO). The timer fires when + * Counter - CompareValue >= 0. + * The CompareValue is guest-writable, via CNTP_CVAL_HI/LO. + * CNTP_TVAL is an alternative view of the CompareValue defined by + * TimerValue = CompareValue[31:0] - Counter[31:0] + * which can be both read and written. + * This part is similar to the generic timer in an Arm A-class CPU. + * + * The timer also has a separate auto-increment timer. When this + * timer is enabled, then the AutoIncrValue is set to: + * AutoIncrValue = Reload + Counter + * and this timer fires when + * Counter - AutoIncrValue >= 0 + * at which point, an interrupt is generated and the new AutoIncrValue + * is calculated. + * When the auto-increment timer is enabled, interrupt generation + * via the compare/timervalue registers is disabled. + */ +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "qemu/timer.h" +#include "qapi/error.h" +#include "trace.h" +#include "hw/timer/sse-timer.h" +#include "hw/timer/sse-counter.h" +#include "hw/sysbus.h" +#include "hw/irq.h" +#include "hw/registerfields.h" +#include "hw/clock.h" +#include "hw/qdev-clock.h" +#include "hw/qdev-properties.h" +#include "migration/vmstate.h" + +REG32(CNTPCT_LO, 0x0) +REG32(CNTPCT_HI, 0x4) +REG32(CNTFRQ, 0x10) +REG32(CNTP_CVAL_LO, 0x20) +REG32(CNTP_CVAL_HI, 0x24) +REG32(CNTP_TVAL, 0x28) +REG32(CNTP_CTL, 0x2c) + FIELD(CNTP_CTL, ENABLE, 0, 1) + FIELD(CNTP_CTL, IMASK, 1, 1) + FIELD(CNTP_CTL, ISTATUS, 2, 1) +REG32(CNTP_AIVAL_LO, 0x40) +REG32(CNTP_AIVAL_HI, 0x44) +REG32(CNTP_AIVAL_RELOAD, 0x48) +REG32(CNTP_AIVAL_CTL, 0x4c) + FIELD(CNTP_AIVAL_CTL, EN, 0, 1) + FIELD(CNTP_AIVAL_CTL, CLR, 1, 1) +REG32(CNTP_CFG, 0x50) + FIELD(CNTP_CFG, AIVAL, 0, 4) +#define R_CNTP_CFG_AIVAL_IMPLEMENTED 1 +REG32(PID4, 0xFD0) +REG32(PID5, 0xFD4) +REG32(PID6, 0xFD8) +REG32(PID7, 0xFDC) +REG32(PID0, 0xFE0) +REG32(PID1, 0xFE4) +REG32(PID2, 0xFE8) +REG32(PID3, 0xFEC) +REG32(CID0, 0xFF0) +REG32(CID1, 0xFF4) +REG32(CID2, 0xFF8) +REG32(CID3, 0xFFC) + +/* PID/CID values */ +static const int timer_id[] = { + 0x04, 0x00, 0x00, 0x00, /* PID4..PID7 */ + 0xb7, 0xb0, 0x0b, 0x00, /* PID0..PID3 */ + 0x0d, 0xf0, 0x05, 0xb1, /* CID0..CID3 */ +}; + +static bool sse_is_autoinc(SSETimer *s) +{ + return (s->cntp_aival_ctl & R_CNTP_AIVAL_CTL_EN_MASK) != 0; +} + +static bool sse_enabled(SSETimer *s) +{ + return (s->cntp_ctl & R_CNTP_CTL_ENABLE_MASK) != 0; +} + +static uint64_t sse_cntpct(SSETimer *s) +{ + /* Return the CNTPCT value for the current time */ + return sse_counter_for_timestamp(s->counter, + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL)); +} + +static bool sse_timer_status(SSETimer *s) +{ + /* + * Return true if timer condition is met. This is used for both + * the CNTP_CTL.ISTATUS bit and for whether (unless masked) we + * assert our IRQ. + * The documentation is unclear about the behaviour of ISTATUS when + * in autoincrement mode; we assume that it follows CNTP_AIVAL_CTL.CLR + * (ie whether the autoincrement timer is asserting the interrupt). + */ + if (!sse_enabled(s)) { + return false; + } + + if (sse_is_autoinc(s)) { + return s->cntp_aival_ctl & R_CNTP_AIVAL_CTL_CLR_MASK; + } else { + return sse_cntpct(s) >= s->cntp_cval; + } +} + +static void sse_update_irq(SSETimer *s) +{ + bool irqstate = (!(s->cntp_ctl & R_CNTP_CTL_IMASK_MASK) && + sse_timer_status(s)); + + qemu_set_irq(s->irq, irqstate); +} + +static void sse_set_timer(SSETimer *s, uint64_t nexttick) +{ + /* Set the timer to expire at nexttick */ + uint64_t expiry = sse_counter_tick_to_time(s->counter, nexttick); + + if (expiry <= INT64_MAX) { + timer_mod_ns(&s->timer, expiry); + } else { + /* + * nexttick is so far in the future that it would overflow the + * signed 64-bit range of a QEMUTimer. Since timer_mod_ns() + * expiry times are absolute, not relative, we are never going + * to be able to set the timer to this value, so we must just + * assume that guest execution can never run so long that it + * reaches the theoretical point when the timer fires. + * This is also the code path for "counter is not running", + * which is signalled by expiry == UINT64_MAX. + */ + timer_del(&s->timer); + } +} + +static void sse_recalc_timer(SSETimer *s) +{ + /* Recalculate the normal timer */ + uint64_t count, nexttick; + + if (sse_is_autoinc(s)) { + return; + } + + if (!sse_enabled(s)) { + timer_del(&s->timer); + return; + } + + count = sse_cntpct(s); + + if (count >= s->cntp_cval) { + /* + * Timer condition already met. In theory we have a transition when + * the count rolls back over to 0, but that is so far in the future + * that it is not representable as a timer_mod() expiry, so in + * fact sse_set_timer() will always just delete the timer. + */ + nexttick = UINT64_MAX; + } else { + /* Next transition is when count hits cval */ + nexttick = s->cntp_cval; + } + sse_set_timer(s, nexttick); + sse_update_irq(s); +} + +static void sse_autoinc(SSETimer *s) +{ + /* Auto-increment the AIVAL, and set the timer accordingly */ + s->cntp_aival = sse_cntpct(s) + s->cntp_aival_reload; + sse_set_timer(s, s->cntp_aival); +} + +static void sse_timer_cb(void *opaque) +{ + SSETimer *s = SSE_TIMER(opaque); + + if (sse_is_autoinc(s)) { + uint64_t count = sse_cntpct(s); + + if (count >= s->cntp_aival) { + /* Timer condition met, set CLR and do another autoinc */ + s->cntp_aival_ctl |= R_CNTP_AIVAL_CTL_CLR_MASK; + s->cntp_aival = count + s->cntp_aival_reload; + } + sse_set_timer(s, s->cntp_aival); + sse_update_irq(s); + } else { + sse_recalc_timer(s); + } +} + +static uint64_t sse_timer_read(void *opaque, hwaddr offset, unsigned size) +{ + SSETimer *s = SSE_TIMER(opaque); + uint64_t r; + + switch (offset) { + case A_CNTPCT_LO: + r = extract64(sse_cntpct(s), 0, 32); + break; + case A_CNTPCT_HI: + r = extract64(sse_cntpct(s), 32, 32); + break; + case A_CNTFRQ: + r = s->cntfrq; + break; + case A_CNTP_CVAL_LO: + r = extract64(s->cntp_cval, 0, 32); + break; + case A_CNTP_CVAL_HI: + r = extract64(s->cntp_cval, 32, 32); + break; + case A_CNTP_TVAL: + r = extract64(s->cntp_cval - sse_cntpct(s), 0, 32); + break; + case A_CNTP_CTL: + r = s->cntp_ctl; + if (sse_timer_status(s)) { + r |= R_CNTP_CTL_ISTATUS_MASK; + } + break; + case A_CNTP_AIVAL_LO: + r = extract64(s->cntp_aival, 0, 32); + break; + case A_CNTP_AIVAL_HI: + r = extract64(s->cntp_aival, 32, 32); + break; + case A_CNTP_AIVAL_RELOAD: + r = s->cntp_aival_reload; + break; + case A_CNTP_AIVAL_CTL: + /* + * All the bits of AIVAL_CTL are documented as WO, but this is probably + * a documentation error. We implement them as readable. + */ + r = s->cntp_aival_ctl; + break; + case A_CNTP_CFG: + r = R_CNTP_CFG_AIVAL_IMPLEMENTED << R_CNTP_CFG_AIVAL_SHIFT; + break; + case A_PID4 ... A_CID3: + r = timer_id[(offset - A_PID4) / 4]; + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "SSE System Timer read: bad offset 0x%x", + (unsigned) offset); + r = 0; + break; + } + + trace_sse_timer_read(offset, r, size); + return r; +} + +static void sse_timer_write(void *opaque, hwaddr offset, uint64_t value, + unsigned size) +{ + SSETimer *s = SSE_TIMER(opaque); + + trace_sse_timer_write(offset, value, size); + + switch (offset) { + case A_CNTFRQ: + s->cntfrq = value; + break; + case A_CNTP_CVAL_LO: + s->cntp_cval = deposit64(s->cntp_cval, 0, 32, value); + sse_recalc_timer(s); + break; + case A_CNTP_CVAL_HI: + s->cntp_cval = deposit64(s->cntp_cval, 32, 32, value); + sse_recalc_timer(s); + break; + case A_CNTP_TVAL: + s->cntp_cval = sse_cntpct(s) + sextract64(value, 0, 32); + sse_recalc_timer(s); + break; + case A_CNTP_CTL: + { + uint32_t old_ctl = s->cntp_ctl; + value &= R_CNTP_CTL_ENABLE_MASK | R_CNTP_CTL_IMASK_MASK; + s->cntp_ctl = value; + if ((old_ctl ^ s->cntp_ctl) & R_CNTP_CTL_ENABLE_MASK) { + if (sse_enabled(s)) { + if (sse_is_autoinc(s)) { + sse_autoinc(s); + } else { + sse_recalc_timer(s); + } + } + } + sse_update_irq(s); + break; + } + case A_CNTP_AIVAL_RELOAD: + s->cntp_aival_reload = value; + break; + case A_CNTP_AIVAL_CTL: + { + uint32_t old_ctl = s->cntp_aival_ctl; + + /* EN bit is writeable; CLR bit is write-0-to-clear, write-1-ignored */ + s->cntp_aival_ctl &= ~R_CNTP_AIVAL_CTL_EN_MASK; + s->cntp_aival_ctl |= value & R_CNTP_AIVAL_CTL_EN_MASK; + if (!(value & R_CNTP_AIVAL_CTL_CLR_MASK)) { + s->cntp_aival_ctl &= ~R_CNTP_AIVAL_CTL_CLR_MASK; + } + if ((old_ctl ^ s->cntp_aival_ctl) & R_CNTP_AIVAL_CTL_EN_MASK) { + /* Auto-increment toggled on/off */ + if (sse_enabled(s)) { + if (sse_is_autoinc(s)) { + sse_autoinc(s); + } else { + sse_recalc_timer(s); + } + } + } + sse_update_irq(s); + break; + } + case A_CNTPCT_LO: + case A_CNTPCT_HI: + case A_CNTP_CFG: + case A_CNTP_AIVAL_LO: + case A_CNTP_AIVAL_HI: + case A_PID4 ... A_CID3: + qemu_log_mask(LOG_GUEST_ERROR, + "SSE System Timer write: write to RO offset 0x%x\n", + (unsigned)offset); + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "SSE System Timer write: bad offset 0x%x\n", + (unsigned)offset); + break; + } +} + +static const MemoryRegionOps sse_timer_ops = { + .read = sse_timer_read, + .write = sse_timer_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid.min_access_size = 4, + .valid.max_access_size = 4, +}; + +static void sse_timer_reset(DeviceState *dev) +{ + SSETimer *s = SSE_TIMER(dev); + + trace_sse_timer_reset(); + + timer_del(&s->timer); + s->cntfrq = 0; + s->cntp_ctl = 0; + s->cntp_cval = 0; + s->cntp_aival = 0; + s->cntp_aival_ctl = 0; + s->cntp_aival_reload = 0; +} + +static void sse_timer_counter_callback(Notifier *notifier, void *data) +{ + SSETimer *s = container_of(notifier, SSETimer, counter_notifier); + + /* System counter told us we need to recalculate */ + if (sse_enabled(s)) { + if (sse_is_autoinc(s)) { + sse_set_timer(s, s->cntp_aival); + } else { + sse_recalc_timer(s); + } + } +} + +static void sse_timer_init(Object *obj) +{ + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + SSETimer *s = SSE_TIMER(obj); + + memory_region_init_io(&s->iomem, obj, &sse_timer_ops, + s, "sse-timer", 0x1000); + sysbus_init_mmio(sbd, &s->iomem); + sysbus_init_irq(sbd, &s->irq); +} + +static void sse_timer_realize(DeviceState *dev, Error **errp) +{ + SSETimer *s = SSE_TIMER(dev); + + if (!s->counter) { + error_setg(errp, "counter property was not set"); + } + + s->counter_notifier.notify = sse_timer_counter_callback; + sse_counter_register_consumer(s->counter, &s->counter_notifier); + + timer_init_ns(&s->timer, QEMU_CLOCK_VIRTUAL, sse_timer_cb, s); +} + +static const VMStateDescription sse_timer_vmstate = { + .name = "sse-timer", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_TIMER(timer, SSETimer), + VMSTATE_UINT32(cntfrq, SSETimer), + VMSTATE_UINT32(cntp_ctl, SSETimer), + VMSTATE_UINT64(cntp_cval, SSETimer), + VMSTATE_UINT64(cntp_aival, SSETimer), + VMSTATE_UINT32(cntp_aival_ctl, SSETimer), + VMSTATE_UINT32(cntp_aival_reload, SSETimer), + VMSTATE_END_OF_LIST() + } +}; + +static Property sse_timer_properties[] = { + DEFINE_PROP_LINK("counter", SSETimer, counter, TYPE_SSE_COUNTER, SSECounter *), + DEFINE_PROP_END_OF_LIST(), +}; + +static void sse_timer_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = sse_timer_realize; + dc->vmsd = &sse_timer_vmstate; + dc->reset = sse_timer_reset; + device_class_set_props(dc, sse_timer_properties); +} + +static const TypeInfo sse_timer_info = { + .name = TYPE_SSE_TIMER, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(SSETimer), + .instance_init = sse_timer_init, + .class_init = sse_timer_class_init, +}; + +static void sse_timer_register_types(void) +{ + type_register_static(&sse_timer_info); +} + +type_init(sse_timer_register_types); diff --git a/hw/timer/trace-events b/hw/timer/trace-events index 7a4326d956..f8b9db25c2 100644 --- a/hw/timer/trace-events +++ b/hw/timer/trace-events @@ -93,3 +93,15 @@ avr_timer16_interrupt_count(uint8_t cnt) "count: %u" avr_timer16_interrupt_overflow(const char *reason) "overflow: %s" avr_timer16_next_alarm(uint64_t delay_ns) "next alarm: %" PRIu64 " ns from now" avr_timer16_clksrc_update(uint64_t freq_hz, uint64_t period_ns, uint64_t delay_s) "timer frequency: %" PRIu64 " Hz, period: %" PRIu64 " ns (%" PRId64 " us)" + +# sse_counter.c +sse_counter_control_read(uint64_t offset, uint64_t data, unsigned size) "SSE system counter control frame read: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u" +sse_counter_control_write(uint64_t offset, uint64_t data, unsigned size) "SSE system counter control framen write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u" +sse_counter_status_read(uint64_t offset, uint64_t data, unsigned size) "SSE system counter status frame read: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u" +sse_counter_status_write(uint64_t offset, uint64_t data, unsigned size) "SSE system counter status frame write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u" +sse_counter_reset(void) "SSE system counter: reset" + +# sse_timer.c +sse_timer_read(uint64_t offset, uint64_t data, unsigned size) "SSE system timer read: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u" +sse_timer_write(uint64_t offset, uint64_t data, unsigned size) "SSE system timer write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u" +sse_timer_reset(void) "SSE system timer: reset" diff --git a/hw/watchdog/cmsdk-apb-watchdog.c b/hw/watchdog/cmsdk-apb-watchdog.c index 302f171173..5a2cd46eb7 100644 --- a/hw/watchdog/cmsdk-apb-watchdog.c +++ b/hw/watchdog/cmsdk-apb-watchdog.c @@ -310,7 +310,7 @@ static void cmsdk_apb_watchdog_reset(DeviceState *dev) ptimer_transaction_commit(s->timer); } -static void cmsdk_apb_watchdog_clk_update(void *opaque) +static void cmsdk_apb_watchdog_clk_update(void *opaque, ClockEvent event) { CMSDKAPBWatchdog *s = CMSDK_APB_WATCHDOG(opaque); @@ -329,7 +329,8 @@ static void cmsdk_apb_watchdog_init(Object *obj) sysbus_init_mmio(sbd, &s->iomem); sysbus_init_irq(sbd, &s->wdogint); s->wdogclk = qdev_init_clock_in(DEVICE(s), "WDOGCLK", - cmsdk_apb_watchdog_clk_update, s); + cmsdk_apb_watchdog_clk_update, s, + ClockUpdate); s->is_luminary = false; s->id = cmsdk_apb_watchdog_id; |