mirror of
https://github.com/clearlinux/kvmtool.git
synced 2026-08-25 15:36:20 +00:00
9dc5430ce7
All architectures are now doing the same thing for irq__alloc_line: 1. Initialise a global counter to some fixed offset 2. Return the current value of the counter and increment it This is better off in core code, with each architecture specifying the initial offset, which is specific to the interrupt controller being used by the guest. Signed-off-by: Will Deacon <will.deacon@arm.com> Signed-off-by: Pekka Enberg <penberg@kernel.org>
496 lines
11 KiB
C
496 lines
11 KiB
C
/*
|
|
* PAPR Virtualized Interrupt System, aka ICS/ICP aka xics
|
|
*
|
|
* Borrowed heavily from QEMU's xics.c,
|
|
* Copyright (c) 2010,2011 David Gibson, IBM Corporation.
|
|
*
|
|
* Modifications copyright 2011 Matt Evans <matt@ozlabs.org>, IBM Corporation.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 as published
|
|
* by the Free Software Foundation.
|
|
*/
|
|
|
|
#include "spapr.h"
|
|
#include "xics.h"
|
|
#include "kvm/util.h"
|
|
#include "kvm/kvm.h"
|
|
|
|
#include <stdio.h>
|
|
#include <malloc.h>
|
|
|
|
#define XICS_NUM_IRQS 1024
|
|
|
|
|
|
/* #define DEBUG_XICS yes */
|
|
#ifdef DEBUG_XICS
|
|
#define xics_dprintf(fmt, ...) \
|
|
do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0)
|
|
#else
|
|
#define xics_dprintf(fmt, ...) \
|
|
do { } while (0)
|
|
#endif
|
|
|
|
/*
|
|
* ICP: Presentation layer
|
|
*/
|
|
|
|
struct icp_server_state {
|
|
uint32_t xirr;
|
|
uint8_t pending_priority;
|
|
uint8_t mfrr;
|
|
struct kvm_cpu *cpu;
|
|
};
|
|
|
|
#define XICS_IRQ_OFFSET KVM_IRQ_OFFSET
|
|
#define XISR_MASK 0x00ffffff
|
|
#define CPPR_MASK 0xff000000
|
|
|
|
#define XISR(ss) (((ss)->xirr) & XISR_MASK)
|
|
#define CPPR(ss) (((ss)->xirr) >> 24)
|
|
|
|
struct ics_state;
|
|
|
|
struct icp_state {
|
|
unsigned long nr_servers;
|
|
struct icp_server_state *ss;
|
|
struct ics_state *ics;
|
|
};
|
|
|
|
static void ics_reject(struct ics_state *ics, int nr);
|
|
static void ics_resend(struct ics_state *ics);
|
|
static void ics_eoi(struct ics_state *ics, int nr);
|
|
|
|
static inline void cpu_irq_raise(struct kvm_cpu *vcpu)
|
|
{
|
|
xics_dprintf("INT1[%p]\n", vcpu);
|
|
kvm_cpu__irq(vcpu, POWER7_EXT_IRQ, 1);
|
|
}
|
|
|
|
static inline void cpu_irq_lower(struct kvm_cpu *vcpu)
|
|
{
|
|
xics_dprintf("INT0[%p]\n", vcpu);
|
|
kvm_cpu__irq(vcpu, POWER7_EXT_IRQ, 0);
|
|
}
|
|
|
|
static void icp_check_ipi(struct icp_state *icp, int server)
|
|
{
|
|
struct icp_server_state *ss = icp->ss + server;
|
|
|
|
if (XISR(ss) && (ss->pending_priority <= ss->mfrr)) {
|
|
return;
|
|
}
|
|
|
|
if (XISR(ss)) {
|
|
ics_reject(icp->ics, XISR(ss));
|
|
}
|
|
|
|
ss->xirr = (ss->xirr & ~XISR_MASK) | XICS_IPI;
|
|
ss->pending_priority = ss->mfrr;
|
|
cpu_irq_raise(ss->cpu);
|
|
}
|
|
|
|
static void icp_resend(struct icp_state *icp, int server)
|
|
{
|
|
struct icp_server_state *ss = icp->ss + server;
|
|
|
|
if (ss->mfrr < CPPR(ss)) {
|
|
icp_check_ipi(icp, server);
|
|
}
|
|
ics_resend(icp->ics);
|
|
}
|
|
|
|
static void icp_set_cppr(struct icp_state *icp, int server, uint8_t cppr)
|
|
{
|
|
struct icp_server_state *ss = icp->ss + server;
|
|
uint8_t old_cppr;
|
|
uint32_t old_xisr;
|
|
|
|
old_cppr = CPPR(ss);
|
|
ss->xirr = (ss->xirr & ~CPPR_MASK) | (cppr << 24);
|
|
|
|
if (cppr < old_cppr) {
|
|
if (XISR(ss) && (cppr <= ss->pending_priority)) {
|
|
old_xisr = XISR(ss);
|
|
ss->xirr &= ~XISR_MASK; /* Clear XISR */
|
|
cpu_irq_lower(ss->cpu);
|
|
ics_reject(icp->ics, old_xisr);
|
|
}
|
|
} else {
|
|
if (!XISR(ss)) {
|
|
icp_resend(icp, server);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void icp_set_mfrr(struct icp_state *icp, int nr, uint8_t mfrr)
|
|
{
|
|
struct icp_server_state *ss = icp->ss + nr;
|
|
|
|
ss->mfrr = mfrr;
|
|
if (mfrr < CPPR(ss)) {
|
|
icp_check_ipi(icp, nr);
|
|
}
|
|
}
|
|
|
|
static uint32_t icp_accept(struct icp_server_state *ss)
|
|
{
|
|
uint32_t xirr;
|
|
|
|
cpu_irq_lower(ss->cpu);
|
|
xirr = ss->xirr;
|
|
ss->xirr = ss->pending_priority << 24;
|
|
return xirr;
|
|
}
|
|
|
|
static void icp_eoi(struct icp_state *icp, int server, uint32_t xirr)
|
|
{
|
|
struct icp_server_state *ss = icp->ss + server;
|
|
|
|
ics_eoi(icp->ics, xirr & XISR_MASK);
|
|
/* Send EOI -> ICS */
|
|
ss->xirr = (ss->xirr & ~CPPR_MASK) | (xirr & CPPR_MASK);
|
|
if (!XISR(ss)) {
|
|
icp_resend(icp, server);
|
|
}
|
|
}
|
|
|
|
static void icp_irq(struct icp_state *icp, int server, int nr, uint8_t priority)
|
|
{
|
|
struct icp_server_state *ss = icp->ss + server;
|
|
xics_dprintf("icp_irq(nr %d, server %d, prio 0x%x)\n", nr, server, priority);
|
|
if ((priority >= CPPR(ss))
|
|
|| (XISR(ss) && (ss->pending_priority <= priority))) {
|
|
xics_dprintf("reject %d, CPPR 0x%x, XISR 0x%x, pprio 0x%x, prio 0x%x\n",
|
|
nr, CPPR(ss), XISR(ss), ss->pending_priority, priority);
|
|
ics_reject(icp->ics, nr);
|
|
} else {
|
|
if (XISR(ss)) {
|
|
xics_dprintf("reject %d, CPPR 0x%x, XISR 0x%x, pprio 0x%x, prio 0x%x\n",
|
|
nr, CPPR(ss), XISR(ss), ss->pending_priority, priority);
|
|
ics_reject(icp->ics, XISR(ss));
|
|
}
|
|
ss->xirr = (ss->xirr & ~XISR_MASK) | (nr & XISR_MASK);
|
|
ss->pending_priority = priority;
|
|
cpu_irq_raise(ss->cpu);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* ICS: Source layer
|
|
*/
|
|
|
|
struct ics_irq_state {
|
|
int server;
|
|
uint8_t priority;
|
|
uint8_t saved_priority;
|
|
int rejected:1;
|
|
int masked_pending:1;
|
|
};
|
|
|
|
struct ics_state {
|
|
unsigned int nr_irqs;
|
|
unsigned int offset;
|
|
struct ics_irq_state *irqs;
|
|
struct icp_state *icp;
|
|
};
|
|
|
|
static int ics_valid_irq(struct ics_state *ics, uint32_t nr)
|
|
{
|
|
return (nr >= ics->offset)
|
|
&& (nr < (ics->offset + ics->nr_irqs));
|
|
}
|
|
|
|
static void ics_set_irq_msi(struct ics_state *ics, int srcno, int val)
|
|
{
|
|
struct ics_irq_state *irq = ics->irqs + srcno;
|
|
|
|
if (val) {
|
|
if (irq->priority == 0xff) {
|
|
xics_dprintf(" irq pri ff, masked pending\n");
|
|
irq->masked_pending = 1;
|
|
} else {
|
|
icp_irq(ics->icp, irq->server, srcno + ics->offset, irq->priority);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void ics_reject_msi(struct ics_state *ics, int nr)
|
|
{
|
|
struct ics_irq_state *irq = ics->irqs + nr - ics->offset;
|
|
|
|
irq->rejected = 1;
|
|
}
|
|
|
|
static void ics_resend_msi(struct ics_state *ics)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < ics->nr_irqs; i++) {
|
|
struct ics_irq_state *irq = ics->irqs + i;
|
|
|
|
/* FIXME: filter by server#? */
|
|
if (irq->rejected) {
|
|
irq->rejected = 0;
|
|
if (irq->priority != 0xff) {
|
|
icp_irq(ics->icp, irq->server, i + ics->offset, irq->priority);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void ics_write_xive_msi(struct ics_state *ics, int nr, int server,
|
|
uint8_t priority)
|
|
{
|
|
struct ics_irq_state *irq = ics->irqs + nr - ics->offset;
|
|
|
|
irq->server = server;
|
|
irq->priority = priority;
|
|
xics_dprintf("ics_write_xive_msi(nr %d, server %d, pri 0x%x)\n", nr, server, priority);
|
|
|
|
if (!irq->masked_pending || (priority == 0xff)) {
|
|
return;
|
|
}
|
|
|
|
irq->masked_pending = 0;
|
|
icp_irq(ics->icp, server, nr, priority);
|
|
}
|
|
|
|
static void ics_reject(struct ics_state *ics, int nr)
|
|
{
|
|
ics_reject_msi(ics, nr);
|
|
}
|
|
|
|
static void ics_resend(struct ics_state *ics)
|
|
{
|
|
ics_resend_msi(ics);
|
|
}
|
|
|
|
static void ics_eoi(struct ics_state *ics, int nr)
|
|
{
|
|
}
|
|
|
|
/*
|
|
* Exported functions
|
|
*/
|
|
|
|
static target_ulong h_cppr(struct kvm_cpu *vcpu,
|
|
target_ulong opcode, target_ulong *args)
|
|
{
|
|
target_ulong cppr = args[0];
|
|
|
|
xics_dprintf("h_cppr(%lx)\n", cppr);
|
|
icp_set_cppr(vcpu->kvm->arch.icp, vcpu->cpu_id, cppr);
|
|
return H_SUCCESS;
|
|
}
|
|
|
|
static target_ulong h_ipi(struct kvm_cpu *vcpu,
|
|
target_ulong opcode, target_ulong *args)
|
|
{
|
|
target_ulong server = args[0];
|
|
target_ulong mfrr = args[1];
|
|
|
|
xics_dprintf("h_ipi(%lx, %lx)\n", server, mfrr);
|
|
if (server >= vcpu->kvm->arch.icp->nr_servers) {
|
|
return H_PARAMETER;
|
|
}
|
|
|
|
icp_set_mfrr(vcpu->kvm->arch.icp, server, mfrr);
|
|
return H_SUCCESS;
|
|
}
|
|
|
|
static target_ulong h_xirr(struct kvm_cpu *vcpu,
|
|
target_ulong opcode, target_ulong *args)
|
|
{
|
|
uint32_t xirr = icp_accept(vcpu->kvm->arch.icp->ss + vcpu->cpu_id);
|
|
|
|
xics_dprintf("h_xirr() = %x\n", xirr);
|
|
args[0] = xirr;
|
|
return H_SUCCESS;
|
|
}
|
|
|
|
static target_ulong h_eoi(struct kvm_cpu *vcpu,
|
|
target_ulong opcode, target_ulong *args)
|
|
{
|
|
target_ulong xirr = args[0];
|
|
|
|
xics_dprintf("h_eoi(%lx)\n", xirr);
|
|
icp_eoi(vcpu->kvm->arch.icp, vcpu->cpu_id, xirr);
|
|
return H_SUCCESS;
|
|
}
|
|
|
|
static void rtas_set_xive(struct kvm_cpu *vcpu, uint32_t token,
|
|
uint32_t nargs, target_ulong args,
|
|
uint32_t nret, target_ulong rets)
|
|
{
|
|
struct ics_state *ics = vcpu->kvm->arch.icp->ics;
|
|
uint32_t nr, server, priority;
|
|
|
|
if ((nargs != 3) || (nret != 1)) {
|
|
rtas_st(vcpu->kvm, rets, 0, -3);
|
|
return;
|
|
}
|
|
|
|
nr = rtas_ld(vcpu->kvm, args, 0);
|
|
server = rtas_ld(vcpu->kvm, args, 1);
|
|
priority = rtas_ld(vcpu->kvm, args, 2);
|
|
|
|
xics_dprintf("rtas_set_xive(%x,%x,%x)\n", nr, server, priority);
|
|
if (!ics_valid_irq(ics, nr) || (server >= ics->icp->nr_servers)
|
|
|| (priority > 0xff)) {
|
|
rtas_st(vcpu->kvm, rets, 0, -3);
|
|
return;
|
|
}
|
|
|
|
ics_write_xive_msi(ics, nr, server, priority);
|
|
|
|
rtas_st(vcpu->kvm, rets, 0, 0); /* Success */
|
|
}
|
|
|
|
static void rtas_get_xive(struct kvm_cpu *vcpu, uint32_t token,
|
|
uint32_t nargs, target_ulong args,
|
|
uint32_t nret, target_ulong rets)
|
|
{
|
|
struct ics_state *ics = vcpu->kvm->arch.icp->ics;
|
|
uint32_t nr;
|
|
|
|
if ((nargs != 1) || (nret != 3)) {
|
|
rtas_st(vcpu->kvm, rets, 0, -3);
|
|
return;
|
|
}
|
|
|
|
nr = rtas_ld(vcpu->kvm, args, 0);
|
|
|
|
if (!ics_valid_irq(ics, nr)) {
|
|
rtas_st(vcpu->kvm, rets, 0, -3);
|
|
return;
|
|
}
|
|
|
|
rtas_st(vcpu->kvm, rets, 0, 0); /* Success */
|
|
rtas_st(vcpu->kvm, rets, 1, ics->irqs[nr - ics->offset].server);
|
|
rtas_st(vcpu->kvm, rets, 2, ics->irqs[nr - ics->offset].priority);
|
|
}
|
|
|
|
static void rtas_int_off(struct kvm_cpu *vcpu, uint32_t token,
|
|
uint32_t nargs, target_ulong args,
|
|
uint32_t nret, target_ulong rets)
|
|
{
|
|
struct ics_state *ics = vcpu->kvm->arch.icp->ics;
|
|
uint32_t nr;
|
|
|
|
if ((nargs != 1) || (nret != 1)) {
|
|
rtas_st(vcpu->kvm, rets, 0, -3);
|
|
return;
|
|
}
|
|
|
|
nr = rtas_ld(vcpu->kvm, args, 0);
|
|
|
|
if (!ics_valid_irq(ics, nr)) {
|
|
rtas_st(vcpu->kvm, rets, 0, -3);
|
|
return;
|
|
}
|
|
|
|
/* ME: QEMU wrote xive_msi here, in #if 0. Deleted. */
|
|
|
|
rtas_st(vcpu->kvm, rets, 0, 0); /* Success */
|
|
}
|
|
|
|
static void rtas_int_on(struct kvm_cpu *vcpu, uint32_t token,
|
|
uint32_t nargs, target_ulong args,
|
|
uint32_t nret, target_ulong rets)
|
|
{
|
|
struct ics_state *ics = vcpu->kvm->arch.icp->ics;
|
|
uint32_t nr;
|
|
|
|
if ((nargs != 1) || (nret != 1)) {
|
|
rtas_st(vcpu->kvm, rets, 0, -3);
|
|
return;
|
|
}
|
|
|
|
nr = rtas_ld(vcpu->kvm, args, 0);
|
|
|
|
if (!ics_valid_irq(ics, nr)) {
|
|
rtas_st(vcpu->kvm, rets, 0, -3);
|
|
return;
|
|
}
|
|
|
|
/* ME: QEMU wrote xive_msi here, in #if 0. Deleted. */
|
|
|
|
rtas_st(vcpu->kvm, rets, 0, 0); /* Success */
|
|
}
|
|
|
|
static int xics_init(struct kvm *kvm)
|
|
{
|
|
unsigned int i;
|
|
struct icp_state *icp;
|
|
struct ics_state *ics;
|
|
int j;
|
|
|
|
icp = malloc(sizeof(*icp));
|
|
icp->nr_servers = kvm->nrcpus;
|
|
icp->ss = malloc(icp->nr_servers * sizeof(struct icp_server_state));
|
|
|
|
for (i = 0; i < icp->nr_servers; i++) {
|
|
icp->ss[i].xirr = 0;
|
|
icp->ss[i].pending_priority = 0;
|
|
icp->ss[i].cpu = 0;
|
|
icp->ss[i].mfrr = 0xff;
|
|
}
|
|
|
|
/*
|
|
* icp->ss[env->cpu_index].cpu is set by CPUs calling in to
|
|
* xics_cpu_register().
|
|
*/
|
|
|
|
ics = malloc(sizeof(*ics));
|
|
ics->nr_irqs = XICS_NUM_IRQS;
|
|
ics->offset = XICS_IRQ_OFFSET;
|
|
ics->irqs = malloc(ics->nr_irqs * sizeof(struct ics_irq_state));
|
|
|
|
icp->ics = ics;
|
|
ics->icp = icp;
|
|
|
|
for (i = 0; i < ics->nr_irqs; i++) {
|
|
ics->irqs[i].server = 0;
|
|
ics->irqs[i].priority = 0xff;
|
|
ics->irqs[i].saved_priority = 0xff;
|
|
ics->irqs[i].rejected = 0;
|
|
ics->irqs[i].masked_pending = 0;
|
|
}
|
|
|
|
spapr_register_hypercall(H_CPPR, h_cppr);
|
|
spapr_register_hypercall(H_IPI, h_ipi);
|
|
spapr_register_hypercall(H_XIRR, h_xirr);
|
|
spapr_register_hypercall(H_EOI, h_eoi);
|
|
|
|
spapr_rtas_register("ibm,set-xive", rtas_set_xive);
|
|
spapr_rtas_register("ibm,get-xive", rtas_get_xive);
|
|
spapr_rtas_register("ibm,int-off", rtas_int_off);
|
|
spapr_rtas_register("ibm,int-on", rtas_int_on);
|
|
|
|
for (j = 0; j < kvm->nrcpus; j++) {
|
|
struct kvm_cpu *vcpu = kvm->cpus[j];
|
|
|
|
if (vcpu->cpu_id >= icp->nr_servers)
|
|
die("Invalid server number for cpuid %ld\n", vcpu->cpu_id);
|
|
|
|
icp->ss[vcpu->cpu_id].cpu = vcpu;
|
|
}
|
|
|
|
kvm->arch.icp = icp;
|
|
|
|
return 0;
|
|
}
|
|
dev_base_init(xics_init);
|
|
|
|
|
|
void kvm__irq_line(struct kvm *kvm, int irq, int level)
|
|
{
|
|
/*
|
|
* Route event to ICS, which routes to ICP, which eventually does a
|
|
* kvm_cpu__irq(vcpu, POWER7_EXT_IRQ, 1)
|
|
*/
|
|
xics_dprintf("Raising IRQ %d -> %d\n", irq, level);
|
|
ics_set_irq_msi(kvm->arch.icp->ics, irq - kvm->arch.icp->ics->offset, level);
|
|
}
|