/* Copyright (c) 2024 Barcelona Supercomputing Center (BSC) * SPDX-License-Identifier: MIT * Author: Rodrigo Arias Mallo */ /* Small utility to manage 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. * v0.0.5 (2024-10-02): Support other read/write operations. */ #define VERSION "v0.0.5" #include #include #include #include #include #include #include #include #include int operation; const char *plic_address_str = "0x40800000"; long ncontexts = 2L; long maxsources = 1024L; long nsources = 1024L; long context = -1; long source = -1; long value = -1; bool value_set = NULL; struct ctx { uint32_t threshold; }; struct source_ctx { bool enabled; bool masked; uint32_t threshold; const char *state; }; struct source { bool pending; bool show; uint32_t priority; struct source_ctx *ctx; long ncontexts; }; 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; } uint32_t claim_get(void *base, uint32_t ctx) { return read_reg(base, 0x200004L + ctx * 0x1000); } static void claim_set(void *base, uint32_t ctx, uint32_t value) { write_reg(base, 0x200004L + ctx * 0x1000, value); } uint32_t thre_get(void *base, uint32_t ctx) { return read_reg(base, 0x200000L + (ctx * 0x1000L)); } static void thre_set(void *base, uint32_t ctx, uint32_t value) { write_reg(base, 0x200000L + (ctx * 0x1000L), value); } uint32_t prio_get(void *base, uint32_t s) { return read_reg(base, s * 4L); } static void prio_set(void *base, uint32_t s, uint32_t value) { write_reg(base, s * 4L, value); } uint32_t pending_get(void *base, uint32_t s) { uint32_t offset = 0x1000L + (s / 32L) * 4L; uint32_t pending = read_reg(base, offset); long shift = s % 32L; return (pending >> shift) & 1; } static void pending_set(void *base, uint32_t s, uint32_t value) { uint32_t offset = 0x1000L + (s / 32L) * 4L; uint32_t pending = read_reg(base, offset); long shift = s % 32L; if (value) pending |= (1L << shift); else pending &= ~(1L << shift); write_reg(base, offset, pending); } uint32_t enable_get(void *base, uint32_t c, uint32_t s) { size_t off_en = 0x2000L + 0x80L * c + (s / 32L) * 4L; uint32_t enabled_reg = read_reg(base, off_en); long shift = s % 32L; return (enabled_reg >> shift) & 1; } static void enable_set(void *base, uint32_t c, uint32_t s, uint32_t value) { size_t off_en = 0x2000L + 0x80L * c + (s / 32L) * 4L; uint32_t enabled_reg = read_reg(base, off_en); long shift = s % 32L; if (value) enabled_reg |= (1L << shift); else enabled_reg &= ~(1L << shift); write_reg(base, off_en, enabled_reg); } static void source_init(struct source *src, long ncontexts) { memset(src, 0, sizeof(struct source)); src->ctx = calloc(ncontexts, sizeof(struct source_ctx)); src->ncontexts = ncontexts; if (src->ctx == NULL) { perror("calloc failed"); exit(1); } } static void source_reset(struct source *src) { src->pending = false; src->show = false; src->priority = 0; memset(src->ctx, 0, src->ncontexts * sizeof(struct source_ctx)); } static void source_free(struct source *src) { free(src->ctx); } static void source_read(struct source *src, void *base, long s) { 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)); bool ctx_show = 0; for (long c = 0; c < src->ncontexts; c++) { struct source_ctx *ctx = &src->ctx[c]; size_t off_en = 0x2000L + 0x80L * c + (s / 32L) * 4L; uint32_t enabled_reg = read_reg(base, off_en); ctx->enabled = (enabled_reg >> shift) & 1; ctx->threshold = read_reg(base, 0x200000L + (c * 0x1000L)); ctx->masked = src->priority <= ctx->threshold; ctx_show = ctx_show || ctx->enabled; if (!ctx->enabled) ctx->state = "-"; else if (ctx->masked) ctx->state = "masked"; else ctx->state = "firing"; } /* Show the source if it has some bit to non-zero */ src->show = src->pending || src->priority || ctx_show; } static void list_sources(void *base) { printf("Source\tPend\tPrio"); for (long i = 0; i < ncontexts; i++) { uint32_t threshold = read_reg(base, 0x200000L + (i * 0x1000L)); printf("\tC%ld(%u)", i, threshold); } printf("\n"); struct source s; source_init(&s, ncontexts); for (long i = 0; i < nsources; i++) { source_reset(&s); source_read(&s, base, i); if (!s.show) continue; printf("%ld\t%s\t%u", i, s.pending ? "yes" : "-", s.priority); for (long j = 0; j < ncontexts; j++) printf("\t%s", s.ctx[j].state); printf("\n"); } source_free(&s); } static void usage(void) { printf("plictool "VERSION" -- Rodrigo Arias Mallo \n"); fprintf(stderr, "Usage:\n" " plictool [-a addr] [-L] [-n nsrc] [-x nctx] # List (default)\n" " plictool [-a addr] -C ctx [-w value] # Claim\n" " plictool [-a addr] -T ctx [-w value] # Threshold\n" " plictool [-a addr] -I src [-w value] # Priority\n" " plictool [-a addr] -P src [-w value] # Pending\n" " plictool [-a addr] -E src -c ctx [-w value] # Enabled\n" " plictool -v # Version\n" ); exit(1); } int main(int argc, char *argv[]) { const char *memfile = "/dev/mem"; int opt; while ((opt = getopt(argc, argv, "f:a:LC:T:P:I:E:n:x:c:w:vh")) != -1) { switch (opt) { /* Common flags */ case 'f': memfile = optarg; break; case 'a': plic_address_str = optarg; break; case 'n': nsources = atol(optarg); break; case 'x': ncontexts = atol(optarg); break; case 'C': /* claim */ case 'T': /* threshold */ operation = opt; context = atol(optarg); break; case 'P': /* pending */ case 'I': /* priority */ case 'E': /* enable */ operation = opt; source = atol(optarg); break; case 'L': /* list */ operation = opt; break; case 'c': context = atol(optarg); break; case 'w': value = atol(optarg); value_set = true; break; case 'v': printf("plictool "VERSION"\n"); exit(0); case 'h': default: /* '?' */ usage(); break; } } if (operation == 'P' || operation == 'I' || operation == 'E') { if (source < 0) { fprintf(stderr, "missing source\n"); exit(1); } } if (operation == 'C' || operation == 'T' || operation == 'E') { if (context < 0) { fprintf(stderr, "missing context\n"); exit(1); } } 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 == 'C') { /* claim */ if (value_set) claim_set(map_base, context, value); else printf("%u\n", claim_get(map_base, context)); } else if (operation == 'T') { /* threshold */ if (value_set) thre_set(map_base, context, value); else printf("%u\n", thre_get(map_base, context)); } else if (operation == 'I') { /* priority */ if (value_set) prio_set(map_base, source, value); else printf("%u\n", prio_get(map_base, source)); } else if (operation == 'P') { /* pending */ if (value_set) pending_set(map_base, source, value); else printf("%u\n", pending_get(map_base, source)); } else if (operation == 'E') { /* enable */ if (value_set) enable_set(map_base, context, source, value); else printf("%u\n", enable_get(map_base, context, source)); } else /* list */ { list_sources(map_base); } munmap(map_base, map_size); close(fd); return 0; }