mirror of
https://github.com/qemu/qemu.git
synced 2025-08-06 03:44:14 +00:00

This patch fixes four different things, to maintain bisectability they have been merged into a single patch. The following fixes are below: sifive_plic: Fix incorrect irq calculation The irq is incorrectly calculated to be off by one. It has worked in the past as the priority_base offset has also been set incorrectly. We are about to fix the priority_base offset so first first the irq calculation. sifive_u: Fix PLIC priority base offset and numbering According to the FU540 manual the PLIC source priority address starts at an offset of 0x04 and not 0x00. The same manual also specifies that the PLIC only has 53 source priorities. Fix these two incorrect header files. We also need to over extend the plic_gpios[] array as the PLIC sources count from 1 and not 0. riscv: sifive_e: Fix PLIC priority base offset According to the FE31 manual the PLIC source priority address starts at an offset of 0x04 and not 0x00. riscv: virt: Fix PLIC priority base offset Update the virt offsets based on the newly updated SiFive U and SiFive E offsets. Signed-off-by: Alistair Francis <alistair.francis@wdc.com> Signed-off-by: Palmer Dabbelt <palmer@sifive.com>
515 lines
17 KiB
C
515 lines
17 KiB
C
/*
|
|
* SiFive PLIC (Platform Level Interrupt Controller)
|
|
*
|
|
* Copyright (c) 2017 SiFive, Inc.
|
|
*
|
|
* This provides a parameterizable interrupt controller based on SiFive's PLIC.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms and conditions of the GNU General Public License,
|
|
* version 2 or later, as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with
|
|
* this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
#include "qemu/log.h"
|
|
#include "qemu/error-report.h"
|
|
#include "hw/sysbus.h"
|
|
#include "hw/pci/msi.h"
|
|
#include "target/riscv/cpu.h"
|
|
#include "sysemu/sysemu.h"
|
|
#include "hw/riscv/sifive_plic.h"
|
|
|
|
#define RISCV_DEBUG_PLIC 0
|
|
|
|
static PLICMode char_to_mode(char c)
|
|
{
|
|
switch (c) {
|
|
case 'U': return PLICMode_U;
|
|
case 'S': return PLICMode_S;
|
|
case 'H': return PLICMode_H;
|
|
case 'M': return PLICMode_M;
|
|
default:
|
|
error_report("plic: invalid mode '%c'", c);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
static char mode_to_char(PLICMode m)
|
|
{
|
|
switch (m) {
|
|
case PLICMode_U: return 'U';
|
|
case PLICMode_S: return 'S';
|
|
case PLICMode_H: return 'H';
|
|
case PLICMode_M: return 'M';
|
|
default: return '?';
|
|
}
|
|
}
|
|
|
|
static void sifive_plic_print_state(SiFivePLICState *plic)
|
|
{
|
|
int i;
|
|
int addrid;
|
|
|
|
/* pending */
|
|
qemu_log("pending : ");
|
|
for (i = plic->bitfield_words - 1; i >= 0; i--) {
|
|
qemu_log("%08x", plic->pending[i]);
|
|
}
|
|
qemu_log("\n");
|
|
|
|
/* pending */
|
|
qemu_log("claimed : ");
|
|
for (i = plic->bitfield_words - 1; i >= 0; i--) {
|
|
qemu_log("%08x", plic->claimed[i]);
|
|
}
|
|
qemu_log("\n");
|
|
|
|
for (addrid = 0; addrid < plic->num_addrs; addrid++) {
|
|
qemu_log("hart%d-%c enable: ",
|
|
plic->addr_config[addrid].hartid,
|
|
mode_to_char(plic->addr_config[addrid].mode));
|
|
for (i = plic->bitfield_words - 1; i >= 0; i--) {
|
|
qemu_log("%08x", plic->enable[addrid * plic->bitfield_words + i]);
|
|
}
|
|
qemu_log("\n");
|
|
}
|
|
}
|
|
|
|
static uint32_t atomic_set_masked(uint32_t *a, uint32_t mask, uint32_t value)
|
|
{
|
|
uint32_t old, new, cmp = atomic_read(a);
|
|
|
|
do {
|
|
old = cmp;
|
|
new = (old & ~mask) | (value & mask);
|
|
cmp = atomic_cmpxchg(a, old, new);
|
|
} while (old != cmp);
|
|
|
|
return old;
|
|
}
|
|
|
|
static void sifive_plic_set_pending(SiFivePLICState *plic, int irq, bool level)
|
|
{
|
|
atomic_set_masked(&plic->pending[irq >> 5], 1 << (irq & 31), -!!level);
|
|
}
|
|
|
|
static void sifive_plic_set_claimed(SiFivePLICState *plic, int irq, bool level)
|
|
{
|
|
atomic_set_masked(&plic->claimed[irq >> 5], 1 << (irq & 31), -!!level);
|
|
}
|
|
|
|
static int sifive_plic_irqs_pending(SiFivePLICState *plic, uint32_t addrid)
|
|
{
|
|
int i, j;
|
|
for (i = 0; i < plic->bitfield_words; i++) {
|
|
uint32_t pending_enabled_not_claimed =
|
|
(plic->pending[i] & ~plic->claimed[i]) &
|
|
plic->enable[addrid * plic->bitfield_words + i];
|
|
if (!pending_enabled_not_claimed) {
|
|
continue;
|
|
}
|
|
for (j = 0; j < 32; j++) {
|
|
int irq = (i << 5) + j;
|
|
uint32_t prio = plic->source_priority[irq];
|
|
int enabled = pending_enabled_not_claimed & (1 << j);
|
|
if (enabled && prio > plic->target_priority[addrid]) {
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void sifive_plic_update(SiFivePLICState *plic)
|
|
{
|
|
int addrid;
|
|
|
|
/* raise irq on harts where this irq is enabled */
|
|
for (addrid = 0; addrid < plic->num_addrs; addrid++) {
|
|
uint32_t hartid = plic->addr_config[addrid].hartid;
|
|
PLICMode mode = plic->addr_config[addrid].mode;
|
|
CPUState *cpu = qemu_get_cpu(hartid);
|
|
CPURISCVState *env = cpu ? cpu->env_ptr : NULL;
|
|
if (!env) {
|
|
continue;
|
|
}
|
|
int level = sifive_plic_irqs_pending(plic, addrid);
|
|
switch (mode) {
|
|
case PLICMode_M:
|
|
riscv_cpu_update_mip(RISCV_CPU(cpu), MIP_MEIP, BOOL_TO_MASK(level));
|
|
break;
|
|
case PLICMode_S:
|
|
riscv_cpu_update_mip(RISCV_CPU(cpu), MIP_SEIP, BOOL_TO_MASK(level));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (RISCV_DEBUG_PLIC) {
|
|
sifive_plic_print_state(plic);
|
|
}
|
|
}
|
|
|
|
void sifive_plic_raise_irq(SiFivePLICState *plic, uint32_t irq)
|
|
{
|
|
sifive_plic_set_pending(plic, irq, true);
|
|
sifive_plic_update(plic);
|
|
}
|
|
|
|
void sifive_plic_lower_irq(SiFivePLICState *plic, uint32_t irq)
|
|
{
|
|
sifive_plic_set_pending(plic, irq, false);
|
|
sifive_plic_update(plic);
|
|
}
|
|
|
|
static uint32_t sifive_plic_claim(SiFivePLICState *plic, uint32_t addrid)
|
|
{
|
|
int i, j;
|
|
for (i = 0; i < plic->bitfield_words; i++) {
|
|
uint32_t pending_enabled_not_claimed =
|
|
(plic->pending[i] & ~plic->claimed[i]) &
|
|
plic->enable[addrid * plic->bitfield_words + i];
|
|
if (!pending_enabled_not_claimed) {
|
|
continue;
|
|
}
|
|
for (j = 0; j < 32; j++) {
|
|
int irq = (i << 5) + j;
|
|
uint32_t prio = plic->source_priority[irq];
|
|
int enabled = pending_enabled_not_claimed & (1 << j);
|
|
if (enabled && prio > plic->target_priority[addrid]) {
|
|
sifive_plic_set_pending(plic, irq, false);
|
|
sifive_plic_set_claimed(plic, irq, true);
|
|
return irq;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static uint64_t sifive_plic_read(void *opaque, hwaddr addr, unsigned size)
|
|
{
|
|
SiFivePLICState *plic = opaque;
|
|
|
|
/* writes must be 4 byte words */
|
|
if ((addr & 0x3) != 0) {
|
|
goto err;
|
|
}
|
|
|
|
if (addr >= plic->priority_base && /* 4 bytes per source */
|
|
addr < plic->priority_base + (plic->num_sources << 2))
|
|
{
|
|
uint32_t irq = ((addr - plic->priority_base) >> 2) + 1;
|
|
if (RISCV_DEBUG_PLIC) {
|
|
qemu_log("plic: read priority: irq=%d priority=%d\n",
|
|
irq, plic->source_priority[irq]);
|
|
}
|
|
return plic->source_priority[irq];
|
|
} else if (addr >= plic->pending_base && /* 1 bit per source */
|
|
addr < plic->pending_base + (plic->num_sources >> 3))
|
|
{
|
|
uint32_t word = (addr - plic->pending_base) >> 2;
|
|
if (RISCV_DEBUG_PLIC) {
|
|
qemu_log("plic: read pending: word=%d value=%d\n",
|
|
word, plic->pending[word]);
|
|
}
|
|
return plic->pending[word];
|
|
} else if (addr >= plic->enable_base && /* 1 bit per source */
|
|
addr < plic->enable_base + plic->num_addrs * plic->enable_stride)
|
|
{
|
|
uint32_t addrid = (addr - plic->enable_base) / plic->enable_stride;
|
|
uint32_t wordid = (addr & (plic->enable_stride - 1)) >> 2;
|
|
if (wordid < plic->bitfield_words) {
|
|
if (RISCV_DEBUG_PLIC) {
|
|
qemu_log("plic: read enable: hart%d-%c word=%d value=%x\n",
|
|
plic->addr_config[addrid].hartid,
|
|
mode_to_char(plic->addr_config[addrid].mode), wordid,
|
|
plic->enable[addrid * plic->bitfield_words + wordid]);
|
|
}
|
|
return plic->enable[addrid * plic->bitfield_words + wordid];
|
|
}
|
|
} else if (addr >= plic->context_base && /* 1 bit per source */
|
|
addr < plic->context_base + plic->num_addrs * plic->context_stride)
|
|
{
|
|
uint32_t addrid = (addr - plic->context_base) / plic->context_stride;
|
|
uint32_t contextid = (addr & (plic->context_stride - 1));
|
|
if (contextid == 0) {
|
|
if (RISCV_DEBUG_PLIC) {
|
|
qemu_log("plic: read priority: hart%d-%c priority=%x\n",
|
|
plic->addr_config[addrid].hartid,
|
|
mode_to_char(plic->addr_config[addrid].mode),
|
|
plic->target_priority[addrid]);
|
|
}
|
|
return plic->target_priority[addrid];
|
|
} else if (contextid == 4) {
|
|
uint32_t value = sifive_plic_claim(plic, addrid);
|
|
if (RISCV_DEBUG_PLIC) {
|
|
qemu_log("plic: read claim: hart%d-%c irq=%x\n",
|
|
plic->addr_config[addrid].hartid,
|
|
mode_to_char(plic->addr_config[addrid].mode),
|
|
value);
|
|
sifive_plic_print_state(plic);
|
|
}
|
|
return value;
|
|
}
|
|
}
|
|
|
|
err:
|
|
error_report("plic: invalid register read: %08x", (uint32_t)addr);
|
|
return 0;
|
|
}
|
|
|
|
static void sifive_plic_write(void *opaque, hwaddr addr, uint64_t value,
|
|
unsigned size)
|
|
{
|
|
SiFivePLICState *plic = opaque;
|
|
|
|
/* writes must be 4 byte words */
|
|
if ((addr & 0x3) != 0) {
|
|
goto err;
|
|
}
|
|
|
|
if (addr >= plic->priority_base && /* 4 bytes per source */
|
|
addr < plic->priority_base + (plic->num_sources << 2))
|
|
{
|
|
uint32_t irq = ((addr - plic->priority_base) >> 2) + 1;
|
|
plic->source_priority[irq] = value & 7;
|
|
if (RISCV_DEBUG_PLIC) {
|
|
qemu_log("plic: write priority: irq=%d priority=%d\n",
|
|
irq, plic->source_priority[irq]);
|
|
}
|
|
return;
|
|
} else if (addr >= plic->pending_base && /* 1 bit per source */
|
|
addr < plic->pending_base + (plic->num_sources >> 3))
|
|
{
|
|
error_report("plic: invalid pending write: %08x", (uint32_t)addr);
|
|
return;
|
|
} else if (addr >= plic->enable_base && /* 1 bit per source */
|
|
addr < plic->enable_base + plic->num_addrs * plic->enable_stride)
|
|
{
|
|
uint32_t addrid = (addr - plic->enable_base) / plic->enable_stride;
|
|
uint32_t wordid = (addr & (plic->enable_stride - 1)) >> 2;
|
|
if (wordid < plic->bitfield_words) {
|
|
plic->enable[addrid * plic->bitfield_words + wordid] = value;
|
|
if (RISCV_DEBUG_PLIC) {
|
|
qemu_log("plic: write enable: hart%d-%c word=%d value=%x\n",
|
|
plic->addr_config[addrid].hartid,
|
|
mode_to_char(plic->addr_config[addrid].mode), wordid,
|
|
plic->enable[addrid * plic->bitfield_words + wordid]);
|
|
}
|
|
return;
|
|
}
|
|
} else if (addr >= plic->context_base && /* 4 bytes per reg */
|
|
addr < plic->context_base + plic->num_addrs * plic->context_stride)
|
|
{
|
|
uint32_t addrid = (addr - plic->context_base) / plic->context_stride;
|
|
uint32_t contextid = (addr & (plic->context_stride - 1));
|
|
if (contextid == 0) {
|
|
if (RISCV_DEBUG_PLIC) {
|
|
qemu_log("plic: write priority: hart%d-%c priority=%x\n",
|
|
plic->addr_config[addrid].hartid,
|
|
mode_to_char(plic->addr_config[addrid].mode),
|
|
plic->target_priority[addrid]);
|
|
}
|
|
if (value <= plic->num_priorities) {
|
|
plic->target_priority[addrid] = value;
|
|
sifive_plic_update(plic);
|
|
}
|
|
return;
|
|
} else if (contextid == 4) {
|
|
if (RISCV_DEBUG_PLIC) {
|
|
qemu_log("plic: write claim: hart%d-%c irq=%x\n",
|
|
plic->addr_config[addrid].hartid,
|
|
mode_to_char(plic->addr_config[addrid].mode),
|
|
(uint32_t)value);
|
|
}
|
|
if (value < plic->num_sources) {
|
|
sifive_plic_set_claimed(plic, value, false);
|
|
sifive_plic_update(plic);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
err:
|
|
error_report("plic: invalid register write: %08x", (uint32_t)addr);
|
|
}
|
|
|
|
static const MemoryRegionOps sifive_plic_ops = {
|
|
.read = sifive_plic_read,
|
|
.write = sifive_plic_write,
|
|
.endianness = DEVICE_LITTLE_ENDIAN,
|
|
.valid = {
|
|
.min_access_size = 4,
|
|
.max_access_size = 4
|
|
}
|
|
};
|
|
|
|
static Property sifive_plic_properties[] = {
|
|
DEFINE_PROP_STRING("hart-config", SiFivePLICState, hart_config),
|
|
DEFINE_PROP_UINT32("num-sources", SiFivePLICState, num_sources, 0),
|
|
DEFINE_PROP_UINT32("num-priorities", SiFivePLICState, num_priorities, 0),
|
|
DEFINE_PROP_UINT32("priority-base", SiFivePLICState, priority_base, 0),
|
|
DEFINE_PROP_UINT32("pending-base", SiFivePLICState, pending_base, 0),
|
|
DEFINE_PROP_UINT32("enable-base", SiFivePLICState, enable_base, 0),
|
|
DEFINE_PROP_UINT32("enable-stride", SiFivePLICState, enable_stride, 0),
|
|
DEFINE_PROP_UINT32("context-base", SiFivePLICState, context_base, 0),
|
|
DEFINE_PROP_UINT32("context-stride", SiFivePLICState, context_stride, 0),
|
|
DEFINE_PROP_UINT32("aperture-size", SiFivePLICState, aperture_size, 0),
|
|
DEFINE_PROP_END_OF_LIST(),
|
|
};
|
|
|
|
/*
|
|
* parse PLIC hart/mode address offset config
|
|
*
|
|
* "M" 1 hart with M mode
|
|
* "MS,MS" 2 harts, 0-1 with M and S mode
|
|
* "M,MS,MS,MS,MS" 5 harts, 0 with M mode, 1-5 with M and S mode
|
|
*/
|
|
static void parse_hart_config(SiFivePLICState *plic)
|
|
{
|
|
int addrid, hartid, modes;
|
|
const char *p;
|
|
char c;
|
|
|
|
/* count and validate hart/mode combinations */
|
|
addrid = 0, hartid = 0, modes = 0;
|
|
p = plic->hart_config;
|
|
while ((c = *p++)) {
|
|
if (c == ',') {
|
|
addrid += ctpop8(modes);
|
|
modes = 0;
|
|
hartid++;
|
|
} else {
|
|
int m = 1 << char_to_mode(c);
|
|
if (modes == (modes | m)) {
|
|
error_report("plic: duplicate mode '%c' in config: %s",
|
|
c, plic->hart_config);
|
|
exit(1);
|
|
}
|
|
modes |= m;
|
|
}
|
|
}
|
|
if (modes) {
|
|
addrid += ctpop8(modes);
|
|
}
|
|
hartid++;
|
|
|
|
/* store hart/mode combinations */
|
|
plic->num_addrs = addrid;
|
|
plic->addr_config = g_new(PLICAddr, plic->num_addrs);
|
|
addrid = 0, hartid = 0;
|
|
p = plic->hart_config;
|
|
while ((c = *p++)) {
|
|
if (c == ',') {
|
|
hartid++;
|
|
} else {
|
|
plic->addr_config[addrid].addrid = addrid;
|
|
plic->addr_config[addrid].hartid = hartid;
|
|
plic->addr_config[addrid].mode = char_to_mode(c);
|
|
addrid++;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void sifive_plic_irq_request(void *opaque, int irq, int level)
|
|
{
|
|
SiFivePLICState *plic = opaque;
|
|
if (RISCV_DEBUG_PLIC) {
|
|
qemu_log("sifive_plic_irq_request: irq=%d level=%d\n", irq, level);
|
|
}
|
|
sifive_plic_set_pending(plic, irq, level > 0);
|
|
sifive_plic_update(plic);
|
|
}
|
|
|
|
static void sifive_plic_realize(DeviceState *dev, Error **errp)
|
|
{
|
|
SiFivePLICState *plic = SIFIVE_PLIC(dev);
|
|
int i;
|
|
|
|
memory_region_init_io(&plic->mmio, OBJECT(dev), &sifive_plic_ops, plic,
|
|
TYPE_SIFIVE_PLIC, plic->aperture_size);
|
|
parse_hart_config(plic);
|
|
plic->bitfield_words = (plic->num_sources + 31) >> 5;
|
|
plic->source_priority = g_new0(uint32_t, plic->num_sources);
|
|
plic->target_priority = g_new(uint32_t, plic->num_addrs);
|
|
plic->pending = g_new0(uint32_t, plic->bitfield_words);
|
|
plic->claimed = g_new0(uint32_t, plic->bitfield_words);
|
|
plic->enable = g_new0(uint32_t, plic->bitfield_words * plic->num_addrs);
|
|
sysbus_init_mmio(SYS_BUS_DEVICE(dev), &plic->mmio);
|
|
qdev_init_gpio_in(dev, sifive_plic_irq_request, plic->num_sources);
|
|
|
|
/* We can't allow the supervisor to control SEIP as this would allow the
|
|
* supervisor to clear a pending external interrupt which will result in
|
|
* lost a interrupt in the case a PLIC is attached. The SEIP bit must be
|
|
* hardware controlled when a PLIC is attached.
|
|
*/
|
|
for (i = 0; i < smp_cpus; i++) {
|
|
RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(i));
|
|
if (riscv_cpu_claim_interrupts(cpu, MIP_SEIP) < 0) {
|
|
error_report("SEIP already claimed");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
msi_nonbroken = true;
|
|
}
|
|
|
|
static void sifive_plic_class_init(ObjectClass *klass, void *data)
|
|
{
|
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
|
|
|
dc->props = sifive_plic_properties;
|
|
dc->realize = sifive_plic_realize;
|
|
}
|
|
|
|
static const TypeInfo sifive_plic_info = {
|
|
.name = TYPE_SIFIVE_PLIC,
|
|
.parent = TYPE_SYS_BUS_DEVICE,
|
|
.instance_size = sizeof(SiFivePLICState),
|
|
.class_init = sifive_plic_class_init,
|
|
};
|
|
|
|
static void sifive_plic_register_types(void)
|
|
{
|
|
type_register_static(&sifive_plic_info);
|
|
}
|
|
|
|
type_init(sifive_plic_register_types)
|
|
|
|
/*
|
|
* Create PLIC device.
|
|
*/
|
|
DeviceState *sifive_plic_create(hwaddr addr, char *hart_config,
|
|
uint32_t num_sources, uint32_t num_priorities,
|
|
uint32_t priority_base, uint32_t pending_base,
|
|
uint32_t enable_base, uint32_t enable_stride,
|
|
uint32_t context_base, uint32_t context_stride,
|
|
uint32_t aperture_size)
|
|
{
|
|
DeviceState *dev = qdev_create(NULL, TYPE_SIFIVE_PLIC);
|
|
assert(enable_stride == (enable_stride & -enable_stride));
|
|
assert(context_stride == (context_stride & -context_stride));
|
|
qdev_prop_set_string(dev, "hart-config", hart_config);
|
|
qdev_prop_set_uint32(dev, "num-sources", num_sources);
|
|
qdev_prop_set_uint32(dev, "num-priorities", num_priorities);
|
|
qdev_prop_set_uint32(dev, "priority-base", priority_base);
|
|
qdev_prop_set_uint32(dev, "pending-base", pending_base);
|
|
qdev_prop_set_uint32(dev, "enable-base", enable_base);
|
|
qdev_prop_set_uint32(dev, "enable-stride", enable_stride);
|
|
qdev_prop_set_uint32(dev, "context-base", context_base);
|
|
qdev_prop_set_uint32(dev, "context-stride", context_stride);
|
|
qdev_prop_set_uint32(dev, "aperture-size", aperture_size);
|
|
qdev_init_nofail(dev);
|
|
sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, addr);
|
|
return dev;
|
|
}
|