nixos-riscv/tools/plictool.c

389 lines
8.3 KiB
C

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