/*
* GICv3 distributor and redistributor emulation
*
* GICv3 emulation is currently only supported on a GICv3 host (because
* we rely on the hardware's CPU interface virtualization support), but
* supports both hardware with or without the optional GICv2 backwards
* compatibility features.
*
* Limitations of the emulation:
* (RAZ/WI: read as zero, write ignore, RAO/WI: read as one, write ignore)
* - We do not support LPIs (yet). TYPER.LPIS is reported as 0 and is RAZ/WI.
* - We do not support the message based interrupts (MBIs) triggered by
* writes to the GICD_{SET,CLR}SPI_* registers. TYPER.MBIS is reported as 0.
* - We do not support the (optional) backwards compatibility feature.
* GICD_CTLR.ARE resets to 1 and is RAO/WI. If the _host_ GIC supports
* the compatiblity feature, you can use a GICv2 in the guest, though.
* - We only support a single security state. GICD_CTLR.DS is 1 and is RAO/WI.
* - Priorities are not emulated (same as the GICv2 emulation). Linux
* as a guest is fine with this, because it does not use priorities.
* - We only support Group1 interrupts. Again Linux uses only those.
*
* Copyright (C) 2014 ARM Ltd.
* Author: Andre Przywara <andre.przywara@arm.com>
*
* 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.
*
* This program is distributed in the hope that 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 <linux/cpu.h>
#include <linux/kvm.h>
#include <linux/kvm_host.h>
#include <linux/interrupt.h>
#include <linux/irqchip/arm-gic-v3.h>
#include <kvm/arm_vgic.h>
#include <asm/kvm_emulate.h>
#include <asm/kvm_arm.h>
#include <asm/kvm_mmu.h>
#include "vgic.h"
static bool handle_mmio_rao_wi(struct kvm_vcpu *vcpu,
struct kvm_exit_mmio *mmio, phys_addr_t offset)
{
u32 reg = 0xffffffff;
vgic_reg_access(mmio, ®, offset,
ACCESS_READ_VALUE | ACCESS_WRITE_IGNORED);
return false;
}
static bool handle_mmio_ctlr(struct kvm_vcpu *vcpu,
struct kvm_exit_mmio *mmio, phys_addr_t offset)
{
u32 reg = 0;
/*
* Force ARE and DS to 1, the guest cannot change this.
* For the time being we only support Group1 interrupts.
*/
if (vcpu->kvm->arch.vgic.enabled)
reg = GICD_CTLR_ENABLE_SS_G1;
reg |= GICD_CTLR_ARE_NS | GICD_CTLR_DS;
vgic_reg_access(mmio, ®, offset,
ACCESS_READ_VALUE | ACCESS_WRITE_VALUE);
if (mmio->is_write) {
vcpu->kvm->arch.vgic.enabled = !!(reg & GICD_CTLR_ENABLE_SS_G1);
vgic_update_state(vcpu->kvm);
return true;
}
return false;
}
/*
* As this implementation does not provide compatibility
* with GICv2 (ARE==1), we report zero CPUs in bits [5..7].
* Also LPIs and MBIs are not supported, so we set the respective bits to 0.
* Also we report at most 2**10=1024 interrupt IDs (to match 1024 SPIs).
*/
#define INTERRUPT_ID_BITS 10
static bool handle_mmio_typer(struct kvm_vcpu *vcpu,
struct kvm_exit_mmio *mmio, phys_addr_t offset)
{
u32 reg;
reg = (min(vcpu->kvm->arch.vgic.nr_irqs, 1024) >> 5) - 1;
reg |= (INTERRUPT_ID_BITS - 1) << 19;
vgic_reg_access(mmio, ®, offset,
ACCESS_READ_VALUE | ACCESS_WRITE_IGNORED);
return false;
}
static bool handle_mmio_iidr(struct kvm_vcpu *vcpu,
struct kvm_exit_mmio *mmio, phys_addr_t offset)
{
u32 reg;
reg = (PRODUCT_ID_KVM << 24) | (IMPLEMENTER_ARM << 0);
vgic_reg_access(mmio, ®, offset,
ACCESS_READ_VALUE | ACCESS_WRITE_IGNORED);
return false;
}
static bool handle_mmio_set_enable_reg_dist(struct kvm_vcpu *vcpu,
struct kvm_exit_mmio *mmio,
phys_addr_t offset)
{
if (likely(offset >= VGIC_NR_PRIVATE_IRQS / 8))
return vgic_handle_enable_reg(vcpu->kvm, mmio, offset,
vcpu->vcpu_id,
ACCESS_WRITE_SETBIT);
vgic_reg_access(mmio, NULL, offset,
ACCESS_READ_RAZ | ACCESS_WRITE_IGNORED);
return false;
}
static bool handle_mmio_clear_enable_reg_dist(struct kvm_vcpu *vcpu,
struct kvm_exit_mmio *mmio,
phys_addr_t offset)
{
if (likely(offset >= VGIC_NR_PRIVATE_IRQS / 8))
return vgic_handle_enable_reg(vcpu->kvm, mmio, offset,
vcpu->vcpu_id,
ACCESS_WRITE_CLEARBIT);
vgic_reg_access(mmio, NULL, offset,
ACCESS_READ_RAZ | ACCESS_WRITE_IGNORED);
return false;
}
static bool handle_mmio_set_pending_reg_dist(struct kvm_vcpu *vcpu,
struct kvm_exit_mmio *