* [PATCH kvm-unit-tests v3] arm: Add PL031 test
@ 2019-07-25 12:17 Alexander Graf
2019-07-25 12:59 ` Andrew Jones
0 siblings, 1 reply; 2+ messages in thread
From: Alexander Graf @ 2019-07-25 12:17 UTC (permalink / raw)
To: kvm; +Cc: Marc Zyngier, Andre Przywara, Paolo Bonzini, kvmarm
This patch adds a unit test for the PL031 RTC that is used in the virt machine.
It just pokes basic functionality. I've mostly written it to familiarize myself
with the device, but I suppose having the test around does not hurt, as it also
exercises the GIC SPI interrupt path.
Signed-off-by: Alexander Graf <graf@amazon.com>
---
v1 -> v2:
- Use FDT to find base, irq and existence
- Put isb after timer read
- Use dist_base for gicv3
v2 -> v3
- Enable compilation on 32bit ARM target
- Use ioremap
---
arm/Makefile.common | 1 +
arm/pl031.c | 263 ++++++++++++++++++++++++++++++++++++++++++++
lib/arm/asm/gic.h | 1 +
3 files changed, 265 insertions(+)
create mode 100644 arm/pl031.c
diff --git a/arm/Makefile.common b/arm/Makefile.common
index f0c4b5d..b8988f2 100644
--- a/arm/Makefile.common
+++ b/arm/Makefile.common
@@ -11,6 +11,7 @@ tests-common += $(TEST_DIR)/pmu.flat
tests-common += $(TEST_DIR)/gic.flat
tests-common += $(TEST_DIR)/psci.flat
tests-common += $(TEST_DIR)/sieve.flat
+tests-common += $(TEST_DIR)/pl031.flat
tests-all = $(tests-common) $(tests)
all: directories $(tests-all)
diff --git a/arm/pl031.c b/arm/pl031.c
new file mode 100644
index 0000000..febc56e
--- /dev/null
+++ b/arm/pl031.c
@@ -0,0 +1,263 @@
+/*
+ * Verify PL031 functionality
+ *
+ * This test verifies whether the emulated PL031 behaves correctly.
+ *
+ * Copyright 2019 Amazon.com, Inc. or its affiliates.
+ * Author: Alexander Graf <graf@amazon.com>
+ *
+ * This work is licensed under the terms of the GNU LGPL, version 2.
+ */
+#include <libcflat.h>
+#include <devicetree.h>
+#include <asm/processor.h>
+#include <asm/io.h>
+#include <asm/gic.h>
+
+struct pl031_regs {
+ uint32_t dr; /* Data Register */
+ uint32_t mr; /* Match Register */
+ uint32_t lr; /* Load Register */
+ union {
+ uint8_t cr; /* Control Register */
+ uint32_t cr32;
+ };
+ union {
+ uint8_t imsc; /* Interrupt Mask Set or Clear register */
+ uint32_t imsc32;
+ };
+ union {
+ uint8_t ris; /* Raw Interrupt Status */
+ uint32_t ris32;
+ };
+ union {
+ uint8_t mis; /* Masked Interrupt Status */
+ uint32_t mis32;
+ };
+ union {
+ uint8_t icr; /* Interrupt Clear Register */
+ uint32_t icr32;
+ };
+ uint32_t reserved[1008];
+ uint32_t periph_id[4];
+ uint32_t pcell_id[4];
+};
+
+static u32 cntfrq;
+static struct pl031_regs *pl031;
+static int pl031_irq;
+static void *gic_ispendr;
+static void *gic_isenabler;
+static bool irq_triggered;
+
+static int check_id(void)
+{
+ uint32_t id[] = { 0x31, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1 };
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(id); i++)
+ if (id[i] != readl(&pl031->periph_id[i]))
+ return 1;
+
+ return 0;
+}
+
+static int check_ro(void)
+{
+ uint32_t offs[] = { offsetof(struct pl031_regs, ris),
+ offsetof(struct pl031_regs, mis),
+ offsetof(struct pl031_regs, periph_id[0]),
+ offsetof(struct pl031_regs, periph_id[1]),
+ offsetof(struct pl031_regs, periph_id[2]),
+ offsetof(struct pl031_regs, periph_id[3]),
+ offsetof(struct pl031_regs, pcell_id[0]),
+ offsetof(struct pl031_regs, pcell_id[1]),
+ offsetof(struct pl031_regs, pcell_id[2]),
+ offsetof(struct pl031_regs, pcell_id[3]) };
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(offs); i++) {
+ uint32_t before32;
+ uint16_t before16;
+ uint8_t before8;
+ void *addr = (void*)pl031 + offs[i];
+ uint32_t poison = 0xdeadbeefULL;
+
+ before8 = readb(addr);
+ before16 = readw(addr);
+ before32 = readl(addr);
+
+ writeb(poison, addr);
+ writew(poison, addr);
+ writel(poison, addr);
+
+ if (before8 != readb(addr))
+ return 1;
+ if (before16 != readw(addr))
+ return 1;
+ if (before32 != readl(addr))
+ return 1;
+ }
+
+ return 0;
+}
+
+static int check_rtc_freq(void)
+{
+ uint32_t seconds_to_wait = 2;
+ uint32_t before = readl(&pl031->dr);
+ uint64_t before_tick = get_cntvct();
+ uint64_t target_tick = before_tick + (cntfrq * seconds_to_wait);
+
+ /* Wait for 2 seconds */
+ while (get_cntvct() < target_tick) ;
+
+ if (readl(&pl031->dr) != before + seconds_to_wait)
+ return 1;
+
+ return 0;
+}
+
+static bool gic_irq_pending(void)
+{
+ uint32_t offset = (pl031_irq / 32) * 4;
+
+ return readl(gic_ispendr + offset) & (1 << (pl031_irq & 31));
+}
+
+static void gic_irq_unmask(void)
+{
+ uint32_t offset = (pl031_irq / 32) * 4;
+
+ writel(1 << (pl031_irq & 31), gic_isenabler + offset);
+}
+
+static void irq_handler(struct pt_regs *regs)
+{
+ u32 irqstat = gic_read_iar();
+ u32 irqnr = gic_iar_irqnr(irqstat);
+
+ gic_write_eoir(irqstat);
+
+ if (irqnr == pl031_irq) {
+ report(" RTC RIS == 1", readl(&pl031->ris) == 1);
+ report(" RTC MIS == 1", readl(&pl031->mis) == 1);
+
+ /* Writing any value should clear IRQ status */
+ writel(0x80000000ULL, &pl031->icr);
+
+ report(" RTC RIS == 0", readl(&pl031->ris) == 0);
+ report(" RTC MIS == 0", readl(&pl031->mis) == 0);
+ irq_triggered = true;
+ } else {
+ report_info("Unexpected interrupt: %d\n", irqnr);
+ return;
+ }
+}
+
+static int check_rtc_irq(void)
+{
+ uint32_t seconds_to_wait = 1;
+ uint32_t before = readl(&pl031->dr);
+ uint64_t before_tick = get_cntvct();
+ uint64_t target_tick = before_tick + (cntfrq * (seconds_to_wait + 1));
+
+ report_info("Checking IRQ trigger (MR)");
+
+ irq_triggered = false;
+
+ /* Fire IRQ in 1 second */
+ writel(before + seconds_to_wait, &pl031->mr);
+
+#ifdef __aarch64__
+ install_irq_handler(EL1H_IRQ, irq_handler);
+#else
+ install_exception_handler(EXCPTN_IRQ, irq_handler);
+#endif
+
+ /* Wait until 2 seconds are over */
+ while (get_cntvct() < target_tick) ;
+
+ report(" RTC IRQ not delivered without mask", !gic_irq_pending());
+
+ /* Mask the IRQ so that it gets delivered */
+ writel(1, &pl031->imsc);
+ report(" RTC IRQ pending now", gic_irq_pending());
+
+ /* Enable retrieval of IRQ */
+ gic_irq_unmask();
+ local_irq_enable();
+
+ report(" IRQ triggered", irq_triggered);
+ report(" RTC IRQ not pending anymore", !gic_irq_pending());
+ if (!irq_triggered) {
+ report_info(" RTC RIS: %x", readl(&pl031->ris));
+ report_info(" RTC MIS: %x", readl(&pl031->mis));
+ report_info(" RTC IMSC: %x", readl(&pl031->imsc));
+ report_info(" GIC IRQs pending: %08x %08x", readl(gic_ispendr), readl(gic_ispendr + 4));
+ }
+
+ local_irq_disable();
+ return 0;
+}
+
+static void rtc_irq_init(void)
+{
+ gic_enable_defaults();
+
+ switch (gic_version()) {
+ case 2:
+ gic_ispendr = gicv2_dist_base() + GICD_ISPENDR;
+ gic_isenabler = gicv2_dist_base() + GICD_ISENABLER;
+ break;
+ case 3:
+ gic_ispendr = gicv3_dist_base() + GICD_ISPENDR;
+ gic_isenabler = gicv3_dist_base() + GICD_ISENABLER;
+ break;
+ }
+}
+
+static int rtc_fdt_init(void)
+{
+ const struct fdt_property *prop;
+ const void *fdt = dt_fdt();
+ int node, len;
+ u32 *data;
+
+ node = fdt_node_offset_by_compatible(fdt, -1, "arm,pl031");
+ if (node < 0)
+ return -1;
+
+ prop = fdt_get_property(fdt, node, "interrupts", &len);
+ assert(prop && len == (3 * sizeof(u32)));
+ data = (u32 *)prop->data;
+ assert(data[0] == 0); /* SPI */
+ pl031_irq = SPI(fdt32_to_cpu(data[1]));
+
+ prop = fdt_get_property(fdt, node, "reg", &len);
+ assert(prop && len == (2 * sizeof(u64)));
+ data = (u32 *)prop->data;
+ pl031 = ioremap((uint64_t)fdt32_to_cpu(data[0]) << 32 |
+ fdt32_to_cpu(data[1]),
+ fdt32_to_cpu(data[3]));
+
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ cntfrq = get_cntfrq();
+ rtc_irq_init();
+ if (rtc_fdt_init()) {
+ report_skip("Skipping PL031 tests. No device present.");
+ return 0;
+ }
+
+ report("Periph/PCell IDs match", !check_id());
+ report("R/O fields are R/O", !check_ro());
+ report("RTC ticks at 1HZ", !check_rtc_freq());
+ report("RTC IRQ not pending yet", !gic_irq_pending());
+ check_rtc_irq();
+
+ return report_summary();
+}
diff --git a/lib/arm/asm/gic.h b/lib/arm/asm/gic.h
index f6dfb90..1fc10a0 100644
--- a/lib/arm/asm/gic.h
+++ b/lib/arm/asm/gic.h
@@ -41,6 +41,7 @@
#include <asm/gic-v3.h>
#define PPI(irq) ((irq) + 16)
+#define SPI(irq) ((irq) + GIC_FIRST_SPI)
#ifndef __ASSEMBLY__
#include <asm/cpumask.h>
--
2.17.1
_______________________________________________
kvmarm mailing list
kvmarm@lists.cs.columbia.edu
https://lists.cs.columbia.edu/mailman/listinfo/kvmarm
^ permalink raw reply related [flat|nested] 2+ messages in thread
* Re: [PATCH kvm-unit-tests v3] arm: Add PL031 test
2019-07-25 12:17 [PATCH kvm-unit-tests v3] arm: Add PL031 test Alexander Graf
@ 2019-07-25 12:59 ` Andrew Jones
0 siblings, 0 replies; 2+ messages in thread
From: Andrew Jones @ 2019-07-25 12:59 UTC (permalink / raw)
To: Alexander Graf; +Cc: kvm, Marc Zyngier, Andre Przywara, Paolo Bonzini, kvmarm
On Thu, Jul 25, 2019 at 02:17:32PM +0200, Alexander Graf wrote:
> This patch adds a unit test for the PL031 RTC that is used in the virt machine.
> It just pokes basic functionality. I've mostly written it to familiarize myself
> with the device, but I suppose having the test around does not hurt, as it also
> exercises the GIC SPI interrupt path.
>
> Signed-off-by: Alexander Graf <graf@amazon.com>
>
> ---
>
> v1 -> v2:
>
> - Use FDT to find base, irq and existence
> - Put isb after timer read
> - Use dist_base for gicv3
>
> v2 -> v3
>
> - Enable compilation on 32bit ARM target
> - Use ioremap
> ---
> arm/Makefile.common | 1 +
> arm/pl031.c | 263 ++++++++++++++++++++++++++++++++++++++++++++
> lib/arm/asm/gic.h | 1 +
> 3 files changed, 265 insertions(+)
> create mode 100644 arm/pl031.c
>
> diff --git a/arm/Makefile.common b/arm/Makefile.common
> index f0c4b5d..b8988f2 100644
> --- a/arm/Makefile.common
> +++ b/arm/Makefile.common
> @@ -11,6 +11,7 @@ tests-common += $(TEST_DIR)/pmu.flat
> tests-common += $(TEST_DIR)/gic.flat
> tests-common += $(TEST_DIR)/psci.flat
> tests-common += $(TEST_DIR)/sieve.flat
> +tests-common += $(TEST_DIR)/pl031.flat
>
> tests-all = $(tests-common) $(tests)
> all: directories $(tests-all)
> diff --git a/arm/pl031.c b/arm/pl031.c
> new file mode 100644
> index 0000000..febc56e
> --- /dev/null
> +++ b/arm/pl031.c
> @@ -0,0 +1,263 @@
> +/*
> + * Verify PL031 functionality
> + *
> + * This test verifies whether the emulated PL031 behaves correctly.
> + *
> + * Copyright 2019 Amazon.com, Inc. or its affiliates.
> + * Author: Alexander Graf <graf@amazon.com>
> + *
> + * This work is licensed under the terms of the GNU LGPL, version 2.
> + */
> +#include <libcflat.h>
> +#include <devicetree.h>
> +#include <asm/processor.h>
> +#include <asm/io.h>
> +#include <asm/gic.h>
> +
> +struct pl031_regs {
> + uint32_t dr; /* Data Register */
> + uint32_t mr; /* Match Register */
> + uint32_t lr; /* Load Register */
> + union {
> + uint8_t cr; /* Control Register */
> + uint32_t cr32;
> + };
> + union {
> + uint8_t imsc; /* Interrupt Mask Set or Clear register */
> + uint32_t imsc32;
> + };
> + union {
> + uint8_t ris; /* Raw Interrupt Status */
> + uint32_t ris32;
> + };
> + union {
> + uint8_t mis; /* Masked Interrupt Status */
> + uint32_t mis32;
> + };
> + union {
> + uint8_t icr; /* Interrupt Clear Register */
> + uint32_t icr32;
> + };
> + uint32_t reserved[1008];
> + uint32_t periph_id[4];
> + uint32_t pcell_id[4];
> +};
> +
> +static u32 cntfrq;
> +static struct pl031_regs *pl031;
> +static int pl031_irq;
> +static void *gic_ispendr;
> +static void *gic_isenabler;
> +static bool irq_triggered;
It'd be safer to make irq_triggered volatile, but...
> +
> +static int check_id(void)
> +{
> + uint32_t id[] = { 0x31, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1 };
> + int i;
> +
> + for (i = 0; i < ARRAY_SIZE(id); i++)
> + if (id[i] != readl(&pl031->periph_id[i]))
> + return 1;
> +
> + return 0;
> +}
> +
> +static int check_ro(void)
> +{
> + uint32_t offs[] = { offsetof(struct pl031_regs, ris),
> + offsetof(struct pl031_regs, mis),
> + offsetof(struct pl031_regs, periph_id[0]),
> + offsetof(struct pl031_regs, periph_id[1]),
> + offsetof(struct pl031_regs, periph_id[2]),
> + offsetof(struct pl031_regs, periph_id[3]),
> + offsetof(struct pl031_regs, pcell_id[0]),
> + offsetof(struct pl031_regs, pcell_id[1]),
> + offsetof(struct pl031_regs, pcell_id[2]),
> + offsetof(struct pl031_regs, pcell_id[3]) };
> + int i;
> +
> + for (i = 0; i < ARRAY_SIZE(offs); i++) {
> + uint32_t before32;
> + uint16_t before16;
> + uint8_t before8;
> + void *addr = (void*)pl031 + offs[i];
> + uint32_t poison = 0xdeadbeefULL;
> +
> + before8 = readb(addr);
> + before16 = readw(addr);
> + before32 = readl(addr);
> +
> + writeb(poison, addr);
> + writew(poison, addr);
> + writel(poison, addr);
> +
> + if (before8 != readb(addr))
> + return 1;
> + if (before16 != readw(addr))
> + return 1;
> + if (before32 != readl(addr))
> + return 1;
> + }
> +
> + return 0;
> +}
> +
> +static int check_rtc_freq(void)
> +{
> + uint32_t seconds_to_wait = 2;
> + uint32_t before = readl(&pl031->dr);
> + uint64_t before_tick = get_cntvct();
> + uint64_t target_tick = before_tick + (cntfrq * seconds_to_wait);
> +
> + /* Wait for 2 seconds */
> + while (get_cntvct() < target_tick) ;
> +
> + if (readl(&pl031->dr) != before + seconds_to_wait)
> + return 1;
> +
> + return 0;
> +}
> +
> +static bool gic_irq_pending(void)
> +{
> + uint32_t offset = (pl031_irq / 32) * 4;
> +
> + return readl(gic_ispendr + offset) & (1 << (pl031_irq & 31));
> +}
> +
> +static void gic_irq_unmask(void)
> +{
> + uint32_t offset = (pl031_irq / 32) * 4;
> +
> + writel(1 << (pl031_irq & 31), gic_isenabler + offset);
> +}
> +
> +static void irq_handler(struct pt_regs *regs)
> +{
> + u32 irqstat = gic_read_iar();
> + u32 irqnr = gic_iar_irqnr(irqstat);
> +
> + gic_write_eoir(irqstat);
> +
> + if (irqnr == pl031_irq) {
> + report(" RTC RIS == 1", readl(&pl031->ris) == 1);
> + report(" RTC MIS == 1", readl(&pl031->mis) == 1);
> +
> + /* Writing any value should clear IRQ status */
> + writel(0x80000000ULL, &pl031->icr);
> +
> + report(" RTC RIS == 0", readl(&pl031->ris) == 0);
> + report(" RTC MIS == 0", readl(&pl031->mis) == 0);
> + irq_triggered = true;
> + } else {
> + report_info("Unexpected interrupt: %d\n", irqnr);
> + return;
> + }
> +}
> +
> +static int check_rtc_irq(void)
> +{
> + uint32_t seconds_to_wait = 1;
> + uint32_t before = readl(&pl031->dr);
> + uint64_t before_tick = get_cntvct();
> + uint64_t target_tick = before_tick + (cntfrq * (seconds_to_wait + 1));
> +
> + report_info("Checking IRQ trigger (MR)");
> +
> + irq_triggered = false;
> +
> + /* Fire IRQ in 1 second */
> + writel(before + seconds_to_wait, &pl031->mr);
> +
> +#ifdef __aarch64__
> + install_irq_handler(EL1H_IRQ, irq_handler);
> +#else
> + install_exception_handler(EXCPTN_IRQ, irq_handler);
> +#endif
> +
> + /* Wait until 2 seconds are over */
> + while (get_cntvct() < target_tick) ;
> +
> + report(" RTC IRQ not delivered without mask", !gic_irq_pending());
> +
> + /* Mask the IRQ so that it gets delivered */
> + writel(1, &pl031->imsc);
> + report(" RTC IRQ pending now", gic_irq_pending());
> +
> + /* Enable retrieval of IRQ */
> + gic_irq_unmask();
> + local_irq_enable();
...things are working because local_irq_enable() also works as a compiler
barrier.
> +
> + report(" IRQ triggered", irq_triggered);
> + report(" RTC IRQ not pending anymore", !gic_irq_pending());
> + if (!irq_triggered) {
> + report_info(" RTC RIS: %x", readl(&pl031->ris));
> + report_info(" RTC MIS: %x", readl(&pl031->mis));
> + report_info(" RTC IMSC: %x", readl(&pl031->imsc));
> + report_info(" GIC IRQs pending: %08x %08x", readl(gic_ispendr), readl(gic_ispendr + 4));
> + }
> +
> + local_irq_disable();
> + return 0;
> +}
> +
> +static void rtc_irq_init(void)
> +{
> + gic_enable_defaults();
> +
> + switch (gic_version()) {
> + case 2:
> + gic_ispendr = gicv2_dist_base() + GICD_ISPENDR;
> + gic_isenabler = gicv2_dist_base() + GICD_ISENABLER;
> + break;
> + case 3:
> + gic_ispendr = gicv3_dist_base() + GICD_ISPENDR;
> + gic_isenabler = gicv3_dist_base() + GICD_ISENABLER;
> + break;
> + }
> +}
> +
> +static int rtc_fdt_init(void)
> +{
> + const struct fdt_property *prop;
> + const void *fdt = dt_fdt();
> + int node, len;
> + u32 *data;
> +
> + node = fdt_node_offset_by_compatible(fdt, -1, "arm,pl031");
> + if (node < 0)
> + return -1;
> +
> + prop = fdt_get_property(fdt, node, "interrupts", &len);
> + assert(prop && len == (3 * sizeof(u32)));
> + data = (u32 *)prop->data;
> + assert(data[0] == 0); /* SPI */
> + pl031_irq = SPI(fdt32_to_cpu(data[1]));
> +
> + prop = fdt_get_property(fdt, node, "reg", &len);
> + assert(prop && len == (2 * sizeof(u64)));
> + data = (u32 *)prop->data;
> + pl031 = ioremap((uint64_t)fdt32_to_cpu(data[0]) << 32 |
> + fdt32_to_cpu(data[1]),
> + fdt32_to_cpu(data[3]));
> +
> + return 0;
> +}
> +
> +int main(int argc, char **argv)
> +{
> + cntfrq = get_cntfrq();
> + rtc_irq_init();
> + if (rtc_fdt_init()) {
> + report_skip("Skipping PL031 tests. No device present.");
> + return 0;
> + }
> +
> + report("Periph/PCell IDs match", !check_id());
> + report("R/O fields are R/O", !check_ro());
> + report("RTC ticks at 1HZ", !check_rtc_freq());
> + report("RTC IRQ not pending yet", !gic_irq_pending());
> + check_rtc_irq();
> +
> + return report_summary();
> +}
> diff --git a/lib/arm/asm/gic.h b/lib/arm/asm/gic.h
> index f6dfb90..1fc10a0 100644
> --- a/lib/arm/asm/gic.h
> +++ b/lib/arm/asm/gic.h
> @@ -41,6 +41,7 @@
> #include <asm/gic-v3.h>
>
> #define PPI(irq) ((irq) + 16)
> +#define SPI(irq) ((irq) + GIC_FIRST_SPI)
>
> #ifndef __ASSEMBLY__
> #include <asm/cpumask.h>
> --
> 2.17.1
>
I didn't review wrt to the device, only wrt to the test framework, but
besides my suggestion that volatile or an explicit compiler barrier be
used for irq_triggered, then it looks good to me.
Reviewed-by: Andrew Jones <drjones@redhat.com>
Thanks,
drew
_______________________________________________
kvmarm mailing list
kvmarm@lists.cs.columbia.edu
https://lists.cs.columbia.edu/mailman/listinfo/kvmarm
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2019-07-25 12:59 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-07-25 12:17 [PATCH kvm-unit-tests v3] arm: Add PL031 test Alexander Graf
2019-07-25 12:59 ` Andrew Jones
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).