aboutsummaryrefslogtreecommitdiff
path: root/hw
diff options
context:
space:
mode:
Diffstat (limited to 'hw')
-rw-r--r--hw/9pfs/9p.c1
-rw-r--r--hw/Kconfig1
-rw-r--r--hw/adc/npcm7xx_adc.c2
-rw-r--r--hw/arm/Kconfig10
-rw-r--r--hw/arm/armsse.c944
-rw-r--r--hw/arm/armv7m.c1
-rw-r--r--hw/arm/aspeed_ast2600.c51
-rw-r--r--hw/arm/aspeed_soc.c34
-rw-r--r--hw/arm/mainstone.c1
-rw-r--r--hw/arm/mps2-tz.c168
-rw-r--r--hw/arm/virt.c356
-rw-r--r--hw/arm/xlnx-zcu102.c1
-rw-r--r--hw/arm/xlnx-zynqmp.c21
-rw-r--r--hw/arm/z2.c1
-rw-r--r--hw/block/meson.build2
-rw-r--r--hw/block/nvme-ns.c38
-rw-r--r--hw/block/nvme-ns.h13
-rw-r--r--hw/block/nvme-subsys.c116
-rw-r--r--hw/block/nvme-subsys.h60
-rw-r--r--hw/block/nvme.c1439
-rw-r--r--hw/block/nvme.h63
-rw-r--r--hw/block/trace-events21
-rw-r--r--hw/block/vhost-user-blk.c7
-rw-r--r--hw/char/cadence_uart.c4
-rw-r--r--hw/char/ibex_uart.c4
-rw-r--r--hw/char/pl011.c5
-rw-r--r--hw/core/clock.c24
-rw-r--r--hw/core/guest-loader.c145
-rw-r--r--hw/core/guest-loader.h34
-rw-r--r--hw/core/meson.build2
-rw-r--r--hw/core/qdev-clock.c8
-rw-r--r--hw/dma/Kconfig4
-rw-r--r--hw/dma/meson.build1
-rw-r--r--hw/dma/xlnx_csu_dma.c745
-rw-r--r--hw/i386/pc.c1
-rw-r--r--hw/meson.build1
-rw-r--r--hw/mips/cps.c2
-rw-r--r--hw/mips/malta.c2
-rw-r--r--hw/misc/Kconfig9
-rw-r--r--hw/misc/armsse-cpu-pwrctrl.c149
-rw-r--r--hw/misc/aspeed_lpc.c486
-rw-r--r--hw/misc/bcm2835_cprman.c23
-rw-r--r--hw/misc/iotkit-secctl.c50
-rw-r--r--hw/misc/iotkit-sysctl.c522
-rw-r--r--hw/misc/iotkit-sysinfo.c51
-rw-r--r--hw/misc/ivshmem.c1
-rw-r--r--hw/misc/meson.build8
-rw-r--r--hw/misc/mps2-fpgaio.c52
-rw-r--r--hw/misc/mps2-scc.c15
-rw-r--r--hw/misc/npcm7xx_clk.c26
-rw-r--r--hw/misc/npcm7xx_pwm.c2
-rw-r--r--hw/misc/trace-events4
-rw-r--r--hw/misc/zynq_slcr.c5
-rw-r--r--hw/ppc/ppc440_bamboo.c1
-rw-r--r--hw/ppc/prep.c1
-rw-r--r--hw/ppc/sam460ex.c1
-rw-r--r--hw/ppc/spapr_caps.c1
-rw-r--r--hw/ppc/spapr_pci_vfio.c1
-rw-r--r--hw/ppc/spapr_vio.c1
-rw-r--r--hw/ppc/virtex_ml507.c1
-rw-r--r--hw/riscv/spike.c1
-rw-r--r--hw/riscv/virt.c20
-rw-r--r--hw/rx/rx62n.c1
-rw-r--r--hw/semihosting/Kconfig7
-rw-r--r--hw/semihosting/arm-compat-semi.c1306
-rw-r--r--hw/semihosting/common-semi.h39
-rw-r--r--hw/semihosting/config.c187
-rw-r--r--hw/semihosting/console.c180
-rw-r--r--hw/semihosting/meson.build7
-rw-r--r--hw/ssi/xilinx_spips.c33
-rw-r--r--hw/timer/Kconfig6
-rw-r--r--hw/timer/cmsdk-apb-dualtimer.c5
-rw-r--r--hw/timer/cmsdk-apb-timer.c4
-rw-r--r--hw/timer/meson.build2
-rw-r--r--hw/timer/npcm7xx_timer.c6
-rw-r--r--hw/timer/renesas_tmr.c33
-rw-r--r--hw/timer/sse-counter.c474
-rw-r--r--hw/timer/sse-timer.c470
-rw-r--r--hw/timer/trace-events12
-rw-r--r--hw/watchdog/cmsdk-apb-watchdog.c5
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 *)&timestamp, sizeof(timestamp),
- DMA_DIRECTION_FROM_DEVICE, req);
+ return nvme_c2h(n, (uint8_t *)&timestamp, 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 *)&timestamp, sizeof(timestamp),
- DMA_DIRECTION_TO_DEVICE, req);
+ ret = nvme_h2c(n, (uint8_t *)&timestamp, 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", &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,
+ &reg_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;