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 <[email protected]>

---

v1 -> v2:

  - Use FDT to find base, irq and existence
  - Put isb after timer read
  - Use dist_base for gicv3
---
 arm/Makefile.common |   1 +
 arm/pl031.c         | 265 ++++++++++++++++++++++++++++++++++++++++++++
 lib/arm/asm/gic.h   |   1 +
 3 files changed, 267 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..d975937
--- /dev/null
+++ b/arm/pl031.c
@@ -0,0 +1,265 @@
+/*
+ * Verify PL031 functionality
+ *
+ * This test verifies whether the emulated PL031 behaves correctly.
+ *
+ * Copyright 2019 Amazon.com, Inc. or its affiliates.
+ * Author: Alexander Graf <[email protected]>
+ *
+ * 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 uint64_t read_timer(void)
+{
+       uint64_t r = read_sysreg(cntpct_el0);
+       isb();
+
+       return r;
+}
+
+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 = read_timer();
+       uint64_t target_tick = before_tick + (cntfrq * seconds_to_wait);
+
+       /* Wait for 2 seconds */
+       while (read_timer() < 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 = read_timer();
+       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);
+
+       install_irq_handler(EL1H_IRQ, irq_handler);
+
+       /* Wait until 2 seconds are over */
+       while (read_timer() < 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 = (void*)((ulong)fdt32_to_cpu(data[0]) << 32 | 
fdt32_to_cpu(data[1]));
+
+       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
[email protected]
https://lists.cs.columbia.edu/mailman/listinfo/kvmarm

Reply via email to