c447312747
The global smp variables in riscv are replaced with smp machine properties. A local variable of the same name would be introduced in the declaration phase if it's used widely in the context OR replace it on the spot if it's only used once. No semantic changes. Signed-off-by: Like Xu <like.xu@linux.intel.com> Message-Id: <20190518205428.90532-6-like.xu@linux.intel.com> Reviewed-by: Alistair Francis <alistair.francis@wdc.com> [ehabkost: fix spike_board_init()] [ehabkost: fix riscv_sifive_e_soc_init()] Signed-off-by: Eduardo Habkost <ehabkost@redhat.com>
525 lines
18 KiB
C
525 lines
18 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/module.h"
|
|
#include "qemu/error-report.h"
|
|
#include "hw/sysbus.h"
|
|
#include "hw/pci/msi.h"
|
|
#include "hw/boards.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:
|
|
qemu_log_mask(LOG_GUEST_ERROR,
|
|
"%s: Invalid register read 0x%" HWADDR_PRIx "\n",
|
|
__func__, 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))
|
|
{
|
|
qemu_log_mask(LOG_GUEST_ERROR,
|
|
"%s: invalid pending write: 0x%" HWADDR_PRIx "",
|
|
__func__, 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:
|
|
qemu_log_mask(LOG_GUEST_ERROR,
|
|
"%s: Invalid register write 0x%" HWADDR_PRIx "\n",
|
|
__func__, 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)
|
|
{
|
|
MachineState *ms = MACHINE(qdev_get_machine());
|
|
unsigned int smp_cpus = ms->smp.cpus;
|
|
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;
|
|
}
|