nixos-riscv/tools/plictool.c

301 lines
7.0 KiB
C

/* Copyright (c) 2024 Barcelona Supercomputing Center (BSC)
* SPDX-License-Identifier: MIT
* Author: Rodrigo Arias Mallo <rodrigo.arias@bsc.es> */
/* 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 <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <errno.h>
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 <rodrigo.arias@bsc.es>\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;
}