/* Copyright (c) 2024 Barcelona Supercomputing Center (BSC) * SPDX-License-Identifier: MIT * Author: Rodrigo Arias Mallo */ /* Small utility to dump the state of the PLIC. */ /* Changelog: * v0.0.1 (2024-09-03): Initial version. * v0.0.2 (2024-09-04): Print contexts in another line and masked information. * v0.0.3 (2024-09-04): Make output format more clear and add manual. * v0.0.4 (2024-09-30): Implement support for claiming an interrupt. */ #define VERSION "v0.0.4" #include #include #include #include #include #include #include #include enum op { LIST, CLAIM } operation = LIST; const char *plic_address_str = "0x40800000"; long ncontexts = 2L; long maxsources = 1024L; long nsources = 1024L; long claim_ctx = 0; uint32_t claim_value = 0; uint32_t *claim_pvalue = NULL; struct source_ctx { unsigned enabled; unsigned threshold; unsigned masked; }; struct source { }; static void usage(void) { printf( "NAME\n" " plictool - dump PLIC information\n" "\n" "SYNOPSIS\n" " plictool [-a addr] [-L] [-s nsources] [-c ncontexts]\n" " plictool [-a addr] [-C] [-c context] [-w value]\n" "\n" "DESCRIPTION\n" " Dumps information about the RISC-V PLIC interrupt controller.\n" " Optional arguments:\n" "\n" " -L Show PLIC information (default operation).\n" "\n" " -C context Claim an interrupt on the given context.\n" "\n" " -a addr The starting address of the PLIC.\n" " The default is 0x40800000.\n" "\n" " -s nsources Limit the number of sources to print.\n" " The default is 1024.\n" "\n" " -c ncontexts Limit the number of contexts to print.\n" " The default is 15872.\n" "\n" " -C ctx Claim the interrupt on the given context.\n" "\n" " -w value Use this value to claim an interrupt.\n" "\n" "OUTPUT FORMAT\n" " Information is printed for each source that has some bit set\n" " in the pending or priority registers, or it has some context\n" " enabled. The the following attributes are printed:\n" "\n" " src source number\n" " pend pending bit\n" " prio priority of the source\n" "\n" " Additionally, for each enabled context of the source the\n" " following attributes are printed for that context:\n" "\n" " ctx context number\n" " thre threshold value\n" " masked the source is masked (prio <= thre)\n" "\n" "AUTHOR\n" " Rodrigo Arias Mallo \n" "\n" "SEE ALSO\n" " See https://github.com/riscv/riscv-plic-spec/\n" "\n" ); exit(1); } static uint32_t read_reg(void *base, size_t offset) { volatile uint32_t *p = base + offset; return *p; } static void write_reg(void *base, size_t offset, uint32_t value) { volatile uint32_t *p = base + offset; *p = value; } static void claim_interrupt(void *base, uint32_t ctx, uint32_t *value) { size_t offset = 0x200004L + ctx * 0x1000; uint32_t r = read_reg(base, offset); uint32_t w = value ? *value : r; write_reg(base, offset, w); printf("claim ctx=%u r=%u w=%u\n", ctx, r, w); } static void source_init(struct source *src, long ncontexts) { memset(src, 0, sizeof(struct source)); src->ctx = calloc(ncontexts, sizeof(struct source_ctx)); if (src->ctx == NULL) { perror("calloc failed"); exit(1); } } static void source_reset(struct source *src, long ncontexts) { memset(src, 0, sizeof(struct source)); memset(src->ctx, 0, ncontexts * sizeof(struct source_ctx)); } static void source_free(struct source *src, long ncontexts) { free(src->ctx); } static void source_read(struct source *src, void *base, long s, long ncontexts) { uint32_t pending_reg = read_reg(base, 0x1000L + (s / 32L) * 4L) long shift = s % 32L; src->pending = (pending_reg >> shift) & 1; src->priority = read_reg(base, 0x0000L + (s * 4L)); for (long c = 0; c < ncontexts; c++) { struct source_ctx *ctx = &src->ctx[c]; size_t off_en = 0x2000L + 0x80L * c + (s / 32L) * 4L; uint32_t enabled_reg = reg_read(base, off_en); ctx->enabled = (enable_reg >> shift) & 1; ctx->threshold = read_reg(base, 0x200000L + (c * 0x1000L)); ctx->masked = priority <= threshold; } } static void list_sources(void *base) { struct source src; source_init(&src, ncontexts); for (long s = 0; s < nsources; s++) { source_read(&src, base, s, ncontexts); int printed_source = 0; uint32_t *pending_reg = base + 0x1000L + (s / 32L) * 4L; long shift = s % 32L; uint32_t pending = ((*pending_reg) >> shift) & 1; uint32_t *priority_reg = base + 0x0000L + (s * 4L); uint32_t priority = *priority_reg; const char *fmt = "src=%ld pend=%u prio=%u"; if (pending || priority) { printf(fmt, s, pending, priority); printed_source = 1; } int first_context = 1; int enabled_contexts = 0; int unmasked = 0; for (long c = 0; c < ncontexts; c++) { uint32_t *enable_reg = base + 0x2000L + 0x80L * c + (s / 32L) * 4L; uint32_t enabled = ((*enable_reg) >> shift) & 1; uint32_t *threshold_reg = base + 0x200000L + (c * 0x1000L); uint32_t threshold = *threshold_reg; if (!enabled) continue; if (!printed_source) { printf(fmt, s, pending, priority); printed_source = 1; } int is_masked = priority <= threshold; if (!is_masked) unmasked++; const char *masked = is_masked ? "masked" : "unmasked"; printf("\n ctx=%ld thre=%u %s", c, threshold, masked); enabled_contexts++; } if (printed_source) printf("\n"); } } int main(int argc, char *argv[]) { const char *memfile = "/dev/mem"; int opt; while ((opt = getopt(argc, argv, "f:a:hLs:c:C:w:")) != -1) { switch (opt) { case 'f': memfile = optarg; break; case 'a': plic_address_str = optarg; break; case 'L': /* default operation */ operation = LIST; break; case 's': nsources = atol(optarg); break; case 'c': ncontexts = atol(optarg); break; case 'C': operation = CLAIM; claim_ctx = atol(optarg); break; case 'w': claim_value = atol(optarg); claim_pvalue = &claim_value; break; case 'h': default: /* '?' */ usage(); } } unsigned long long plic_address = strtoull(plic_address_str, NULL, 16); printf("plictool "VERSION" addr=0x%08llx nsrc=%ld nctx=%ld\n", plic_address, nsources, ncontexts); int fd = open(memfile, O_RDWR | O_SYNC); if (fd == -1) { fprintf(stderr, "cannot open %s: %s", memfile, strerror(errno)); exit(1); } size_t map_size = 0x4000000UL; void *map_base = mmap(0, map_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, plic_address); if (map_base == MAP_FAILED) { perror("mmap failed"); if (errno == EPERM) { fprintf(stderr, "Have you disabled 'CONFIG_STRICT_DEVMEM' and " "'CONFIG_IO_STRICT_DEVMEM' in the kernel config?\n" "Hint: zgrep STRICT_DEVMEM /proc/config.gz\n"); } exit(1); } if (operation == CLAIM) claim_interrupt(map_base, claim_ctx, claim_pvalue); else if (operation == LIST) list_sources(map_base); munmap(map_base, map_size); close(fd); return 0; }