arm: Add PL031 test
PATCH v3 1/1 arm: Add PL031 test
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.
---
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
arm/Makefile.common +1 -0
@@ -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)
arm/pl031.c new file +263 -0
263 lines not on this page · raw message
lib/arm/asm/gic.h +1 -0
@@ -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>
Signature · 1 line
2.17.1
Quoted 85 lines · Alexander Graf
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
- Usedist_basefor gicv3v2 -> 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.carm/Makefile.common +1
-0@@ -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)arm/pl031.c new file +41
-0@@ -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 <[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 ];
arm/pl031.c line 51
Quoted 10 lines
arm/pl031.c new file +10
-0+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...
Quoted 128 lines
arm/pl031.c new file +128
-0+ +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) ;
arm/pl031.c line 189
Quoted 10 lines
arm/pl031.c new file +10
-0+ +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.
Quoted 89 lines
arm/pl031.c new file +74
-0+ +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 (); +}lib/arm/asm/gic.h +1
-0@@ -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> Signature · 1 line
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.
Thanks,
drew