/* * Verify PL031 functionality * * This test verifies whether the emulated PL031 behaves correctly. * * Copyright 2019 Amazon.com, Inc. or its affiliates. * Author: Alexander Graf * * This work is licensed under the terms of the GNU LGPL, version 2. */ #include #include #include #include #include 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 volatile 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 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(readl(&pl031->ris) == 1, " RTC RIS == 1"); report(readl(&pl031->mis) == 1, " RTC MIS == 1"); /* Writing one to bit zero should clear IRQ status */ writel(1, &pl031->icr); report(readl(&pl031->ris) == 0, " RTC RIS == 0"); report(readl(&pl031->mis) == 0, " RTC MIS == 0"); irq_triggered = true; } else { report_info("Unexpected interrupt: %"PRIu32"\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(!gic_irq_pending(), " RTC IRQ not delivered without mask"); /* Mask the IRQ so that it gets delivered */ writel(1, &pl031->imsc); report(gic_irq_pending(), " RTC IRQ pending now"); /* Enable retrieval of IRQ */ gic_enable_irq(pl031_irq); local_irq_enable(); report(irq_triggered, " IRQ triggered"); report(!gic_irq_pending(), " RTC IRQ not pending anymore"); if (!irq_triggered) { report_info(" RTC RIS: %"PRIx32, readl(&pl031->ris)); report_info(" RTC MIS: %"PRIx32, readl(&pl031->mis)); report_info(" RTC IMSC: %"PRIx32, readl(&pl031->imsc)); report_info(" GIC IRQs pending: %08"PRIx32" %08"PRIx32, 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; break; case 3: gic_ispendr = gicv3_dist_base() + GICD_ISPENDR; break; } } static int rtc_fdt_init(void) { const struct fdt_property *prop; const void *fdt = dt_fdt(); struct dt_pbus_reg base; int node, len; u32 *data; int ret; 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])); ret = dt_pbus_translate_node(node, 0, &base); assert(!ret); pl031 = ioremap(base.addr, base.size); 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_prefix_push("pl031"); report(!check_id(), "Periph/PCell IDs match"); report(!check_ro(), "R/O fields are R/O"); report(!check_rtc_freq(), "RTC ticks at 1HZ"); report(!gic_irq_pending(), "RTC IRQ not pending yet"); check_rtc_irq(); return report_summary(); }