aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Maydell <peter.maydell@linaro.org>2020-12-10 11:48:25 +0000
committerPeter Maydell <peter.maydell@linaro.org>2020-12-10 11:48:25 +0000
commit180834dcb8277a687b62f035b477abfd5a1ff978 (patch)
tree435634a8ba46ac2177b86a5d1c12b23a467a7974
parent5e7b204dbfae9a562fc73684986f936b97f63877 (diff)
parent71f916be1c7e9ede0e37d9cabc781b5a9e8638ff (diff)
downloadqemu-180834dcb8277a687b62f035b477abfd5a1ff978.zip
qemu-180834dcb8277a687b62f035b477abfd5a1ff978.tar.gz
qemu-180834dcb8277a687b62f035b477abfd5a1ff978.tar.bz2
Merge remote-tracking branch 'remotes/pmaydell/tags/pull-target-arm-20201210' into staging
target-arm queue: * hw/arm/smmuv3: Fix up L1STD_SPAN decoding * xlnx-zynqmp: Support Xilinx ZynqMP CAN controllers * sbsa-ref: allow to use Cortex-A53/57/72 cpus * Various minor code cleanups * hw/intc/armv7m_nvic: Make all of system PPB range be RAZWI/BusFault * Implement more pieces of ARMv8.1M support # gpg: Signature made Thu 10 Dec 2020 11:46:43 GMT # gpg: using RSA key E1A5C593CD419DE28E8315CF3C2525ED14360CDE # gpg: issuer "peter.maydell@linaro.org" # gpg: Good signature from "Peter Maydell <peter.maydell@linaro.org>" [ultimate] # gpg: aka "Peter Maydell <pmaydell@gmail.com>" [ultimate] # gpg: aka "Peter Maydell <pmaydell@chiark.greenend.org.uk>" [ultimate] # Primary key fingerprint: E1A5 C593 CD41 9DE2 8E83 15CF 3C25 25ED 1436 0CDE * remotes/pmaydell/tags/pull-target-arm-20201210: (36 commits) hw/arm/armv7m: Correct typo in QOM object name hw/intc/armv7m_nvic: Implement read/write for RAS register block target/arm: Implement M-profile "minimal RAS implementation" hw/intc/armv7m_nvic: Fix "return from inactive handler" check target/arm: Implement CCR_S.TRD behaviour for SG insns hw/intc/armv7m_nvic: Support v8.1M CCR.TRD bit target/arm: Implement new v8.1M VLLDM and VLSTM encodings target/arm: Implement new v8.1M NOCP check for exception return target/arm: Implement v8.1M REVIDR register target/arm: In v8.1M, don't set HFSR.FORCED on vector table fetch failures target/arm: For v8.1M, always clear R0-R3, R12, APSR, EPSR on exception entry hw/intc/armv7m_nvic: Update FPDSCR masking for v8.1M target/arm: Implement FPCXT_S fp system register target/arm: Factor out preserve-fp-state from full_vfp_access_check() target/arm: Use new FPCR_NZCV_MASK constant target/arm: Implement M-profile FPSCR_nzcvqc target/arm: Implement VLDR/VSTR system register target/arm: Move general-use constant expanders up in translate.c target/arm: Refactor M-profile VMSR/VMRS handling target/arm: Enforce M-profile VMRS/VMSR register restrictions ... Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
-rw-r--r--MAINTAINERS8
-rw-r--r--hw/Kconfig1
-rw-r--r--hw/arm/armv7m.c4
-rw-r--r--hw/arm/sbsa-ref.c23
-rw-r--r--hw/arm/smmuv3-internal.h2
-rw-r--r--hw/arm/xlnx-zcu102.c20
-rw-r--r--hw/arm/xlnx-zynqmp.c34
-rw-r--r--hw/intc/armv7m_nvic.c246
-rw-r--r--hw/misc/imx25_ccm.c12
-rw-r--r--hw/misc/imx31_ccm.c14
-rw-r--r--hw/misc/imx6_ccm.c20
-rw-r--r--hw/misc/imx6_src.c2
-rw-r--r--hw/misc/imx6ul_ccm.c4
-rw-r--r--hw/misc/imx_ccm.c4
-rw-r--r--hw/net/can/meson.build1
-rw-r--r--hw/net/can/trace-events9
-rw-r--r--hw/net/can/trace.h1
-rw-r--r--hw/net/can/xlnx-zynqmp-can.c1161
-rw-r--r--include/hw/arm/xlnx-zynqmp.h8
-rw-r--r--include/hw/intc/armv7m_nvic.h2
-rw-r--r--include/hw/net/xlnx-zynqmp-can.h78
-rw-r--r--meson.build1
-rw-r--r--target/arm/cpu.c5
-rw-r--r--target/arm/cpu.h46
-rw-r--r--target/arm/helper.c7
-rw-r--r--target/arm/m-nocp.decode10
-rw-r--r--target/arm/m_helper.c130
-rw-r--r--target/arm/t32.decode10
-rw-r--r--target/arm/translate-vfp.c.inc505
-rw-r--r--target/arm/translate.c105
-rw-r--r--target/arm/vfp.decode14
-rw-r--r--tests/qtest/meson.build1
-rw-r--r--tests/qtest/npcm7xx_rng-test.c12
-rw-r--r--tests/qtest/xlnx-can-test.c360
34 files changed, 2710 insertions, 150 deletions
diff --git a/MAINTAINERS b/MAINTAINERS
index 68bc160..a83416d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1563,6 +1563,14 @@ F: hw/net/opencores_eth.c
Devices
-------
+Xilinx CAN
+M: Vikram Garhwal <fnu.vikram@xilinx.com>
+M: Francisco Iglesias <francisco.iglesias@xilinx.com>
+S: Maintained
+F: hw/net/can/xlnx-*
+F: include/hw/net/xlnx-*
+F: tests/qtest/xlnx-can-test*
+
EDU
M: Jiri Slaby <jslaby@suse.cz>
S: Maintained
diff --git a/hw/Kconfig b/hw/Kconfig
index 4de1797ffd..5ad3c6b 100644
--- a/hw/Kconfig
+++ b/hw/Kconfig
@@ -80,3 +80,4 @@ config XILINX_AXI
config XLNX_ZYNQMP
bool
select REGISTER
+ select CAN_BUS
diff --git a/hw/arm/armv7m.c b/hw/arm/armv7m.c
index 8113b29..8224d4a 100644
--- a/hw/arm/armv7m.c
+++ b/hw/arm/armv7m.c
@@ -136,7 +136,7 @@ static void armv7m_instance_init(Object *obj)
memory_region_init(&s->container, obj, "armv7m-container", UINT64_MAX);
- object_initialize_child(obj, "nvnic", &s->nvic, TYPE_NVIC);
+ object_initialize_child(obj, "nvic", &s->nvic, TYPE_NVIC);
object_property_add_alias(obj, "num-irq",
OBJECT(&s->nvic), "num-irq");
@@ -225,7 +225,7 @@ static void armv7m_realize(DeviceState *dev, Error **errp)
sysbus_connect_irq(sbd, 0,
qdev_get_gpio_in(DEVICE(s->cpu), ARM_CPU_IRQ));
- memory_region_add_subregion(&s->container, 0xe000e000,
+ memory_region_add_subregion(&s->container, 0xe0000000,
sysbus_mmio_get_region(sbd, 0));
for (i = 0; i < ARRAY_SIZE(s->bitband); i++) {
diff --git a/hw/arm/sbsa-ref.c b/hw/arm/sbsa-ref.c
index 7d9e180..4a5ea42 100644
--- a/hw/arm/sbsa-ref.c
+++ b/hw/arm/sbsa-ref.c
@@ -143,6 +143,24 @@ static const int sbsa_ref_irqmap[] = {
[SBSA_GWDT] = 16,
};
+static const char * const valid_cpus[] = {
+ ARM_CPU_TYPE_NAME("cortex-a53"),
+ ARM_CPU_TYPE_NAME("cortex-a57"),
+ ARM_CPU_TYPE_NAME("cortex-a72"),
+};
+
+static bool cpu_type_valid(const char *cpu)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(valid_cpus); i++) {
+ if (strcmp(cpu, valid_cpus[i]) == 0) {
+ return true;
+ }
+ }
+ return false;
+}
+
static uint64_t sbsa_ref_cpu_mp_affinity(SBSAMachineState *sms, int idx)
{
uint8_t clustersz = ARM_DEFAULT_CPUS_PER_CLUSTER;
@@ -649,9 +667,8 @@ static void sbsa_ref_init(MachineState *machine)
const CPUArchIdList *possible_cpus;
int n, sbsa_max_cpus;
- if (strcmp(machine->cpu_type, ARM_CPU_TYPE_NAME("cortex-a57"))) {
- error_report("sbsa-ref: CPU type other than the built-in "
- "cortex-a57 not supported");
+ if (!cpu_type_valid(machine->cpu_type)) {
+ error_report("mach-virt: CPU type %s not supported", machine->cpu_type);
exit(1);
}
diff --git a/hw/arm/smmuv3-internal.h b/hw/arm/smmuv3-internal.h
index fa3c088..b6f7e53 100644
--- a/hw/arm/smmuv3-internal.h
+++ b/hw/arm/smmuv3-internal.h
@@ -633,6 +633,6 @@ static inline uint64_t l1std_l2ptr(STEDesc *desc)
return hi << 32 | lo;
}
-#define L1STD_SPAN(stm) (extract32((stm)->word[0], 0, 4))
+#define L1STD_SPAN(stm) (extract32((stm)->word[0], 0, 5))
#endif
diff --git a/hw/arm/xlnx-zcu102.c b/hw/arm/xlnx-zcu102.c
index ad7fff9..4ef0c51 100644
--- a/hw/arm/xlnx-zcu102.c
+++ b/hw/arm/xlnx-zcu102.c
@@ -25,6 +25,7 @@
#include "sysemu/qtest.h"
#include "sysemu/device_tree.h"
#include "qom/object.h"
+#include "net/can_emu.h"
struct XlnxZCU102 {
MachineState parent_obj;
@@ -34,6 +35,8 @@ struct XlnxZCU102 {
bool secure;
bool virt;
+ CanBusState *canbus[XLNX_ZYNQMP_NUM_CAN];
+
struct arm_boot_info binfo;
};
@@ -125,6 +128,14 @@ static void xlnx_zcu102_init(MachineState *machine)
object_property_set_bool(OBJECT(&s->soc), "virtualization", s->virt,
&error_fatal);
+ for (i = 0; i < XLNX_ZYNQMP_NUM_CAN; i++) {
+ gchar *bus_name = g_strdup_printf("canbus%d", i);
+
+ object_property_set_link(OBJECT(&s->soc), bus_name,
+ OBJECT(s->canbus[i]), &error_fatal);
+ g_free(bus_name);
+ }
+
qdev_realize(DEVICE(&s->soc), NULL, &error_fatal);
/* Create and plug in the SD cards */
@@ -208,6 +219,15 @@ static void xlnx_zcu102_machine_instance_init(Object *obj)
s->secure = false;
/* Default to virt (EL2) being disabled */
s->virt = false;
+ object_property_add_link(obj, "xlnx-zcu102.canbus0", TYPE_CAN_BUS,
+ (Object **)&s->canbus[0],
+ object_property_allow_set_link,
+ 0);
+
+ object_property_add_link(obj, "xlnx-zcu102.canbus1", TYPE_CAN_BUS,
+ (Object **)&s->canbus[1],
+ object_property_allow_set_link,
+ 0);
}
static void xlnx_zcu102_machine_class_init(ObjectClass *oc, void *data)
diff --git a/hw/arm/xlnx-zynqmp.c b/hw/arm/xlnx-zynqmp.c
index 7885bb1..8818472 100644
--- a/hw/arm/xlnx-zynqmp.c
+++ b/hw/arm/xlnx-zynqmp.c
@@ -81,6 +81,14 @@ static const int uart_intr[XLNX_ZYNQMP_NUM_UARTS] = {
21, 22,
};
+static const uint64_t can_addr[XLNX_ZYNQMP_NUM_CAN] = {
+ 0xFF060000, 0xFF070000,
+};
+
+static const int can_intr[XLNX_ZYNQMP_NUM_CAN] = {
+ 23, 24,
+};
+
static const uint64_t sdhci_addr[XLNX_ZYNQMP_NUM_SDHCI] = {
0xFF160000, 0xFF170000,
};
@@ -243,6 +251,11 @@ static void xlnx_zynqmp_init(Object *obj)
TYPE_CADENCE_UART);
}
+ for (i = 0; i < XLNX_ZYNQMP_NUM_CAN; i++) {
+ object_initialize_child(obj, "can[*]", &s->can[i],
+ TYPE_XLNX_ZYNQMP_CAN);
+ }
+
object_initialize_child(obj, "sata", &s->sata, TYPE_SYSBUS_AHCI);
for (i = 0; i < XLNX_ZYNQMP_NUM_SDHCI; i++) {
@@ -482,6 +495,23 @@ static void xlnx_zynqmp_realize(DeviceState *dev, Error **errp)
gic_spi[uart_intr[i]]);
}
+ for (i = 0; i < XLNX_ZYNQMP_NUM_CAN; i++) {
+ object_property_set_int(OBJECT(&s->can[i]), "ext_clk_freq",
+ XLNX_ZYNQMP_CAN_REF_CLK, &error_abort);
+
+ object_property_set_link(OBJECT(&s->can[i]), "canbus",
+ OBJECT(s->canbus[i]), &error_fatal);
+
+ sysbus_realize(SYS_BUS_DEVICE(&s->can[i]), &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
+ }
+ sysbus_mmio_map(SYS_BUS_DEVICE(&s->can[i]), 0, can_addr[i]);
+ sysbus_connect_irq(SYS_BUS_DEVICE(&s->can[i]), 0,
+ gic_spi[can_intr[i]]);
+ }
+
object_property_set_int(OBJECT(&s->sata), "num-ports", SATA_NUM_PORTS,
&error_abort);
if (!sysbus_realize(SYS_BUS_DEVICE(&s->sata), errp)) {
@@ -619,6 +649,10 @@ static Property xlnx_zynqmp_props[] = {
DEFINE_PROP_BOOL("has_rpu", XlnxZynqMPState, has_rpu, false),
DEFINE_PROP_LINK("ddr-ram", XlnxZynqMPState, ddr_ram, TYPE_MEMORY_REGION,
MemoryRegion *),
+ DEFINE_PROP_LINK("canbus0", XlnxZynqMPState, canbus[0], TYPE_CAN_BUS,
+ CanBusState *),
+ DEFINE_PROP_LINK("canbus1", XlnxZynqMPState, canbus[1], TYPE_CAN_BUS,
+ CanBusState *),
DEFINE_PROP_END_OF_LIST()
};
diff --git a/hw/intc/armv7m_nvic.c b/hw/intc/armv7m_nvic.c
index 42b1ad5..f63aa2d 100644
--- a/hw/intc/armv7m_nvic.c
+++ b/hw/intc/armv7m_nvic.c
@@ -832,10 +832,40 @@ int armv7m_nvic_complete_irq(void *opaque, int irq, bool secure)
{
NVICState *s = (NVICState *)opaque;
VecInfo *vec = NULL;
- int ret;
+ int ret = 0;
assert(irq > ARMV7M_EXCP_RESET && irq < s->num_irq);
+ trace_nvic_complete_irq(irq, secure);
+
+ if (secure && exc_is_banked(irq)) {
+ vec = &s->sec_vectors[irq];
+ } else {
+ vec = &s->vectors[irq];
+ }
+
+ /*
+ * Identify illegal exception return cases. We can't immediately
+ * return at this point because we still need to deactivate
+ * (either this exception or NMI/HardFault) first.
+ */
+ if (!exc_is_banked(irq) && exc_targets_secure(s, irq) != secure) {
+ /*
+ * Return from a configurable exception targeting the opposite
+ * security state from the one we're trying to complete it for.
+ * Clear vec because it's not really the VecInfo for this
+ * (irq, secstate) so we mustn't deactivate it.
+ */
+ ret = -1;
+ vec = NULL;
+ } else if (!vec->active) {
+ /* Return from an inactive interrupt */
+ ret = -1;
+ } else {
+ /* Legal return, we will return the RETTOBASE bit value to the caller */
+ ret = nvic_rettobase(s);
+ }
+
/*
* For negative priorities, v8M will forcibly deactivate the appropriate
* NMI or HardFault regardless of what interrupt we're being asked to
@@ -865,32 +895,7 @@ int armv7m_nvic_complete_irq(void *opaque, int irq, bool secure)
}
if (!vec) {
- if (secure && exc_is_banked(irq)) {
- vec = &s->sec_vectors[irq];
- } else {
- vec = &s->vectors[irq];
- }
- }
-
- trace_nvic_complete_irq(irq, secure);
-
- if (!vec->active) {
- /* Tell the caller this was an illegal exception return */
- return -1;
- }
-
- /*
- * If this is a configurable exception and it is currently
- * targeting the opposite security state from the one we're trying
- * to complete it for, this counts as an illegal exception return.
- * We still need to deactivate whatever vector the logic above has
- * selected, though, as it might not be the same as the one for the
- * requested exception number.
- */
- if (!exc_is_banked(irq) && exc_targets_secure(s, irq) != secure) {
- ret = -1;
- } else {
- ret = nvic_rettobase(s);
+ return ret;
}
vec->active = 0;
@@ -1025,6 +1030,11 @@ static uint32_t nvic_readl(NVICState *s, uint32_t offset, MemTxAttrs attrs)
}
return val;
}
+ case 0xcfc:
+ if (!arm_feature(&cpu->env, ARM_FEATURE_V8_1M)) {
+ goto bad_offset;
+ }
+ return cpu->revidr;
case 0xd00: /* CPUID Base. */
return cpu->midr;
case 0xd04: /* Interrupt Control State (ICSR) */
@@ -1090,8 +1100,9 @@ static uint32_t nvic_readl(NVICState *s, uint32_t offset, MemTxAttrs attrs)
}
return cpu->env.v7m.scr[attrs.secure];
case 0xd14: /* Configuration Control. */
- /* The BFHFNMIGN bit is the only non-banked bit; we
- * keep it in the non-secure copy of the register.
+ /*
+ * Non-banked bits: BFHFNMIGN (stored in the NS copy of the register)
+ * and TRD (stored in the S copy of the register)
*/
val = cpu->env.v7m.ccr[attrs.secure];
val |= cpu->env.v7m.ccr[M_REG_NS] & R_V7M_CCR_BFHFNMIGN_MASK;
@@ -1472,6 +1483,12 @@ static uint32_t nvic_readl(NVICState *s, uint32_t offset, MemTxAttrs attrs)
return 0;
}
return cpu->env.v7m.sfar;
+ case 0xf04: /* RFSR */
+ if (!cpu_isar_feature(aa32_ras, cpu)) {
+ goto bad_offset;
+ }
+ /* We provide minimal-RAS only: RFSR is RAZ/WI */
+ return 0;
case 0xf34: /* FPCCR */
if (!cpu_isar_feature(aa32_vfp_simd, cpu)) {
return 0;
@@ -1600,6 +1617,7 @@ static void nvic_writel(NVICState *s, uint32_t offset, uint32_t value,
R_V7M_AIRCR_PRIGROUP_SHIFT,
R_V7M_AIRCR_PRIGROUP_LENGTH);
}
+ /* AIRCR.IESB is RAZ/WI because we implement only minimal RAS */
if (attrs.secure) {
/* These bits are only writable by secure */
cpu->env.v7m.aircr = value &
@@ -1634,17 +1652,25 @@ static void nvic_writel(NVICState *s, uint32_t offset, uint32_t value,
cpu->env.v7m.scr[attrs.secure] = value;
break;
case 0xd14: /* Configuration Control. */
+ {
+ uint32_t mask;
+
if (!arm_feature(&cpu->env, ARM_FEATURE_M_MAIN)) {
goto bad_offset;
}
/* Enforce RAZ/WI on reserved and must-RAZ/WI bits */
- value &= (R_V7M_CCR_STKALIGN_MASK |
- R_V7M_CCR_BFHFNMIGN_MASK |
- R_V7M_CCR_DIV_0_TRP_MASK |
- R_V7M_CCR_UNALIGN_TRP_MASK |
- R_V7M_CCR_USERSETMPEND_MASK |
- R_V7M_CCR_NONBASETHRDENA_MASK);
+ mask = R_V7M_CCR_STKALIGN_MASK |
+ R_V7M_CCR_BFHFNMIGN_MASK |
+ R_V7M_CCR_DIV_0_TRP_MASK |
+ R_V7M_CCR_UNALIGN_TRP_MASK |
+ R_V7M_CCR_USERSETMPEND_MASK |
+ R_V7M_CCR_NONBASETHRDENA_MASK;
+ if (arm_feature(&cpu->env, ARM_FEATURE_V8_1M) && attrs.secure) {
+ /* TRD is always RAZ/WI from NS */
+ mask |= R_V7M_CCR_TRD_MASK;
+ }
+ value &= mask;
if (arm_feature(&cpu->env, ARM_FEATURE_V8)) {
/* v8M makes NONBASETHRDENA and STKALIGN be RES1 */
@@ -1661,6 +1687,7 @@ static void nvic_writel(NVICState *s, uint32_t offset, uint32_t value,
cpu->env.v7m.ccr[attrs.secure] = value;
break;
+ }
case 0xd24: /* System Handler Control and State (SHCSR) */
if (!arm_feature(&cpu->env, ARM_FEATURE_V7)) {
goto bad_offset;
@@ -2006,6 +2033,12 @@ static void nvic_writel(NVICState *s, uint32_t offset, uint32_t value,
}
break;
}
+ case 0xf04: /* RFSR */
+ if (!cpu_isar_feature(aa32_ras, cpu)) {
+ goto bad_offset;
+ }
+ /* We provide minimal-RAS only: RFSR is RAZ/WI */
+ break;
case 0xf34: /* FPCCR */
if (cpu_isar_feature(aa32_vfp_simd, cpu)) {
/* Not all bits here are banked. */
@@ -2068,7 +2101,14 @@ static void nvic_writel(NVICState *s, uint32_t offset, uint32_t value,
break;
case 0xf3c: /* FPDSCR */
if (cpu_isar_feature(aa32_vfp_simd, cpu)) {
- value &= 0x07c00000;
+ uint32_t mask = FPCR_AHP | FPCR_DN | FPCR_FZ | FPCR_RMODE_MASK;
+ if (cpu_isar_feature(any_fp16, cpu)) {
+ mask |= FPCR_FZ16;
+ }
+ value &= mask;
+ if (cpu_isar_feature(aa32_lob, cpu)) {
+ value |= 4 << FPCR_LTPSIZE_SHIFT;
+ }
cpu->env.v7m.fpdscr[attrs.secure] = value;
}
break;
@@ -2479,6 +2519,93 @@ static const MemoryRegionOps nvic_systick_ops = {
.endianness = DEVICE_NATIVE_ENDIAN,
};
+
+static MemTxResult ras_read(void *opaque, hwaddr addr,
+ uint64_t *data, unsigned size,
+ MemTxAttrs attrs)
+{
+ if (attrs.user) {
+ return MEMTX_ERROR;
+ }
+
+ switch (addr) {
+ case 0xe10: /* ERRIIDR */
+ /* architect field = Arm; product/variant/revision 0 */
+ *data = 0x43b;
+ break;
+ case 0xfc8: /* ERRDEVID */
+ /* Minimal RAS: we implement 0 error record indexes */
+ *data = 0;
+ break;
+ default:
+ qemu_log_mask(LOG_UNIMP, "Read RAS register offset 0x%x\n",
+ (uint32_t)addr);
+ *data = 0;
+ break;
+ }
+ return MEMTX_OK;
+}
+
+static MemTxResult ras_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size,
+ MemTxAttrs attrs)
+{
+ if (attrs.user) {
+ return MEMTX_ERROR;
+ }
+
+ switch (addr) {
+ default:
+ qemu_log_mask(LOG_UNIMP, "Write to RAS register offset 0x%x\n",
+ (uint32_t)addr);
+ break;
+ }
+ return MEMTX_OK;
+}
+
+static const MemoryRegionOps ras_ops = {
+ .read_with_attrs = ras_read,
+ .write_with_attrs = ras_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+/*
+ * Unassigned portions of the PPB space are RAZ/WI for privileged
+ * accesses, and fault for non-privileged accesses.
+ */
+static MemTxResult ppb_default_read(void *opaque, hwaddr addr,
+ uint64_t *data, unsigned size,
+ MemTxAttrs attrs)
+{
+ qemu_log_mask(LOG_UNIMP, "Read of unassigned area of PPB: offset 0x%x\n",
+ (uint32_t)addr);
+ if (attrs.user) {
+ return MEMTX_ERROR;
+ }
+ *data = 0;
+ return MEMTX_OK;
+}
+
+static MemTxResult ppb_default_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size,
+ MemTxAttrs attrs)
+{
+ qemu_log_mask(LOG_UNIMP, "Write of unassigned area of PPB: offset 0x%x\n",
+ (uint32_t)addr);
+ if (attrs.user) {
+ return MEMTX_ERROR;
+ }
+ return MEMTX_OK;
+}
+
+static const MemoryRegionOps ppb_default_ops = {
+ .read_with_attrs = ppb_default_read,
+ .write_with_attrs = ppb_default_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 8,
+};
+
static int nvic_post_load(void *opaque, int version_id)
{
NVICState *s = opaque;
@@ -2675,7 +2802,6 @@ static void nvic_systick_trigger(void *opaque, int n, int level)
static void armv7m_nvic_realize(DeviceState *dev, Error **errp)
{
NVICState *s = NVIC(dev);
- int regionlen;
/* The armv7m container object will have set our CPU pointer */
if (!s->cpu || !arm_feature(&s->cpu->env, ARM_FEATURE_M)) {
@@ -2718,7 +2844,20 @@ static void armv7m_nvic_realize(DeviceState *dev, Error **errp)
M_REG_S));
}
- /* The NVIC and System Control Space (SCS) starts at 0xe000e000
+ /*
+ * This device provides a single sysbus memory region which
+ * represents the whole of the "System PPB" space. This is the
+ * range from 0xe0000000 to 0xe00fffff and includes the NVIC,
+ * the System Control Space (system registers), the systick timer,
+ * and for CPUs with the Security extension an NS banked version
+ * of all of these.
+ *
+ * The default behaviour for unimplemented registers/ranges
+ * (for instance the Data Watchpoint and Trace unit at 0xe0001000)
+ * is to RAZ/WI for privileged access and BusFault for non-privileged
+ * access.
+ *
+ * The NVIC and System Control Space (SCS) starts at 0xe000e000
* and looks like this:
* 0x004 - ICTR
* 0x010 - 0xff - systick
@@ -2741,35 +2880,48 @@ static void armv7m_nvic_realize(DeviceState *dev, Error **errp)
* generally code determining which banked register to use should
* use attrs.secure; code determining actual behaviour of the system
* should use env->v7m.secure.
+ *
+ * The container covers the whole PPB space. Within it the priority
+ * of overlapping regions is:
+ * - default region (for RAZ/WI and BusFault) : -1
+ * - system register regions : 0
+ * - systick : 1
+ * This is because the systick device is a small block of registers
+ * in the middle of the other system control registers.
*/
- regionlen = arm_feature(&s->cpu->env, ARM_FEATURE_V8) ? 0x21000 : 0x1000;
- memory_region_init(&s->container, OBJECT(s), "nvic", regionlen);
- /* The system register region goes at the bottom of the priority
- * stack as it covers the whole page.
- */
+ memory_region_init(&s->container, OBJECT(s), "nvic", 0x100000);
+ memory_region_init_io(&s->defaultmem, OBJECT(s), &ppb_default_ops, s,
+ "nvic-default", 0x100000);
+ memory_region_add_subregion_overlap(&s->container, 0, &s->defaultmem, -1);
memory_region_init_io(&s->sysregmem, OBJECT(s), &nvic_sysreg_ops, s,
"nvic_sysregs", 0x1000);
- memory_region_add_subregion(&s->container, 0, &s->sysregmem);
+ memory_region_add_subregion(&s->container, 0xe000, &s->sysregmem);
memory_region_init_io(&s->systickmem, OBJECT(s),
&nvic_systick_ops, s,
"nvic_systick", 0xe0);
- memory_region_add_subregion_overlap(&s->container, 0x10,
+ memory_region_add_subregion_overlap(&s->container, 0xe010,
&s->systickmem, 1);
if (arm_feature(&s->cpu->env, ARM_FEATURE_V8)) {
memory_region_init_io(&s->sysreg_ns_mem, OBJECT(s),
&nvic_sysreg_ns_ops, &s->sysregmem,
"nvic_sysregs_ns", 0x1000);
- memory_region_add_subregion(&s->container, 0x20000, &s->sysreg_ns_mem);
+ memory_region_add_subregion(&s->container, 0x2e000, &s->sysreg_ns_mem);
memory_region_init_io(&s->systick_ns_mem, OBJECT(s),
&nvic_sysreg_ns_ops, &s->systickmem,
"nvic_systick_ns", 0xe0);
- memory_region_add_subregion_overlap(&s->container, 0x20010,
+ memory_region_add_subregion_overlap(&s->container, 0x2e010,
&s->systick_ns_mem, 1);
}
+ if (cpu_isar_feature(aa32_ras, s->cpu)) {
+ memory_region_init_io(&s->ras_mem, OBJECT(s),
+ &ras_ops, s, "nvic_ras", 0x1000);
+ memory_region_add_subregion(&s->container, 0x5000, &s->ras_mem);
+ }
+
sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->container);
}
diff --git a/hw/misc/imx25_ccm.c b/hw/misc/imx25_ccm.c
index d3107e5..ff996e2 100644
--- a/hw/misc/imx25_ccm.c
+++ b/hw/misc/imx25_ccm.c
@@ -91,7 +91,7 @@ static const char *imx25_ccm_reg_name(uint32_t reg)
case IMX25_CCM_LPIMR1_REG:
return "lpimr1";
default:
- sprintf(unknown, "[%d ?]", reg);
+ sprintf(unknown, "[%u ?]", reg);
return unknown;
}
}
@@ -118,7 +118,7 @@ static uint32_t imx25_ccm_get_mpll_clk(IMXCCMState *dev)
freq = imx_ccm_calc_pll(s->reg[IMX25_CCM_MPCTL_REG], CKIH_FREQ);
}
- DPRINTF("freq = %d\n", freq);
+ DPRINTF("freq = %u\n", freq);
return freq;
}
@@ -136,7 +136,7 @@ static uint32_t imx25_ccm_get_mcu_clk(IMXCCMState *dev)
freq = freq / (1 + EXTRACT(s->reg[IMX25_CCM_CCTL_REG], ARM_CLK_DIV));
- DPRINTF("freq = %d\n", freq);
+ DPRINTF("freq = %u\n", freq);
return freq;
}
@@ -149,7 +149,7 @@ static uint32_t imx25_ccm_get_ahb_clk(IMXCCMState *dev)
freq = imx25_ccm_get_mcu_clk(dev)
/ (1 + EXTRACT(s->reg[IMX25_CCM_CCTL_REG], AHB_CLK_DIV));
- DPRINTF("freq = %d\n", freq);
+ DPRINTF("freq = %u\n", freq);
return freq;
}
@@ -160,7 +160,7 @@ static uint32_t imx25_ccm_get_ipg_clk(IMXCCMState *dev)
freq = imx25_ccm_get_ahb_clk(dev) / 2;
- DPRINTF("freq = %d\n", freq);
+ DPRINTF("freq = %u\n", freq);
return freq;
}
@@ -186,7 +186,7 @@ static uint32_t imx25_ccm_get_clock_frequency(IMXCCMState *dev, IMXClk clock)
break;
}
- DPRINTF("Clock = %d) = %d\n", clock, freq);
+ DPRINTF("Clock = %d) = %u\n", clock, freq);
return freq;
}
diff --git a/hw/misc/imx31_ccm.c b/hw/misc/imx31_ccm.c
index 6e24682..ad30a4b 100644
--- a/hw/misc/imx31_ccm.c
+++ b/hw/misc/imx31_ccm.c
@@ -89,7 +89,7 @@ static const char *imx31_ccm_reg_name(uint32_t reg)
case IMX31_CCM_PDR2_REG:
return "PDR2";
default:
- sprintf(unknown, "[%d ?]", reg);
+ sprintf(unknown, "[%u ?]", reg);
return unknown;
}
}
@@ -120,7 +120,7 @@ static uint32_t imx31_ccm_get_pll_ref_clk(IMXCCMState *dev)
freq = CKIH_FREQ;
}
- DPRINTF("freq = %d\n", freq);
+ DPRINTF("freq = %u\n", freq);
return freq;
}
@@ -133,7 +133,7 @@ static uint32_t imx31_ccm_get_mpll_clk(IMXCCMState *dev)
freq = imx_ccm_calc_pll(s->reg[IMX31_CCM_MPCTL_REG],
imx31_ccm_get_pll_ref_clk(dev));
- DPRINTF("freq = %d\n", freq);
+ DPRINTF("freq = %u\n", freq);
return freq;
}
@@ -150,7 +150,7 @@ static uint32_t imx31_ccm_get_mcu_main_clk(IMXCCMState *dev)
freq = imx31_ccm_get_mpll_clk(dev);
}
- DPRINTF("freq = %d\n", freq);
+ DPRINTF("freq = %u\n", freq);
return freq;
}
@@ -163,7 +163,7 @@ static uint32_t imx31_ccm_get_hclk_clk(IMXCCMState *dev)
freq = imx31_ccm_get_mcu_main_clk(dev)
/ (1 + EXTRACT(s->reg[IMX31_CCM_PDR0_REG], MAX));
- DPRINTF("freq = %d\n", freq);
+ DPRINTF("freq = %u\n", freq);
return freq;
}
@@ -176,7 +176,7 @@ static uint32_t imx31_ccm_get_ipg_clk(IMXCCMState *dev)
freq = imx31_ccm_get_hclk_clk(dev)
/ (1 + EXTRACT(s->reg[IMX31_CCM_PDR0_REG], IPG));
- DPRINTF("freq = %d\n", freq);
+ DPRINTF("freq = %u\n", freq);
return freq;
}
@@ -201,7 +201,7 @@ static uint32_t imx31_ccm_get_clock_frequency(IMXCCMState *dev, IMXClk clock)
break;
}
- DPRINTF("Clock = %d) = %d\n", clock, freq);
+ DPRINTF("Clock = %d) = %u\n", clock, freq);
return freq;
}
diff --git a/hw/misc/imx6_ccm.c b/hw/misc/imx6_ccm.c
index 7fec8f0..cb74042 100644
--- a/hw/misc/imx6_ccm.c
+++ b/hw/misc/imx6_ccm.c
@@ -96,7 +96,7 @@ static const char *imx6_ccm_reg_name(uint32_t reg)
case CCM_CMEOR:
return "CMEOR";
default:
- sprintf(unknown, "%d ?", reg);
+ sprintf(unknown, "%u ?", reg);
return unknown;
}
}
@@ -235,7 +235,7 @@ static const char *imx6_analog_reg_name(uint32_t reg)
case USB_ANALOG_DIGPROG:
return "USB_ANALOG_DIGPROG";
default:
- sprintf(unknown, "%d ?", reg);
+ sprintf(unknown, "%u ?", reg);
return unknown;
}
}
@@ -263,7 +263,7 @@ static uint64_t imx6_analog_get_pll2_clk(IMX6CCMState *dev)
freq *= 20;
}
- DPRINTF("freq = %d\n", (uint32_t)freq);
+ DPRINTF("freq = %u\n", (uint32_t)freq);
return freq;
}
@@ -275,7 +275,7 @@ static uint64_t imx6_analog_get_pll2_pfd0_clk(IMX6CCMState *dev)
freq = imx6_analog_get_pll2_clk(dev) * 18
/ EXTRACT(dev->analog[CCM_ANALOG_PFD_528], PFD0_FRAC);
- DPRINTF("freq = %d\n", (uint32_t)freq);
+ DPRINTF("freq = %u\n", (uint32_t)freq);
return freq;
}
@@ -287,7 +287,7 @@ static uint64_t imx6_analog_get_pll2_pfd2_clk(IMX6CCMState *dev)
freq = imx6_analog_get_pll2_clk(dev) * 18
/ EXTRACT(dev->analog[CCM_ANALOG_PFD_528], PFD2_FRAC);
- DPRINTF("freq = %d\n", (uint32_t)freq);
+ DPRINTF("freq = %u\n", (uint32_t)freq);
return freq;
}
@@ -315,7 +315,7 @@ static uint64_t imx6_analog_get_periph_clk(IMX6CCMState *dev)
break;
}
- DPRINTF("freq = %d\n", (uint32_t)freq);
+ DPRINTF("freq = %u\n", (uint32_t)freq);
return freq;
}
@@ -327,7 +327,7 @@ static uint64_t imx6_ccm_get_ahb_clk(IMX6CCMState *dev)
freq = imx6_analog_get_periph_clk(dev)
/ (1 + EXTRACT(dev->ccm[CCM_CBCDR], AHB_PODF));
- DPRINTF("freq = %d\n", (uint32_t)freq);
+ DPRINTF("freq = %u\n", (uint32_t)freq);
return freq;
}
@@ -339,7 +339,7 @@ static uint64_t imx6_ccm_get_ipg_clk(IMX6CCMState *dev)
freq = imx6_ccm_get_ahb_clk(dev)
/ (1 + EXTRACT(dev->ccm[CCM_CBCDR], IPG_PODF));
- DPRINTF("freq = %d\n", (uint32_t)freq);
+ DPRINTF("freq = %u\n", (uint32_t)freq);
return freq;
}
@@ -351,7 +351,7 @@ static uint64_t imx6_ccm_get_per_clk(IMX6CCMState *dev)
freq = imx6_ccm_get_ipg_clk(dev)
/ (1 + EXTRACT(dev->ccm[CCM_CSCMR1], PERCLK_PODF));
- DPRINTF("freq = %d\n", (uint32_t)freq);
+ DPRINTF("freq = %u\n", (uint32_t)freq);
return freq;
}
@@ -385,7 +385,7 @@ static uint32_t imx6_ccm_get_clock_frequency(IMXCCMState *dev, IMXClk clock)
break;
}
- DPRINTF("Clock = %d) = %d\n", clock, freq);
+ DPRINTF("Clock = %d) = %u\n", clock, freq);
return freq;
}
diff --git a/hw/misc/imx6_src.c b/hw/misc/imx6_src.c
index dd99cc7..79f4375 100644
--- a/hw/misc/imx6_src.c
+++ b/hw/misc/imx6_src.c
@@ -68,7 +68,7 @@ static const char *imx6_src_reg_name(uint32_t reg)
case SRC_GPR10:
return "SRC_GPR10";
default:
- sprintf(unknown, "%d ?", reg);
+ sprintf(unknown, "%u ?", reg);
return unknown;
}
}
diff --git a/hw/misc/imx6ul_ccm.c b/hw/misc/imx6ul_ccm.c
index 5e0661d..a65d031 100644
--- a/hw/misc/imx6ul_ccm.c
+++ b/hw/misc/imx6ul_ccm.c
@@ -143,7 +143,7 @@ static const char *imx6ul_ccm_reg_name(uint32_t reg)
case CCM_CMEOR:
return "CMEOR";
default:
- sprintf(unknown, "%d ?", reg);
+ sprintf(unknown, "%u ?", reg);
return unknown;
}
}
@@ -274,7 +274,7 @@ static const char *imx6ul_analog_reg_name(uint32_t reg)
case USB_ANALOG_DIGPROG:
return "USB_ANALOG_DIGPROG";
default:
- sprintf(unknown, "%d ?", reg);
+ sprintf(unknown, "%u ?", reg);
return unknown;
}
}
diff --git a/hw/misc/imx_ccm.c b/hw/misc/imx_ccm.c
index 5288207..08a50ee 100644
--- a/hw/misc/imx_ccm.c
+++ b/hw/misc/imx_ccm.c
@@ -38,7 +38,7 @@ uint32_t imx_ccm_get_clock_frequency(IMXCCMState *dev, IMXClk clock)
freq = klass->get_clock_frequency(dev, clock);
}
- DPRINTF("(clock = %d) = %d\n", clock, freq);
+ DPRINTF("(clock = %d) = %u\n", clock, freq);
return freq;
}
@@ -65,7 +65,7 @@ uint32_t imx_ccm_calc_pll(uint32_t pllreg, uint32_t base_freq)
freq = ((2 * (base_freq >> 10) * (mfi * mfd + mfn)) /
(mfd * pd)) << 10;
- DPRINTF("(pllreg = 0x%08x, base_freq = %d) = %d\n", pllreg, base_freq,
+ DPRINTF("(pllreg = 0x%08x, base_freq = %u) = %d\n", pllreg, base_freq,
freq);
return freq;
diff --git a/hw/net/can/meson.build b/hw/net/can/meson.build
index 714951f..8fabbd9 100644
--- a/hw/net/can/meson.build
+++ b/hw/net/can/meson.build
@@ -4,3 +4,4 @@ softmmu_ss.add(when: 'CONFIG_CAN_PCI', if_true: files('can_pcm3680_pci.c'))
softmmu_ss.add(when: 'CONFIG_CAN_PCI', if_true: files('can_mioe3680_pci.c'))
softmmu_ss.add(when: 'CONFIG_CAN_CTUCANFD', if_true: files('ctucan_core.c'))
softmmu_ss.add(when: 'CONFIG_CAN_CTUCANFD_PCI', if_true: files('ctucan_pci.c'))
+softmmu_ss.add(when: 'CONFIG_XLNX_ZYNQMP', if_true: files('xlnx-zynqmp-can.c'))
diff --git a/hw/net/can/trace-events b/hw/net/can/trace-events
new file mode 100644
index 0000000..8346a98
--- /dev/null
+++ b/hw/net/can/trace-events
@@ -0,0 +1,9 @@
+# xlnx-zynqmp-can.c
+xlnx_can_update_irq(uint32_t isr, uint32_t ier, uint32_t irq) "ISR: 0x%08x IER: 0x%08x IRQ: 0x%08x"
+xlnx_can_reset(uint32_t val) "Resetting controller with value = 0x%08x"
+xlnx_can_rx_fifo_filter_reject(uint32_t id, uint8_t dlc) "Frame: ID: 0x%08x DLC: 0x%02x"
+xlnx_can_filter_id_pre_write(uint8_t filter_num, uint32_t value) "Filter%d ID: 0x%08x"
+xlnx_can_filter_mask_pre_write(uint8_t filter_num, uint32_t value) "Filter%d MASK: 0x%08x"
+xlnx_can_tx_data(uint32_t id, uint8_t dlc, uint8_t db0, uint8_t db1, uint8_t db2, uint8_t db3, uint8_t db4, uint8_t db5, uint8_t db6, uint8_t db7) "Frame: ID: 0x%08x DLC: 0x%02x DATA: 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x"
+xlnx_can_rx_data(uint32_t id, uint32_t dlc, uint8_t db0, uint8_t db1, uint8_t db2, uint8_t db3, uint8_t db4, uint8_t db5, uint8_t db6, uint8_t db7) "Frame: ID: 0x%08x DLC: 0x%02x DATA: 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x"
+xlnx_can_rx_discard(uint32_t status) "Controller is not enabled for bus communication. Status Register: 0x%08x"
diff --git a/hw/net/can/trace.h b/hw/net/can/trace.h
new file mode 100644
index 0000000..d391c64
--- /dev/null
+++ b/hw/net/can/trace.h
@@ -0,0 +1 @@
+#include "trace/trace-hw_net_can.h"
diff --git a/hw/net/can/xlnx-zynqmp-can.c b/hw/net/can/xlnx-zynqmp-can.c
new file mode 100644
index 0000000..affa21a
--- /dev/null
+++ b/hw/net/can/xlnx-zynqmp-can.c
@@ -0,0 +1,1161 @@
+/*
+ * QEMU model of the Xilinx ZynqMP CAN controller.
+ * This implementation is based on the following datasheet:
+ * https://www.xilinx.com/support/documentation/user_guides/ug1085-zynq-ultrascale-trm.pdf
+ *
+ * Copyright (c) 2020 Xilinx Inc.
+ *
+ * Written-by: Vikram Garhwal<fnu.vikram@xilinx.com>
+ *
+ * Based on QEMU CAN Device emulation implemented by Jin Yang, Deniz Eren and
+ * Pavel Pisa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/sysbus.h"
+#include "hw/register.h"
+#include "hw/irq.h"
+#include "qapi/error.h"
+#include "qemu/bitops.h"
+#include "qemu/log.h"
+#include "qemu/cutils.h"
+#include "sysemu/sysemu.h"
+#include "migration/vmstate.h"
+#include "hw/qdev-properties.h"
+#include "net/can_emu.h"
+#include "net/can_host.h"
+#include "qemu/event_notifier.h"
+#include "qom/object_interfaces.h"
+#include "hw/net/xlnx-zynqmp-can.h"
+#include "trace.h"
+
+#ifndef XLNX_ZYNQMP_CAN_ERR_DEBUG
+#define XLNX_ZYNQMP_CAN_ERR_DEBUG 0
+#endif
+
+#define MAX_DLC 8
+#undef ERROR
+
+REG32(SOFTWARE_RESET_REGISTER, 0x0)
+ FIELD(SOFTWARE_RESET_REGISTER, CEN, 1, 1)
+ FIELD(SOFTWARE_RESET_REGISTER, SRST, 0, 1)
+REG32(MODE_SELECT_REGISTER, 0x4)
+ FIELD(MODE_SELECT_REGISTER, SNOOP, 2, 1)
+ FIELD(MODE_SELECT_REGISTER, LBACK, 1, 1)
+ FIELD(MODE_SELECT_REGISTER, SLEEP, 0, 1)
+REG32(ARBITRATION_PHASE_BAUD_RATE_PRESCALER_REGISTER, 0x8)
+ FIELD(ARBITRATION_PHASE_BAUD_RATE_PRESCALER_REGISTER, BRP, 0, 8)
+REG32(ARBITRATION_PHASE_BIT_TIMING_REGISTER, 0xc)
+ FIELD(ARBITRATION_PHASE_BIT_TIMING_REGISTER, SJW, 7, 2)
+ FIELD(ARBITRATION_PHASE_BIT_TIMING_REGISTER, TS2, 4, 3)
+ FIELD(ARBITRATION_PHASE_BIT_TIMING_REGISTER, TS1, 0, 4)
+REG32(ERROR_COUNTER_REGISTER, 0x10)
+ FIELD(ERROR_COUNTER_REGISTER, REC, 8, 8)
+ FIELD(ERROR_COUNTER_REGISTER, TEC, 0, 8)
+REG32(ERROR_STATUS_REGISTER, 0x14)
+ FIELD(ERROR_STATUS_REGISTER, ACKER, 4, 1)
+ FIELD(ERROR_STATUS_REGISTER, BERR, 3, 1)
+ FIELD(ERROR_STATUS_REGISTER, STER, 2, 1)
+ FIELD(ERROR_STATUS_REGISTER, FMER, 1, 1)
+ FIELD(ERROR_STATUS_REGISTER, CRCER, 0, 1)
+REG32(STATUS_REGISTER, 0x18)
+ FIELD(STATUS_REGISTER, SNOOP, 12, 1)
+ FIELD(STATUS_REGISTER, ACFBSY, 11, 1)
+ FIELD(STATUS_REGISTER, TXFLL, 10, 1)
+ FIELD(STATUS_REGISTER, TXBFLL, 9, 1)
+ FIELD(STATUS_REGISTER, ESTAT, 7, 2)
+ FIELD(STATUS_REGISTER, ERRWRN, 6, 1)
+ FIELD(STATUS_REGISTER, BBSY, 5, 1)
+ FIELD(STATUS_REGISTER, BIDLE, 4, 1)
+ FIELD(STATUS_REGISTER, NORMAL, 3, 1)
+ FIELD(STATUS_REGISTER, SLEEP, 2, 1)
+ FIELD(STATUS_REGISTER, LBACK, 1, 1)
+ FIELD(STATUS_REGISTER, CONFIG, 0, 1)
+REG32(INTERRUPT_STATUS_REGISTER, 0x1c)
+ FIELD(INTERRUPT_STATUS_REGISTER, TXFEMP, 14, 1)
+ FIELD(INTERRUPT_STATUS_REGISTER, TXFWMEMP, 13, 1)
+ FIELD(INTERRUPT_STATUS_REGISTER, RXFWMFLL, 12, 1)
+ FIELD(INTERRUPT_STATUS_REGISTER, WKUP, 11, 1)
+ FIELD(INTERRUPT_STATUS_REGISTER, SLP, 10, 1)
+ FIELD(INTERRUPT_STATUS_REGISTER, BSOFF, 9, 1)
+ FIELD(INTERRUPT_STATUS_REGISTER, ERROR, 8, 1)
+ FIELD(INTERRUPT_STATUS_REGISTER, RXNEMP, 7, 1)
+ FIELD(INTERRUPT_STATUS_REGISTER, RXOFLW, 6, 1)
+ FIELD(INTERRUPT_STATUS_REGISTER, RXUFLW, 5, 1)
+ FIELD(INTERRUPT_STATUS_REGISTER, RXOK, 4, 1)
+ FIELD(INTERRUPT_STATUS_REGISTER, TXBFLL, 3, 1)
+ FIELD(INTERRUPT_STATUS_REGISTER, TXFLL, 2, 1)
+ FIELD(INTERRUPT_STATUS_REGISTER, TXOK, 1, 1)
+ FIELD(INTERRUPT_STATUS_REGISTER, ARBLST, 0, 1)
+REG32(INTERRUPT_ENABLE_REGISTER, 0x20)
+ FIELD(INTERRUPT_ENABLE_REGISTER, ETXFEMP, 14, 1)
+ FIELD(INTERRUPT_ENABLE_REGISTER, ETXFWMEMP, 13, 1)
+ FIELD(INTERRUPT_ENABLE_REGISTER, ERXFWMFLL, 12, 1)
+ FIELD(INTERRUPT_ENABLE_REGISTER, EWKUP, 11, 1)
+ FIELD(INTERRUPT_ENABLE_REGISTER, ESLP, 10, 1)
+ FIELD(INTERRUPT_ENABLE_REGISTER, EBSOFF, 9, 1)
+ FIELD(INTERRUPT_ENABLE_REGISTER, EERROR, 8, 1)
+ FIELD(INTERRUPT_ENABLE_REGISTER, ERXNEMP, 7, 1)
+ FIELD(INTERRUPT_ENABLE_REGISTER, ERXOFLW, 6, 1)
+ FIELD(INTERRUPT_ENABLE_REGISTER, ERXUFLW, 5, 1)
+ FIELD(INTERRUPT_ENABLE_REGISTER, ERXOK, 4, 1)
+ FIELD(INTERRUPT_ENABLE_REGISTER, ETXBFLL, 3, 1)
+ FIELD(INTERRUPT_ENABLE_REGISTER, ETXFLL, 2, 1)
+ FIELD(INTERRUPT_ENABLE_REGISTER, ETXOK, 1, 1)
+ FIELD(INTERRUPT_ENABLE_REGISTER, EARBLST, 0, 1)
+REG32(INTERRUPT_CLEAR_REGISTER, 0x24)
+ FIELD(INTERRUPT_CLEAR_REGISTER, CTXFEMP, 14, 1)
+ FIELD(INTERRUPT_CLEAR_REGISTER, CTXFWMEMP, 13, 1)
+ FIELD(INTERRUPT_CLEAR_REGISTER, CRXFWMFLL, 12, 1)
+ FIELD(INTERRUPT_CLEAR_REGISTER, CWKUP, 11, 1)
+ FIELD(INTERRUPT_CLEAR_REGISTER, CSLP, 10, 1)
+ FIELD(INTERRUPT_CLEAR_REGISTER, CBSOFF, 9, 1)
+ FIELD(INTERRUPT_CLEAR_REGISTER, CERROR, 8, 1)
+ FIELD(INTERRUPT_CLEAR_REGISTER, CRXNEMP, 7, 1)
+ FIELD(INTERRUPT_CLEAR_REGISTER, CRXOFLW, 6, 1)
+ FIELD(INTERRUPT_CLEAR_REGISTER, CRXUFLW, 5, 1)
+ FIELD(INTERRUPT_CLEAR_REGISTER, CRXOK, 4, 1)
+ FIELD(INTERRUPT_CLEAR_REGISTER, CTXBFLL, 3, 1)
+ FIELD(INTERRUPT_CLEAR_REGISTER, CTXFLL, 2, 1)
+ FIELD(INTERRUPT_CLEAR_REGISTER, CTXOK, 1, 1)
+ FIELD(INTERRUPT_CLEAR_REGISTER, CARBLST, 0, 1)
+REG32(TIMESTAMP_REGISTER, 0x28)
+ FIELD(TIMESTAMP_REGISTER, CTS, 0, 1)
+REG32(WIR, 0x2c)
+ FIELD(WIR, EW, 8, 8)
+ FIELD(WIR, FW, 0, 8)
+REG32(TXFIFO_ID, 0x30)
+ FIELD(TXFIFO_ID, IDH, 21, 11)
+ FIELD(TXFIFO_ID, SRRRTR, 20, 1)
+ FIELD(TXFIFO_ID, IDE, 19, 1)
+ FIELD(TXFIFO_ID, IDL, 1, 18)
+ FIELD(TXFIFO_ID, RTR, 0, 1)
+REG32(TXFIFO_DLC, 0x34)
+ FIELD(TXFIFO_DLC, DLC, 28, 4)
+REG32(TXFIFO_DATA1, 0x38)
+ FIELD(TXFIFO_DATA1, DB0, 24, 8)
+ FIELD(TXFIFO_DATA1, DB1, 16, 8)
+ FIELD(TXFIFO_DATA1, DB2, 8, 8)
+ FIELD(TXFIFO_DATA1, DB3, 0, 8)
+REG32(TXFIFO_DATA2, 0x3c)
+ FIELD(TXFIFO_DATA2, DB4, 24, 8)
+ FIELD(TXFIFO_DATA2, DB5, 16, 8)
+ FIELD(TXFIFO_DATA2, DB6, 8, 8)
+ FIELD(TXFIFO_DATA2, DB7, 0, 8)
+REG32(TXHPB_ID, 0x40)
+ FIELD(TXHPB_ID, IDH, 21, 11)
+ FIELD(TXHPB_ID, SRRRTR, 20, 1)
+ FIELD(TXHPB_ID, IDE, 19, 1)
+ FIELD(TXHPB_ID, IDL, 1, 18)
+ FIELD(TXHPB_ID, RTR, 0, 1)
+REG32(TXHPB_DLC, 0x44)
+ FIELD(TXHPB_DLC, DLC, 28, 4)
+REG32(TXHPB_DATA1, 0x48)
+ FIELD(TXHPB_DATA1, DB0, 24, 8)
+ FIELD(TXHPB_DATA1, DB1, 16, 8)
+ FIELD(TXHPB_DATA1, DB2, 8, 8)
+ FIELD(TXHPB_DATA1, DB3, 0, 8)
+REG32(TXHPB_DATA2, 0x4c)
+ FIELD(TXHPB_DATA2, DB4, 24, 8)
+ FIELD(TXHPB_DATA2, DB5, 16, 8)
+ FIELD(TXHPB_DATA2, DB6, 8, 8)
+ FIELD(TXHPB_DATA2, DB7, 0, 8)
+REG32(RXFIFO_ID, 0x50)
+ FIELD(RXFIFO_ID, IDH, 21, 11)
+ FIELD(RXFIFO_ID, SRRRTR, 20, 1)
+ FIELD(RXFIFO_ID, IDE, 19, 1)
+ FIELD(RXFIFO_ID, IDL, 1, 18)
+ FIELD(RXFIFO_ID, RTR, 0, 1)
+REG32(RXFIFO_DLC, 0x54)
+ FIELD(RXFIFO_DLC, DLC, 28, 4)
+ FIELD(RXFIFO_DLC, RXT, 0, 16)
+REG32(RXFIFO_DATA1, 0x58)
+ FIELD(RXFIFO_DATA1, DB0, 24, 8)
+ FIELD(RXFIFO_DATA1, DB1, 16, 8)
+ FIELD(RXFIFO_DATA1, DB2, 8, 8)
+ FIELD(RXFIFO_DATA1, DB3, 0, 8)
+REG32(RXFIFO_DATA2, 0x5c)
+ FIELD(RXFIFO_DATA2, DB4, 24, 8)
+ FIELD(RXFIFO_DATA2, DB5, 16, 8)
+ FIELD(RXFIFO_DATA2, DB6, 8, 8)
+ FIELD(RXFIFO_DATA2, DB7, 0, 8)
+REG32(AFR, 0x60)
+ FIELD(AFR, UAF4, 3, 1)
+ FIELD(AFR, UAF3, 2, 1)
+ FIELD(AFR, UAF2, 1, 1)
+ FIELD(AFR, UAF1, 0, 1)
+REG32(AFMR1, 0x64)
+ FIELD(AFMR1, AMIDH, 21, 11)
+ FIELD(AFMR1, AMSRR, 20, 1)
+ FIELD(AFMR1, AMIDE, 19, 1)
+ FIELD(AFMR1, AMIDL, 1, 18)
+ FIELD(AFMR1, AMRTR, 0, 1)
+REG32(AFIR1, 0x68)
+ FIELD(AFIR1, AIIDH, 21, 11)
+ FIELD(AFIR1, AISRR, 20, 1)
+ FIELD(AFIR1, AIIDE, 19, 1)
+ FIELD(AFIR1, AIIDL, 1, 18)
+ FIELD(AFIR1, AIRTR, 0, 1)
+REG32(AFMR2, 0x6c)
+ FIELD(AFMR2, AMIDH, 21, 11)
+ FIELD(AFMR2, AMSRR, 20, 1)
+ FIELD(AFMR2, AMIDE, 19, 1)
+ FIELD(AFMR2, AMIDL, 1, 18)
+ FIELD(AFMR2, AMRTR, 0, 1)
+REG32(AFIR2, 0x70)
+ FIELD(AFIR2, AIIDH, 21, 11)
+ FIELD(AFIR2, AISRR, 20, 1)
+ FIELD(AFIR2, AIIDE, 19, 1)
+ FIELD(AFIR2, AIIDL, 1, 18)
+ FIELD(AFIR2, AIRTR, 0, 1)
+REG32(AFMR3, 0x74)
+ FIELD(AFMR3, AMIDH, 21, 11)
+ FIELD(AFMR3, AMSRR, 20, 1)
+ FIELD(AFMR3, AMIDE, 19, 1)
+ FIELD(AFMR3, AMIDL, 1, 18)
+ FIELD(AFMR3, AMRTR, 0, 1)
+REG32(AFIR3, 0x78)
+ FIELD(AFIR3, AIIDH, 21, 11)
+ FIELD(AFIR3, AISRR, 20, 1)
+ FIELD(AFIR3, AIIDE, 19, 1)
+ FIELD(AFIR3, AIIDL, 1, 18)
+ FIELD(AFIR3, AIRTR, 0, 1)
+REG32(AFMR4, 0x7c)
+ FIELD(AFMR4, AMIDH, 21, 11)
+ FIELD(AFMR4, AMSRR, 20, 1)
+ FIELD(AFMR4, AMIDE, 19, 1)
+ FIELD(AFMR4, AMIDL, 1, 18)
+ FIELD(AFMR4, AMRTR, 0, 1)
+REG32(AFIR4, 0x80)
+ FIELD(AFIR4, AIIDH, 21, 11)
+ FIELD(AFIR4, AISRR, 20, 1)
+ FIELD(AFIR4, AIIDE, 19, 1)
+ FIELD(AFIR4, AIIDL, 1, 18)
+ FIELD(AFIR4, AIRTR, 0, 1)
+
+static void can_update_irq(XlnxZynqMPCANState *s)
+{
+ uint32_t irq;
+
+ /* Watermark register interrupts. */
+ if ((fifo32_num_free(&s->tx_fifo) / CAN_FRAME_SIZE) >
+ ARRAY_FIELD_EX32(s->regs, WIR, EW)) {
+ ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, TXFWMEMP, 1);
+ }
+
+ if ((fifo32_num_used(&s->rx_fifo) / CAN_FRAME_SIZE) >
+ ARRAY_FIELD_EX32(s->regs, WIR, FW)) {
+ ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, RXFWMFLL, 1);
+ }
+
+ /* RX Interrupts. */
+ if (fifo32_num_used(&s->rx_fifo) >= CAN_FRAME_SIZE) {
+ ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, RXNEMP, 1);
+ }
+
+ /* TX interrupts. */
+ if (fifo32_is_empty(&s->tx_fifo)) {
+ ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, TXFEMP, 1);
+ }
+
+ if (fifo32_is_full(&s->tx_fifo)) {
+ ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, TXFLL, 1);
+ }
+
+ if (fifo32_is_full(&s->txhpb_fifo)) {
+ ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, TXBFLL, 1);
+ }
+
+ irq = s->regs[R_INTERRUPT_STATUS_REGISTER];
+ irq &= s->regs[R_INTERRUPT_ENABLE_REGISTER];
+
+ trace_xlnx_can_update_irq(s->regs[R_INTERRUPT_STATUS_REGISTER],
+ s->regs[R_INTERRUPT_ENABLE_REGISTER], irq);
+ qemu_set_irq(s->irq, irq);
+}
+
+static void can_ier_post_write(RegisterInfo *reg, uint64_t val)
+{
+ XlnxZynqMPCANState *s = XLNX_ZYNQMP_CAN(reg->opaque);
+
+ can_update_irq(s);
+}
+
+static uint64_t can_icr_pre_write(RegisterInfo *reg, uint64_t val)
+{
+ XlnxZynqMPCANState *s = XLNX_ZYNQMP_CAN(reg->opaque);
+
+ s->regs[R_INTERRUPT_STATUS_REGISTER] &= ~val;
+ can_update_irq(s);
+
+ return 0;
+}
+
+static void can_config_reset(XlnxZynqMPCANState *s)
+{
+ /* Reset all the configuration registers. */
+ register_reset(&s->reg_info[R_SOFTWARE_RESET_REGISTER]);
+ register_reset(&s->reg_info[R_MODE_SELECT_REGISTER]);
+ register_reset(
+ &s->reg_info[R_ARBITRATION_PHASE_BAUD_RATE_PRESCALER_REGISTER]);
+ register_reset(&s->reg_info[R_ARBITRATION_PHASE_BIT_TIMING_REGISTER]);
+ register_reset(&s->reg_info[R_STATUS_REGISTER]);
+ register_reset(&s->reg_info[R_INTERRUPT_STATUS_REGISTER]);
+ register_reset(&s->reg_info[R_INTERRUPT_ENABLE_REGISTER]);
+ register_reset(&s->reg_info[R_INTERRUPT_CLEAR_REGISTER]);
+ register_reset(&s->reg_info[R_WIR]);
+}
+
+static void can_config_mode(XlnxZynqMPCANState *s)
+{
+ register_reset(&s->reg_info[R_ERROR_COUNTER_REGISTER]);
+ register_reset(&s->reg_info[R_ERROR_STATUS_REGISTER]);
+
+ /* Put XlnxZynqMPCAN in configuration mode. */
+ ARRAY_FIELD_DP32(s->regs, STATUS_REGISTER, CONFIG, 1);
+ ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, WKUP, 0);
+ ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, SLP, 0);
+ ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, BSOFF, 0);
+ ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, ERROR, 0);
+ ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, RXOFLW, 0);
+ ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, RXOK, 0);
+ ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, TXOK, 0);
+ ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, ARBLST, 0);
+
+ can_update_irq(s);
+}
+
+static void update_status_register_mode_bits(XlnxZynqMPCANState *s)
+{
+ bool sleep_status = ARRAY_FIELD_EX32(s->regs, STATUS_REGISTER, SLEEP);
+ bool sleep_mode = ARRAY_FIELD_EX32(s->regs, MODE_SELECT_REGISTER, SLEEP);
+ /* Wake up interrupt bit. */
+ bool wakeup_irq_val = sleep_status && (sleep_mode == 0);
+ /* Sleep interrupt bit. */
+ bool sleep_irq_val = sleep_mode && (sleep_status == 0);
+
+ /* Clear previous core mode status bits. */
+ ARRAY_FIELD_DP32(s->regs, STATUS_REGISTER, LBACK, 0);
+ ARRAY_FIELD_DP32(s->regs, STATUS_REGISTER, SLEEP, 0);
+ ARRAY_FIELD_DP32(s->regs, STATUS_REGISTER, SNOOP, 0);
+ ARRAY_FIELD_DP32(s->regs, STATUS_REGISTER, NORMAL, 0);
+
+ /* set current mode bit and generate irqs accordingly. */
+ if (ARRAY_FIELD_EX32(s->regs, MODE_SELECT_REGISTER, LBACK)) {
+ ARRAY_FIELD_DP32(s->regs, STATUS_REGISTER, LBACK, 1);
+ } else if (ARRAY_FIELD_EX32(s->regs, MODE_SELECT_REGISTER, SLEEP)) {
+ ARRAY_FIELD_DP32(s->regs, STATUS_REGISTER, SLEEP, 1);
+ ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, SLP,
+ sleep_irq_val);
+ } else if (ARRAY_FIELD_EX32(s->regs, MODE_SELECT_REGISTER, SNOOP)) {
+ ARRAY_FIELD_DP32(s->regs, STATUS_REGISTER, SNOOP, 1);
+ } else {
+ /*
+ * If all bits are zero then XlnxZynqMPCAN is set in normal mode.
+ */
+ ARRAY_FIELD_DP32(s->regs, STATUS_REGISTER, NORMAL, 1);
+ /* Set wakeup interrupt bit. */
+ ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, WKUP,
+ wakeup_irq_val);
+ }
+
+ can_update_irq(s);
+}
+
+static void can_exit_sleep_mode(XlnxZynqMPCANState *s)
+{
+ ARRAY_FIELD_DP32(s->regs, MODE_SELECT_REGISTER, SLEEP, 0);
+ update_status_register_mode_bits(s);
+}
+
+static void generate_frame(qemu_can_frame *frame, uint32_t *data)
+{
+ frame->can_id = data[0];
+ frame->can_dlc = FIELD_EX32(data[1], TXFIFO_DLC, DLC);
+
+ frame->data[0] = FIELD_EX32(data[2], TXFIFO_DATA1, DB3);
+ frame->data[1] = FIELD_EX32(data[2], TXFIFO_DATA1, DB2);
+ frame->data[2] = FIELD_EX32(data[2], TXFIFO_DATA1, DB1);
+ frame->data[3] = FIELD_EX32(data[2], TXFIFO_DATA1, DB0);
+
+ frame->data[4] = FIELD_EX32(data[3], TXFIFO_DATA2, DB7);
+ frame->data[5] = FIELD_EX32(data[3], TXFIFO_DATA2, DB6);
+ frame->data[6] = FIELD_EX32(data[3], TXFIFO_DATA2, DB5);
+ frame->data[7] = FIELD_EX32(data[3], TXFIFO_DATA2, DB4);
+}
+
+static bool tx_ready_check(XlnxZynqMPCANState *s)
+{
+ if (ARRAY_FIELD_EX32(s->regs, SOFTWARE_RESET_REGISTER, SRST)) {
+ g_autofree char *path = object_get_canonical_path(OBJECT(s));
+
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Attempting to transfer data while"
+ " data while controller is in reset mode.\n",
+ path);
+ return false;
+ }
+
+ if (ARRAY_FIELD_EX32(s->regs, SOFTWARE_RESET_REGISTER, CEN) == 0) {
+ g_autofree char *path = object_get_canonical_path(OBJECT(s));
+
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Attempting to transfer"
+ " data while controller is in configuration mode. Reset"
+ " the core so operations can start fresh.\n",
+ path);
+ return false;
+ }
+
+ if (ARRAY_FIELD_EX32(s->regs, STATUS_REGISTER, SNOOP)) {
+ g_autofree char *path = object_get_canonical_path(OBJECT(s));
+
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Attempting to transfer"
+ " data while controller is in SNOOP MODE.\n",
+ path);
+ return false;
+ }
+
+ return true;
+}
+
+static void transfer_fifo(XlnxZynqMPCANState *s, Fifo32 *fifo)
+{
+ qemu_can_frame frame;
+ uint32_t data[CAN_FRAME_SIZE];
+ int i;
+ bool can_tx = tx_ready_check(s);
+
+ if (!can_tx) {
+ g_autofree char *path = object_get_canonical_path(OBJECT(s));
+
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Controller is not enabled for data"
+ " transfer.\n", path);
+ can_update_irq(s);
+ return;
+ }
+
+ while (!fifo32_is_empty(fifo)) {
+ for (i = 0; i < CAN_FRAME_SIZE; i++) {
+ data[i] = fifo32_pop(fifo);
+ }
+
+ if (ARRAY_FIELD_EX32(s->regs, STATUS_REGISTER, LBACK)) {
+ /*
+ * Controller is in loopback. In Loopback mode, the CAN core
+ * transmits a recessive bitstream on to the XlnxZynqMPCAN Bus.
+ * Any message transmitted is looped back to the RX line and
+ * acknowledged. The XlnxZynqMPCAN core receives any message
+ * that it transmits.
+ */
+ if (fifo32_is_full(&s->rx_fifo)) {
+ ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, RXOFLW, 1);
+ } else {
+ for (i = 0; i < CAN_FRAME_SIZE; i++) {
+ fifo32_push(&s->rx_fifo, data[i]);
+ }
+
+ ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, RXOK, 1);
+ }
+ } else {
+ /* Normal mode Tx. */
+ generate_frame(&frame, data);
+
+ trace_xlnx_can_tx_data(frame.can_id, frame.can_dlc,
+ frame.data[0], frame.data[1],
+ frame.data[2], frame.data[3],
+ frame.data[4], frame.data[5],
+ frame.data[6], frame.data[7]);
+ can_bus_client_send(&s->bus_client, &frame, 1);
+ }
+ }
+
+ ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, TXOK, 1);
+ ARRAY_FIELD_DP32(s->regs, STATUS_REGISTER, TXBFLL, 0);
+
+ if (ARRAY_FIELD_EX32(s->regs, STATUS_REGISTER, SLEEP)) {
+ can_exit_sleep_mode(s);
+ }
+
+ can_update_irq(s);
+}
+
+static uint64_t can_srr_pre_write(RegisterInfo *reg, uint64_t val)
+{
+ XlnxZynqMPCANState *s = XLNX_ZYNQMP_CAN(reg->opaque);
+
+ ARRAY_FIELD_DP32(s->regs, SOFTWARE_RESET_REGISTER, CEN,
+ FIELD_EX32(val, SOFTWARE_RESET_REGISTER, CEN));
+
+ if (FIELD_EX32(val, SOFTWARE_RESET_REGISTER, SRST)) {
+ trace_xlnx_can_reset(val);
+
+ /* First, core will do software reset then will enter in config mode. */
+ can_config_reset(s);
+ }
+
+ if (ARRAY_FIELD_EX32(s->regs, SOFTWARE_RESET_REGISTER, CEN) == 0) {
+ can_config_mode(s);
+ } else {
+ /*
+ * Leave config mode. Now XlnxZynqMPCAN core will enter normal,
+ * sleep, snoop or loopback mode depending upon LBACK, SLEEP, SNOOP
+ * register states.
+ */
+ ARRAY_FIELD_DP32(s->regs, STATUS_REGISTER, CONFIG, 0);
+
+ ptimer_transaction_begin(s->can_timer);
+ ptimer_set_count(s->can_timer, 0);
+ ptimer_transaction_commit(s->can_timer);
+
+ /* XlnxZynqMPCAN is out of config mode. It will send pending data. */
+ transfer_fifo(s, &s->txhpb_fifo);
+ transfer_fifo(s, &s->tx_fifo);
+ }
+
+ update_status_register_mode_bits(s);
+
+ return s->regs[R_SOFTWARE_RESET_REGISTER];
+}
+
+static uint64_t can_msr_pre_write(RegisterInfo *reg, uint64_t val)
+{
+ XlnxZynqMPCANState *s = XLNX_ZYNQMP_CAN(reg->opaque);
+ uint8_t multi_mode;
+
+ /*
+ * Multiple mode set check. This is done to make sure user doesn't set
+ * multiple modes.
+ */
+ multi_mode = FIELD_EX32(val, MODE_SELECT_REGISTER, LBACK) +
+ FIELD_EX32(val, MODE_SELECT_REGISTER, SLEEP) +
+ FIELD_EX32(val, MODE_SELECT_REGISTER, SNOOP);
+
+ if (multi_mode > 1) {
+ g_autofree char *path = object_get_canonical_path(OBJECT(s));
+
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Attempting to config"
+ " several modes simultaneously. One mode will be selected"
+ " according to their priority: LBACK > SLEEP > SNOOP.\n",
+ path);
+ }
+
+ if (ARRAY_FIELD_EX32(s->regs, SOFTWARE_RESET_REGISTER, CEN) == 0) {
+ /* We are in configuration mode, any mode can be selected. */
+ s->regs[R_MODE_SELECT_REGISTER] = val;
+ } else {
+ bool sleep_mode_bit = FIELD_EX32(val, MODE_SELECT_REGISTER, SLEEP);
+
+ ARRAY_FIELD_DP32(s->regs, MODE_SELECT_REGISTER, SLEEP, sleep_mode_bit);
+
+ if (FIELD_EX32(val, MODE_SELECT_REGISTER, LBACK)) {
+ g_autofree char *path = object_get_canonical_path(OBJECT(s));
+
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Attempting to set"
+ " LBACK mode without setting CEN bit as 0.\n",
+ path);
+ } else if (FIELD_EX32(val, MODE_SELECT_REGISTER, SNOOP)) {
+ g_autofree char *path = object_get_canonical_path(OBJECT(s));
+
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Attempting to set"
+ " SNOOP mode without setting CEN bit as 0.\n",
+ path);
+ }
+
+ update_status_register_mode_bits(s);
+ }
+
+ return s->regs[R_MODE_SELECT_REGISTER];
+}
+
+static uint64_t can_brpr_pre_write(RegisterInfo *reg, uint64_t val)
+{
+ XlnxZynqMPCANState *s = XLNX_ZYNQMP_CAN(reg->opaque);
+
+ /* Only allow writes when in config mode. */
+ if (ARRAY_FIELD_EX32(s->regs, SOFTWARE_RESET_REGISTER, CEN)) {
+ return s->regs[R_ARBITRATION_PHASE_BAUD_RATE_PRESCALER_REGISTER];
+ }
+
+ return val;
+}
+
+static uint64_t can_btr_pre_write(RegisterInfo *reg, uint64_t val)
+{
+ XlnxZynqMPCANState *s = XLNX_ZYNQMP_CAN(reg->opaque);
+
+ /* Only allow writes when in config mode. */
+ if (ARRAY_FIELD_EX32(s->regs, SOFTWARE_RESET_REGISTER, CEN)) {
+ return s->regs[R_ARBITRATION_PHASE_BIT_TIMING_REGISTER];
+ }
+
+ return val;
+}
+
+static uint64_t can_tcr_pre_write(RegisterInfo *reg, uint64_t val)
+{
+ XlnxZynqMPCANState *s = XLNX_ZYNQMP_CAN(reg->opaque);
+
+ if (FIELD_EX32(val, TIMESTAMP_REGISTER, CTS)) {
+ ptimer_transaction_begin(s->can_timer);
+ ptimer_set_count(s->can_timer, 0);
+ ptimer_transaction_commit(s->can_timer);
+ }
+
+ return 0;
+}
+
+static void update_rx_fifo(XlnxZynqMPCANState *s, const qemu_can_frame *frame)
+{
+ bool filter_pass = false;
+ uint16_t timestamp = 0;
+
+ /* If no filter is enabled. Message will be stored in FIFO. */
+ if (!((ARRAY_FIELD_EX32(s->regs, AFR, UAF1)) |
+ (ARRAY_FIELD_EX32(s->regs, AFR, UAF2)) |
+ (ARRAY_FIELD_EX32(s->regs, AFR, UAF3)) |
+ (ARRAY_FIELD_EX32(s->regs, AFR, UAF4)))) {
+ filter_pass = true;
+ }
+
+ /*
+ * Messages that pass any of the acceptance filters will be stored in
+ * the RX FIFO.
+ */
+ if (ARRAY_FIELD_EX32(s->regs, AFR, UAF1)) {
+ uint32_t id_masked = s->regs[R_AFMR1] & frame->can_id;
+ uint32_t filter_id_masked = s->regs[R_AFMR1] & s->regs[R_AFIR1];
+
+ if (filter_id_masked == id_masked) {
+ filter_pass = true;
+ }
+ }
+
+ if (ARRAY_FIELD_EX32(s->regs, AFR, UAF2)) {
+ uint32_t id_masked = s->regs[R_AFMR2] & frame->can_id;
+ uint32_t filter_id_masked = s->regs[R_AFMR2] & s->regs[R_AFIR2];
+
+ if (filter_id_masked == id_masked) {
+ filter_pass = true;
+ }
+ }
+
+ if (ARRAY_FIELD_EX32(s->regs, AFR, UAF3)) {
+ uint32_t id_masked = s->regs[R_AFMR3] & frame->can_id;
+ uint32_t filter_id_masked = s->regs[R_AFMR3] & s->regs[R_AFIR3];
+
+ if (filter_id_masked == id_masked) {
+ filter_pass = true;
+ }
+ }
+
+ if (ARRAY_FIELD_EX32(s->regs, AFR, UAF4)) {
+ uint32_t id_masked = s->regs[R_AFMR4] & frame->can_id;
+ uint32_t filter_id_masked = s->regs[R_AFMR4] & s->regs[R_AFIR4];
+
+ if (filter_id_masked == id_masked) {
+ filter_pass = true;
+ }
+ }
+
+ if (!filter_pass) {
+ trace_xlnx_can_rx_fifo_filter_reject(frame->can_id, frame->can_dlc);
+ return;
+ }
+
+ /* Store the message in fifo if it passed through any of the filters. */
+ if (filter_pass && frame->can_dlc <= MAX_DLC) {
+
+ if (fifo32_is_full(&s->rx_fifo)) {
+ ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, RXOFLW, 1);
+ } else {
+ timestamp = CAN_TIMER_MAX - ptimer_get_count(s->can_timer);
+
+ fifo32_push(&s->rx_fifo, frame->can_id);
+
+ fifo32_push(&s->rx_fifo, deposit32(0, R_RXFIFO_DLC_DLC_SHIFT,
+ R_RXFIFO_DLC_DLC_LENGTH,
+ frame->can_dlc) |
+ deposit32(0, R_RXFIFO_DLC_RXT_SHIFT,
+ R_RXFIFO_DLC_RXT_LENGTH,
+ timestamp));
+
+ /* First 32 bit of the data. */
+ fifo32_push(&s->rx_fifo, deposit32(0, R_TXFIFO_DATA1_DB3_SHIFT,
+ R_TXFIFO_DATA1_DB3_LENGTH,
+ frame->data[0]) |
+ deposit32(0, R_TXFIFO_DATA1_DB2_SHIFT,
+ R_TXFIFO_DATA1_DB2_LENGTH,
+ frame->data[1]) |
+ deposit32(0, R_TXFIFO_DATA1_DB1_SHIFT,
+ R_TXFIFO_DATA1_DB1_LENGTH,
+ frame->data[2]) |
+ deposit32(0, R_TXFIFO_DATA1_DB0_SHIFT,
+ R_TXFIFO_DATA1_DB0_LENGTH,
+ frame->data[3]));
+ /* Last 32 bit of the data. */
+ fifo32_push(&s->rx_fifo, deposit32(0, R_TXFIFO_DATA2_DB7_SHIFT,
+ R_TXFIFO_DATA2_DB7_LENGTH,
+ frame->data[4]) |
+ deposit32(0, R_TXFIFO_DATA2_DB6_SHIFT,
+ R_TXFIFO_DATA2_DB6_LENGTH,
+ frame->data[5]) |
+ deposit32(0, R_TXFIFO_DATA2_DB5_SHIFT,
+ R_TXFIFO_DATA2_DB5_LENGTH,
+ frame->data[6]) |
+ deposit32(0, R_TXFIFO_DATA2_DB4_SHIFT,
+ R_TXFIFO_DATA2_DB4_LENGTH,
+ frame->data[7]));
+
+ ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, RXOK, 1);
+ trace_xlnx_can_rx_data(frame->can_id, frame->can_dlc,
+ frame->data[0], frame->data[1],
+ frame->data[2], frame->data[3],
+ frame->data[4], frame->data[5],
+ frame->data[6], frame->data[7]);
+ }
+
+ can_update_irq(s);
+ }
+}
+
+static uint64_t can_rxfifo_pre_read(RegisterInfo *reg, uint64_t val)
+{
+ XlnxZynqMPCANState *s = XLNX_ZYNQMP_CAN(reg->opaque);
+
+ if (!fifo32_is_empty(&s->rx_fifo)) {
+ val = fifo32_pop(&s->rx_fifo);
+ } else {
+ ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, RXUFLW, 1);
+ }
+
+ can_update_irq(s);
+ return val;
+}
+
+static void can_filter_enable_post_write(RegisterInfo *reg, uint64_t val)
+{
+ XlnxZynqMPCANState *s = XLNX_ZYNQMP_CAN(reg->opaque);
+
+ if (ARRAY_FIELD_EX32(s->regs, AFR, UAF1) &&
+ ARRAY_FIELD_EX32(s->regs, AFR, UAF2) &&
+ ARRAY_FIELD_EX32(s->regs, AFR, UAF3) &&
+ ARRAY_FIELD_EX32(s->regs, AFR, UAF4)) {
+ ARRAY_FIELD_DP32(s->regs, STATUS_REGISTER, ACFBSY, 1);
+ } else {
+ ARRAY_FIELD_DP32(s->regs, STATUS_REGISTER, ACFBSY, 0);
+ }
+}
+
+static uint64_t can_filter_mask_pre_write(RegisterInfo *reg, uint64_t val)
+{
+ XlnxZynqMPCANState *s = XLNX_ZYNQMP_CAN(reg->opaque);
+ uint32_t reg_idx = (reg->access->addr) / 4;
+ uint32_t filter_number = (reg_idx - R_AFMR1) / 2;
+
+ /* modify an acceptance filter, the corresponding UAF bit should be '0'. */
+ if (!(s->regs[R_AFR] & (1 << filter_number))) {
+ s->regs[reg_idx] = val;
+
+ trace_xlnx_can_filter_mask_pre_write(filter_number, s->regs[reg_idx]);
+ } else {
+ g_autofree char *path = object_get_canonical_path(OBJECT(s));
+
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Acceptance filter %d"
+ " mask is not set as corresponding UAF bit is not 0.\n",
+ path, filter_number + 1);
+ }
+
+ return s->regs[reg_idx];
+}
+
+static uint64_t can_filter_id_pre_write(RegisterInfo *reg, uint64_t val)
+{
+ XlnxZynqMPCANState *s = XLNX_ZYNQMP_CAN(reg->opaque);
+ uint32_t reg_idx = (reg->access->addr) / 4;
+ uint32_t filter_number = (reg_idx - R_AFIR1) / 2;
+
+ if (!(s->regs[R_AFR] & (1 << filter_number))) {
+ s->regs[reg_idx] = val;
+
+ trace_xlnx_can_filter_id_pre_write(filter_number, s->regs[reg_idx]);
+ } else {
+ g_autofree char *path = object_get_canonical_path(OBJECT(s));
+
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Acceptance filter %d"
+ " id is not set as corresponding UAF bit is not 0.\n",
+ path, filter_number + 1);
+ }
+
+ return s->regs[reg_idx];
+}
+
+static void can_tx_post_write(RegisterInfo *reg, uint64_t val)
+{
+ XlnxZynqMPCANState *s = XLNX_ZYNQMP_CAN(reg->opaque);
+
+ bool is_txhpb = reg->access->addr > A_TXFIFO_DATA2;
+
+ bool initiate_transfer = (reg->access->addr == A_TXFIFO_DATA2) ||
+ (reg->access->addr == A_TXHPB_DATA2);
+
+ Fifo32 *f = is_txhpb ? &s->txhpb_fifo : &s->tx_fifo;
+
+ if (!fifo32_is_full(f)) {
+ fifo32_push(f, val);
+ } else {
+ g_autofree char *path = object_get_canonical_path(OBJECT(s));
+
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: TX FIFO is full.\n", path);
+ }
+
+ /* Initiate the message send if TX register is written. */
+ if (initiate_transfer &&
+ ARRAY_FIELD_EX32(s->regs, SOFTWARE_RESET_REGISTER, CEN)) {
+ transfer_fifo(s, f);
+ }
+
+ can_update_irq(s);
+}
+
+static const RegisterAccessInfo can_regs_info[] = {
+ { .name = "SOFTWARE_RESET_REGISTER",
+ .addr = A_SOFTWARE_RESET_REGISTER,
+ .rsvd = 0xfffffffc,
+ .pre_write = can_srr_pre_write,
+ },{ .name = "MODE_SELECT_REGISTER",
+ .addr = A_MODE_SELECT_REGISTER,
+ .rsvd = 0xfffffff8,
+ .pre_write = can_msr_pre_write,
+ },{ .name = "ARBITRATION_PHASE_BAUD_RATE_PRESCALER_REGISTER",
+ .addr = A_ARBITRATION_PHASE_BAUD_RATE_PRESCALER_REGISTER,
+ .rsvd = 0xffffff00,
+ .pre_write = can_brpr_pre_write,
+ },{ .name = "ARBITRATION_PHASE_BIT_TIMING_REGISTER",
+ .addr = A_ARBITRATION_PHASE_BIT_TIMING_REGISTER,
+ .rsvd = 0xfffffe00,
+ .pre_write = can_btr_pre_write,
+ },{ .name = "ERROR_COUNTER_REGISTER",
+ .addr = A_ERROR_COUNTER_REGISTER,
+ .rsvd = 0xffff0000,
+ .ro = 0xffffffff,
+ },{ .name = "ERROR_STATUS_REGISTER",
+ .addr = A_ERROR_STATUS_REGISTER,
+ .rsvd = 0xffffffe0,
+ .w1c = 0x1f,
+ },{ .name = "STATUS_REGISTER", .addr = A_STATUS_REGISTER,
+ .reset = 0x1,
+ .rsvd = 0xffffe000,
+ .ro = 0x1fff,
+ },{ .name = "INTERRUPT_STATUS_REGISTER",
+ .addr = A_INTERRUPT_STATUS_REGISTER,
+ .reset = 0x6000,
+ .rsvd = 0xffff8000,
+ .ro = 0x7fff,
+ },{ .name = "INTERRUPT_ENABLE_REGISTER",
+ .addr = A_INTERRUPT_ENABLE_REGISTER,
+ .rsvd = 0xffff8000,
+ .post_write = can_ier_post_write,
+ },{ .name = "INTERRUPT_CLEAR_REGISTER",
+ .addr = A_INTERRUPT_CLEAR_REGISTER,
+ .rsvd = 0xffff8000,
+ .pre_write = can_icr_pre_write,
+ },{ .name = "TIMESTAMP_REGISTER",
+ .addr = A_TIMESTAMP_REGISTER,
+ .rsvd = 0xfffffffe,
+ .pre_write = can_tcr_pre_write,
+ },{ .name = "WIR", .addr = A_WIR,
+ .reset = 0x3f3f,
+ .rsvd = 0xffff0000,
+ },{ .name = "TXFIFO_ID", .addr = A_TXFIFO_ID,
+ .post_write = can_tx_post_write,
+ },{ .name = "TXFIFO_DLC", .addr = A_TXFIFO_DLC,
+ .rsvd = 0xfffffff,
+ .post_write = can_tx_post_write,
+ },{ .name = "TXFIFO_DATA1", .addr = A_TXFIFO_DATA1,
+ .post_write = can_tx_post_write,
+ },{ .name = "TXFIFO_DATA2", .addr = A_TXFIFO_DATA2,
+ .post_write = can_tx_post_write,
+ },{ .name = "TXHPB_ID", .addr = A_TXHPB_ID,
+ .post_write = can_tx_post_write,
+ },{ .name = "TXHPB_DLC", .addr = A_TXHPB_DLC,
+ .rsvd = 0xfffffff,
+ .post_write = can_tx_post_write,
+ },{ .name = "TXHPB_DATA1", .addr = A_TXHPB_DATA1,
+ .post_write = can_tx_post_write,
+ },{ .name = "TXHPB_DATA2", .addr = A_TXHPB_DATA2,
+ .post_write = can_tx_post_write,
+ },{ .name = "RXFIFO_ID", .addr = A_RXFIFO_ID,
+ .ro = 0xffffffff,
+ .post_read = can_rxfifo_pre_read,
+ },{ .name = "RXFIFO_DLC", .addr = A_RXFIFO_DLC,
+ .rsvd = 0xfff0000,
+ .post_read = can_rxfifo_pre_read,
+ },{ .name = "RXFIFO_DATA1", .addr = A_RXFIFO_DATA1,
+ .post_read = can_rxfifo_pre_read,
+ },{ .name = "RXFIFO_DATA2", .addr = A_RXFIFO_DATA2,
+ .post_read = can_rxfifo_pre_read,
+ },{ .name = "AFR", .addr = A_AFR,
+ .rsvd = 0xfffffff0,
+ .post_write = can_filter_enable_post_write,
+ },{ .name = "AFMR1", .addr = A_AFMR1,
+ .pre_write = can_filter_mask_pre_write,
+ },{ .name = "AFIR1", .addr = A_AFIR1,
+ .pre_write = can_filter_id_pre_write,
+ },{ .name = "AFMR2", .addr = A_AFMR2,
+ .pre_write = can_filter_mask_pre_write,
+ },{ .name = "AFIR2", .addr = A_AFIR2,
+ .pre_write = can_filter_id_pre_write,
+ },{ .name = "AFMR3", .addr = A_AFMR3,
+ .pre_write = can_filter_mask_pre_write,
+ },{ .name = "AFIR3", .addr = A_AFIR3,
+ .pre_write = can_filter_id_pre_write,
+ },{ .name = "AFMR4", .addr = A_AFMR4,
+ .pre_write = can_filter_mask_pre_write,
+ },{ .name = "AFIR4", .addr = A_AFIR4,
+ .pre_write = can_filter_id_pre_write,
+ }
+};
+
+static void xlnx_zynqmp_can_ptimer_cb(void *opaque)
+{
+ /* No action required on the timer rollover. */
+}
+
+static const MemoryRegionOps can_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_zynqmp_can_reset_init(Object *obj, ResetType type)
+{
+ XlnxZynqMPCANState *s = XLNX_ZYNQMP_CAN(obj);
+ unsigned int i;
+
+ for (i = R_RXFIFO_ID; i < ARRAY_SIZE(s->reg_info); ++i) {
+ register_reset(&s->reg_info[i]);
+ }
+
+ ptimer_transaction_begin(s->can_timer);
+ ptimer_set_count(s->can_timer, 0);
+ ptimer_transaction_commit(s->can_timer);
+}
+
+static void xlnx_zynqmp_can_reset_hold(Object *obj)
+{
+ XlnxZynqMPCANState *s = XLNX_ZYNQMP_CAN(obj);
+ unsigned int i;
+
+ for (i = 0; i < R_RXFIFO_ID; ++i) {
+ register_reset(&s->reg_info[i]);
+ }
+
+ /*
+ * Reset FIFOs when CAN model is reset. This will clear the fifo writes
+ * done by post_write which gets called from register_reset function,
+ * post_write handle will not be able to trigger tx because CAN will be
+ * disabled when software_reset_register is cleared first.
+ */
+ fifo32_reset(&s->rx_fifo);
+ fifo32_reset(&s->tx_fifo);
+ fifo32_reset(&s->txhpb_fifo);
+}
+
+static bool xlnx_zynqmp_can_can_receive(CanBusClientState *client)
+{
+ XlnxZynqMPCANState *s = container_of(client, XlnxZynqMPCANState,
+ bus_client);
+
+ if (ARRAY_FIELD_EX32(s->regs, SOFTWARE_RESET_REGISTER, SRST)) {
+ g_autofree char *path = object_get_canonical_path(OBJECT(s));
+
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Controller is in reset state.\n",
+ path);
+ return false;
+ }
+
+ if ((ARRAY_FIELD_EX32(s->regs, SOFTWARE_RESET_REGISTER, CEN)) == 0) {
+ g_autofree char *path = object_get_canonical_path(OBJECT(s));
+
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Controller is disabled. Incoming"
+ " messages will be discarded.\n", path);
+ return false;
+ }
+
+ return true;
+}
+
+static ssize_t xlnx_zynqmp_can_receive(CanBusClientState *client,
+ const qemu_can_frame *buf, size_t buf_size) {
+ XlnxZynqMPCANState *s = container_of(client, XlnxZynqMPCANState,
+ bus_client);
+ const qemu_can_frame *frame = buf;
+
+ if (buf_size <= 0) {
+ g_autofree char *path = object_get_canonical_path(OBJECT(s));
+
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Error in the data received.\n",
+ path);
+ return 0;
+ }
+
+ if (ARRAY_FIELD_EX32(s->regs, STATUS_REGISTER, SNOOP)) {
+ /* Snoop Mode: Just keep the data. no response back. */
+ update_rx_fifo(s, frame);
+ } else if ((ARRAY_FIELD_EX32(s->regs, STATUS_REGISTER, SLEEP))) {
+ /*
+ * XlnxZynqMPCAN is in sleep mode. Any data on bus will bring it to wake
+ * up state.
+ */
+ can_exit_sleep_mode(s);
+ update_rx_fifo(s, frame);
+ } else if ((ARRAY_FIELD_EX32(s->regs, STATUS_REGISTER, SLEEP)) == 0) {
+ update_rx_fifo(s, frame);
+ } else {
+ /*
+ * XlnxZynqMPCAN will not participate in normal bus communication
+ * and will not receive any messages transmitted by other CAN nodes.
+ */
+ trace_xlnx_can_rx_discard(s->regs[R_STATUS_REGISTER]);
+ }
+
+ return 1;
+}
+
+static CanBusClientInfo can_xilinx_bus_client_info = {
+ .can_receive = xlnx_zynqmp_can_can_receive,
+ .receive = xlnx_zynqmp_can_receive,
+};
+
+static int xlnx_zynqmp_can_connect_to_bus(XlnxZynqMPCANState *s,
+ CanBusState *bus)
+{
+ s->bus_client.info = &can_xilinx_bus_client_info;
+
+ if (can_bus_insert_client(bus, &s->bus_client) < 0) {
+ return -1;
+ }
+ return 0;
+}
+
+static void xlnx_zynqmp_can_realize(DeviceState *dev, Error **errp)
+{
+ XlnxZynqMPCANState *s = XLNX_ZYNQMP_CAN(dev);
+
+ if (s->canbus) {
+ if (xlnx_zynqmp_can_connect_to_bus(s, s->canbus) < 0) {
+ g_autofree char *path = object_get_canonical_path(OBJECT(s));
+
+ error_setg(errp, "%s: xlnx_zynqmp_can_connect_to_bus"
+ " failed.", path);
+ return;
+ }
+ }
+
+ /* Create RX FIFO, TXFIFO, TXHPB storage. */
+ fifo32_create(&s->rx_fifo, RXFIFO_SIZE);
+ fifo32_create(&s->tx_fifo, RXFIFO_SIZE);
+ fifo32_create(&s->txhpb_fifo, CAN_FRAME_SIZE);
+
+ /* Allocate a new timer. */
+ s->can_timer = ptimer_init(xlnx_zynqmp_can_ptimer_cb, s,
+ PTIMER_POLICY_DEFAULT);
+
+ ptimer_transaction_begin(s->can_timer);
+
+ ptimer_set_freq(s->can_timer, s->cfg.ext_clk_freq);
+ ptimer_set_limit(s->can_timer, CAN_TIMER_MAX, 1);
+ ptimer_run(s->can_timer, 0);
+ ptimer_transaction_commit(s->can_timer);
+}
+
+static void xlnx_zynqmp_can_init(Object *obj)
+{
+ XlnxZynqMPCANState *s = XLNX_ZYNQMP_CAN(obj);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+
+ RegisterInfoArray *reg_array;
+
+ memory_region_init(&s->iomem, obj, TYPE_XLNX_ZYNQMP_CAN,
+ XLNX_ZYNQMP_CAN_R_MAX * 4);
+ reg_array = register_init_block32(DEVICE(obj), can_regs_info,
+ ARRAY_SIZE(can_regs_info),
+ s->reg_info, s->regs,
+ &can_ops,
+ XLNX_ZYNQMP_CAN_ERR_DEBUG,
+ XLNX_ZYNQMP_CAN_R_MAX * 4);
+
+ memory_region_add_subregion(&s->iomem, 0x00, &reg_array->mem);
+ sysbus_init_mmio(sbd, &s->iomem);
+ sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq);
+}
+
+static const VMStateDescription vmstate_can = {
+ .name = TYPE_XLNX_ZYNQMP_CAN,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_FIFO32(rx_fifo, XlnxZynqMPCANState),
+ VMSTATE_FIFO32(tx_fifo, XlnxZynqMPCANState),
+ VMSTATE_FIFO32(txhpb_fifo, XlnxZynqMPCANState),
+ VMSTATE_UINT32_ARRAY(regs, XlnxZynqMPCANState, XLNX_ZYNQMP_CAN_R_MAX),
+ VMSTATE_PTIMER(can_timer, XlnxZynqMPCANState),
+ VMSTATE_END_OF_LIST(),
+ }
+};
+
+static Property xlnx_zynqmp_can_properties[] = {
+ DEFINE_PROP_UINT32("ext_clk_freq", XlnxZynqMPCANState, cfg.ext_clk_freq,
+ CAN_DEFAULT_CLOCK),
+ DEFINE_PROP_LINK("canbus", XlnxZynqMPCANState, canbus, TYPE_CAN_BUS,
+ CanBusState *),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void xlnx_zynqmp_can_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+ rc->phases.enter = xlnx_zynqmp_can_reset_init;
+ rc->phases.hold = xlnx_zynqmp_can_reset_hold;
+ dc->realize = xlnx_zynqmp_can_realize;
+ device_class_set_props(dc, xlnx_zynqmp_can_properties);
+ dc->vmsd = &vmstate_can;
+}
+
+static const TypeInfo can_info = {
+ .name = TYPE_XLNX_ZYNQMP_CAN,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(XlnxZynqMPCANState),
+ .class_init = xlnx_zynqmp_can_class_init,
+ .instance_init = xlnx_zynqmp_can_init,
+};
+
+static void can_register_types(void)
+{
+ type_register_static(&can_info);
+}
+
+type_init(can_register_types)
diff --git a/include/hw/arm/xlnx-zynqmp.h b/include/hw/arm/xlnx-zynqmp.h
index 567d0db..6f45387 100644
--- a/include/hw/arm/xlnx-zynqmp.h
+++ b/include/hw/arm/xlnx-zynqmp.h
@@ -22,6 +22,7 @@
#include "hw/intc/arm_gic.h"
#include "hw/net/cadence_gem.h"
#include "hw/char/cadence_uart.h"
+#include "hw/net/xlnx-zynqmp-can.h"
#include "hw/ide/ahci.h"
#include "hw/sd/sdhci.h"
#include "hw/ssi/xilinx_spips.h"
@@ -33,6 +34,7 @@
#include "hw/cpu/cluster.h"
#include "target/arm/cpu.h"
#include "qom/object.h"
+#include "net/can_emu.h"
#define TYPE_XLNX_ZYNQMP "xlnx,zynqmp"
OBJECT_DECLARE_SIMPLE_TYPE(XlnxZynqMPState, XLNX_ZYNQMP)
@@ -41,6 +43,8 @@ OBJECT_DECLARE_SIMPLE_TYPE(XlnxZynqMPState, XLNX_ZYNQMP)
#define XLNX_ZYNQMP_NUM_RPU_CPUS 2
#define XLNX_ZYNQMP_NUM_GEMS 4
#define XLNX_ZYNQMP_NUM_UARTS 2
+#define XLNX_ZYNQMP_NUM_CAN 2
+#define XLNX_ZYNQMP_CAN_REF_CLK (24 * 1000 * 1000)
#define XLNX_ZYNQMP_NUM_SDHCI 2
#define XLNX_ZYNQMP_NUM_SPIS 2
#define XLNX_ZYNQMP_NUM_GDMA_CH 8
@@ -92,6 +96,7 @@ struct XlnxZynqMPState {
CadenceGEMState gem[XLNX_ZYNQMP_NUM_GEMS];
CadenceUARTState uart[XLNX_ZYNQMP_NUM_UARTS];
+ XlnxZynqMPCANState can[XLNX_ZYNQMP_NUM_CAN];
SysbusAHCIState sata;
SDHCIState sdhci[XLNX_ZYNQMP_NUM_SDHCI];
XilinxSPIPS spi[XLNX_ZYNQMP_NUM_SPIS];
@@ -112,6 +117,9 @@ struct XlnxZynqMPState {
bool virt;
/* Has the RPU subsystem? */
bool has_rpu;
+
+ /* CAN bus. */
+ CanBusState *canbus[XLNX_ZYNQMP_NUM_CAN];
};
#endif
diff --git a/include/hw/intc/armv7m_nvic.h b/include/hw/intc/armv7m_nvic.h
index bb087b2..39c71e1 100644
--- a/include/hw/intc/armv7m_nvic.h
+++ b/include/hw/intc/armv7m_nvic.h
@@ -83,7 +83,9 @@ struct NVICState {
MemoryRegion sysreg_ns_mem;
MemoryRegion systickmem;
MemoryRegion systick_ns_mem;
+ MemoryRegion ras_mem;
MemoryRegion container;
+ MemoryRegion defaultmem;
uint32_t num_irq;
qemu_irq excpout;
diff --git a/include/hw/net/xlnx-zynqmp-can.h b/include/hw/net/xlnx-zynqmp-can.h
new file mode 100644
index 0000000..eb15587
--- /dev/null
+++ b/include/hw/net/xlnx-zynqmp-can.h
@@ -0,0 +1,78 @@
+/*
+ * QEMU model of the Xilinx ZynqMP CAN controller.
+ *
+ * Copyright (c) 2020 Xilinx Inc.
+ *
+ * Written-by: Vikram Garhwal<fnu.vikram@xilinx.com>
+ *
+ * Based on QEMU CAN Device emulation implemented by Jin Yang, Deniz Eren and
+ * Pavel Pisa.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#ifndef XLNX_ZYNQMP_CAN_H
+#define XLNX_ZYNQMP_CAN_H
+
+#include "hw/register.h"
+#include "net/can_emu.h"
+#include "net/can_host.h"
+#include "qemu/fifo32.h"
+#include "hw/ptimer.h"
+#include "hw/qdev-clock.h"
+
+#define TYPE_XLNX_ZYNQMP_CAN "xlnx.zynqmp-can"
+
+#define XLNX_ZYNQMP_CAN(obj) \
+ OBJECT_CHECK(XlnxZynqMPCANState, (obj), TYPE_XLNX_ZYNQMP_CAN)
+
+#define MAX_CAN_CTRLS 2
+#define XLNX_ZYNQMP_CAN_R_MAX (0x84 / 4)
+#define MAILBOX_CAPACITY 64
+#define CAN_TIMER_MAX 0XFFFFUL
+#define CAN_DEFAULT_CLOCK (24 * 1000 * 1000)
+
+/* Each CAN_FRAME will have 4 * 32bit size. */
+#define CAN_FRAME_SIZE 4
+#define RXFIFO_SIZE (MAILBOX_CAPACITY * CAN_FRAME_SIZE)
+
+typedef struct XlnxZynqMPCANState {
+ SysBusDevice parent_obj;
+ MemoryRegion iomem;
+
+ qemu_irq irq;
+
+ CanBusClientState bus_client;
+ CanBusState *canbus;
+
+ struct {
+ uint32_t ext_clk_freq;
+ } cfg;
+
+ RegisterInfo reg_info[XLNX_ZYNQMP_CAN_R_MAX];
+ uint32_t regs[XLNX_ZYNQMP_CAN_R_MAX];
+
+ Fifo32 rx_fifo;
+ Fifo32 tx_fifo;
+ Fifo32 txhpb_fifo;
+
+ ptimer_state *can_timer;
+} XlnxZynqMPCANState;
+
+#endif
diff --git a/meson.build b/meson.build
index 732b29a..9ea05ab 100644
--- a/meson.build
+++ b/meson.build
@@ -1433,6 +1433,7 @@ if have_system
'hw/misc',
'hw/misc/macio',
'hw/net',
+ 'hw/net/can',
'hw/nvram',
'hw/pci',
'hw/pci-host',
diff --git a/target/arm/cpu.c b/target/arm/cpu.c
index 07492e9..d6188f6 100644
--- a/target/arm/cpu.c
+++ b/target/arm/cpu.c
@@ -262,6 +262,9 @@ static void arm_cpu_reset(DeviceState *dev)
* always reset to 4.
*/
env->v7m.ltpsize = 4;
+ /* The LTPSIZE field in FPDSCR is constant and reads as 4. */
+ env->v7m.fpdscr[M_REG_NS] = 4 << FPCR_LTPSIZE_SHIFT;
+ env->v7m.fpdscr[M_REG_S] = 4 << FPCR_LTPSIZE_SHIFT;
}
if (arm_feature(env, ARM_FEATURE_M_SECURITY)) {
@@ -1674,7 +1677,7 @@ static void arm_cpu_realizefn(DeviceState *dev, Error **errp)
}
}
- if (!cpu->has_el3) {
+ if (!arm_feature(env, ARM_FEATURE_M) && !cpu->has_el3) {
/* If the has_el3 CPU property is disabled then we need to disable the
* feature.
*/
diff --git a/target/arm/cpu.h b/target/arm/cpu.h
index e5514c8..7e6c881 100644
--- a/target/arm/cpu.h
+++ b/target/arm/cpu.h
@@ -1521,9 +1521,21 @@ void vfp_set_fpscr(CPUARMState *env, uint32_t val);
#define FPCR_IXE (1 << 12) /* Inexact exception trap enable */
#define FPCR_IDE (1 << 15) /* Input Denormal exception trap enable */
#define FPCR_FZ16 (1 << 19) /* ARMv8.2+, FP16 flush-to-zero */
+#define FPCR_RMODE_MASK (3 << 22) /* Rounding mode */
#define FPCR_FZ (1 << 24) /* Flush-to-zero enable bit */
#define FPCR_DN (1 << 25) /* Default NaN enable bit */
+#define FPCR_AHP (1 << 26) /* Alternative half-precision */
#define FPCR_QC (1 << 27) /* Cumulative saturation bit */
+#define FPCR_V (1 << 28) /* FP overflow flag */
+#define FPCR_C (1 << 29) /* FP carry flag */
+#define FPCR_Z (1 << 30) /* FP zero flag */
+#define FPCR_N (1 << 31) /* FP negative flag */
+
+#define FPCR_LTPSIZE_SHIFT 16 /* LTPSIZE, M-profile only */
+#define FPCR_LTPSIZE_MASK (7 << FPCR_LTPSIZE_SHIFT)
+
+#define FPCR_NZCV_MASK (FPCR_N | FPCR_Z | FPCR_C | FPCR_V)
+#define FPCR_NZCVQC_MASK (FPCR_NZCV_MASK | FPCR_QC)
static inline uint32_t vfp_get_fpsr(CPUARMState *env)
{
@@ -1568,6 +1580,15 @@ enum arm_cpu_mode {
#define ARM_VFP_FPEXC 8
#define ARM_VFP_FPINST 9
#define ARM_VFP_FPINST2 10
+/* These ones are M-profile only */
+#define ARM_VFP_FPSCR_NZCVQC 2
+#define ARM_VFP_VPR 12
+#define ARM_VFP_P0 13
+#define ARM_VFP_FPCXT_NS 14
+#define ARM_VFP_FPCXT_S 15
+
+/* QEMU-internal value meaning "FPSCR, but we care only about NZCV" */
+#define QEMU_VFP_FPSCR_NZCV 0xffff
/* iwMMXt coprocessor control registers. */
#define ARM_IWMMXT_wCID 0
@@ -1590,6 +1611,8 @@ FIELD(V7M_CCR, STKOFHFNMIGN, 10, 1)
FIELD(V7M_CCR, DC, 16, 1)
FIELD(V7M_CCR, IC, 17, 1)
FIELD(V7M_CCR, BP, 18, 1)
+FIELD(V7M_CCR, LOB, 19, 1)
+FIELD(V7M_CCR, TRD, 20, 1)
/* V7M SCR bits */
FIELD(V7M_SCR, SLEEPONEXIT, 1, 1)
@@ -1804,6 +1827,15 @@ FIELD(ID_MMFR4, LSM, 20, 4)
FIELD(ID_MMFR4, CCIDX, 24, 4)
FIELD(ID_MMFR4, EVT, 28, 4)
+FIELD(ID_PFR0, STATE0, 0, 4)
+FIELD(ID_PFR0, STATE1, 4, 4)
+FIELD(ID_PFR0, STATE2, 8, 4)
+FIELD(ID_PFR0, STATE3, 12, 4)
+FIELD(ID_PFR0, CSV2, 16, 4)
+FIELD(ID_PFR0, AMU, 20, 4)
+FIELD(ID_PFR0, DIT, 24, 4)
+FIELD(ID_PFR0, RAS, 28, 4)
+
FIELD(ID_PFR1, PROGMOD, 0, 4)
FIELD(ID_PFR1, SECURITY, 4, 4)
FIELD(ID_PFR1, MPROGMOD, 8, 4)
@@ -3550,11 +3582,25 @@ static inline bool isar_feature_aa32_predinv(const ARMISARegisters *id)
return FIELD_EX32(id->id_isar6, ID_ISAR6, SPECRES) != 0;
}
+static inline bool isar_feature_aa32_ras(const ARMISARegisters *id)
+{
+ return FIELD_EX32(id->id_pfr0, ID_PFR0, RAS) != 0;
+}
+
static inline bool isar_feature_aa32_mprofile(const ARMISARegisters *id)
{
return FIELD_EX32(id->id_pfr1, ID_PFR1, MPROGMOD) != 0;
}
+static inline bool isar_feature_aa32_m_sec_state(const ARMISARegisters *id)
+{
+ /*
+ * Return true if M-profile state handling insns
+ * (VSCCLRM, CLRM, FPCTX access insns) are implemented
+ */
+ return FIELD_EX32(id->id_pfr1, ID_PFR1, SECURITY) >= 3;
+}
+
static inline bool isar_feature_aa32_fp16_arith(const ARMISARegisters *id)
{
/* Sadly this is encoded differently for A-profile and M-profile */
diff --git a/target/arm/helper.c b/target/arm/helper.c
index 38cd35c..7b8bcd6 100644
--- a/target/arm/helper.c
+++ b/target/arm/helper.c
@@ -11754,6 +11754,11 @@ bool pmsav8_mpu_lookup(CPUARMState *env, uint32_t address,
} else {
uint32_t ap = extract32(env->pmsav8.rbar[secure][matchregion], 1, 2);
uint32_t xn = extract32(env->pmsav8.rbar[secure][matchregion], 0, 1);
+ bool pxn = false;
+
+ if (arm_feature(env, ARM_FEATURE_V8_1M)) {
+ pxn = extract32(env->pmsav8.rlar[secure][matchregion], 4, 1);
+ }
if (m_is_system_region(env, address)) {
/* System space is always execute never */
@@ -11761,7 +11766,7 @@ bool pmsav8_mpu_lookup(CPUARMState *env, uint32_t address,
}
*prot = simple_ap_to_rw_prot(env, mmu_idx, ap);
- if (*prot && !xn) {
+ if (*prot && !xn && !(pxn && !is_user)) {
*prot |= PAGE_EXEC;
}
/* We don't need to look the attribute up in the MAIR0/MAIR1
diff --git a/target/arm/m-nocp.decode b/target/arm/m-nocp.decode
index 28c8ac6..6699626 100644
--- a/target/arm/m-nocp.decode
+++ b/target/arm/m-nocp.decode
@@ -29,13 +29,17 @@
# If the coprocessor is not present or disabled then we will generate
# the NOCP exception; otherwise we let the insn through to the main decode.
+%vd_dp 22:1 12:4
+%vd_sp 12:4 22:1
+
&nocp cp
{
# Special cases which do not take an early NOCP: VLLDM and VLSTM
- VLLDM_VLSTM 1110 1100 001 l:1 rn:4 0000 1010 0000 0000
- # TODO: VSCCLRM (new in v8.1M) is similar:
- #VSCCLRM 1110 1100 1-01 1111 ---- 1011 ---- ---0
+ VLLDM_VLSTM 1110 1100 001 l:1 rn:4 0000 1010 op:1 000 0000
+ # VSCCLRM (new in v8.1M) is similar:
+ VSCCLRM 1110 1100 1.01 1111 .... 1011 imm:7 0 vd=%vd_dp size=3
+ VSCCLRM 1110 1100 1.01 1111 .... 1010 imm:8 vd=%vd_sp size=2
NOCP 111- 1110 ---- ---- ---- cp:4 ---- ---- &nocp
NOCP 111- 110- ---- ---- ---- cp:4 ---- ---- &nocp
diff --git a/target/arm/m_helper.c b/target/arm/m_helper.c
index aad01ea..643dcaf 100644
--- a/target/arm/m_helper.c
+++ b/target/arm/m_helper.c
@@ -722,11 +722,15 @@ load_fail:
* The HardFault is Secure if BFHFNMINS is 0 (meaning that all HFs are
* secure); otherwise it targets the same security state as the
* underlying exception.
+ * In v8.1M HardFaults from vector table fetch fails don't set FORCED.
*/
if (!(cpu->env.v7m.aircr & R_V7M_AIRCR_BFHFNMINS_MASK)) {
exc_secure = true;
}
- env->v7m.hfsr |= R_V7M_HFSR_VECTTBL_MASK | R_V7M_HFSR_FORCED_MASK;
+ env->v7m.hfsr |= R_V7M_HFSR_VECTTBL_MASK;
+ if (!arm_feature(env, ARM_FEATURE_V8_1M)) {
+ env->v7m.hfsr |= R_V7M_HFSR_FORCED_MASK;
+ }
armv7m_nvic_set_pending_derived(env->nvic, ARMV7M_EXCP_HARD, exc_secure);
return false;
}
@@ -897,10 +901,12 @@ static void v7m_exception_taken(ARMCPU *cpu, uint32_t lr, bool dotailchain,
* Clear registers if necessary to prevent non-secure exception
* code being able to see register values from secure code.
* Where register values become architecturally UNKNOWN we leave
- * them with their previous values.
+ * them with their previous values. v8.1M is tighter than v8.0M
+ * here and always zeroes the caller-saved registers regardless
+ * of the security state the exception is targeting.
*/
if (arm_feature(env, ARM_FEATURE_M_SECURITY)) {
- if (!targets_secure) {
+ if (!targets_secure || arm_feature(env, ARM_FEATURE_V8_1M)) {
/*
* Always clear the caller-saved registers (they have been
* pushed to the stack earlier in v7m_push_stack()).
@@ -909,10 +915,16 @@ static void v7m_exception_taken(ARMCPU *cpu, uint32_t lr, bool dotailchain,
* v7m_push_callee_stack()).
*/
int i;
+ /*
+ * r4..r11 are callee-saves, zero only if background
+ * state was Secure (EXCRET.S == 1) and exception
+ * targets Non-secure state
+ */
+ bool zero_callee_saves = !targets_secure &&
+ (lr & R_V7M_EXCRET_S_MASK);
for (i = 0; i < 13; i++) {
- /* r4..r11 are callee-saves, zero only if EXCRET.S == 1 */
- if (i < 4 || i > 11 || (lr & R_V7M_EXCRET_S_MASK)) {
+ if (i < 4 || i > 11 || zero_callee_saves) {
env->regs[i] = 0;
}
}
@@ -1503,7 +1515,27 @@ static void do_v7m_exception_exit(ARMCPU *cpu)
v7m_exception_taken(cpu, excret, true, false);
return;
} else {
- /* Clear s0..s15 and FPSCR */
+ if (arm_feature(env, ARM_FEATURE_V8_1M)) {
+ /* v8.1M adds this NOCP check */
+ bool nsacr_pass = exc_secure ||
+ extract32(env->v7m.nsacr, 10, 1);
+ bool cpacr_pass = v7m_cpacr_pass(env, exc_secure, true);
+ if (!nsacr_pass) {
+ armv7m_nvic_set_pending(env->nvic, ARMV7M_EXCP_USAGE, true);
+ env->v7m.cfsr[M_REG_S] |= R_V7M_CFSR_NOCP_MASK;
+ qemu_log_mask(CPU_LOG_INT, "...taking UsageFault on existing "
+ "stackframe: NSACR prevents clearing FPU registers\n");
+ v7m_exception_taken(cpu, excret, true, false);
+ } else if (!cpacr_pass) {
+ armv7m_nvic_set_pending(env->nvic, ARMV7M_EXCP_USAGE,
+ exc_secure);
+ env->v7m.cfsr[exc_secure] |= R_V7M_CFSR_NOCP_MASK;
+ qemu_log_mask(CPU_LOG_INT, "...taking UsageFault on existing "
+ "stackframe: CPACR prevents clearing FPU registers\n");
+ v7m_exception_taken(cpu, excret, true, false);
+ }
+ }
+ /* Clear s0..s15 and FPSCR; TODO also VPR when MVE is implemented */
int i;
for (i = 0; i < 16; i += 2) {
@@ -1967,6 +1999,64 @@ static bool v7m_read_half_insn(ARMCPU *cpu, ARMMMUIdx mmu_idx,
return true;
}
+static bool v7m_read_sg_stack_word(ARMCPU *cpu, ARMMMUIdx mmu_idx,
+ uint32_t addr, uint32_t *spdata)
+{
+ /*
+ * Read a word of data from the stack for the SG instruction,
+ * writing the value into *spdata. If the load succeeds, return
+ * true; otherwise pend an appropriate exception and return false.
+ * (We can't use data load helpers here that throw an exception
+ * because of the context we're called in, which is halfway through
+ * arm_v7m_cpu_do_interrupt().)
+ */
+ CPUState *cs = CPU(cpu);
+ CPUARMState *env = &cpu->env;
+ MemTxAttrs attrs = {};
+ MemTxResult txres;
+ target_ulong page_size;
+ hwaddr physaddr;
+ int prot;
+ ARMMMUFaultInfo fi = {};
+ ARMCacheAttrs cacheattrs = {};
+ uint32_t value;
+
+ if (get_phys_addr(env, addr, MMU_DATA_LOAD, mmu_idx, &physaddr,
+ &attrs, &prot, &page_size, &fi, &cacheattrs)) {
+ /* MPU/SAU lookup failed */
+ if (fi.type == ARMFault_QEMU_SFault) {
+ qemu_log_mask(CPU_LOG_INT,
+ "...SecureFault during stack word read\n");
+ env->v7m.sfsr |= R_V7M_SFSR_AUVIOL_MASK | R_V7M_SFSR_SFARVALID_MASK;
+ env->v7m.sfar = addr;
+ armv7m_nvic_set_pending(env->nvic, ARMV7M_EXCP_SECURE, false);
+ } else {
+ qemu_log_mask(CPU_LOG_INT,
+ "...MemManageFault during stack word read\n");
+ env->v7m.cfsr[M_REG_S] |= R_V7M_CFSR_DACCVIOL_MASK |
+ R_V7M_CFSR_MMARVALID_MASK;
+ env->v7m.mmfar[M_REG_S] = addr;
+ armv7m_nvic_set_pending(env->nvic, ARMV7M_EXCP_MEM, false);
+ }
+ return false;
+ }
+ value = address_space_ldl(arm_addressspace(cs, attrs), physaddr,
+ attrs, &txres);
+ if (txres != MEMTX_OK) {
+ /* BusFault trying to read the data */
+ qemu_log_mask(CPU_LOG_INT,
+ "...BusFault during stack word read\n");
+ env->v7m.cfsr[M_REG_NS] |=
+ (R_V7M_CFSR_PRECISERR_MASK | R_V7M_CFSR_BFARVALID_MASK);
+ env->v7m.bfar = addr;
+ armv7m_nvic_set_pending(env->nvic, ARMV7M_EXCP_BUS, false);
+ return false;
+ }
+
+ *spdata = value;
+ return true;
+}
+
static bool v7m_handle_execute_nsc(ARMCPU *cpu)
{
/*
@@ -2023,6 +2113,34 @@ static bool v7m_handle_execute_nsc(ARMCPU *cpu)
*/
qemu_log_mask(CPU_LOG_INT, "...really an SG instruction at 0x%08" PRIx32
", executing it\n", env->regs[15]);
+
+ if (cpu_isar_feature(aa32_m_sec_state, cpu) &&
+ !arm_v7m_is_handler_mode(env)) {
+ /*
+ * v8.1M exception stack frame integrity check. Note that we
+ * must perform the memory access even if CCR_S.TRD is zero
+ * and we aren't going to check what the data loaded is.
+ */
+ uint32_t spdata, sp;
+
+ /*
+ * We know we are currently NS, so the S stack pointers must be
+ * in other_ss_{psp,msp}, not in regs[13]/other_sp.
+ */
+ sp = v7m_using_psp(env) ? env->v7m.other_ss_psp : env->v7m.other_ss_msp;
+ if (!v7m_read_sg_stack_word(cpu, mmu_idx, sp, &spdata)) {
+ /* Stack access failed and an exception has been pended */
+ return false;
+ }
+
+ if (env->v7m.ccr[M_REG_S] & R_V7M_CCR_TRD_MASK) {
+ if (((spdata & ~1) == 0xfefa125a) ||
+ !(env->v7m.control[M_REG_S] & 1)) {
+ goto gen_invep;
+ }
+ }
+ }
+
env->regs[14] &= ~1;
env->v7m.control[M_REG_S] &= ~R_V7M_CONTROL_SFPA_MASK;
switch_v7m_security_state(env, true);
diff --git a/target/arm/t32.decode b/target/arm/t32.decode
index cfcc71b..8b2c487 100644
--- a/target/arm/t32.decode
+++ b/target/arm/t32.decode
@@ -307,6 +307,10 @@ CLZ 1111 1010 1011 ---- 1111 .... 1000 .... @rdm
# SEV 1111 0011 1010 1111 1000 0000 0000 0100
# SEVL 1111 0011 1010 1111 1000 0000 0000 0101
+ # For M-profile minimal-RAS ESB can be a NOP, which is the
+ # default behaviour since it is in the hint space.
+ # ESB 1111 0011 1010 1111 1000 0000 0001 0000
+
# The canonical nop ends in 0000 0000, but the whole rest
# of the space is "reserved hint, behaves as nop".
NOP 1111 0011 1010 1111 1000 0000 ---- ----
@@ -609,7 +613,11 @@ UXTAB 1111 1010 0101 .... 1111 .... 10.. .... @rrr_rot
STM_t32 1110 1000 10.0 .... ................ @ldstm i=1 b=0
STM_t32 1110 1001 00.0 .... ................ @ldstm i=0 b=1
-LDM_t32 1110 1000 10.1 .... ................ @ldstm i=1 b=0
+{
+ # Rn=15 UNDEFs for LDM; M-profile CLRM uses that encoding
+ CLRM 1110 1000 1001 1111 list:16
+ LDM_t32 1110 1000 10.1 .... ................ @ldstm i=1 b=0
+}
LDM_t32 1110 1001 00.1 .... ................ @ldstm i=0 b=1
&rfe !extern rn w pu
diff --git a/target/arm/translate-vfp.c.inc b/target/arm/translate-vfp.c.inc
index 96948f5..0db9360 100644
--- a/target/arm/translate-vfp.c.inc
+++ b/target/arm/translate-vfp.c.inc
@@ -84,6 +84,32 @@ static inline long vfp_f16_offset(unsigned reg, bool top)
}
/*
+ * Generate code for M-profile lazy FP state preservation if needed;
+ * this corresponds to the pseudocode PreserveFPState() function.
+ */
+static void gen_preserve_fp_state(DisasContext *s)
+{
+ if (s->v7m_lspact) {
+ /*
+ * Lazy state saving affects external memory and also the NVIC,
+ * so we must mark it as an IO operation for icount (and cause
+ * this to be the last insn in the TB).
+ */
+ if (tb_cflags(s->base.tb) & CF_USE_ICOUNT) {
+ s->base.is_jmp = DISAS_UPDATE_EXIT;
+ gen_io_start();
+ }
+ gen_helper_v7m_preserve_fp_state(cpu_env);
+ /*
+ * If the preserve_fp_state helper doesn't throw an exception
+ * then it will clear LSPACT; we don't need to repeat this for
+ * any further FP insns in this TB.
+ */
+ s->v7m_lspact = false;
+ }
+}
+
+/*
* Check that VFP access is enabled. If it is, do the necessary
* M-profile lazy-FP handling and then return true.
* If not, emit code to generate an appropriate exception and
@@ -113,24 +139,7 @@ static bool full_vfp_access_check(DisasContext *s, bool ignore_vfp_enabled)
/* Handle M-profile lazy FP state mechanics */
/* Trigger lazy-state preservation if necessary */
- if (s->v7m_lspact) {
- /*
- * Lazy state saving affects external memory and also the NVIC,
- * so we must mark it as an IO operation for icount (and cause
- * this to be the last insn in the TB).
- */
- if (tb_cflags(s->base.tb) & CF_USE_ICOUNT) {
- s->base.is_jmp = DISAS_UPDATE_EXIT;
- gen_io_start();
- }
- gen_helper_v7m_preserve_fp_state(cpu_env);
- /*
- * If the preserve_fp_state helper doesn't throw an exception
- * then it will clear LSPACT; we don't need to repeat this for
- * any further FP insns in this TB.
- */
- s->v7m_lspact = false;
- }
+ gen_preserve_fp_state(s);
/* Update ownership of FP context: set FPCCR.S to match current state */
if (s->v8m_fpccr_s_wrong) {
@@ -607,26 +616,268 @@ static bool trans_VDUP(DisasContext *s, arg_VDUP *a)
return true;
}
-static bool trans_VMSR_VMRS(DisasContext *s, arg_VMSR_VMRS *a)
+/*
+ * M-profile provides two different sets of instructions that can
+ * access floating point system registers: VMSR/VMRS (which move
+ * to/from a general purpose register) and VLDR/VSTR sysreg (which
+ * move directly to/from memory). In some cases there are also side
+ * effects which must happen after any write to memory (which could
+ * cause an exception). So we implement the common logic for the
+ * sysreg access in gen_M_fp_sysreg_write() and gen_M_fp_sysreg_read(),
+ * which take pointers to callback functions which will perform the
+ * actual "read/write general purpose register" and "read/write
+ * memory" operations.
+ */
+
+/*
+ * Emit code to store the sysreg to its final destination; frees the
+ * TCG temp 'value' it is passed.
+ */
+typedef void fp_sysreg_storefn(DisasContext *s, void *opaque, TCGv_i32 value);
+/*
+ * Emit code to load the value to be copied to the sysreg; returns
+ * a new TCG temporary
+ */
+typedef TCGv_i32 fp_sysreg_loadfn(DisasContext *s, void *opaque);
+
+/* Common decode/access checks for fp sysreg read/write */
+typedef enum FPSysRegCheckResult {
+ FPSysRegCheckFailed, /* caller should return false */
+ FPSysRegCheckDone, /* caller should return true */
+ FPSysRegCheckContinue, /* caller should continue generating code */
+} FPSysRegCheckResult;
+
+static FPSysRegCheckResult fp_sysreg_checks(DisasContext *s, int regno)
+{
+ if (!dc_isar_feature(aa32_fpsp_v2, s)) {
+ return FPSysRegCheckFailed;
+ }
+
+ switch (regno) {
+ case ARM_VFP_FPSCR:
+ case QEMU_VFP_FPSCR_NZCV:
+ break;
+ case ARM_VFP_FPSCR_NZCVQC:
+ if (!arm_dc_feature(s, ARM_FEATURE_V8_1M)) {
+ return false;
+ }
+ break;
+ case ARM_VFP_FPCXT_S:
+ if (!arm_dc_feature(s, ARM_FEATURE_V8_1M)) {
+ return false;
+ }
+ if (!s->v8m_secure) {
+ return false;
+ }
+ break;
+ default:
+ return FPSysRegCheckFailed;
+ }
+
+ if (!vfp_access_check(s)) {
+ return FPSysRegCheckDone;
+ }
+
+ return FPSysRegCheckContinue;
+}
+
+static bool gen_M_fp_sysreg_write(DisasContext *s, int regno,
+
+ fp_sysreg_loadfn *loadfn,
+ void *opaque)
{
+ /* Do a write to an M-profile floating point system register */
TCGv_i32 tmp;
- bool ignore_vfp_enabled = false;
- if (!dc_isar_feature(aa32_fpsp_v2, s)) {
+ switch (fp_sysreg_checks(s, regno)) {
+ case FPSysRegCheckFailed:
return false;
+ case FPSysRegCheckDone:
+ return true;
+ case FPSysRegCheckContinue:
+ break;
}
- if (arm_dc_feature(s, ARM_FEATURE_M)) {
+ switch (regno) {
+ case ARM_VFP_FPSCR:
+ tmp = loadfn(s, opaque);
+ gen_helper_vfp_set_fpscr(cpu_env, tmp);
+ tcg_temp_free_i32(tmp);
+ gen_lookup_tb(s);
+ break;
+ case ARM_VFP_FPSCR_NZCVQC:
+ {
+ TCGv_i32 fpscr;
+ tmp = loadfn(s, opaque);
+ /*
+ * TODO: when we implement MVE, write the QC bit.
+ * For non-MVE, QC is RES0.
+ */
+ tcg_gen_andi_i32(tmp, tmp, FPCR_NZCV_MASK);
+ fpscr = load_cpu_field(vfp.xregs[ARM_VFP_FPSCR]);
+ tcg_gen_andi_i32(fpscr, fpscr, ~FPCR_NZCV_MASK);
+ tcg_gen_or_i32(fpscr, fpscr, tmp);
+ store_cpu_field(fpscr, vfp.xregs[ARM_VFP_FPSCR]);
+ tcg_temp_free_i32(tmp);
+ break;
+ }
+ case ARM_VFP_FPCXT_S:
+ {
+ TCGv_i32 sfpa, control, fpscr;
+ /* Set FPSCR[27:0] and CONTROL.SFPA from value */
+ tmp = loadfn(s, opaque);
+ sfpa = tcg_temp_new_i32();
+ tcg_gen_shri_i32(sfpa, tmp, 31);
+ control = load_cpu_field(v7m.control[M_REG_S]);
+ tcg_gen_deposit_i32(control, control, sfpa,
+ R_V7M_CONTROL_SFPA_SHIFT, 1);
+ store_cpu_field(control, v7m.control[M_REG_S]);
+ fpscr = load_cpu_field(vfp.xregs[ARM_VFP_FPSCR]);
+ tcg_gen_andi_i32(fpscr, fpscr, FPCR_NZCV_MASK);
+ tcg_gen_andi_i32(tmp, tmp, ~FPCR_NZCV_MASK);
+ tcg_gen_or_i32(fpscr, fpscr, tmp);
+ store_cpu_field(fpscr, vfp.xregs[ARM_VFP_FPSCR]);
+ tcg_temp_free_i32(tmp);
+ tcg_temp_free_i32(sfpa);
+ break;
+ }
+ default:
+ g_assert_not_reached();
+ }
+ return true;
+}
+
+static bool gen_M_fp_sysreg_read(DisasContext *s, int regno,
+ fp_sysreg_storefn *storefn,
+ void *opaque)
+{
+ /* Do a read from an M-profile floating point system register */
+ TCGv_i32 tmp;
+
+ switch (fp_sysreg_checks(s, regno)) {
+ case FPSysRegCheckFailed:
+ return false;
+ case FPSysRegCheckDone:
+ return true;
+ case FPSysRegCheckContinue:
+ break;
+ }
+
+ switch (regno) {
+ case ARM_VFP_FPSCR:
+ tmp = tcg_temp_new_i32();
+ gen_helper_vfp_get_fpscr(tmp, cpu_env);
+ storefn(s, opaque, tmp);
+ break;
+ case ARM_VFP_FPSCR_NZCVQC:
+ /*
+ * TODO: MVE has a QC bit, which we probably won't store
+ * in the xregs[] field. For non-MVE, where QC is RES0,
+ * we can just fall through to the FPSCR_NZCV case.
+ */
+ case QEMU_VFP_FPSCR_NZCV:
/*
- * The only M-profile VFP vmrs/vmsr sysreg is FPSCR.
- * Accesses to R15 are UNPREDICTABLE; we choose to undef.
- * (FPSCR -> r15 is a special case which writes to the PSR flags.)
+ * Read just NZCV; this is a special case to avoid the
+ * helper call for the "VMRS to CPSR.NZCV" insn.
*/
- if (a->rt == 15 && (!a->l || a->reg != ARM_VFP_FPSCR)) {
+ tmp = load_cpu_field(vfp.xregs[ARM_VFP_FPSCR]);
+ tcg_gen_andi_i32(tmp, tmp, FPCR_NZCV_MASK);
+ storefn(s, opaque, tmp);
+ break;
+ case ARM_VFP_FPCXT_S:
+ {
+ TCGv_i32 control, sfpa, fpscr;
+ /* Bits [27:0] from FPSCR, bit [31] from CONTROL.SFPA */
+ tmp = tcg_temp_new_i32();
+ sfpa = tcg_temp_new_i32();
+ gen_helper_vfp_get_fpscr(tmp, cpu_env);
+ tcg_gen_andi_i32(tmp, tmp, ~FPCR_NZCV_MASK);
+ control = load_cpu_field(v7m.control[M_REG_S]);
+ tcg_gen_andi_i32(sfpa, control, R_V7M_CONTROL_SFPA_MASK);
+ tcg_gen_shli_i32(sfpa, sfpa, 31 - R_V7M_CONTROL_SFPA_SHIFT);
+ tcg_gen_or_i32(tmp, tmp, sfpa);
+ tcg_temp_free_i32(sfpa);
+ /*
+ * Store result before updating FPSCR etc, in case
+ * it is a memory write which causes an exception.
+ */
+ storefn(s, opaque, tmp);
+ /*
+ * Now we must reset FPSCR from FPDSCR_NS, and clear
+ * CONTROL.SFPA; so we'll end the TB here.
+ */
+ tcg_gen_andi_i32(control, control, ~R_V7M_CONTROL_SFPA_MASK);
+ store_cpu_field(control, v7m.control[M_REG_S]);
+ fpscr = load_cpu_field(v7m.fpdscr[M_REG_NS]);
+ gen_helper_vfp_set_fpscr(cpu_env, fpscr);
+ tcg_temp_free_i32(fpscr);
+ gen_lookup_tb(s);
+ break;
+ }
+ default:
+ g_assert_not_reached();
+ }
+ return true;
+}
+
+static void fp_sysreg_to_gpr(DisasContext *s, void *opaque, TCGv_i32 value)
+{
+ arg_VMSR_VMRS *a = opaque;
+
+ if (a->rt == 15) {
+ /* Set the 4 flag bits in the CPSR */
+ gen_set_nzcv(value);
+ tcg_temp_free_i32(value);
+ } else {
+ store_reg(s, a->rt, value);
+ }
+}
+
+static TCGv_i32 gpr_to_fp_sysreg(DisasContext *s, void *opaque)
+{
+ arg_VMSR_VMRS *a = opaque;
+
+ return load_reg(s, a->rt);
+}
+
+static bool gen_M_VMSR_VMRS(DisasContext *s, arg_VMSR_VMRS *a)
+{
+ /*
+ * Accesses to R15 are UNPREDICTABLE; we choose to undef.
+ * FPSCR -> r15 is a special case which writes to the PSR flags;
+ * set a->reg to a special value to tell gen_M_fp_sysreg_read()
+ * we only care about the top 4 bits of FPSCR there.
+ */
+ if (a->rt == 15) {
+ if (a->l && a->reg == ARM_VFP_FPSCR) {
+ a->reg = QEMU_VFP_FPSCR_NZCV;
+ } else {
return false;
}
}
+ if (a->l) {
+ /* VMRS, move FP system register to gp register */
+ return gen_M_fp_sysreg_read(s, a->reg, fp_sysreg_to_gpr, a);
+ } else {
+ /* VMSR, move gp register to FP system register */
+ return gen_M_fp_sysreg_write(s, a->reg, gpr_to_fp_sysreg, a);
+ }
+}
+
+static bool trans_VMSR_VMRS(DisasContext *s, arg_VMSR_VMRS *a)
+{
+ TCGv_i32 tmp;
+ bool ignore_vfp_enabled = false;
+
+ if (arm_dc_feature(s, ARM_FEATURE_M)) {
+ return gen_M_VMSR_VMRS(s, a);
+ }
+
+ if (!dc_isar_feature(aa32_fpsp_v2, s)) {
+ return false;
+ }
+
switch (a->reg) {
case ARM_VFP_FPSID:
/*
@@ -701,7 +952,7 @@ static bool trans_VMSR_VMRS(DisasContext *s, arg_VMSR_VMRS *a)
case ARM_VFP_FPSCR:
if (a->rt == 15) {
tmp = load_cpu_field(vfp.xregs[ARM_VFP_FPSCR]);
- tcg_gen_andi_i32(tmp, tmp, 0xf0000000);
+ tcg_gen_andi_i32(tmp, tmp, FPCR_NZCV_MASK);
} else {
tmp = tcg_temp_new_i32();
gen_helper_vfp_get_fpscr(tmp, cpu_env);
@@ -756,6 +1007,97 @@ static bool trans_VMSR_VMRS(DisasContext *s, arg_VMSR_VMRS *a)
return true;
}
+static void fp_sysreg_to_memory(DisasContext *s, void *opaque, TCGv_i32 value)
+{
+ arg_vldr_sysreg *a = opaque;
+ uint32_t offset = a->imm;
+ TCGv_i32 addr;
+
+ if (!a->a) {
+ offset = - offset;
+ }
+
+ addr = load_reg(s, a->rn);
+ if (a->p) {
+ tcg_gen_addi_i32(addr, addr, offset);
+ }
+
+ if (s->v8m_stackcheck && a->rn == 13 && a->w) {
+ gen_helper_v8m_stackcheck(cpu_env, addr);
+ }
+
+ gen_aa32_st_i32(s, value, addr, get_mem_index(s),
+ MO_UL | MO_ALIGN | s->be_data);
+ tcg_temp_free_i32(value);
+
+ if (a->w) {
+ /* writeback */
+ if (!a->p) {
+ tcg_gen_addi_i32(addr, addr, offset);
+ }
+ store_reg(s, a->rn, addr);
+ } else {
+ tcg_temp_free_i32(addr);
+ }
+}
+
+static TCGv_i32 memory_to_fp_sysreg(DisasContext *s, void *opaque)
+{
+ arg_vldr_sysreg *a = opaque;
+ uint32_t offset = a->imm;
+ TCGv_i32 addr;
+ TCGv_i32 value = tcg_temp_new_i32();
+
+ if (!a->a) {
+ offset = - offset;
+ }
+
+ addr = load_reg(s, a->rn);
+ if (a->p) {
+ tcg_gen_addi_i32(addr, addr, offset);
+ }
+
+ if (s->v8m_stackcheck && a->rn == 13 && a->w) {
+ gen_helper_v8m_stackcheck(cpu_env, addr);
+ }
+
+ gen_aa32_ld_i32(s, value, addr, get_mem_index(s),
+ MO_UL | MO_ALIGN | s->be_data);
+
+ if (a->w) {
+ /* writeback */
+ if (!a->p) {
+ tcg_gen_addi_i32(addr, addr, offset);
+ }
+ store_reg(s, a->rn, addr);
+ } else {
+ tcg_temp_free_i32(addr);
+ }
+ return value;
+}
+
+static bool trans_VLDR_sysreg(DisasContext *s, arg_vldr_sysreg *a)
+{
+ if (!arm_dc_feature(s, ARM_FEATURE_V8_1M)) {
+ return false;
+ }
+ if (a->rn == 15) {
+ return false;
+ }
+ return gen_M_fp_sysreg_write(s, a->reg, memory_to_fp_sysreg, a);
+}
+
+static bool trans_VSTR_sysreg(DisasContext *s, arg_vldr_sysreg *a)
+{
+ if (!arm_dc_feature(s, ARM_FEATURE_V8_1M)) {
+ return false;
+ }
+ if (a->rn == 15) {
+ return false;
+ }
+ return gen_M_fp_sysreg_read(s, a->reg, fp_sysreg_to_memory, a);
+}
+
static bool trans_VMOV_half(DisasContext *s, arg_VMOV_single *a)
{
TCGv_i32 tmp;
@@ -3379,6 +3721,31 @@ static bool trans_VLLDM_VLSTM(DisasContext *s, arg_VLLDM_VLSTM *a)
!arm_dc_feature(s, ARM_FEATURE_V8)) {
return false;
}
+
+ if (a->op) {
+ /*
+ * T2 encoding ({D0-D31} reglist): v8.1M and up. We choose not
+ * to take the IMPDEF option to make memory accesses to the stack
+ * slots that correspond to the D16-D31 registers (discarding
+ * read data and writing UNKNOWN values), so for us the T2
+ * encoding behaves identically to the T1 encoding.
+ */
+ if (!arm_dc_feature(s, ARM_FEATURE_V8_1M)) {
+ return false;
+ }
+ } else {
+ /*
+ * T1 encoding ({D0-D15} reglist); undef if we have 32 Dregs.
+ * This is currently architecturally impossible, but we add the
+ * check to stay in line with the pseudocode. Note that we must
+ * emit code for the UNDEF so it takes precedence over the NOCP.
+ */
+ if (dc_isar_feature(aa32_simd_r32, s)) {
+ unallocated_encoding(s);
+ return true;
+ }
+ }
+
/*
* If not secure, UNDEF. We must emit code for this
* rather than returning false so that this takes
@@ -3406,6 +3773,90 @@ static bool trans_VLLDM_VLSTM(DisasContext *s, arg_VLLDM_VLSTM *a)
return true;
}
+static bool trans_VSCCLRM(DisasContext *s, arg_VSCCLRM *a)
+{
+ int btmreg, topreg;
+ TCGv_i64 zero;
+ TCGv_i32 aspen, sfpa;
+
+ if (!dc_isar_feature(aa32_m_sec_state, s)) {
+ /* Before v8.1M, fall through in decode to NOCP check */
+ return false;
+ }
+
+ /* Explicitly UNDEF because this takes precedence over NOCP */
+ if (!arm_dc_feature(s, ARM_FEATURE_M_MAIN) || !s->v8m_secure) {
+ unallocated_encoding(s);
+ return true;
+ }
+
+ if (!dc_isar_feature(aa32_vfp_simd, s)) {
+ /* NOP if we have neither FP nor MVE */
+ return true;
+ }
+
+ /*
+ * If FPCCR.ASPEN != 0 && CONTROL_S.SFPA == 0 then there is no
+ * active floating point context so we must NOP (without doing
+ * any lazy state preservation or the NOCP check).
+ */
+ aspen = load_cpu_field(v7m.fpccr[M_REG_S]);
+ sfpa = load_cpu_field(v7m.control[M_REG_S]);
+ tcg_gen_andi_i32(aspen, aspen, R_V7M_FPCCR_ASPEN_MASK);
+ tcg_gen_xori_i32(aspen, aspen, R_V7M_FPCCR_ASPEN_MASK);
+ tcg_gen_andi_i32(sfpa, sfpa, R_V7M_CONTROL_SFPA_MASK);
+ tcg_gen_or_i32(sfpa, sfpa, aspen);
+ arm_gen_condlabel(s);
+ tcg_gen_brcondi_i32(TCG_COND_EQ, sfpa, 0, s->condlabel);
+
+ if (s->fp_excp_el != 0) {
+ gen_exception_insn(s, s->pc_curr, EXCP_NOCP,
+ syn_uncategorized(), s->fp_excp_el);
+ return true;
+ }
+
+ topreg = a->vd + a->imm - 1;
+ btmreg = a->vd;
+
+ /* Convert to Sreg numbers if the insn specified in Dregs */
+ if (a->size == 3) {
+ topreg = topreg * 2 + 1;
+ btmreg *= 2;
+ }
+
+ if (topreg > 63 || (topreg > 31 && !(topreg & 1))) {
+ /* UNPREDICTABLE: we choose to undef */
+ unallocated_encoding(s);
+ return true;
+ }
+
+ /* Silently ignore requests to clear D16-D31 if they don't exist */
+ if (topreg > 31 && !dc_isar_feature(aa32_simd_r32, s)) {
+ topreg = 31;
+ }
+
+ if (!vfp_access_check(s)) {
+ return true;
+ }
+
+ /* Zero the Sregs from btmreg to topreg inclusive. */
+ zero = tcg_const_i64(0);
+ if (btmreg & 1) {
+ write_neon_element64(zero, btmreg >> 1, 1, MO_32);
+ btmreg++;
+ }
+ for (; btmreg + 1 <= topreg; btmreg += 2) {
+ write_neon_element64(zero, btmreg >> 1, 0, MO_64);
+ }
+ if (btmreg == topreg) {
+ write_neon_element64(zero, btmreg >> 1, 0, MO_32);
+ btmreg++;
+ }
+ assert(btmreg == topreg + 1);
+ /* TODO: when MVE is implemented, zero VPR here */
+ return true;
+}
+
static bool trans_NOCP(DisasContext *s, arg_nocp *a)
{
/*
diff --git a/target/arm/translate.c b/target/arm/translate.c
index 6d04ca3..f5acd32 100644
--- a/target/arm/translate.c
+++ b/target/arm/translate.c
@@ -100,6 +100,39 @@ void arm_translate_init(void)
a64_translate_init();
}
+/* Generate a label used for skipping this instruction */
+static void arm_gen_condlabel(DisasContext *s)
+{
+ if (!s->condjmp) {
+ s->condlabel = gen_new_label();
+ s->condjmp = 1;
+ }
+}
+
+/*
+ * Constant expanders for the decoders.
+ */
+
+static int negate(DisasContext *s, int x)
+{
+ return -x;
+}
+
+static int plus_2(DisasContext *s, int x)
+{
+ return x + 2;
+}
+
+static int times_2(DisasContext *s, int x)
+{
+ return x * 2;
+}
+
+static int times_4(DisasContext *s, int x)
+{
+ return x * 4;
+}
+
/* Flags for the disas_set_da_iss info argument:
* lower bits hold the Rt register number, higher bits are flags.
*/
@@ -1221,6 +1254,9 @@ static void write_neon_element64(TCGv_i64 src, int reg, int ele, MemOp memop)
long off = neon_element_offset(reg, ele, memop);
switch (memop) {
+ case MO_32:
+ tcg_gen_st32_i64(src, cpu_env, off);
+ break;
case MO_64:
tcg_gen_st_i64(src, cpu_env, off);
break;
@@ -5156,15 +5192,6 @@ static void gen_srs(DisasContext *s,
s->base.is_jmp = DISAS_UPDATE_EXIT;
}
-/* Generate a label used for skipping this instruction */
-static void arm_gen_condlabel(DisasContext *s)
-{
- if (!s->condjmp) {
- s->condlabel = gen_new_label();
- s->condjmp = 1;
- }
-}
-
/* Skip this instruction if the ARM condition is false */
static void arm_skip_unless(DisasContext *s, uint32_t cond)
{
@@ -5174,29 +5201,9 @@ static void arm_skip_unless(DisasContext *s, uint32_t cond)
/*
- * Constant expanders for the decoders.
+ * Constant expanders used by T16/T32 decode
*/
-static int negate(DisasContext *s, int x)
-{
- return -x;
-}
-
-static int plus_2(DisasContext *s, int x)
-{
- return x + 2;
-}
-
-static int times_2(DisasContext *s, int x)
-{
- return x * 2;
-}
-
-static int times_4(DisasContext *s, int x)
-{
- return x * 4;
-}
-
/* Return only the rotation part of T32ExpandImm. */
static int t32_expandimm_rot(DisasContext *s, int x)
{
@@ -7965,6 +7972,44 @@ static bool trans_LDM_t16(DisasContext *s, arg_ldst_block *a)
return do_ldm(s, a, 1);
}
+static bool trans_CLRM(DisasContext *s, arg_CLRM *a)
+{
+ int i;
+ TCGv_i32 zero;
+
+ if (!dc_isar_feature(aa32_m_sec_state, s)) {
+ return false;
+ }
+
+ if (extract32(a->list, 13, 1)) {
+ return false;
+ }
+
+ if (!a->list) {
+ /* UNPREDICTABLE; we choose to UNDEF */
+ return false;
+ }
+
+ zero = tcg_const_i32(0);
+ for (i = 0; i < 15; i++) {
+ if (extract32(a->list, i, 1)) {
+ /* Clear R[i] */
+ tcg_gen_mov_i32(cpu_R[i], zero);
+ }
+ }
+ if (extract32(a->list, 15, 1)) {
+ /*
+ * Clear APSR (by calling the MSR helper with the same argument
+ * as for "MSR APSR_nzcvqg, Rn": mask = 0b1100, SYSM=0)
+ */
+ TCGv_i32 maskreg = tcg_const_i32(0xc << 8);
+ gen_helper_v7m_msr(cpu_env, maskreg, zero);
+ tcg_temp_free_i32(maskreg);
+ }
+ tcg_temp_free_i32(zero);
+ return true;
+}
+
/*
* Branch, branch with link
*/
diff --git a/target/arm/vfp.decode b/target/arm/vfp.decode
index 1300ba0..6f7f28f 100644
--- a/target/arm/vfp.decode
+++ b/target/arm/vfp.decode
@@ -84,6 +84,20 @@ VLDR_VSTR_hp ---- 1101 u:1 .0 l:1 rn:4 .... 1001 imm:8 vd=%vd_sp
VLDR_VSTR_sp ---- 1101 u:1 .0 l:1 rn:4 .... 1010 imm:8 vd=%vd_sp
VLDR_VSTR_dp ---- 1101 u:1 .0 l:1 rn:4 .... 1011 imm:8 vd=%vd_dp
+# M-profile VLDR/VSTR to sysreg
+%vldr_sysreg 22:1 13:3
+%imm7_0x4 0:7 !function=times_4
+
+&vldr_sysreg rn reg imm a w p
+@vldr_sysreg .... ... . a:1 . . . rn:4 ... . ... .. ....... \
+ reg=%vldr_sysreg imm=%imm7_0x4 &vldr_sysreg
+
+# P=0 W=0 is SEE "Related encodings", so split into two patterns
+VLDR_sysreg ---- 110 1 . . w:1 1 .... ... 0 111 11 ....... @vldr_sysreg p=1
+VLDR_sysreg ---- 110 0 . . 1 1 .... ... 0 111 11 ....... @vldr_sysreg p=0 w=1
+VSTR_sysreg ---- 110 1 . . w:1 0 .... ... 0 111 11 ....... @vldr_sysreg p=1
+VSTR_sysreg ---- 110 0 . . 1 0 .... ... 0 111 11 ....... @vldr_sysreg p=0 w=1
+
# We split the load/store multiple up into two patterns to avoid
# overlap with other insns in the "Advanced SIMD load/store and 64-bit move"
# grouping:
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index c19f1c8..4ca83ce 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -156,6 +156,7 @@ qtests_aarch64 = \
['arm-cpu-features',
'numa-test',
'boot-serial-test',
+ 'xlnx-can-test',
'migration-test']
qtests_s390x = \
diff --git a/tests/qtest/npcm7xx_rng-test.c b/tests/qtest/npcm7xx_rng-test.c
index c614968..797f832 100644
--- a/tests/qtest/npcm7xx_rng-test.c
+++ b/tests/qtest/npcm7xx_rng-test.c
@@ -20,6 +20,7 @@
#include "libqtest-single.h"
#include "qemu/bitops.h"
+#include "qemu-common.h"
#define RNG_BASE_ADDR 0xf000b000
@@ -36,6 +37,13 @@
/* Number of bits to collect for randomness tests. */
#define TEST_INPUT_BITS (128)
+static void dump_buf_if_failed(const uint8_t *buf, size_t size)
+{
+ if (g_test_failed()) {
+ qemu_hexdump(stderr, "", buf, size);
+ }
+}
+
static void rng_writeb(unsigned int offset, uint8_t value)
{
writeb(RNG_BASE_ADDR + offset, value);
@@ -188,6 +196,7 @@ static void test_continuous_monobit(void)
}
g_assert_cmpfloat(calc_monobit_p(buf, sizeof(buf)), >, 0.01);
+ dump_buf_if_failed(buf, sizeof(buf));
}
/*
@@ -209,6 +218,7 @@ static void test_continuous_runs(void)
}
g_assert_cmpfloat(calc_runs_p(buf.l, sizeof(buf) * BITS_PER_BYTE), >, 0.01);
+ dump_buf_if_failed(buf.c, sizeof(buf));
}
/*
@@ -230,6 +240,7 @@ static void test_first_byte_monobit(void)
}
g_assert_cmpfloat(calc_monobit_p(buf, sizeof(buf)), >, 0.01);
+ dump_buf_if_failed(buf, sizeof(buf));
}
/*
@@ -254,6 +265,7 @@ static void test_first_byte_runs(void)
}
g_assert_cmpfloat(calc_runs_p(buf.l, sizeof(buf) * BITS_PER_BYTE), >, 0.01);
+ dump_buf_if_failed(buf.c, sizeof(buf));
}
int main(int argc, char **argv)
diff --git a/tests/qtest/xlnx-can-test.c b/tests/qtest/xlnx-can-test.c
new file mode 100644
index 0000000..3d11200
--- /dev/null
+++ b/tests/qtest/xlnx-can-test.c
@@ -0,0 +1,360 @@
+/*
+ * QTests for the Xilinx ZynqMP CAN controller.
+ *
+ * Copyright (c) 2020 Xilinx Inc.
+ *
+ * Written-by: Vikram Garhwal<fnu.vikram@xilinx.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "libqos/libqtest.h"
+
+/* Base address. */
+#define CAN0_BASE_ADDR 0xFF060000
+#define CAN1_BASE_ADDR 0xFF070000
+
+/* Register addresses. */
+#define R_SRR_OFFSET 0x00
+#define R_MSR_OFFSET 0x04
+#define R_SR_OFFSET 0x18
+#define R_ISR_OFFSET 0x1C
+#define R_ICR_OFFSET 0x24
+#define R_TXID_OFFSET 0x30
+#define R_TXDLC_OFFSET 0x34
+#define R_TXDATA1_OFFSET 0x38
+#define R_TXDATA2_OFFSET 0x3C
+#define R_RXID_OFFSET 0x50
+#define R_RXDLC_OFFSET 0x54
+#define R_RXDATA1_OFFSET 0x58
+#define R_RXDATA2_OFFSET 0x5C
+#define R_AFR 0x60
+#define R_AFMR1 0x64
+#define R_AFIR1 0x68
+#define R_AFMR2 0x6C
+#define R_AFIR2 0x70
+#define R_AFMR3 0x74
+#define R_AFIR3 0x78
+#define R_AFMR4 0x7C
+#define R_AFIR4 0x80
+
+/* CAN modes. */
+#define CONFIG_MODE 0x00
+#define NORMAL_MODE 0x00
+#define LOOPBACK_MODE 0x02
+#define SNOOP_MODE 0x04
+#define SLEEP_MODE 0x01
+#define ENABLE_CAN (1 << 1)
+#define STATUS_NORMAL_MODE (1 << 3)
+#define STATUS_LOOPBACK_MODE (1 << 1)
+#define STATUS_SNOOP_MODE (1 << 12)
+#define STATUS_SLEEP_MODE (1 << 2)
+#define ISR_TXOK (1 << 1)
+#define ISR_RXOK (1 << 4)
+
+static void match_rx_tx_data(const uint32_t *buf_tx, const uint32_t *buf_rx,
+ uint8_t can_timestamp)
+{
+ uint16_t size = 0;
+ uint8_t len = 4;
+
+ while (size < len) {
+ if (R_RXID_OFFSET + 4 * size == R_RXDLC_OFFSET) {
+ g_assert_cmpint(buf_rx[size], ==, buf_tx[size] + can_timestamp);
+ } else {
+ g_assert_cmpint(buf_rx[size], ==, buf_tx[size]);
+ }
+
+ size++;
+ }
+}
+
+static void read_data(QTestState *qts, uint64_t can_base_addr, uint32_t *buf_rx)
+{
+ uint32_t int_status;
+
+ /* Read the interrupt on CAN rx. */
+ int_status = qtest_readl(qts, can_base_addr + R_ISR_OFFSET) & ISR_RXOK;
+
+ g_assert_cmpint(int_status, ==, ISR_RXOK);
+
+ /* Read the RX register data for CAN. */
+ buf_rx[0] = qtest_readl(qts, can_base_addr + R_RXID_OFFSET);
+ buf_rx[1] = qtest_readl(qts, can_base_addr + R_RXDLC_OFFSET);
+ buf_rx[2] = qtest_readl(qts, can_base_addr + R_RXDATA1_OFFSET);
+ buf_rx[3] = qtest_readl(qts, can_base_addr + R_RXDATA2_OFFSET);
+
+ /* Clear the RX interrupt. */
+ qtest_writel(qts, CAN1_BASE_ADDR + R_ICR_OFFSET, ISR_RXOK);
+}
+
+static void send_data(QTestState *qts, uint64_t can_base_addr,
+ const uint32_t *buf_tx)
+{
+ uint32_t int_status;
+
+ /* Write the TX register data for CAN. */
+ qtest_writel(qts, can_base_addr + R_TXID_OFFSET, buf_tx[0]);
+ qtest_writel(qts, can_base_addr + R_TXDLC_OFFSET, buf_tx[1]);
+ qtest_writel(qts, can_base_addr + R_TXDATA1_OFFSET, buf_tx[2]);
+ qtest_writel(qts, can_base_addr + R_TXDATA2_OFFSET, buf_tx[3]);
+
+ /* Read the interrupt on CAN for tx. */
+ int_status = qtest_readl(qts, can_base_addr + R_ISR_OFFSET) & ISR_TXOK;
+
+ g_assert_cmpint(int_status, ==, ISR_TXOK);
+
+ /* Clear the interrupt for tx. */
+ qtest_writel(qts, CAN0_BASE_ADDR + R_ICR_OFFSET, ISR_TXOK);
+}
+
+/*
+ * This test will be transferring data from CAN0 and CAN1 through canbus. CAN0
+ * initiate the data transfer to can-bus, CAN1 receives the data. Test compares
+ * the data sent from CAN0 with received on CAN1.
+ */
+static void test_can_bus(void)
+{
+ const uint32_t buf_tx[4] = { 0xFF, 0x80000000, 0x12345678, 0x87654321 };
+ uint32_t buf_rx[4] = { 0x00, 0x00, 0x00, 0x00 };
+ uint32_t status = 0;
+ uint8_t can_timestamp = 1;
+
+ QTestState *qts = qtest_init("-machine xlnx-zcu102"
+ " -object can-bus,id=canbus0"
+ " -machine xlnx-zcu102.canbus0=canbus0"
+ " -machine xlnx-zcu102.canbus1=canbus0"
+ );
+
+ /* Configure the CAN0 and CAN1. */
+ qtest_writel(qts, CAN0_BASE_ADDR + R_SRR_OFFSET, ENABLE_CAN);
+ qtest_writel(qts, CAN0_BASE_ADDR + R_MSR_OFFSET, NORMAL_MODE);
+ qtest_writel(qts, CAN1_BASE_ADDR + R_SRR_OFFSET, ENABLE_CAN);
+ qtest_writel(qts, CAN1_BASE_ADDR + R_MSR_OFFSET, NORMAL_MODE);
+
+ /* Check here if CAN0 and CAN1 are in normal mode. */
+ status = qtest_readl(qts, CAN0_BASE_ADDR + R_SR_OFFSET);
+ g_assert_cmpint(status, ==, STATUS_NORMAL_MODE);
+
+ status = qtest_readl(qts, CAN1_BASE_ADDR + R_SR_OFFSET);
+ g_assert_cmpint(status, ==, STATUS_NORMAL_MODE);
+
+ send_data(qts, CAN0_BASE_ADDR, buf_tx);
+
+ read_data(qts, CAN1_BASE_ADDR, buf_rx);
+ match_rx_tx_data(buf_tx, buf_rx, can_timestamp);
+
+ qtest_quit(qts);
+}
+
+/*
+ * This test is performing loopback mode on CAN0 and CAN1. Data sent from TX of
+ * each CAN0 and CAN1 are compared with RX register data for respective CAN.
+ */
+static void test_can_loopback(void)
+{
+ uint32_t buf_tx[4] = { 0xFF, 0x80000000, 0x12345678, 0x87654321 };
+ uint32_t buf_rx[4] = { 0x00, 0x00, 0x00, 0x00 };
+ uint32_t status = 0;
+
+ QTestState *qts = qtest_init("-machine xlnx-zcu102"
+ " -object can-bus,id=canbus0"
+ " -machine xlnx-zcu102.canbus0=canbus0"
+ " -machine xlnx-zcu102.canbus1=canbus0"
+ );
+
+ /* Configure the CAN0 in loopback mode. */
+ qtest_writel(qts, CAN0_BASE_ADDR + R_SRR_OFFSET, CONFIG_MODE);
+ qtest_writel(qts, CAN0_BASE_ADDR + R_MSR_OFFSET, LOOPBACK_MODE);
+ qtest_writel(qts, CAN0_BASE_ADDR + R_SRR_OFFSET, ENABLE_CAN);
+
+ /* Check here if CAN0 is set in loopback mode. */
+ status = qtest_readl(qts, CAN0_BASE_ADDR + R_SR_OFFSET);
+
+ g_assert_cmpint(status, ==, STATUS_LOOPBACK_MODE);
+
+ send_data(qts, CAN0_BASE_ADDR, buf_tx);
+ read_data(qts, CAN0_BASE_ADDR, buf_rx);
+ match_rx_tx_data(buf_tx, buf_rx, 0);
+
+ /* Configure the CAN1 in loopback mode. */
+ qtest_writel(qts, CAN1_BASE_ADDR + R_SRR_OFFSET, CONFIG_MODE);
+ qtest_writel(qts, CAN1_BASE_ADDR + R_MSR_OFFSET, LOOPBACK_MODE);
+ qtest_writel(qts, CAN1_BASE_ADDR + R_SRR_OFFSET, ENABLE_CAN);
+
+ /* Check here if CAN1 is set in loopback mode. */
+ status = qtest_readl(qts, CAN1_BASE_ADDR + R_SR_OFFSET);
+
+ g_assert_cmpint(status, ==, STATUS_LOOPBACK_MODE);
+
+ send_data(qts, CAN1_BASE_ADDR, buf_tx);
+ read_data(qts, CAN1_BASE_ADDR, buf_rx);
+ match_rx_tx_data(buf_tx, buf_rx, 0);
+
+ qtest_quit(qts);
+}
+
+/*
+ * Enable filters for CAN1. This will filter incoming messages with ID. In this
+ * test message will pass through filter 2.
+ */
+static void test_can_filter(void)
+{
+ uint32_t buf_tx[4] = { 0x14, 0x80000000, 0x12345678, 0x87654321 };
+ uint32_t buf_rx[4] = { 0x00, 0x00, 0x00, 0x00 };
+ uint32_t status = 0;
+ uint8_t can_timestamp = 1;
+
+ QTestState *qts = qtest_init("-machine xlnx-zcu102"
+ " -object can-bus,id=canbus0"
+ " -machine xlnx-zcu102.canbus0=canbus0"
+ " -machine xlnx-zcu102.canbus1=canbus0"
+ );
+
+ /* Configure the CAN0 and CAN1. */
+ qtest_writel(qts, CAN0_BASE_ADDR + R_SRR_OFFSET, ENABLE_CAN);
+ qtest_writel(qts, CAN0_BASE_ADDR + R_MSR_OFFSET, NORMAL_MODE);
+ qtest_writel(qts, CAN1_BASE_ADDR + R_SRR_OFFSET, ENABLE_CAN);
+ qtest_writel(qts, CAN1_BASE_ADDR + R_MSR_OFFSET, NORMAL_MODE);
+
+ /* Check here if CAN0 and CAN1 are in normal mode. */
+ status = qtest_readl(qts, CAN0_BASE_ADDR + R_SR_OFFSET);
+ g_assert_cmpint(status, ==, STATUS_NORMAL_MODE);
+
+ status = qtest_readl(qts, CAN1_BASE_ADDR + R_SR_OFFSET);
+ g_assert_cmpint(status, ==, STATUS_NORMAL_MODE);
+
+ /* Set filter for CAN1 for incoming messages. */
+ qtest_writel(qts, CAN1_BASE_ADDR + R_AFR, 0x0);
+ qtest_writel(qts, CAN1_BASE_ADDR + R_AFMR1, 0xF7);
+ qtest_writel(qts, CAN1_BASE_ADDR + R_AFIR1, 0x121F);
+ qtest_writel(qts, CAN1_BASE_ADDR + R_AFMR2, 0x5431);
+ qtest_writel(qts, CAN1_BASE_ADDR + R_AFIR2, 0x14);
+ qtest_writel(qts, CAN1_BASE_ADDR + R_AFMR3, 0x1234);
+ qtest_writel(qts, CAN1_BASE_ADDR + R_AFIR3, 0x5431);
+ qtest_writel(qts, CAN1_BASE_ADDR + R_AFMR4, 0xFFF);
+ qtest_writel(qts, CAN1_BASE_ADDR + R_AFIR4, 0x1234);
+
+ qtest_writel(qts, CAN1_BASE_ADDR + R_AFR, 0xF);
+
+ send_data(qts, CAN0_BASE_ADDR, buf_tx);
+
+ read_data(qts, CAN1_BASE_ADDR, buf_rx);
+ match_rx_tx_data(buf_tx, buf_rx, can_timestamp);
+
+ qtest_quit(qts);
+}
+
+/* Testing sleep mode on CAN0 while CAN1 is in normal mode. */
+static void test_can_sleepmode(void)
+{
+ uint32_t buf_tx[4] = { 0x14, 0x80000000, 0x12345678, 0x87654321 };
+ uint32_t buf_rx[4] = { 0x00, 0x00, 0x00, 0x00 };
+ uint32_t status = 0;
+ uint8_t can_timestamp = 1;
+
+ QTestState *qts = qtest_init("-machine xlnx-zcu102"
+ " -object can-bus,id=canbus0"
+ " -machine xlnx-zcu102.canbus0=canbus0"
+ " -machine xlnx-zcu102.canbus1=canbus0"
+ );
+
+ /* Configure the CAN0. */
+ qtest_writel(qts, CAN0_BASE_ADDR + R_SRR_OFFSET, CONFIG_MODE);
+ qtest_writel(qts, CAN0_BASE_ADDR + R_MSR_OFFSET, SLEEP_MODE);
+ qtest_writel(qts, CAN0_BASE_ADDR + R_SRR_OFFSET, ENABLE_CAN);
+
+ qtest_writel(qts, CAN1_BASE_ADDR + R_SRR_OFFSET, ENABLE_CAN);
+ qtest_writel(qts, CAN1_BASE_ADDR + R_MSR_OFFSET, NORMAL_MODE);
+
+ /* Check here if CAN0 is in SLEEP mode and CAN1 in normal mode. */
+ status = qtest_readl(qts, CAN0_BASE_ADDR + R_SR_OFFSET);
+ g_assert_cmpint(status, ==, STATUS_SLEEP_MODE);
+
+ status = qtest_readl(qts, CAN1_BASE_ADDR + R_SR_OFFSET);
+ g_assert_cmpint(status, ==, STATUS_NORMAL_MODE);
+
+ send_data(qts, CAN1_BASE_ADDR, buf_tx);
+
+ /*
+ * Once CAN1 sends data on can-bus. CAN0 should exit sleep mode.
+ * Check the CAN0 status now. It should exit the sleep mode and receive the
+ * incoming data.
+ */
+ status = qtest_readl(qts, CAN0_BASE_ADDR + R_SR_OFFSET);
+ g_assert_cmpint(status, ==, STATUS_NORMAL_MODE);
+
+ read_data(qts, CAN0_BASE_ADDR, buf_rx);
+
+ match_rx_tx_data(buf_tx, buf_rx, can_timestamp);
+
+ qtest_quit(qts);
+}
+
+/* Testing Snoop mode on CAN0 while CAN1 is in normal mode. */
+static void test_can_snoopmode(void)
+{
+ uint32_t buf_tx[4] = { 0x14, 0x80000000, 0x12345678, 0x87654321 };
+ uint32_t buf_rx[4] = { 0x00, 0x00, 0x00, 0x00 };
+ uint32_t status = 0;
+ uint8_t can_timestamp = 1;
+
+ QTestState *qts = qtest_init("-machine xlnx-zcu102"
+ " -object can-bus,id=canbus0"
+ " -machine xlnx-zcu102.canbus0=canbus0"
+ " -machine xlnx-zcu102.canbus1=canbus0"
+ );
+
+ /* Configure the CAN0. */
+ qtest_writel(qts, CAN0_BASE_ADDR + R_SRR_OFFSET, CONFIG_MODE);
+ qtest_writel(qts, CAN0_BASE_ADDR + R_MSR_OFFSET, SNOOP_MODE);
+ qtest_writel(qts, CAN0_BASE_ADDR + R_SRR_OFFSET, ENABLE_CAN);
+
+ qtest_writel(qts, CAN1_BASE_ADDR + R_SRR_OFFSET, ENABLE_CAN);
+ qtest_writel(qts, CAN1_BASE_ADDR + R_MSR_OFFSET, NORMAL_MODE);
+
+ /* Check here if CAN0 is in SNOOP mode and CAN1 in normal mode. */
+ status = qtest_readl(qts, CAN0_BASE_ADDR + R_SR_OFFSET);
+ g_assert_cmpint(status, ==, STATUS_SNOOP_MODE);
+
+ status = qtest_readl(qts, CAN1_BASE_ADDR + R_SR_OFFSET);
+ g_assert_cmpint(status, ==, STATUS_NORMAL_MODE);
+
+ send_data(qts, CAN1_BASE_ADDR, buf_tx);
+
+ read_data(qts, CAN0_BASE_ADDR, buf_rx);
+
+ match_rx_tx_data(buf_tx, buf_rx, can_timestamp);
+
+ qtest_quit(qts);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_func("/net/can/can_bus", test_can_bus);
+ qtest_add_func("/net/can/can_loopback", test_can_loopback);
+ qtest_add_func("/net/can/can_filter", test_can_filter);
+ qtest_add_func("/net/can/can_test_snoopmode", test_can_snoopmode);
+ qtest_add_func("/net/can/can_test_sleepmode", test_can_sleepmode);
+
+ return g_test_run();
+}