The GICv3 ITS code currently owns vgic_add_lpi(), even though the first half of the helper is not ITS specific. It allocates or finds the VM-wide vgic_irq object in lpi_xa and initializes the common LPI state before the GICv3 ITS code reads the property table and pending state. GICv5 needs to reuse the same LPI object lifetime and refcounting, but without inheriting GICv3 ITS policy such as forcing LPIs into Group 1 or reading the GICv3 property table. Move the common allocation and xarray insertion logic into vgic.c, expose it through vgic.h, and keep a GICv3 ITS wrapper for the property-table and pending-state synchronization. This keeps the GICv3 behaviour unchanged while making the common LPI storage usable by other VGIC users. Signed-off-by: Sascha Bischoff --- arch/arm64/kvm/vgic/vgic-its.c | 73 +++++----------------------------- arch/arm64/kvm/vgic/vgic.c | 72 +++++++++++++++++++++++++++++++++ arch/arm64/kvm/vgic/vgic.h | 2 + 3 files changed, 83 insertions(+), 64 deletions(-) diff --git a/arch/arm64/kvm/vgic/vgic-its.c b/arch/arm64/kvm/vgic/vgic-its.c index e5657cb603567..8e7bee1bae95d 100644 --- a/arch/arm64/kvm/vgic/vgic-its.c +++ b/arch/arm64/kvm/vgic/vgic-its.c @@ -66,77 +66,22 @@ static int update_lpi_config(struct kvm *kvm, struct vgic_irq *irq, __ret; \ }) -/* - * Creates a new (reference to a) struct vgic_irq for a given LPI. - * If this LPI is already mapped on another ITS, we increase its refcount - * and return a pointer to the existing structure. - * If this is a "new" LPI, we allocate and initialize a new struct vgic_irq. - * This function returns a pointer to the _unlocked_ structure. - */ -static struct vgic_irq *vgic_add_lpi(struct kvm *kvm, u32 intid, - struct kvm_vcpu *vcpu) +static struct vgic_irq *vgic_its_add_lpi(struct kvm *kvm, u32 intid, + struct kvm_vcpu *vcpu) { - struct vgic_dist *dist = &kvm->arch.vgic; - struct vgic_irq *irq = vgic_get_irq(kvm, intid), *oldirq; - unsigned long flags; + struct vgic_irq *irq; int ret; - /* In this case there is no put, since we keep the reference. */ + irq = vgic_get_irq(kvm, intid); if (irq) return irq; - irq = kzalloc_obj(struct vgic_irq, GFP_KERNEL_ACCOUNT); - if (!irq) - return ERR_PTR(-ENOMEM); - - ret = xa_reserve_irq(&dist->lpi_xa, intid, GFP_KERNEL_ACCOUNT); - if (ret) { - kfree(irq); - return ERR_PTR(ret); - } - - INIT_LIST_HEAD(&irq->ap_list); - raw_spin_lock_init(&irq->irq_lock); + irq = vgic_add_lpi(kvm, intid, vcpu); + if (IS_ERR(irq)) + return irq; - irq->config = VGIC_CONFIG_EDGE; - refcount_set(&irq->refcount, 1); - irq->intid = intid; - irq->target_vcpu = vcpu; irq->group = 1; - xa_lock_irqsave(&dist->lpi_xa, flags); - - /* - * There could be a race with another vgic_add_lpi(), so we need to - * check that we don't add a second list entry with the same LPI. - */ - oldirq = xa_load(&dist->lpi_xa, intid); - if (vgic_try_get_irq_ref(oldirq)) { - /* Someone was faster with adding this LPI, lets use that. */ - kfree(irq); - irq = oldirq; - } else { - /* - * The entry is either empty or contains a dead LPI (refcount=0) - * from the deferred release path, pending cleanup by - * vgic_release_deleted_lpis(). Evict and free it if present. - */ - oldirq = __xa_store(&dist->lpi_xa, intid, irq, - GFP_NOWAIT | __GFP_ACCOUNT); - ret = xa_err(oldirq); - if (ret) { - xa_unlock_irqrestore(&dist->lpi_xa, flags); - kfree(irq); - - return ERR_PTR(ret); - } - - if (oldirq && !WARN_ON_ONCE(refcount_read(&oldirq->refcount))) - kfree_rcu(oldirq, rcu); - } - - xa_unlock_irqrestore(&dist->lpi_xa, flags); - /* * We "cache" the configuration table entries in our struct vgic_irq's. * However we only have those structs for mapped IRQs, so we read in @@ -1092,7 +1037,7 @@ static int vgic_its_cmd_handle_mapi(struct kvm *kvm, struct vgic_its *its, if (its_is_collection_mapped(collection)) vcpu = collection_to_vcpu(kvm, collection); - irq = vgic_add_lpi(kvm, lpi_nr, vcpu); + irq = vgic_its_add_lpi(kvm, lpi_nr, vcpu); if (IS_ERR(irq)) { if (new_coll) vgic_its_free_collection(its, coll_id); @@ -2203,7 +2148,7 @@ static int vgic_its_restore_ite(struct vgic_its *its, u32 event_id, if (its_is_collection_mapped(collection)) vcpu = kvm_get_vcpu_by_id(kvm, collection->target_addr); - irq = vgic_add_lpi(kvm, lpi_id, vcpu); + irq = vgic_its_add_lpi(kvm, lpi_id, vcpu); if (IS_ERR(irq)) { its_free_ite(kvm, ite); return PTR_ERR(irq); diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c index c2726d4c06e6e..bc0171c479a95 100644 --- a/arch/arm64/kvm/vgic/vgic.c +++ b/arch/arm64/kvm/vgic/vgic.c @@ -79,6 +79,78 @@ static struct vgic_irq *vgic_get_lpi(struct kvm *kvm, u32 intid) return irq; } +/* + * Creates a new (reference to a) struct vgic_irq for a given LPI. + * If this LPI already exists, we increase its refcount and return it. + * If this is a "new" LPI, we allocate and initialize a new struct vgic_irq. + * This function returns a pointer to the _unlocked_ structure. + */ +struct vgic_irq *vgic_add_lpi(struct kvm *kvm, u32 intid, + struct kvm_vcpu *vcpu) +{ + struct vgic_dist *dist = &kvm->arch.vgic; + struct vgic_irq *irq = vgic_get_irq(kvm, intid), *oldirq; + unsigned long flags; + int ret; + + /* In this case there is no put, since we keep the reference. */ + if (irq) + return irq; + + irq = kzalloc_obj(struct vgic_irq, GFP_KERNEL_ACCOUNT); + if (!irq) + return ERR_PTR(-ENOMEM); + + ret = xa_reserve_irq(&dist->lpi_xa, intid, GFP_KERNEL_ACCOUNT); + if (ret) { + kfree(irq); + return ERR_PTR(ret); + } + + INIT_LIST_HEAD(&irq->ap_list); + raw_spin_lock_init(&irq->irq_lock); + + irq->config = VGIC_CONFIG_EDGE; + refcount_set(&irq->refcount, 1); + irq->intid = intid; + irq->target_vcpu = vcpu; + + xa_lock_irqsave(&dist->lpi_xa, flags); + + /* + * There could be a race with another vgic_add_lpi(), so we need to + * check that we don't add a second list entry with the same LPI. + */ + oldirq = xa_load(&dist->lpi_xa, intid); + if (vgic_try_get_irq_ref(oldirq)) { + /* Someone was faster with adding this LPI, lets use that. */ + kfree(irq); + irq = oldirq; + } else { + /* + * The entry is either empty or contains a dead LPI (refcount=0) + * from the deferred release path, pending cleanup by + * vgic_release_deleted_lpis(). Evict and free it if present. + */ + oldirq = __xa_store(&dist->lpi_xa, intid, irq, + GFP_NOWAIT | __GFP_ACCOUNT); + ret = xa_err(oldirq); + if (ret) { + xa_unlock_irqrestore(&dist->lpi_xa, flags); + kfree(irq); + + return ERR_PTR(ret); + } + + if (oldirq && !WARN_ON_ONCE(refcount_read(&oldirq->refcount))) + kfree_rcu(oldirq, rcu); + } + + xa_unlock_irqrestore(&dist->lpi_xa, flags); + + return irq; +} + /* * This looks up the virtual interrupt ID to get the corresponding * struct vgic_irq. It also increases the refcount, so any caller is expected diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h index 97fc3bdf520e5..c33cdd7098df5 100644 --- a/arch/arm64/kvm/vgic/vgic.h +++ b/arch/arm64/kvm/vgic/vgic.h @@ -280,6 +280,8 @@ vgic_get_mmio_region(struct kvm_vcpu *vcpu, struct vgic_io_device *iodev, gpa_t addr, int len); struct vgic_irq *vgic_get_irq(struct kvm *kvm, u32 intid); struct vgic_irq *vgic_get_vcpu_irq(struct kvm_vcpu *vcpu, u32 intid); +struct vgic_irq *vgic_add_lpi(struct kvm *kvm, u32 intid, + struct kvm_vcpu *vcpu); void vgic_put_irq(struct kvm *kvm, struct vgic_irq *irq); struct kvm_vcpu *vgic_target_oracle(struct vgic_irq *irq); bool vgic_get_phys_line_level(struct vgic_irq *irq); -- 2.34.1 The ITS MMIO callbacks currently take a struct vgic_its pointer, which ties them to the GICv3 ITS. GICv5 needs to use the same MMIO dispatch path with a different device structure. Pass an opaque device pointer to the callbacks and cast it in the GICv3 handlers. This does not change GICv3 behaviour. Signed-off-by: Sascha Bischoff --- arch/arm64/kvm/vgic/vgic-its.c | 55 +++++++++++++++++---------------- arch/arm64/kvm/vgic/vgic-mmio.h | 6 ++-- 2 files changed, 32 insertions(+), 29 deletions(-) diff --git a/arch/arm64/kvm/vgic/vgic-its.c b/arch/arm64/kvm/vgic/vgic-its.c index 8e7bee1bae95d..dff942b4f4233 100644 --- a/arch/arm64/kvm/vgic/vgic-its.c +++ b/arch/arm64/kvm/vgic/vgic-its.c @@ -388,10 +388,10 @@ static int its_sync_lpi_pending_table(struct kvm_vcpu *vcpu) return ret; } -static unsigned long vgic_mmio_read_its_typer(struct kvm *kvm, - struct vgic_its *its, +static unsigned long vgic_mmio_read_its_typer(struct kvm *kvm, void *dev, gpa_t addr, unsigned int len) { + struct vgic_its *its = dev; const struct vgic_its_abi *abi = vgic_its_get_abi(its); u64 reg = GITS_TYPER_PLPIS; @@ -410,10 +410,10 @@ static unsigned long vgic_mmio_read_its_typer(struct kvm *kvm, return extract_bytes(reg, addr & 7, len); } -static unsigned long vgic_mmio_read_its_iidr(struct kvm *kvm, - struct vgic_its *its, +static unsigned long vgic_mmio_read_its_iidr(struct kvm *kvm, void *dev, gpa_t addr, unsigned int len) { + struct vgic_its *its = dev; u32 val; val = (its->abi_rev << GITS_IIDR_REV_SHIFT) & GITS_IIDR_REV_MASK; @@ -421,11 +421,11 @@ static unsigned long vgic_mmio_read_its_iidr(struct kvm *kvm, return val; } -static int vgic_mmio_uaccess_write_its_iidr(struct kvm *kvm, - struct vgic_its *its, +static int vgic_mmio_uaccess_write_its_iidr(struct kvm *kvm, void *dev, gpa_t addr, unsigned int len, unsigned long val) { + struct vgic_its *its = dev; u32 rev = GITS_IIDR_REV(val); if (rev >= NR_ITS_ABIS) @@ -435,7 +435,7 @@ static int vgic_mmio_uaccess_write_its_iidr(struct kvm *kvm, } static unsigned long vgic_mmio_read_its_idregs(struct kvm *kvm, - struct vgic_its *its, + void *dev, gpa_t addr, unsigned int len) { switch (addr & 0xffff) { @@ -1445,17 +1445,18 @@ static u64 vgic_sanitise_its_cbaser(u64 reg) return reg; } -static unsigned long vgic_mmio_read_its_cbaser(struct kvm *kvm, - struct vgic_its *its, +static unsigned long vgic_mmio_read_its_cbaser(struct kvm *kvm, void *dev, gpa_t addr, unsigned int len) { + struct vgic_its *its = dev; return extract_bytes(its->cbaser, addr & 7, len); } -static void vgic_mmio_write_its_cbaser(struct kvm *kvm, struct vgic_its *its, +static void vgic_mmio_write_its_cbaser(struct kvm *kvm, void *dev, gpa_t addr, unsigned int len, unsigned long val) { + struct vgic_its *its = dev; /* When GITS_CTLR.Enable is 1, this register is RO. */ if (its->enabled) return; @@ -1513,10 +1514,11 @@ static void vgic_its_process_commands(struct kvm *kvm, struct vgic_its *its) * protects our ring buffer variables, so that there is only one user * per ITS handling commands at a given time. */ -static void vgic_mmio_write_its_cwriter(struct kvm *kvm, struct vgic_its *its, +static void vgic_mmio_write_its_cwriter(struct kvm *kvm, void *dev, gpa_t addr, unsigned int len, unsigned long val) { + struct vgic_its *its = dev; u64 reg; if (!its) @@ -1537,25 +1539,25 @@ static void vgic_mmio_write_its_cwriter(struct kvm *kvm, struct vgic_its *its, mutex_unlock(&its->cmd_lock); } -static unsigned long vgic_mmio_read_its_cwriter(struct kvm *kvm, - struct vgic_its *its, +static unsigned long vgic_mmio_read_its_cwriter(struct kvm *kvm, void *dev, gpa_t addr, unsigned int len) { + struct vgic_its *its = dev; return extract_bytes(its->cwriter, addr & 0x7, len); } -static unsigned long vgic_mmio_read_its_creadr(struct kvm *kvm, - struct vgic_its *its, +static unsigned long vgic_mmio_read_its_creadr(struct kvm *kvm, void *dev, gpa_t addr, unsigned int len) { + struct vgic_its *its = dev; return extract_bytes(its->creadr, addr & 0x7, len); } -static int vgic_mmio_uaccess_write_its_creadr(struct kvm *kvm, - struct vgic_its *its, +static int vgic_mmio_uaccess_write_its_creadr(struct kvm *kvm, void *dev, gpa_t addr, unsigned int len, unsigned long val) { + struct vgic_its *its = dev; u32 cmd_offset; int ret = 0; @@ -1579,10 +1581,10 @@ static int vgic_mmio_uaccess_write_its_creadr(struct kvm *kvm, } #define BASER_INDEX(addr) (((addr) / sizeof(u64)) & 0x7) -static unsigned long vgic_mmio_read_its_baser(struct kvm *kvm, - struct vgic_its *its, +static unsigned long vgic_mmio_read_its_baser(struct kvm *kvm, void *dev, gpa_t addr, unsigned int len) { + struct vgic_its *its = dev; u64 reg; switch (BASER_INDEX(addr)) { @@ -1601,11 +1603,11 @@ static unsigned long vgic_mmio_read_its_baser(struct kvm *kvm, } #define GITS_BASER_RO_MASK (GENMASK_ULL(52, 48) | GENMASK_ULL(58, 56)) -static void vgic_mmio_write_its_baser(struct kvm *kvm, - struct vgic_its *its, +static void vgic_mmio_write_its_baser(struct kvm *kvm, void *dev, gpa_t addr, unsigned int len, unsigned long val) { + struct vgic_its *its = dev; const struct vgic_its_abi *abi = vgic_its_get_abi(its); u64 old, entry_size, table_type; u64 reg, *regptr, clearbits = 0; @@ -1660,10 +1662,10 @@ static void vgic_mmio_write_its_baser(struct kvm *kvm, } } -static unsigned long vgic_mmio_read_its_ctlr(struct kvm *vcpu, - struct vgic_its *its, +static unsigned long vgic_mmio_read_its_ctlr(struct kvm *vcpu, void *dev, gpa_t addr, unsigned int len) { + struct vgic_its *its = dev; u32 reg = 0; mutex_lock(&its->cmd_lock); @@ -1676,10 +1678,11 @@ static unsigned long vgic_mmio_read_its_ctlr(struct kvm *vcpu, return reg; } -static void vgic_mmio_write_its_ctlr(struct kvm *kvm, struct vgic_its *its, +static void vgic_mmio_write_its_ctlr(struct kvm *kvm, void *dev, gpa_t addr, unsigned int len, unsigned long val) { + struct vgic_its *its = dev; mutex_lock(&its->cmd_lock); /* @@ -1725,8 +1728,8 @@ static void vgic_mmio_write_its_ctlr(struct kvm *kvm, struct vgic_its *its, .uaccess_its_write = uwr, \ } -static void its_mmio_write_wi(struct kvm *kvm, struct vgic_its *its, - gpa_t addr, unsigned int len, unsigned long val) +static void its_mmio_write_wi(struct kvm *kvm, void *dev, gpa_t addr, + unsigned int len, unsigned long val) { /* Ignore */ } diff --git a/arch/arm64/kvm/vgic/vgic-mmio.h b/arch/arm64/kvm/vgic/vgic-mmio.h index 38ed730d68ac3..c3b6d3e752d8d 100644 --- a/arch/arm64/kvm/vgic/vgic-mmio.h +++ b/arch/arm64/kvm/vgic/vgic-mmio.h @@ -13,13 +13,13 @@ struct vgic_register_region { union { unsigned long (*read)(struct kvm_vcpu *vcpu, gpa_t addr, unsigned int len); - unsigned long (*its_read)(struct kvm *kvm, struct vgic_its *its, + unsigned long (*its_read)(struct kvm *kvm, void *dev, gpa_t addr, unsigned int len); }; union { void (*write)(struct kvm_vcpu *vcpu, gpa_t addr, unsigned int len, unsigned long val); - void (*its_write)(struct kvm *kvm, struct vgic_its *its, + void (*its_write)(struct kvm *kvm, void *dev, gpa_t addr, unsigned int len, unsigned long val); }; @@ -28,7 +28,7 @@ struct vgic_register_region { union { int (*uaccess_write)(struct kvm_vcpu *vcpu, gpa_t addr, unsigned int len, unsigned long val); - int (*uaccess_its_write)(struct kvm *kvm, struct vgic_its *its, + int (*uaccess_its_write)(struct kvm *kvm, void *dev, gpa_t addr, unsigned int len, unsigned long val); }; -- 2.34.1 The GICv5 IRS and ITS expose the same CoreSight identification register block. Move the register-value switch into a shared helper and describe the contiguous ID register range with one register-region descriptor. This keeps the IRS behaviour unchanged while giving the ITS MMIO emulation a helper it can reuse when it is added in the following commit. Signed-off-by: Sascha Bischoff --- arch/arm64/kvm/vgic/vgic-irs-v5.c | 72 ++---------------------------- arch/arm64/kvm/vgic/vgic-v5.c | 34 ++++++++++++++ arch/arm64/kvm/vgic/vgic.h | 1 + include/linux/irqchip/arm-gic-v5.h | 2 + 4 files changed, 41 insertions(+), 68 deletions(-) diff --git a/arch/arm64/kvm/vgic/vgic-irs-v5.c b/arch/arm64/kvm/vgic/vgic-irs-v5.c index 1874c3c289a6e..63571dc58546c 100644 --- a/arch/arm64/kvm/vgic/vgic-irs-v5.c +++ b/arch/arm64/kvm/vgic/vgic-irs-v5.c @@ -520,38 +520,9 @@ static void vgic_v5_mmio_write_irs_ist(struct kvm_vcpu *vcpu, gpa_t addr, static unsigned long vgic_v5_coresight_read(struct kvm_vcpu *vcpu, gpa_t addr, unsigned int len) { - const size_t offset = addr & (SZ_64K - 1); + u32 offset = addr & (SZ_64K - 1); - switch (offset) { - case GICV5_CORESIGHT_DEVARCH: - return GICV5_CORESIGHT_DEVARCH_VAL; - case GICV5_CORESIGHT_PIDR4: - return GICV5_CORESIGHT_PIDR4_JEP106_CONT; - case GICV5_CORESIGHT_PIDR5: - return GICV5_CORESIGHT_PIDR5_RES0; - case GICV5_CORESIGHT_PIDR6: - return GICV5_CORESIGHT_PIDR6_RES0; - case GICV5_CORESIGHT_PIDR7: - return GICV5_CORESIGHT_PIDR7_RES0; - case GICV5_CORESIGHT_PIDR0: - return GICV5_CORESIGHT_PIDR0_PART_0; - case GICV5_CORESIGHT_PIDR1: - return GICV5_CORESIGHT_PIDR1_DES_0_PART_1; - case GICV5_CORESIGHT_PIDR2: - return GICV5_CORESIGHT_PIDR2_DES_1; - case GICV5_CORESIGHT_PIDR3: - return GICV5_CORESIGHT_PIDR3_REVAND_CMOD; - case GICV5_CORESIGHT_CIDR0: - return GICV5_CORESIGHT_CIDR0_VAL; - case GICV5_CORESIGHT_CIDR1: - return GICV5_CORESIGHT_CIDR1_VAL; - case GICV5_CORESIGHT_CIDR2: - return GICV5_CORESIGHT_CIDR2_VAL; - case GICV5_CORESIGHT_CIDR3: - return GICV5_CORESIGHT_CIDR3_VAL; - default: - return 0; - } + return vgic_v5_handle_guest_coresight_idreg_read(offset); } static unsigned long vgic_v5_mmio_uaccess_read_irs_status(struct kvm_vcpu *vcpu, @@ -905,43 +876,8 @@ static const struct vgic_register_region vgic_v5_irs_registers[] = { /* CoreSight identification registers */ REGISTER_DESC_WITH_LENGTH(GICV5_CORESIGHT_DEVARCH, - vgic_v5_coresight_read, vgic_mmio_write_wi, 4, - VGIC_ACCESS_32bit), - REGISTER_DESC_WITH_LENGTH(GICV5_CORESIGHT_PIDR4, - vgic_v5_coresight_read, vgic_mmio_write_wi, 4, - VGIC_ACCESS_32bit), - REGISTER_DESC_WITH_LENGTH(GICV5_CORESIGHT_PIDR5, - vgic_v5_coresight_read, vgic_mmio_write_wi, 4, - VGIC_ACCESS_32bit), - REGISTER_DESC_WITH_LENGTH(GICV5_CORESIGHT_PIDR6, - vgic_v5_coresight_read, vgic_mmio_write_wi, 4, - VGIC_ACCESS_32bit), - REGISTER_DESC_WITH_LENGTH(GICV5_CORESIGHT_PIDR7, - vgic_v5_coresight_read, vgic_mmio_write_wi, 4, - VGIC_ACCESS_32bit), - REGISTER_DESC_WITH_LENGTH(GICV5_CORESIGHT_PIDR0, - vgic_v5_coresight_read, vgic_mmio_write_wi, 4, - VGIC_ACCESS_32bit), - REGISTER_DESC_WITH_LENGTH(GICV5_CORESIGHT_PIDR1, - vgic_v5_coresight_read, vgic_mmio_write_wi, 4, - VGIC_ACCESS_32bit), - REGISTER_DESC_WITH_LENGTH(GICV5_CORESIGHT_PIDR2, - vgic_v5_coresight_read, vgic_mmio_write_wi, 4, - VGIC_ACCESS_32bit), - REGISTER_DESC_WITH_LENGTH(GICV5_CORESIGHT_PIDR3, - vgic_v5_coresight_read, vgic_mmio_write_wi, 4, - VGIC_ACCESS_32bit), - REGISTER_DESC_WITH_LENGTH(GICV5_CORESIGHT_CIDR0, - vgic_v5_coresight_read, vgic_mmio_write_wi, 4, - VGIC_ACCESS_32bit), - REGISTER_DESC_WITH_LENGTH(GICV5_CORESIGHT_CIDR1, - vgic_v5_coresight_read, vgic_mmio_write_wi, 4, - VGIC_ACCESS_32bit), - REGISTER_DESC_WITH_LENGTH(GICV5_CORESIGHT_CIDR2, - vgic_v5_coresight_read, vgic_mmio_write_wi, 4, - VGIC_ACCESS_32bit), - REGISTER_DESC_WITH_LENGTH(GICV5_CORESIGHT_CIDR3, - vgic_v5_coresight_read, vgic_mmio_write_wi, 4, + vgic_v5_coresight_read, vgic_mmio_write_wi, + GICV5_CORESIGHT_IDREGS_SIZE, VGIC_ACCESS_32bit), }; diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c index 91810fbd99afa..18184020e188b 100644 --- a/arch/arm64/kvm/vgic/vgic-v5.c +++ b/arch/arm64/kvm/vgic/vgic-v5.c @@ -1601,3 +1601,37 @@ int vgic_v5_irs_restore_ists(struct kvm *kvm, struct kvm_device_attr *attr) return ret; } + +unsigned long vgic_v5_handle_guest_coresight_idreg_read(u32 offset) +{ + switch (offset) { + case GICV5_CORESIGHT_DEVARCH: + return GICV5_CORESIGHT_DEVARCH_VAL; + case GICV5_CORESIGHT_PIDR4: + return GICV5_CORESIGHT_PIDR4_JEP106_CONT; + case GICV5_CORESIGHT_PIDR5: + return GICV5_CORESIGHT_PIDR5_RES0; + case GICV5_CORESIGHT_PIDR6: + return GICV5_CORESIGHT_PIDR6_RES0; + case GICV5_CORESIGHT_PIDR7: + return GICV5_CORESIGHT_PIDR7_RES0; + case GICV5_CORESIGHT_PIDR0: + return GICV5_CORESIGHT_PIDR0_PART_0; + case GICV5_CORESIGHT_PIDR1: + return GICV5_CORESIGHT_PIDR1_DES_0_PART_1; + case GICV5_CORESIGHT_PIDR2: + return GICV5_CORESIGHT_PIDR2_DES_1; + case GICV5_CORESIGHT_PIDR3: + return GICV5_CORESIGHT_PIDR3_REVAND_CMOD; + case GICV5_CORESIGHT_CIDR0: + return GICV5_CORESIGHT_CIDR0_VAL; + case GICV5_CORESIGHT_CIDR1: + return GICV5_CORESIGHT_CIDR1_VAL; + case GICV5_CORESIGHT_CIDR2: + return GICV5_CORESIGHT_CIDR2_VAL; + case GICV5_CORESIGHT_CIDR3: + return GICV5_CORESIGHT_CIDR3_VAL; + default: + return 0; + } +} diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h index c33cdd7098df5..66044232ae025 100644 --- a/arch/arm64/kvm/vgic/vgic.h +++ b/arch/arm64/kvm/vgic/vgic.h @@ -388,6 +388,7 @@ int vgic_v5_probe(const struct gic_kvm_info *info); void vgic_v5_reset(struct kvm_vcpu *vcpu); int vgic_v5_init(struct kvm *kvm); int kvm_vgic_v5_irs_init(struct kvm *kvm, unsigned int nr_spis); +unsigned long vgic_v5_handle_guest_coresight_idreg_read(u32 offset); void vgic_v5_teardown(struct kvm *kvm); int vgic_v5_map_resources(struct kvm *kvm); void vgic_v5_set_ppi_ops(struct kvm_vcpu *vcpu, u32 vintid); diff --git a/include/linux/irqchip/arm-gic-v5.h b/include/linux/irqchip/arm-gic-v5.h index 299e75bc95255..a4879774124f8 100644 --- a/include/linux/irqchip/arm-gic-v5.h +++ b/include/linux/irqchip/arm-gic-v5.h @@ -498,6 +498,8 @@ #define GICV5_CORESIGHT_CIDR1 0xfff4 #define GICV5_CORESIGHT_CIDR2 0xfff8 #define GICV5_CORESIGHT_CIDR3 0xfffc +#define GICV5_CORESIGHT_IDREGS_SIZE \ + (GICV5_CORESIGHT_CIDR3 + 4 - GICV5_CORESIGHT_DEVARCH) #define GICV5_CORESIGHT_DEVARCH_VAL \ (FIELD_PREP(GENMASK(31, 21), 0x23b) | \ -- 2.34.1 Introduce the GICv5 ITS MMIO register offsets and bit definitions in one place before the virtual ITS starts consuming them. Keeping the ITS register frame definitions together avoids spreading register-definition churn across the subsequent KVM ITS emulation patches. Signed-off-by: Sascha Bischoff --- include/linux/irqchip/arm-gic-v5.h | 126 +++++++++++++++++++++-------- 1 file changed, 92 insertions(+), 34 deletions(-) diff --git a/include/linux/irqchip/arm-gic-v5.h b/include/linux/irqchip/arm-gic-v5.h index a4879774124f8..7e64eb0436394 100644 --- a/include/linux/irqchip/arm-gic-v5.h +++ b/include/linux/irqchip/arm-gic-v5.h @@ -364,50 +364,88 @@ /* * ITS registers and tables structures */ -#define GICV5_ITS_IDR1 0x0004 -#define GICV5_ITS_IDR2 0x0008 -#define GICV5_ITS_CR0 0x0080 -#define GICV5_ITS_CR1 0x0084 -#define GICV5_ITS_DT_BASER 0x00c0 -#define GICV5_ITS_DT_CFGR 0x00d0 -#define GICV5_ITS_DIDR 0x0100 -#define GICV5_ITS_EIDR 0x0108 -#define GICV5_ITS_INV_EVENTR 0x010c -#define GICV5_ITS_INV_DEVICER 0x0110 -#define GICV5_ITS_STATUSR 0x0120 -#define GICV5_ITS_SYNCR 0x0140 -#define GICV5_ITS_SYNC_STATUSR 0x0148 - -#define GICV5_ITS_IDR1_L2SZ GENMASK(10, 8) -#define GICV5_ITS_IDR1_ITT_LEVELS BIT(7) -#define GICV5_ITS_IDR1_DT_LEVELS BIT(6) -#define GICV5_ITS_IDR1_DEVICEID_BITS GENMASK(5, 0) +#define GICV5_ITS_IDR0 0x0000 +#define GICV5_ITS_IDR1 0x0004 +#define GICV5_ITS_IDR2 0x0008 +#define GICV5_ITS_IIDR 0x0040 +#define GICV5_ITS_AIDR 0x0044 +#define GICV5_ITS_CR0 0x0080 +#define GICV5_ITS_CR1 0x0084 +#define GICV5_ITS_DT_BASER 0x00c0 +#define GICV5_ITS_DT_CFGR 0x00d0 +#define GICV5_ITS_DIDR 0x0100 +#define GICV5_ITS_EIDR 0x0108 +#define GICV5_ITS_INV_EVENTR 0x010c +#define GICV5_ITS_INV_DEVICER 0x0110 +#define GICV5_ITS_READ_EVENTR 0x0114 +#define GICV5_ITS_READ_EVENT_DATAR 0x0118 +#define GICV5_ITS_STATUSR 0x0120 +#define GICV5_ITS_SYNCR 0x0140 +#define GICV5_ITS_SYNC_STATUSR 0x0148 +#define GICV5_ITS_GEN_EVENT_DIDR 0x0180 +#define GICV5_ITS_GEN_EVENT_EIDR 0x0188 +#define GICV5_ITS_GEN_EVENTR 0x018c +#define GICV5_ITS_GEN_EVENT_STATUSR 0x0190 +#define GICV5_ITS_MEC_IDR 0x01c0 +#define GICV5_ITS_MEC_MECID_R 0x01c4 +#define GICV5_ITS_MPAM_IDR 0x0200 +#define GICV5_ITS_MPAM_PARTID_R 0x0204 +#define GICV5_ITS_SWERR_STATUSR 0x0240 +#define GICV5_ITS_SWERR_SYNDROMER0 0x0248 +#define GICV5_ITS_SWERR_SYNDROMER1 0x0250 +#define GICV5_ITS_MPAM_PMGR 0x0258 + +#define GICV5_ITS_TRANSLATER 0x0000 +#define GICV5_ITS_RL_TRANSLATER 0x0008 + +#define GICV5_ITS_IDR0_ITSID GENMASK(31, 16) +#define GICV5_ITS_IDR0_SWE BIT(8) +#define GICV5_ITS_IDR0_MPAM BIT(7) +#define GICV5_ITS_IDR0_MEC BIT(6) +#define GICV5_ITS_IDR0_PA_RANGE GENMASK(5, 2) +#define GICV5_ITS_IDR0_DOM GENMASK(1, 0) + +#define GICV5_ITS_IDR0_DOMAIN_SECURE 0b00 +#define GICV5_ITS_IDR0_DOMAIN_NON_SECURE 0b01 +#define GICV5_ITS_IDR0_DOMAIN_EL3 0b10 +#define GICV5_ITS_IDR0_DOMAIN_REALM 0b11 + +#define GICV5_ITS_IDR1_L2SZ GENMASK(10, 8) +#define GICV5_ITS_IDR1_ITT_LEVELS BIT(7) +#define GICV5_ITS_IDR1_DT_LEVELS BIT(6) +#define GICV5_ITS_IDR1_DEVICEID_BITS GENMASK(5, 0) #define GICV5_ITS_IDR1_L2SZ_SUPPORT_4KB(r) FIELD_GET(BIT(8), (r)) #define GICV5_ITS_IDR1_L2SZ_SUPPORT_16KB(r) FIELD_GET(BIT(9), (r)) #define GICV5_ITS_IDR1_L2SZ_SUPPORT_64KB(r) FIELD_GET(BIT(10), (r)) -#define GICV5_ITS_IDR2_XDMN_EVENTs GENMASK(6, 5) -#define GICV5_ITS_IDR2_EVENTID_BITS GENMASK(4, 0) +#define GICV5_ITS_IDR2_XDMN_EVENTS GENMASK(6, 5) +#define GICV5_ITS_IDR2_EVENTID_BITS GENMASK(4, 0) -#define GICV5_ITS_CR0_IDLE BIT(1) -#define GICV5_ITS_CR0_ITSEN BIT(0) +#define GICV5_ITS_IIDR_PRODUCT_ID GENMASK(31, 20) +#define GICV5_ITS_IIDR_VARIANT GENMASK(19, 16) +#define GICV5_ITS_IIDR_REVISION GENMASK(15, 12) +#define GICV5_ITS_IIDR_IMPLEMENTER GENMASK(11, 0) -#define GICV5_ITS_CR1_ITT_RA BIT(7) -#define GICV5_ITS_CR1_DT_RA BIT(6) -#define GICV5_ITS_CR1_IC GENMASK(5, 4) -#define GICV5_ITS_CR1_OC GENMASK(3, 2) -#define GICV5_ITS_CR1_SH GENMASK(1, 0) +#define GICV5_ITS_AIDR_COMPONENT GENMASK(11, 8) +#define GICV5_ITS_AIDR_ARCHMAJORREV GENMASK(7, 4) +#define GICV5_ITS_AIDR_ARCHMINORREV GENMASK(3, 0) -#define GICV5_ITS_DT_CFGR_STRUCTURE BIT(16) -#define GICV5_ITS_DT_CFGR_L2SZ GENMASK(7, 6) -#define GICV5_ITS_DT_CFGR_DEVICEID_BITS GENMASK(5, 0) +#define GICV5_ITS_CR0_IDLE BIT(1) +#define GICV5_ITS_CR0_ITSEN BIT(0) -#define GICV5_ITS_DT_BASER_ADDR_MASK GENMASK_ULL(55, 3) +#define GICV5_ITS_CR1_ITT_RA BIT(7) +#define GICV5_ITS_CR1_DT_RA BIT(6) +#define GICV5_ITS_CR1_IC GENMASK(5, 4) +#define GICV5_ITS_CR1_OC GENMASK(3, 2) +#define GICV5_ITS_CR1_SH GENMASK(1, 0) -#define GICV5_ITS_INV_DEVICER_I BIT(31) -#define GICV5_ITS_INV_DEVICER_EVENTID_BITS GENMASK(5, 1) -#define GICV5_ITS_INV_DEVICER_L1 BIT(0) +#define GICV5_ITS_DT_BASER_ADDR_MASK GENMASK_ULL(55, 3) +#define GICV5_ITS_DT_BASER_ADDR_SHIFT 3ULL + +#define GICV5_ITS_DT_CFGR_STRUCTURE BIT(16) +#define GICV5_ITS_DT_CFGR_L2SZ GENMASK(7, 6) +#define GICV5_ITS_DT_CFGR_DEVICEID_BITS GENMASK(5, 0) #define GICV5_ITS_DIDR_DEVICEID GENMASK_ULL(31, 0) @@ -417,6 +455,17 @@ #define GICV5_ITS_INV_EVENTR_ITT_L2SZ GENMASK(2, 1) #define GICV5_ITS_INV_EVENTR_L1 BIT(0) +#define GICV5_ITS_INV_DEVICER_I BIT(31) +#define GICV5_ITS_INV_DEVICER_EVENTID_BITS GENMASK(5, 1) +#define GICV5_ITS_INV_DEVICER_L1 BIT(0) + +#define GICV5_ITS_READ_EVENTR_R BIT(31) + +#define GICV5_ITS_READ_EVENT_DATAR_VIRT BIT_ULL(63) +#define GICV5_ITS_READ_EVENT_DATAR_VM_ID GENMASK_ULL(47, 32) +#define GICV5_ITS_READ_EVENT_DATAR_VALID BIT_ULL(31) +#define GICV5_ITS_READ_EVENT_DATAR_LPI_ID GENMASK_ULL(23, 0) + #define GICV5_ITS_STATUSR_IDLE BIT(0) #define GICV5_ITS_SYNCR_SYNC BIT_ULL(63) @@ -425,6 +474,15 @@ #define GICV5_ITS_SYNC_STATUSR_IDLE BIT(0) +#define GICV5_ITS_GEN_EVENT_DIDR_DEVICEID GENMASK_ULL(31, 0) + +#define GICV5_ITS_GEN_EVENT_EIDR_EVENTID GENMASK(15, 0) + +#define GICV5_ITS_GEN_EVENTR_R BIT(31) +#define GICV5_ITS_GEN_EVENTR_TARGET_DOMAIN GENMASK(1, 0) + +#define GICV5_ITS_GEN_EVENT_STATUSR_IDLE BIT(0) + #define GICV5_DTL1E_VALID BIT_ULL(0) /* Note that there is no shift for the address by design */ #define GICV5_DTL1E_L2_ADDR_MASK GENMASK_ULL(55, 3) -- 2.34.1 Introduce the basic KVM device plumbing for a GICv5 ITS. This adds the GICv5 ITS device type and address attribute, registers the device ops, and wires a GICv5 ITS IODEV into the generic VGIC MMIO dispatch path. Add struct vgic_v5_its to hold the ITS base address, the IODEV plumbing, the backing KVM device pointer, and the general ITS mutex. Allocate this state from the KVM device create path, initialise the mutex and base address, and mark the VM as having an ITS. GICv5 MSIs require a DeviceID, so set msis_require_devid when the ITS is created. Record the VGIC model required by an ITS and reject mismatched device creation in either order. Check GICv3 ITS creation as well, so the two ITS models cannot be combined in one VM. The MMIO region is registered once userspace provides the ITS base address through KVM_VGIC_V5_ADDR_TYPE_ITS. For now the IODEV only provides a RAZ/WI placeholder region; the actual ITS register emulation is added separately. Signed-off-by: Sascha Bischoff --- arch/arm64/include/uapi/asm/kvm.h | 2 + arch/arm64/kvm/Makefile | 1 + arch/arm64/kvm/vgic/vgic-init.c | 5 + arch/arm64/kvm/vgic/vgic-its-v5.c | 187 ++++++++++++++++++++++++++ arch/arm64/kvm/vgic/vgic-its.c | 9 ++ arch/arm64/kvm/vgic/vgic-kvm-device.c | 4 + arch/arm64/kvm/vgic/vgic-mmio-v3.c | 10 -- arch/arm64/kvm/vgic/vgic-mmio.c | 18 +++ arch/arm64/kvm/vgic/vgic-mmio.h | 1 + arch/arm64/kvm/vgic/vgic.h | 1 + include/kvm/arm_vgic.h | 18 ++- include/uapi/linux/kvm.h | 2 + tools/include/uapi/linux/kvm.h | 2 + 13 files changed, 249 insertions(+), 11 deletions(-) create mode 100644 arch/arm64/kvm/vgic/vgic-its-v5.c diff --git a/arch/arm64/include/uapi/asm/kvm.h b/arch/arm64/include/uapi/asm/kvm.h index 65ee09bd14636..8c701173a2d88 100644 --- a/arch/arm64/include/uapi/asm/kvm.h +++ b/arch/arm64/include/uapi/asm/kvm.h @@ -99,8 +99,10 @@ struct kvm_regs { /* Supported VGICv5 address types */ #define KVM_VGIC_V5_ADDR_TYPE_IRS 6 +#define KVM_VGIC_V5_ADDR_TYPE_ITS 7 #define KVM_VGIC_V5_IRS_SIZE (2 * SZ_64K) +#define KVM_VGIC_V5_ITS_SIZE (2 * SZ_64K) #define KVM_ARM_VCPU_POWER_OFF 0 /* CPU is started in OFF state */ #define KVM_ARM_VCPU_EL1_32BIT 1 /* CPU running a 32bit VM */ diff --git a/arch/arm64/kvm/Makefile b/arch/arm64/kvm/Makefile index 7aaeeb84e788e..47923e1047f16 100644 --- a/arch/arm64/kvm/Makefile +++ b/arch/arm64/kvm/Makefile @@ -25,6 +25,7 @@ kvm-y += arm.o mmu.o mmio.o psci.o hypercalls.o pvtime.o \ vgic/vgic-mmio-v3.o vgic/vgic-kvm-device.o \ vgic/vgic-its.o vgic/vgic-debug.o vgic/vgic-v3-nested.o \ vgic/vgic-v5.o vgic/vgic-v5-tables.o vgic/vgic-irs-v5.o \ + vgic/vgic-its-v5.o \ vgic-sys-reg-v5.o kvm-$(CONFIG_HW_PERF_EVENTS) += pmu-emul.o pmu.o diff --git a/arch/arm64/kvm/vgic/vgic-init.c b/arch/arm64/kvm/vgic/vgic-init.c index b5ea1e39c6862..48a92555fa6ab 100644 --- a/arch/arm64/kvm/vgic/vgic-init.c +++ b/arch/arm64/kvm/vgic/vgic-init.c @@ -125,6 +125,11 @@ int kvm_vgic_create(struct kvm *kvm, u32 type) goto out_unlock; } + if (kvm->arch.vgic.its_model && kvm->arch.vgic.its_model != type) { + ret = -ENODEV; + goto out_unlock; + } + if (kvm_id_regs_final(kvm)) goto out_unlock; ret = 0; diff --git a/arch/arm64/kvm/vgic/vgic-its-v5.c b/arch/arm64/kvm/vgic/vgic-its-v5.c new file mode 100644 index 0000000000000..f8184b4ae0ccd --- /dev/null +++ b/arch/arm64/kvm/vgic/vgic-its-v5.c @@ -0,0 +1,187 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2025 ARM Limited, All Rights Reserved. + */ + +#include +#include + +#include + +#include + +#include "vgic.h" +#include "vgic-mmio.h" + +static unsigned long vgic_v5_mmio_read_its_raz(struct kvm *kvm, void *dev, + gpa_t addr, unsigned int len) +{ + return 0; +} + +static void vgic_v5_mmio_write_its_wi(struct kvm *kvm, void *dev, gpa_t addr, + unsigned int len, unsigned long val) +{ +} + +#define REGISTER_ITS_V5_DESC(off, rd, wr, length, acc) \ + { \ + .reg_offset = off, .len = length, .access_flags = acc, \ + .its_read = rd, .its_write = wr, \ + } + +static const struct vgic_register_region vgic_v5_its_registers[] = { + REGISTER_ITS_V5_DESC(0, vgic_v5_mmio_read_its_raz, + vgic_v5_mmio_write_its_wi, KVM_VGIC_V5_ITS_SIZE, + VGIC_ACCESS_32bit), +}; + +static int vgic_v5_its_create(struct kvm_device *dev, u32 type) +{ + struct vgic_v5_its *its; + + if (type != KVM_DEV_TYPE_ARM_VGIC_V5_ITS) + return -ENODEV; + + its = kzalloc_obj(*its, GFP_KERNEL_ACCOUNT); + if (!its) + return -ENOMEM; + + mutex_init(&its->its_lock); + + guard(mutex)(&dev->kvm->arch.config_lock); + if ((irqchip_in_kernel(dev->kvm) && !vgic_is_v5(dev->kvm)) || + (dev->kvm->arch.vgic.its_model && + dev->kvm->arch.vgic.its_model != KVM_DEV_TYPE_ARM_VGIC_V5)) { + kfree(its); + return -ENODEV; + } + + its->vgic_v5_its_base = VGIC_ADDR_UNDEF; + its->dev = dev; + + dev->kvm->arch.vgic.msis_require_devid = true; + dev->kvm->arch.vgic.has_its = true; + dev->kvm->arch.vgic.its_model = KVM_DEV_TYPE_ARM_VGIC_V5; + dev->private = its; + + return 0; +} + +static void vgic_v5_its_destroy(struct kvm_device *kvm_dev) +{ + struct vgic_v5_its *its = kvm_dev->private; + + kfree(its); + kfree(kvm_dev); +} + +static int vgic_v5_register_its_iodev(struct kvm *kvm, struct vgic_v5_its *its, + u64 addr) +{ + struct vgic_io_device *iodev = &its->iodev; + int ret; + + mutex_lock(&kvm->slots_lock); + if (!IS_VGIC_ADDR_UNDEF(its->vgic_v5_its_base)) { + ret = -EBUSY; + goto out; + } + + if (!IS_ALIGNED(addr, SZ_64K)) { + kvm_err("ITS Base address is not aligned to 64k\n"); + ret = -EINVAL; + goto out; + } + + its->vgic_v5_its_base = addr; + iodev->base_addr = addr; + iodev->regions = vgic_v5_its_registers; + iodev->nr_regions = ARRAY_SIZE(vgic_v5_its_registers); + iodev->iodev_type = IODEV_GICV5_ITS; + iodev->redist_vcpu = NULL; + iodev->v5its = its; + kvm_iodevice_init(&iodev->dev, &kvm_io_gic_ops); + + ret = kvm_io_bus_register_dev(kvm, KVM_MMIO_BUS, iodev->base_addr, + KVM_VGIC_V5_ITS_SIZE, &iodev->dev); +out: + mutex_unlock(&kvm->slots_lock); + + return ret; +} + +static int vgic_v5_its_set_attr(struct kvm_device *dev, + struct kvm_device_attr *attr) +{ + struct vgic_v5_its *its = dev->private; + u64 __user *uaddr = (u64 __user *)(long)attr->addr; + unsigned long type = (unsigned long)attr->attr; + u64 addr; + int ret; + + if (attr->group != KVM_DEV_ARM_VGIC_GRP_ADDR) + return -ENXIO; + + if (type != KVM_VGIC_V5_ADDR_TYPE_ITS) + return -ENODEV; + + if (copy_from_user(&addr, uaddr, sizeof(addr))) + return -EFAULT; + + ret = vgic_check_iorange(dev->kvm, its->vgic_v5_its_base, addr, + SZ_64K, KVM_VGIC_V5_ITS_SIZE); + if (ret) + return ret; + + return vgic_v5_register_its_iodev(dev->kvm, its, addr); +} + +static int vgic_v5_its_get_attr(struct kvm_device *dev, + struct kvm_device_attr *attr) +{ + struct vgic_v5_its *its = dev->private; + u64 __user *uaddr = (u64 __user *)(long)attr->addr; + unsigned long type = (unsigned long)attr->attr; + u64 addr = its->vgic_v5_its_base; + + if (attr->group != KVM_DEV_ARM_VGIC_GRP_ADDR) + return -ENXIO; + + if (type != KVM_VGIC_V5_ADDR_TYPE_ITS) + return -ENODEV; + + if (copy_to_user(uaddr, &addr, sizeof(addr))) + return -EFAULT; + + return 0; +} + +static int vgic_v5_its_has_attr(struct kvm_device *dev, + struct kvm_device_attr *attr) +{ + if (attr->group != KVM_DEV_ARM_VGIC_GRP_ADDR) + return -ENXIO; + + switch (attr->attr) { + case KVM_VGIC_V5_ADDR_TYPE_ITS: + return 0; + default: + return -ENXIO; + } +} + +static struct kvm_device_ops kvm_arm_vgic_v5_its_ops = { + .name = "kvm-arm-vgic-v5-its", + .create = vgic_v5_its_create, + .destroy = vgic_v5_its_destroy, + .set_attr = vgic_v5_its_set_attr, + .get_attr = vgic_v5_its_get_attr, + .has_attr = vgic_v5_its_has_attr, +}; + +int kvm_vgic_v5_register_its_device(void) +{ + return kvm_register_device_ops(&kvm_arm_vgic_v5_its_ops, + KVM_DEV_TYPE_ARM_VGIC_V5_ITS); +} diff --git a/arch/arm64/kvm/vgic/vgic-its.c b/arch/arm64/kvm/vgic/vgic-its.c index dff942b4f4233..6dfc2bab3ba60 100644 --- a/arch/arm64/kvm/vgic/vgic-its.c +++ b/arch/arm64/kvm/vgic/vgic-its.c @@ -1823,6 +1823,14 @@ static int vgic_its_create(struct kvm_device *dev, u32 type) mutex_lock(&dev->kvm->arch.config_lock); + if ((irqchip_in_kernel(dev->kvm) && !vgic_is_v3(dev->kvm)) || + (dev->kvm->arch.vgic.its_model && + dev->kvm->arch.vgic.its_model != KVM_DEV_TYPE_ARM_VGIC_V3)) { + mutex_unlock(&dev->kvm->arch.config_lock); + kfree(its); + return -ENODEV; + } + if (vgic_initialized(dev->kvm)) { ret = vgic_v4_init(dev->kvm); if (ret < 0) { @@ -1851,6 +1859,7 @@ static int vgic_its_create(struct kvm_device *dev, u32 type) dev->kvm->arch.vgic.msis_require_devid = true; dev->kvm->arch.vgic.has_its = true; + dev->kvm->arch.vgic.its_model = KVM_DEV_TYPE_ARM_VGIC_V3; its->enabled = false; its->dev = dev; diff --git a/arch/arm64/kvm/vgic/vgic-kvm-device.c b/arch/arm64/kvm/vgic/vgic-kvm-device.c index 1c205cb1361fd..e7630f68e2a74 100644 --- a/arch/arm64/kvm/vgic/vgic-kvm-device.c +++ b/arch/arm64/kvm/vgic/vgic-kvm-device.c @@ -369,6 +369,10 @@ int kvm_register_vgic_device(unsigned long type) case KVM_DEV_TYPE_ARM_VGIC_V5: ret = kvm_register_device_ops(&kvm_arm_vgic_v5_ops, KVM_DEV_TYPE_ARM_VGIC_V5); + if (ret) + break; + + ret = kvm_vgic_v5_register_its_device(); break; } diff --git a/arch/arm64/kvm/vgic/vgic-mmio-v3.c b/arch/arm64/kvm/vgic/vgic-mmio-v3.c index 5ab789accc9d4..f5e6c1fe52431 100644 --- a/arch/arm64/kvm/vgic/vgic-mmio-v3.c +++ b/arch/arm64/kvm/vgic/vgic-mmio-v3.c @@ -38,16 +38,6 @@ u64 update_64bit_reg(u64 reg, unsigned int offset, unsigned int len, return reg | ((u64)val << lower); } -bool vgic_has_its(struct kvm *kvm) -{ - struct vgic_dist *dist = &kvm->arch.vgic; - - if (dist->vgic_model != KVM_DEV_TYPE_ARM_VGIC_V3) - return false; - - return dist->has_its; -} - bool vgic_supports_direct_msis(struct kvm *kvm) { /* diff --git a/arch/arm64/kvm/vgic/vgic-mmio.c b/arch/arm64/kvm/vgic/vgic-mmio.c index fddb9da0403d5..755bc2d74c352 100644 --- a/arch/arm64/kvm/vgic/vgic-mmio.c +++ b/arch/arm64/kvm/vgic/vgic-mmio.c @@ -1068,6 +1068,9 @@ static int dispatch_mmio_read(struct kvm_vcpu *vcpu, struct kvm_io_device *dev, case IODEV_GICV5_IRS: data = region->read(vcpu, addr, len); break; + case IODEV_GICV5_ITS: + data = region->its_read(vcpu->kvm, iodev->v5its, addr, len); + break; } vgic_data_host_to_mmio_bus(val, len, data); @@ -1101,6 +1104,9 @@ static int dispatch_mmio_write(struct kvm_vcpu *vcpu, struct kvm_io_device *dev, case IODEV_GICV5_IRS: region->write(vcpu, addr, len, data); break; + case IODEV_GICV5_ITS: + region->its_write(vcpu->kvm, iodev->v5its, addr, len, data); + break; } return 0; @@ -1135,3 +1141,15 @@ int vgic_register_dist_iodev(struct kvm *kvm, gpa_t dist_base_address, return kvm_io_bus_register_dev(kvm, KVM_MMIO_BUS, dist_base_address, len, &io_device->dev); } + +bool vgic_has_its(struct kvm *kvm) +{ + struct vgic_dist *dist = &kvm->arch.vgic; + + if (vgic_is_v3(kvm) && dist->has_its) + return true; + if (vgic_is_v5(kvm) && dist->has_its) + return true; + + return false; +} diff --git a/arch/arm64/kvm/vgic/vgic-mmio.h b/arch/arm64/kvm/vgic/vgic-mmio.h index c3b6d3e752d8d..37fee94ac2a0a 100644 --- a/arch/arm64/kvm/vgic/vgic-mmio.h +++ b/arch/arm64/kvm/vgic/vgic-mmio.h @@ -218,6 +218,7 @@ unsigned int vgic_v2_init_cpuif_iodev(struct vgic_io_device *dev); unsigned int vgic_v3_init_dist_iodev(struct vgic_io_device *dev); unsigned int vgic_v5_init_irs_iodev(struct vgic_io_device *dev); +unsigned int vgic_v5_init_its_iodev(struct vgic_io_device *dev); u64 vgic_sanitise_outer_cacheability(u64 reg); u64 vgic_sanitise_inner_cacheability(u64 reg); diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h index 66044232ae025..5a9aa9bd11dd7 100644 --- a/arch/arm64/kvm/vgic/vgic.h +++ b/arch/arm64/kvm/vgic/vgic.h @@ -361,6 +361,7 @@ void vgic_v3_put(struct kvm_vcpu *vcpu); bool vgic_has_its(struct kvm *kvm); int kvm_vgic_register_its_device(void); +int kvm_vgic_v5_register_its_device(void); void vgic_enable_lpis(struct kvm_vcpu *vcpu); void vgic_flush_pending_lpis(struct kvm_vcpu *vcpu); int vgic_its_inject_msi(struct kvm *kvm, struct kvm_msi *msi); diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h index 7be81b936bf10..f3bfeee6104c2 100644 --- a/include/kvm/arm_vgic.h +++ b/include/kvm/arm_vgic.h @@ -324,7 +324,8 @@ enum iodev_type { IODEV_DIST, IODEV_REDIST, IODEV_ITS, - IODEV_GICV5_IRS + IODEV_GICV5_IRS, + IODEV_GICV5_ITS }; struct vgic_io_device { @@ -332,6 +333,7 @@ struct vgic_io_device { union { struct kvm_vcpu *redist_vcpu; struct vgic_its *its; + struct vgic_v5_its *v5its; }; const struct vgic_register_region *regions; enum iodev_type iodev_type; @@ -496,6 +498,18 @@ struct vgic_v5_irs { bool lpi_ist_restore_pending; }; +/*** GICv5 ***/ +struct vgic_v5_its { + /* base addresses in guest physical address space: */ + gpa_t vgic_v5_its_base; + + struct vgic_io_device iodev; + struct kvm_device *dev; + + /* Protects the device in general */ + struct mutex its_lock; +}; + struct vgic_dist { bool in_kernel; bool ready; @@ -548,6 +562,8 @@ struct vgic_dist { struct vgic_io_device cpuif_iodev; bool has_its; + /* VGIC model required by the VM's ITS devices. */ + u32 its_model; bool table_write_in_progress; /* diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h index ac2d77d149635..4e16c55902f5e 100644 --- a/include/uapi/linux/kvm.h +++ b/include/uapi/linux/kvm.h @@ -1238,6 +1238,8 @@ enum kvm_device_type { #define KVM_DEV_TYPE_LOONGARCH_DMSINTC KVM_DEV_TYPE_LOONGARCH_DMSINTC KVM_DEV_TYPE_ARM_VGIC_V5, #define KVM_DEV_TYPE_ARM_VGIC_V5 KVM_DEV_TYPE_ARM_VGIC_V5 + KVM_DEV_TYPE_ARM_VGIC_V5_ITS, +#define KVM_DEV_TYPE_ARM_VGIC_V5_ITS KVM_DEV_TYPE_ARM_VGIC_V5_ITS KVM_DEV_TYPE_MAX, diff --git a/tools/include/uapi/linux/kvm.h b/tools/include/uapi/linux/kvm.h index 419011097fa8e..99cf4775ed083 100644 --- a/tools/include/uapi/linux/kvm.h +++ b/tools/include/uapi/linux/kvm.h @@ -1236,6 +1236,8 @@ enum kvm_device_type { #define KVM_DEV_TYPE_LOONGARCH_DMSINTC KVM_DEV_TYPE_LOONGARCH_DMSINTC KVM_DEV_TYPE_ARM_VGIC_V5, #define KVM_DEV_TYPE_ARM_VGIC_V5 KVM_DEV_TYPE_ARM_VGIC_V5 + KVM_DEV_TYPE_ARM_VGIC_V5_ITS, +#define KVM_DEV_TYPE_ARM_VGIC_V5_ITS KVM_DEV_TYPE_ARM_VGIC_V5_ITS KVM_DEV_TYPE_MAX, -- 2.34.1 In order to expose a virtual GICv5 ITS to the guest, KVM needs to emulate the ITS CONFIG_FRAME and its translation-control registers. Replace the placeholder ITS IODEV region with the GICv5 ITS register layout and handlers for the architected MMIO state. Provide read/write emulation for the ITS ID registers, IIDR, AIDR, CR0, CR1, DT_BASER, DT_CFGR, DIDR, EIDR, invalidation registers, READ_EVENT, GEN_EVENT, status registers, and the RAZ/WI MMIO registers that KVM does not implement. Reuse the shared CoreSight identification-register helper so the ITS reports the same component identification values as the IRS. At this stage the translation-triggering registers only record state. Walking the guest's DT/ITT, generating READ_EVENT results, and injecting GEN_EVENT LPIs are added by later commits. Signed-off-by: Sascha Bischoff --- arch/arm64/kvm/vgic/vgic-its-v5.c | 404 +++++++++++++++++++++++++++++- include/kvm/arm_vgic.h | 84 +++++++ 2 files changed, 486 insertions(+), 2 deletions(-) diff --git a/arch/arm64/kvm/vgic/vgic-its-v5.c b/arch/arm64/kvm/vgic/vgic-its-v5.c index f8184b4ae0ccd..fda75314229a5 100644 --- a/arch/arm64/kvm/vgic/vgic-its-v5.c +++ b/arch/arm64/kvm/vgic/vgic-its-v5.c @@ -6,19 +6,280 @@ #include #include +#include #include #include +#include + #include "vgic.h" #include "vgic-mmio.h" +#define VITS_V5_DEVICE_ID_BITS 16 +#define VITS_V5_EVENT_ID_BITS 16 + +static unsigned long vgic_v5_mmio_read_its_misc(struct kvm *kvm, void *dev, + gpa_t addr, unsigned int len) +{ + struct vgic_v5_its *its = dev; + u64 value = 0; + size_t offset = addr & (SZ_64K - 1); + + switch (offset) { + case GICV5_ITS_IDR0: + value = FIELD_PREP(GICV5_ITS_IDR0_ITSID, its->idr0.its_id); + value |= FIELD_PREP(GICV5_ITS_IDR0_SWE, its->idr0.swe); + value |= FIELD_PREP(GICV5_ITS_IDR0_MPAM, its->idr0.mpam); + value |= FIELD_PREP(GICV5_ITS_IDR0_MEC, its->idr0.mec); + value |= FIELD_PREP(GICV5_ITS_IDR0_PA_RANGE, + its->idr0.pa_range); + value |= FIELD_PREP(GICV5_ITS_IDR0_DOM, its->idr0.domain); + break; + case GICV5_ITS_IDR1: + value = FIELD_PREP(GICV5_ITS_IDR1_L2SZ, its->idr1.l2sz); + value |= FIELD_PREP(GICV5_ITS_IDR1_ITT_LEVELS, + its->idr1.itt_levels); + value |= FIELD_PREP(GICV5_ITS_IDR1_DT_LEVELS, + its->idr1.dt_levels); + value |= FIELD_PREP(GICV5_ITS_IDR1_DEVICEID_BITS, + its->idr1.device_id_bits); + break; + case GICV5_ITS_IDR2: + value = FIELD_PREP(GICV5_ITS_IDR2_XDMN_EVENTS, + its->idr2.xdmn_events); + value |= FIELD_PREP(GICV5_ITS_IDR2_EVENTID_BITS, + its->idr2.event_id_bits); + break; + case GICV5_ITS_IIDR: + value = FIELD_PREP(GICV5_ITS_IIDR_PRODUCT_ID, PRODUCT_ID_KVM); + value |= FIELD_PREP(GICV5_ITS_IIDR_VARIANT, 0); + value |= FIELD_PREP(GICV5_ITS_IIDR_REVISION, 0); + value |= FIELD_PREP(GICV5_ITS_IIDR_IMPLEMENTER, + IMPLEMENTER_ARM); + break; + case GICV5_ITS_AIDR: + value = FIELD_PREP(GICV5_ITS_AIDR_COMPONENT, + GICV5_AIDR_COMPONENT_ITS); + value |= FIELD_PREP(GICV5_ITS_AIDR_ARCHMAJORREV, + GICV5_AIDR_ARCH_MAJ_REV_V5); + value |= FIELD_PREP(GICV5_ITS_AIDR_ARCHMINORREV, + GICV5_AIDR_ARCH_MIN_REV_V0); + break; + case GICV5_ITS_CR0: + value = GICV5_ITS_CR0_IDLE; + value |= FIELD_PREP(GICV5_ITS_CR0_ITSEN, its->enabled); + break; + case GICV5_ITS_CR1: + value = FIELD_PREP(GICV5_ITS_CR1_ITT_RA, its->cr1.itt_ra); + value |= FIELD_PREP(GICV5_ITS_CR1_DT_RA, its->cr1.dt_ra); + value |= FIELD_PREP(GICV5_ITS_CR1_IC, its->cr1.ic); + value |= FIELD_PREP(GICV5_ITS_CR1_OC, its->cr1.oc); + value |= FIELD_PREP(GICV5_ITS_CR1_SH, its->cr1.sh); + break; + default: + return 0; + } + + return value; +} + +static void vgic_v5_mmio_write_its_misc(struct kvm *kvm, void *dev, gpa_t addr, + unsigned int len, unsigned long val) +{ + struct vgic_v5_its *its = dev; + size_t offset = addr & (SZ_64K - 1); + + switch (offset) { + case GICV5_ITS_CR0: + its->enabled = FIELD_GET(GICV5_ITS_CR0_ITSEN, val); + return; + case GICV5_ITS_CR1: + its->cr1.sh = FIELD_GET(GICV5_ITS_CR1_SH, val); + its->cr1.oc = FIELD_GET(GICV5_ITS_CR1_OC, val); + its->cr1.ic = FIELD_GET(GICV5_ITS_CR1_IC, val); + its->cr1.dt_ra = FIELD_GET(GICV5_ITS_CR1_DT_RA, val); + its->cr1.itt_ra = FIELD_GET(GICV5_ITS_CR1_ITT_RA, val); + return; + default: + return; + } +} + +static void vgic_v5_write_its_dt_reg(struct vgic_v5_its *its, size_t offset, + unsigned long val) +{ + switch (offset) { + case GICV5_ITS_DT_CFGR: + its->dt_cfgr.device_id_bits = + FIELD_GET(GICV5_ITS_DT_CFGR_DEVICEID_BITS, val); + its->dt_cfgr.l2sz = + FIELD_GET(GICV5_ITS_DT_CFGR_L2SZ, val); + its->dt_cfgr.structure = + FIELD_GET(GICV5_ITS_DT_CFGR_STRUCTURE, val); + return; + case GICV5_ITS_DT_BASER: + its->dt_baser.addr = + FIELD_GET(GICV5_ITS_DT_BASER_ADDR_MASK, val) + << GICV5_ITS_DT_BASER_ADDR_SHIFT; + return; + } +} + +static u64 vgic_v5_read_its_dt_baser(struct vgic_v5_its *its) +{ + return FIELD_PREP(GICV5_ITS_DT_BASER_ADDR_MASK, + its->dt_baser.addr >> GICV5_ITS_DT_BASER_ADDR_SHIFT); +} + +static unsigned long vgic_v5_mmio_read_its_dt_itt(struct kvm *kvm, void *dev, + gpa_t addr, unsigned int len) +{ + struct vgic_v5_its *its = dev; + u64 value = 0; + size_t offset = addr & (SZ_64K - 1); + + switch (offset) { + case GICV5_ITS_DT_CFGR: + value = FIELD_PREP(GICV5_ITS_DT_CFGR_STRUCTURE, + its->dt_cfgr.structure); + value |= FIELD_PREP(GICV5_ITS_DT_CFGR_L2SZ, + its->dt_cfgr.l2sz); + value |= FIELD_PREP(GICV5_ITS_DT_CFGR_DEVICEID_BITS, + its->dt_cfgr.device_id_bits); + break; + case GICV5_ITS_DT_BASER: + case GICV5_ITS_DT_BASER + 4: + value = vgic_v5_read_its_dt_baser(its); + return extract_bytes(value, offset & 4, len); + case GICV5_ITS_DIDR: + case GICV5_ITS_DIDR + 4: + value = FIELD_PREP(GICV5_ITS_DIDR_DEVICEID, + its->didr.device_id); + return extract_bytes(value, offset & 4, len); + case GICV5_ITS_EIDR: + value = FIELD_PREP(GICV5_ITS_EIDR_EVENTID, + its->eidr.event_id); + break; + case GICV5_ITS_STATUSR: + value = GICV5_ITS_STATUSR_IDLE; + break; + case GICV5_ITS_SYNC_STATUSR: + value = GICV5_ITS_SYNC_STATUSR_IDLE; + break; + case GICV5_ITS_READ_EVENT_DATAR: + case GICV5_ITS_READ_EVENT_DATAR + 4: + value = FIELD_PREP(GICV5_ITS_READ_EVENT_DATAR_VIRT, + its->read_event_datar.virt); + value |= FIELD_PREP(GICV5_ITS_READ_EVENT_DATAR_VM_ID, + its->read_event_datar.vm_id); + value |= FIELD_PREP(GICV5_ITS_READ_EVENT_DATAR_VALID, + its->read_event_datar.valid); + value |= FIELD_PREP(GICV5_ITS_READ_EVENT_DATAR_LPI_ID, + its->read_event_datar.lpi_id); + return extract_bytes(value, offset & 4, len); + case GICV5_ITS_GEN_EVENT_EIDR: + value = FIELD_PREP(GICV5_ITS_GEN_EVENT_EIDR_EVENTID, + its->gen_event_eidr.event_id); + break; + case GICV5_ITS_GEN_EVENT_DIDR: + case GICV5_ITS_GEN_EVENT_DIDR + 4: + value = FIELD_PREP(GICV5_ITS_GEN_EVENT_DIDR_DEVICEID, + its->gen_event_didr.device_id); + return extract_bytes(value, offset & 4, len); + case GICV5_ITS_GEN_EVENT_STATUSR: + value = GICV5_ITS_GEN_EVENT_STATUSR_IDLE; + break; + default: + return 0; + } + + return value; +} + +static void vgic_v5_mmio_write_its_dt_itt(struct kvm *kvm, void *dev, + gpa_t addr, unsigned int len, + unsigned long val) +{ + struct vgic_v5_its *its = dev; + size_t offset = addr & (SZ_64K - 1); + + guard(mutex)(&its->its_lock); + + switch (offset) { + case GICV5_ITS_DT_CFGR: + if (!its->enabled) + vgic_v5_write_its_dt_reg(its, offset, val); + return; + case GICV5_ITS_DT_BASER: + case GICV5_ITS_DT_BASER + 4: + if (!its->enabled) { + u64 baser = vgic_v5_read_its_dt_baser(its); + + baser = update_64bit_reg(baser, offset & 4, len, val); + vgic_v5_write_its_dt_reg(its, GICV5_ITS_DT_BASER, + baser); + } + return; + case GICV5_ITS_INV_DEVICER: + its->inv_devicer.l1 = + FIELD_GET(GICV5_ITS_INV_DEVICER_L1, val); + its->inv_devicer.event_id_bits = + FIELD_GET(GICV5_ITS_INV_DEVICER_EVENTID_BITS, val); + its->inv_devicer.i = + FIELD_GET(GICV5_ITS_INV_DEVICER_I, val); + return; + case GICV5_ITS_DIDR: + case GICV5_ITS_DIDR + 4: + if (offset & 4) + return; + its->didr.device_id = FIELD_GET(GICV5_ITS_DIDR_DEVICEID, val); + return; + case GICV5_ITS_EIDR: + its->eidr.event_id = FIELD_GET(GICV5_ITS_EIDR_EVENTID, val); + return; + case GICV5_ITS_INV_EVENTR: + its->inv_eventr.l1 = + FIELD_GET(GICV5_ITS_INV_EVENTR_L1, val); + its->inv_eventr.itt_l2sz = + FIELD_GET(GICV5_ITS_INV_EVENTR_ITT_L2SZ, val); + its->inv_eventr.i = FIELD_GET(GICV5_ITS_INV_EVENTR_I, val); + return; + case GICV5_ITS_SYNCR: + case GICV5_ITS_READ_EVENTR: + return; + case GICV5_ITS_GEN_EVENT_EIDR: + its->gen_event_eidr.event_id = + FIELD_GET(GICV5_ITS_GEN_EVENT_EIDR_EVENTID, val); + return; + case GICV5_ITS_GEN_EVENT_DIDR: + case GICV5_ITS_GEN_EVENT_DIDR + 4: + if (offset & 4) + return; + its->gen_event_didr.device_id = + FIELD_GET(GICV5_ITS_GEN_EVENT_DIDR_DEVICEID, val); + return; + case GICV5_ITS_GEN_EVENTR: + return; + default: + return; + } +} + static unsigned long vgic_v5_mmio_read_its_raz(struct kvm *kvm, void *dev, gpa_t addr, unsigned int len) { return 0; } +static unsigned long vgic_v5_its_coresight_read(struct kvm *kvm, void *dev, + gpa_t addr, unsigned int len) +{ + u32 offset = addr & (SZ_64K - 1); + + return vgic_v5_handle_guest_coresight_idreg_read(offset); +} + static void vgic_v5_mmio_write_its_wi(struct kvm *kvm, void *dev, gpa_t addr, unsigned int len, unsigned long val) { @@ -31,11 +292,148 @@ static void vgic_v5_mmio_write_its_wi(struct kvm *kvm, void *dev, gpa_t addr, } static const struct vgic_register_region vgic_v5_its_registers[] = { - REGISTER_ITS_V5_DESC(0, vgic_v5_mmio_read_its_raz, - vgic_v5_mmio_write_its_wi, KVM_VGIC_V5_ITS_SIZE, + REGISTER_ITS_V5_DESC(GICV5_ITS_IDR0, vgic_v5_mmio_read_its_misc, + vgic_v5_mmio_write_its_wi, 4, VGIC_ACCESS_32bit), + REGISTER_ITS_V5_DESC(GICV5_ITS_IDR1, vgic_v5_mmio_read_its_misc, + vgic_v5_mmio_write_its_wi, 4, VGIC_ACCESS_32bit), + REGISTER_ITS_V5_DESC(GICV5_ITS_IDR2, vgic_v5_mmio_read_its_misc, + vgic_v5_mmio_write_its_wi, 4, VGIC_ACCESS_32bit), + REGISTER_ITS_V5_DESC(GICV5_ITS_IIDR, vgic_v5_mmio_read_its_misc, + vgic_v5_mmio_write_its_wi, 4, VGIC_ACCESS_32bit), + REGISTER_ITS_V5_DESC(GICV5_ITS_AIDR, vgic_v5_mmio_read_its_misc, + vgic_v5_mmio_write_its_wi, 4, VGIC_ACCESS_32bit), + REGISTER_ITS_V5_DESC(GICV5_ITS_CR0, vgic_v5_mmio_read_its_misc, + vgic_v5_mmio_write_its_misc, 4, VGIC_ACCESS_32bit), + REGISTER_ITS_V5_DESC(GICV5_ITS_CR1, vgic_v5_mmio_read_its_misc, + vgic_v5_mmio_write_its_misc, 4, VGIC_ACCESS_32bit), + REGISTER_ITS_V5_DESC(GICV5_ITS_DT_BASER, + vgic_v5_mmio_read_its_dt_itt, + vgic_v5_mmio_write_its_dt_itt, 8, + VGIC_ACCESS_64bit | VGIC_ACCESS_32bit), + REGISTER_ITS_V5_DESC(GICV5_ITS_DT_CFGR, + vgic_v5_mmio_read_its_dt_itt, + vgic_v5_mmio_write_its_dt_itt, 4, + VGIC_ACCESS_32bit), + REGISTER_ITS_V5_DESC(GICV5_ITS_DIDR, vgic_v5_mmio_read_its_dt_itt, + vgic_v5_mmio_write_its_dt_itt, 8, + VGIC_ACCESS_64bit | VGIC_ACCESS_32bit), + REGISTER_ITS_V5_DESC(GICV5_ITS_EIDR, vgic_v5_mmio_read_its_dt_itt, + vgic_v5_mmio_write_its_dt_itt, 4, + VGIC_ACCESS_32bit), + REGISTER_ITS_V5_DESC(GICV5_ITS_INV_EVENTR, vgic_v5_mmio_read_its_raz, + vgic_v5_mmio_write_its_dt_itt, 4, + VGIC_ACCESS_32bit), + REGISTER_ITS_V5_DESC(GICV5_ITS_INV_DEVICER, vgic_v5_mmio_read_its_raz, + vgic_v5_mmio_write_its_dt_itt, 4, + VGIC_ACCESS_32bit), + REGISTER_ITS_V5_DESC(GICV5_ITS_READ_EVENTR, vgic_v5_mmio_read_its_raz, + vgic_v5_mmio_write_its_dt_itt, 4, + VGIC_ACCESS_32bit), + REGISTER_ITS_V5_DESC(GICV5_ITS_READ_EVENT_DATAR, + vgic_v5_mmio_read_its_dt_itt, + vgic_v5_mmio_write_its_wi, 8, + VGIC_ACCESS_64bit | VGIC_ACCESS_32bit), + REGISTER_ITS_V5_DESC(GICV5_ITS_STATUSR, vgic_v5_mmio_read_its_dt_itt, + vgic_v5_mmio_write_its_wi, 4, VGIC_ACCESS_32bit), + REGISTER_ITS_V5_DESC(GICV5_ITS_SYNCR, vgic_v5_mmio_read_its_raz, + vgic_v5_mmio_write_its_dt_itt, 4, + VGIC_ACCESS_32bit), + REGISTER_ITS_V5_DESC(GICV5_ITS_SYNC_STATUSR, + vgic_v5_mmio_read_its_dt_itt, + vgic_v5_mmio_write_its_wi, 4, VGIC_ACCESS_32bit), + REGISTER_ITS_V5_DESC(GICV5_ITS_GEN_EVENT_DIDR, + vgic_v5_mmio_read_its_dt_itt, + vgic_v5_mmio_write_its_dt_itt, 8, + VGIC_ACCESS_64bit | VGIC_ACCESS_32bit), + REGISTER_ITS_V5_DESC(GICV5_ITS_GEN_EVENT_EIDR, + vgic_v5_mmio_read_its_dt_itt, + vgic_v5_mmio_write_its_dt_itt, 4, + VGIC_ACCESS_32bit), + REGISTER_ITS_V5_DESC(GICV5_ITS_GEN_EVENTR, vgic_v5_mmio_read_its_raz, + vgic_v5_mmio_write_its_dt_itt, 4, + VGIC_ACCESS_32bit), + REGISTER_ITS_V5_DESC(GICV5_ITS_GEN_EVENT_STATUSR, + vgic_v5_mmio_read_its_dt_itt, + vgic_v5_mmio_write_its_wi, 4, VGIC_ACCESS_32bit), + REGISTER_ITS_V5_DESC(GICV5_ITS_MEC_IDR, vgic_v5_mmio_read_its_raz, + vgic_v5_mmio_write_its_wi, 4, VGIC_ACCESS_32bit), + REGISTER_ITS_V5_DESC(GICV5_ITS_MEC_MECID_R, vgic_v5_mmio_read_its_raz, + vgic_v5_mmio_write_its_wi, 4, VGIC_ACCESS_32bit), + REGISTER_ITS_V5_DESC(GICV5_ITS_MPAM_IDR, vgic_v5_mmio_read_its_raz, + vgic_v5_mmio_write_its_wi, 4, VGIC_ACCESS_32bit), + REGISTER_ITS_V5_DESC(GICV5_ITS_MPAM_PARTID_R, + vgic_v5_mmio_read_its_raz, + vgic_v5_mmio_write_its_wi, 4, VGIC_ACCESS_32bit), + REGISTER_ITS_V5_DESC(GICV5_ITS_SWERR_STATUSR, vgic_v5_mmio_read_its_raz, + vgic_v5_mmio_write_its_wi, 8, VGIC_ACCESS_64bit), + REGISTER_ITS_V5_DESC(GICV5_ITS_SWERR_SYNDROMER0, + vgic_v5_mmio_read_its_raz, + vgic_v5_mmio_write_its_wi, 8, VGIC_ACCESS_64bit), + REGISTER_ITS_V5_DESC(GICV5_ITS_SWERR_SYNDROMER1, + vgic_v5_mmio_read_its_raz, + vgic_v5_mmio_write_its_wi, 8, + VGIC_ACCESS_64bit), + REGISTER_ITS_V5_DESC(GICV5_ITS_MPAM_PMGR, vgic_v5_mmio_read_its_raz, + vgic_v5_mmio_write_its_wi, 8, VGIC_ACCESS_64bit), + REGISTER_ITS_V5_DESC(GICV5_CORESIGHT_DEVARCH, + vgic_v5_its_coresight_read, + vgic_v5_mmio_write_its_wi, + GICV5_CORESIGHT_IDREGS_SIZE, VGIC_ACCESS_32bit), }; +static void vgic_v5_its_reset(struct vgic_v5_its *its) +{ + u64 mmfr0; + + its->enabled = false; + + mmfr0 = read_sanitised_ftr_reg(SYS_ID_AA64MMFR0_EL1); + its->idr0.pa_range = cpuid_feature_extract_unsigned_field( + mmfr0, ID_AA64MMFR0_EL1_PARANGE_SHIFT); + its->idr0.domain = GICV5_ITS_IDR0_DOMAIN_NON_SECURE; + its->idr0.mec = 0; + its->idr0.mpam = 0; + its->idr0.swe = 0; + its->idr0.its_id = 0; + + its->idr1.dt_levels = 1; + its->idr1.itt_levels = 1; + its->idr1.l2sz = 0; + its->idr2.xdmn_events = 0; + + its->cr1.sh = 0; + its->cr1.oc = 0; + its->cr1.ic = 0; + its->cr1.dt_ra = false; + its->cr1.itt_ra = false; + + its->idr1.device_id_bits = VITS_V5_DEVICE_ID_BITS; + its->idr2.event_id_bits = VITS_V5_EVENT_ID_BITS; + + its->dt_cfgr.device_id_bits = 0; + its->dt_cfgr.l2sz = 0; + its->dt_cfgr.structure = false; + its->dt_baser.addr = 0; + + its->inv_devicer.l1 = false; + its->inv_devicer.event_id_bits = 0; + its->inv_devicer.i = false; + its->didr.device_id = 0; + its->eidr.event_id = 0; + its->inv_eventr.l1 = false; + its->inv_eventr.itt_l2sz = 0; + its->inv_eventr.i = false; + + its->read_event_datar.lpi_id = 0; + its->read_event_datar.valid = false; + its->read_event_datar.vm_id = 0; + its->read_event_datar.virt = false; + its->gen_event_eidr.event_id = 0; + its->gen_event_didr.device_id = 0; + +} + static int vgic_v5_its_create(struct kvm_device *dev, u32 type) { struct vgic_v5_its *its; @@ -60,6 +458,8 @@ static int vgic_v5_its_create(struct kvm_device *dev, u32 type) its->vgic_v5_its_base = VGIC_ADDR_UNDEF; its->dev = dev; + vgic_v5_its_reset(its); + dev->kvm->arch.vgic.msis_require_devid = true; dev->kvm->arch.vgic.has_its = true; dev->kvm->arch.vgic.its_model = KVM_DEV_TYPE_ARM_VGIC_V5; diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h index f3bfeee6104c2..c9eaa7897b57e 100644 --- a/include/kvm/arm_vgic.h +++ b/include/kvm/arm_vgic.h @@ -506,6 +506,90 @@ struct vgic_v5_its { struct vgic_io_device iodev; struct kvm_device *dev; + bool enabled; + + /* ITS state - used for registers etc */ + struct its_idr0 { + u8 domain; + u8 pa_range; + bool mec; + bool mpam; + bool swe; + u16 its_id; + } idr0; + + struct its_idr1 { + u8 device_id_bits; + u8 dt_levels; + u8 itt_levels; + u8 l2sz; + } idr1; + + struct its_idr2 { + u8 event_id_bits; + u8 xdmn_events; + } idr2; + + struct its_iidr { + u16 implementer; + u8 revision; + u8 variant; + u16 product_id; + } iidr; + + struct its_cr1 { + u8 sh; + u8 oc; + u8 ic; + bool dt_ra; + bool itt_ra; + } cr1; + + struct its_dt_cfgr { + u8 device_id_bits; + u8 l2sz; + bool structure; + } dt_cfgr; + + struct its_dt_baser { + u64 addr; + } dt_baser; + + struct its_inv_devicer { + bool l1; + u8 event_id_bits; + bool i; + } inv_devicer; + + struct its_didr { + u32 device_id; + } didr; + + struct its_eidr { + u16 event_id; + } eidr; + + struct its_inv_eventr { + bool l1; + u8 itt_l2sz; + bool i; + } inv_eventr; + + struct its_read_event_datar { + u32 lpi_id; + bool valid; + u16 vm_id; + bool virt; + } read_event_datar; + + struct its_gen_event_eidr { + u16 event_id; + } gen_event_eidr; + + struct its_gen_event_didr { + u32 device_id; + } gen_event_didr; + /* Protects the device in general */ struct mutex its_lock; }; -- 2.34.1 The GICv5 ITS consumes guest-owned translation tables. Unlike the GICv3 ITS, KVM does not maintain a separate software device/table model for GICv5 translations; the guest's DT and ITTs are the authoritative source of DeviceID/EventID to LPI mappings. Add helpers to walk both linear and two-level guest Device Tables and Interrupt Translation Tables. The walker reads the guest table entries under the ITS lock, validates the relevant valid bits and range fields, and resolves a DeviceID/EventID pair to the LPI encoded in the guest's ITTE. Use the table walker to implement the translation side effects of the MMIO interface. READ_EVENTR now resolves DIDR/EIDR and reports the LPI through READ_EVENT_DATAR, while GEN_EVENTR resolves GEN_EVENT_DIDR and GEN_EVENT_EIDR and injects the resulting LPI into the VM. Signed-off-by: Sascha Bischoff --- arch/arm64/kvm/vgic/vgic-its-v5.c | 298 ++++++++++++++++++++++++++++++ 1 file changed, 298 insertions(+) diff --git a/arch/arm64/kvm/vgic/vgic-its-v5.c b/arch/arm64/kvm/vgic/vgic-its-v5.c index fda75314229a5..f6d2acca4ae4c 100644 --- a/arch/arm64/kvm/vgic/vgic-its-v5.c +++ b/arch/arm64/kvm/vgic/vgic-its-v5.c @@ -19,6 +19,300 @@ #define VITS_V5_DEVICE_ID_BITS 16 #define VITS_V5_EVENT_ID_BITS 16 +static int parse_linear_dt(struct kvm *kvm, gpa_t dt_base, u32 device_id, + gpa_t *itt_base, bool *two_level_itt, + unsigned int *event_id_bits, unsigned int *itt_l2sz) +{ + __le64 dt_l2_entry; + gpa_t entry_addr; + u64 val; + int ret; + + entry_addr = dt_base + device_id * sizeof(__le64); + ret = kvm_read_guest_lock(kvm, entry_addr, &dt_l2_entry, + sizeof(dt_l2_entry)); + if (ret) { + kvm_debug("Failed to read guest memory for L2 DT\n"); + return ret; + } + + val = le64_to_cpu(dt_l2_entry); + if (!FIELD_GET(GICV5_DTL2E_VALID, val)) { + kvm_debug("Guest DT entry is invalid\n"); + return -EINVAL; + } + + *itt_base = FIELD_GET(GICV5_DTL2E_ITT_ADDR_MASK, val) << 3; + *two_level_itt = FIELD_GET(GICV5_DTL2E_ITT_STRUCTURE, val); + *event_id_bits = FIELD_GET(GICV5_DTL2E_EVENT_ID_BITS, val); + *itt_l2sz = FIELD_GET(GICV5_DTL2E_ITT_L2SZ, val); + + return 0; +} + +static int parse_two_level_dt(struct kvm *kvm, gpa_t dt_base, + unsigned int dt_l2sz, u32 device_id, + gpa_t *itt_base, bool *two_level_itt, + unsigned int *event_id_bits, + unsigned int *itt_l2sz) +{ + unsigned int device_id_l1_shift, l2_device_id; + __le64 dt_l1_entry; + gpa_t dt_l2_base; + gpa_t entry_addr; + u64 val; + int ret; + + switch (dt_l2sz) { + case 0: + device_id_l1_shift = 9; + break; + case 1: + device_id_l1_shift = 11; + break; + case 2: + device_id_l1_shift = 13; + break; + default: + return -EINVAL; + } + + entry_addr = dt_base + + (device_id >> device_id_l1_shift) * sizeof(__le64); + + ret = kvm_read_guest_lock(kvm, entry_addr, &dt_l1_entry, + sizeof(dt_l1_entry)); + if (ret) { + kvm_debug("Failed to read guest memory for L1 DT\n"); + return ret; + } + + val = le64_to_cpu(dt_l1_entry); + if (!FIELD_GET(GICV5_DTL1E_VALID, val)) { + kvm_debug("Guest DT L1 entry is invalid\n"); + return -EINVAL; + } + + dt_l2_base = FIELD_GET(GICV5_DTL1E_L2_ADDR_MASK, val) << 3; + l2_device_id = device_id % (1UL << device_id_l1_shift); + if (l2_device_id >= (1UL << FIELD_GET(GICV5_DTL1E_SPAN, val))) { + kvm_debug("DeviceID 0x%x out of range of guest DT\n", + device_id); + return -ENXIO; + } + + return parse_linear_dt(kvm, dt_l2_base, l2_device_id, itt_base, + two_level_itt, event_id_bits, itt_l2sz); +} + +static int get_itt(struct kvm *kvm, struct vgic_v5_its *its, u32 device_id, + gpa_t *itt_base, bool *two_level_itt, + unsigned int *event_id_bits, unsigned int *itt_l2sz) +{ + unsigned int dt_l2sz, device_id_bits; + bool two_level_dt; + gpa_t dt_base; + + dt_base = its->dt_baser.addr; + two_level_dt = its->dt_cfgr.structure; + dt_l2sz = its->dt_cfgr.l2sz; + device_id_bits = its->dt_cfgr.device_id_bits; + + if (device_id >= 1UL << device_id_bits) { + kvm_debug("Device ID out of range\n"); + return -E2BIG; + } + + if (!two_level_dt) + return parse_linear_dt(kvm, dt_base, device_id, itt_base, + two_level_itt, event_id_bits, itt_l2sz); + + return parse_two_level_dt(kvm, dt_base, dt_l2sz, device_id, itt_base, + two_level_itt, event_id_bits, itt_l2sz); +} + +static int parse_linear_itt(struct kvm *kvm, u32 event_id, gpa_t itt_base, + __le64 *itt_l2_entry) +{ + gpa_t entry_addr = itt_base + event_id * sizeof(*itt_l2_entry); + int ret; + + ret = kvm_read_guest_lock(kvm, entry_addr, itt_l2_entry, + sizeof(*itt_l2_entry)); + if (ret) + kvm_debug("Failed to read guest memory for L2 ITT\n"); + + return ret; +} + +static int parse_two_level_itt(struct kvm *kvm, u32 event_id, gpa_t itt_base, + unsigned int itt_l2sz, __le64 *itt_l2_entry) +{ + unsigned int itt_split_bits, l2_event_id_bits; + gpa_t itt_l2_base, entry_addr; + u32 l1_event_id, l2_event_id; + __le64 itt_l1_entry; + u64 val; + int ret; + + switch (itt_l2sz) { + case 0: + itt_split_bits = 9; + break; + case 1: + itt_split_bits = 11; + break; + case 2: + itt_split_bits = 13; + break; + default: + return -EINVAL; + } + + l1_event_id = event_id >> itt_split_bits; + entry_addr = itt_base + l1_event_id * sizeof(itt_l1_entry); + ret = kvm_read_guest_lock(kvm, entry_addr, &itt_l1_entry, + sizeof(itt_l1_entry)); + if (ret) { + kvm_debug("Failed to read guest memory for L1 ITT\n"); + return ret; + } + + val = le64_to_cpu(itt_l1_entry); + if (!FIELD_GET(GICV5_ITTL1E_VALID, val)) { + kvm_debug("Guest ITT L1 entry is invalid\n"); + return -EINVAL; + } + + itt_l2_base = FIELD_GET(GICV5_ITTL1E_L2_ADDR_MASK, val) << 3; + l2_event_id_bits = + min(FIELD_GET(GICV5_ITTL1E_SPAN, val), itt_split_bits); + l2_event_id = event_id % (1UL << l2_event_id_bits); + + if ((itt_split_bits != l2_event_id_bits) && + (event_id & ((1UL << itt_split_bits) - 1)) != l2_event_id) { + kvm_debug("EventID is out of range of L2 ITT\n"); + return -ENXIO; + } + + return parse_linear_itt(kvm, l2_event_id, itt_l2_base, itt_l2_entry); +} + +static int get_itte(struct kvm *kvm, u32 event_id, gpa_t itt_base, + bool two_level_itt, unsigned int itt_l2sz, + __le64 *itt_l2_entry) +{ + if (!two_level_itt) + return parse_linear_itt(kvm, event_id, itt_base, itt_l2_entry); + + return parse_two_level_itt(kvm, event_id, itt_base, itt_l2sz, + itt_l2_entry); +} + +static int __vgic_v5_its_resolve_lpi(struct kvm *kvm, struct vgic_v5_its *its, + u32 device_id, u16 event_id, + struct vgic_irq **irq) +{ + unsigned int itt_l2sz, event_id_bits; + __le64 itt_l2_entry; + bool two_level_itt; + gpa_t itt_base; + u32 lpi; + u64 val; + int ret; + + lockdep_assert_held(&its->its_lock); + + if (!its->enabled) { + kvm_debug("Cannot look up LPI; vITS is disabled\n"); + return -ENXIO; + } + + if (WARN_ON_ONCE(kvm->mm != get_current()->mm)) + return -EWOULDBLOCK; + + ret = get_itt(kvm, its, device_id, &itt_base, &two_level_itt, + &event_id_bits, &itt_l2sz); + if (ret) { + kvm_debug("Error parsing guest's DT\n"); + return ret; + } + + if (event_id >= 1UL << event_id_bits) { + kvm_debug("Event ID out of range\n"); + return -E2BIG; + } + + ret = get_itte(kvm, event_id, itt_base, two_level_itt, itt_l2sz, + &itt_l2_entry); + if (ret) + return ret; + + val = le64_to_cpu(itt_l2_entry); + if (!FIELD_GET(GICV5_ITTL2E_VALID, val)) { + kvm_debug("ITTE is not valid\n"); + return -EINVAL; + } + + lpi = FIELD_GET(GICV5_ITTL2E_LPI_ID, val); + + *irq = vgic_add_lpi(kvm, vgic_v5_make_lpi(lpi), NULL); + if (IS_ERR(*irq)) + return PTR_ERR(*irq); + + return 0; +} + +static void vgic_v5_its_inject_lpi(struct kvm *kvm, struct vgic_irq *irq) +{ + kvm_call_hyp(__vgic_v5_vdpend, irq->intid, true, + kvm->arch.vgic.gicv5_vm.vm_id); +} + +static void vgic_v5_its_handle_read_event(struct kvm *kvm, + struct vgic_v5_its *its) +{ + struct vgic_irq *irq; + int ret; + + its->read_event_datar.valid = false; + its->read_event_datar.lpi_id = 0; + its->read_event_datar.vm_id = 0; + its->read_event_datar.virt = false; + + ret = __vgic_v5_its_resolve_lpi(kvm, its, its->didr.device_id, + its->eidr.event_id, &irq); + if (ret) + return; + + its->read_event_datar.lpi_id = vgic_v5_get_hwirq_id(irq->intid); + its->read_event_datar.valid = true; + vgic_put_irq(kvm, irq); +} + +static void vgic_v5_its_handle_gen_event(struct kvm *kvm, + struct vgic_v5_its *its, + unsigned long val) +{ + struct vgic_irq *irq; + int ret; + + if (!FIELD_GET(GICV5_ITS_GEN_EVENTR_R, val)) + return; + + if (FIELD_GET(GICV5_ITS_GEN_EVENTR_TARGET_DOMAIN, val)) + return; + + ret = __vgic_v5_its_resolve_lpi(kvm, its, + its->gen_event_didr.device_id, + its->gen_event_eidr.event_id, &irq); + if (ret) + return; + + vgic_v5_its_inject_lpi(kvm, irq); + vgic_put_irq(kvm, irq); +} + static unsigned long vgic_v5_mmio_read_its_misc(struct kvm *kvm, void *dev, gpa_t addr, unsigned int len) { @@ -246,7 +540,10 @@ static void vgic_v5_mmio_write_its_dt_itt(struct kvm *kvm, void *dev, its->inv_eventr.i = FIELD_GET(GICV5_ITS_INV_EVENTR_I, val); return; case GICV5_ITS_SYNCR: + return; case GICV5_ITS_READ_EVENTR: + if (FIELD_GET(GICV5_ITS_READ_EVENTR_R, val)) + vgic_v5_its_handle_read_event(kvm, its); return; case GICV5_ITS_GEN_EVENT_EIDR: its->gen_event_eidr.event_id = @@ -260,6 +557,7 @@ static void vgic_v5_mmio_write_its_dt_itt(struct kvm *kvm, void *dev, FIELD_GET(GICV5_ITS_GEN_EVENT_DIDR_DEVICEID, val); return; case GICV5_ITS_GEN_EVENTR: + vgic_v5_its_handle_gen_event(kvm, its, val); return; default: return; -- 2.34.1 Walking the guest DT and ITTs on every MSI or ITS event is unnecessary once a DeviceID/EventID pair has been resolved. Add a small hash table to cache resolved translations as a disposable acceleration structure which avoids iterating over guest memory whenever possible. Cache entries hold references to the resolved struct vgic_irq objects stored in the VM-wide LPI xarray. Lookups take an IRQ reference before returning an entry to the caller, and cache eviction drops the reference held by the cache. This keeps the cache tied to the normal LPI object lifetime instead of storing raw LPI numbers. Invalidate cached translations when the table configuration or guest invalidation commands make them stale. Changes to DT_BASER or DT_CFGR drop the entire cache, while writes of unchanged values leave it intact. INV_EVENTR removes either one event or the events covered by an L1 ITT entry, and INV_DEVICER removes the translations covered by either an L2 DT entry or an L1 DT entry. The cache is not part of any userspace ABI and is never authoritative: entries can always be rebuilt from guest memory on the next lookup. The vgic_irq objects for LPIs track valid translations rather than mirror interrupt state. Mirroring that state would require querying the GIC on every guest exit. Once direct injection is added, the objects also record whether an LPI is mapped into the host ITT. Signed-off-by: Sascha Bischoff --- arch/arm64/kvm/vgic/vgic-its-v5.c | 343 ++++++++++++++++++++++++++++-- include/kvm/arm_vgic.h | 7 + 2 files changed, 335 insertions(+), 15 deletions(-) diff --git a/arch/arm64/kvm/vgic/vgic-its-v5.c b/arch/arm64/kvm/vgic/vgic-its-v5.c index f6d2acca4ae4c..e4c0415137605 100644 --- a/arch/arm64/kvm/vgic/vgic-its-v5.c +++ b/arch/arm64/kvm/vgic/vgic-its-v5.c @@ -19,6 +19,272 @@ #define VITS_V5_DEVICE_ID_BITS 16 #define VITS_V5_EVENT_ID_BITS 16 +struct its_cache_translation_entry { + struct hlist_node node; + u64 key; + struct vgic_irq *irq; +}; + +static void its_cache_drop_translation(struct kvm *kvm, + struct its_cache_translation_entry *entry) +{ + struct vgic_irq *irq = entry->irq; + + vgic_put_irq(kvm, irq); + kfree(entry); +} + +static void its_cache_drop_removed_translations(struct kvm *kvm, + struct hlist_head *removed) +{ + struct its_cache_translation_entry *entry; + struct hlist_node *tmp; + + hlist_for_each_entry_safe(entry, tmp, removed, node) { + hlist_del(&entry->node); + its_cache_drop_translation(kvm, entry); + } +} + +static int its_cache_add_translation(struct kvm *kvm, struct vgic_v5_its *its, + u32 device_id, u16 event_id, + struct vgic_irq *irq) +{ + u64 key = ((u64)device_id << 16) | (u64)event_id; + struct its_cache_translation_entry *entry; + struct its_cache_translation_entry *old; + struct hlist_node *tmp; + HLIST_HEAD(removed); + + entry = kzalloc_obj(*entry, GFP_KERNEL); + if (!entry) + return -ENOMEM; + + entry->key = key; + entry->irq = irq; + + scoped_guard(raw_spinlock, &its->translation_cache_lock) { + hash_for_each_possible_safe(its->translation_cache, old, tmp, + node, key) { + if (old->key == key) { + hash_del(&old->node); + hlist_add_head(&old->node, &removed); + } + } + + hash_add(its->translation_cache, &entry->node, entry->key); + } + + its_cache_drop_removed_translations(kvm, &removed); + + return 0; +} + +static int its_cache_remove_translation(struct vgic_v5_its *its, u32 device_id, + u16 event_id) +{ + u64 key = ((u64)device_id << 16) | (u64)event_id; + struct its_cache_translation_entry *entry; + struct hlist_node *tmp; + HLIST_HEAD(removed); + bool found = false; + + scoped_guard(raw_spinlock, &its->translation_cache_lock) { + hash_for_each_possible_safe(its->translation_cache, entry, tmp, + node, key) { + if (entry->key == key) { + hash_del(&entry->node); + hlist_add_head(&entry->node, &removed); + found = true; + } + } + } + + its_cache_drop_removed_translations(its->dev->kvm, &removed); + + return found ? 0 : -EINVAL; +} + +static void its_cache_clear_translations(struct vgic_v5_its *its) +{ + struct its_cache_translation_entry *entry; + struct hlist_node *tmp; + HLIST_HEAD(removed); + int bkt; + + scoped_guard(raw_spinlock, &its->translation_cache_lock) { + hash_for_each_safe(its->translation_cache, bkt, tmp, entry, + node) { + hash_del(&entry->node); + hlist_add_head(&entry->node, &removed); + } + + WARN_ON(!hash_empty(its->translation_cache)); + } + + its_cache_drop_removed_translations(its->dev->kvm, &removed); +} + +static int its_cache_look_up_translation(struct vgic_v5_its *its, + u32 device_id, u16 event_id, + struct vgic_irq **irq) +{ + u64 key = ((u64)device_id << 16) | (u64)event_id; + struct its_cache_translation_entry *entry; + + guard(raw_spinlock)(&its->translation_cache_lock); + + hash_for_each_possible(its->translation_cache, entry, node, key) { + if (entry->key == key) { + if (!vgic_try_get_irq_ref(entry->irq)) + return -ENXIO; + + *irq = entry->irq; + return 0; + } + } + + return -ENXIO; +} + +static void its_cache_handle_inv_devicer_l2(struct vgic_v5_its *its, + u32 device_id, u8 event_id_bits) +{ + struct its_cache_translation_entry *entry; + struct hlist_node *tmp; + HLIST_HEAD(removed); + u64 event_id_limit; + int bkt; + + if (event_id_bits > 16) + event_id_bits = 16; + event_id_limit = 1ULL << event_id_bits; + + scoped_guard(raw_spinlock, &its->translation_cache_lock) { + hash_for_each_safe(its->translation_cache, bkt, tmp, entry, + node) { + if ((entry->key >> 16 == device_id) && + ((entry->key & 0xffffULL) < event_id_limit)) { + hash_del(&entry->node); + hlist_add_head(&entry->node, &removed); + } + } + } + + its_cache_drop_removed_translations(its->dev->kvm, &removed); +} + +static void its_cache_handle_inv_devicer_l1(struct vgic_v5_its *its, + u32 device_id, u8 l2sz) +{ + struct its_cache_translation_entry *entry; + struct hlist_node *tmp; + HLIST_HEAD(removed); + u8 device_id_split_bits; + u32 device_id_mask; + int bkt; + + switch (l2sz) { + case 0: + device_id_split_bits = 9; + break; + case 1: + device_id_split_bits = 11; + break; + case 2: + device_id_split_bits = 13; + break; + default: + return; + } + + device_id_mask = ~((1U << device_id_split_bits) - 1); + device_id &= device_id_mask; + + scoped_guard(raw_spinlock, &its->translation_cache_lock) { + hash_for_each_safe(its->translation_cache, bkt, tmp, entry, + node) { + if (((entry->key >> 16) & device_id_mask) == + device_id) { + hash_del(&entry->node); + hlist_add_head(&entry->node, &removed); + } + } + } + + its_cache_drop_removed_translations(its->dev->kvm, &removed); +} + +static void its_cache_handle_inv_devicer(struct vgic_v5_its *its) +{ + const u8 event_id_bits = its->inv_devicer.event_id_bits; + const bool structure = its->dt_cfgr.structure; + const bool l1 = its->inv_devicer.l1 && structure; + const u32 device_id = its->didr.device_id; + const u8 l2sz = its->dt_cfgr.l2sz; + + if (!l1) + its_cache_handle_inv_devicer_l2(its, device_id, event_id_bits); + else + its_cache_handle_inv_devicer_l1(its, device_id, l2sz); +} + +static void its_cache_handle_inv_eventr_l1(struct vgic_v5_its *its, + u32 device_id, u16 event_id, + u8 itt_l2sz) +{ + struct its_cache_translation_entry *entry; + struct hlist_node *tmp; + HLIST_HEAD(removed); + u8 lower_event_id_bit; + u16 event_id_mask; + int bkt; + + switch (itt_l2sz) { + case 0: + lower_event_id_bit = 9; + break; + case 1: + lower_event_id_bit = 11; + break; + case 2: + lower_event_id_bit = 13; + break; + default: + return; + } + + event_id_mask = (u16)((0xffffU << lower_event_id_bit) & 0xffffU); + event_id &= event_id_mask; + + scoped_guard(raw_spinlock, &its->translation_cache_lock) { + hash_for_each_safe(its->translation_cache, bkt, tmp, entry, + node) { + if ((entry->key >> 16 == device_id) && + ((entry->key & event_id_mask) == event_id)) { + hash_del(&entry->node); + hlist_add_head(&entry->node, &removed); + } + } + } + + its_cache_drop_removed_translations(its->dev->kvm, &removed); +} + +static void its_cache_handle_inv_eventr(struct vgic_v5_its *its) +{ + const bool l1 = its->inv_eventr.l1; + const u8 itt_l2sz = its->inv_eventr.itt_l2sz; + const u16 event_id = its->eidr.event_id; + const u32 device_id = its->didr.device_id; + + if (!l1) + its_cache_remove_translation(its, device_id, event_id); + else + its_cache_handle_inv_eventr_l1(its, device_id, event_id, + itt_l2sz); +} + static int parse_linear_dt(struct kvm *kvm, gpa_t dt_base, u32 device_id, gpa_t *itt_base, bool *two_level_itt, unsigned int *event_id_bits, unsigned int *itt_l2sz) @@ -228,6 +494,10 @@ static int __vgic_v5_its_resolve_lpi(struct kvm *kvm, struct vgic_v5_its *its, return -ENXIO; } + ret = its_cache_look_up_translation(its, device_id, event_id, irq); + if (!ret) + return 0; + if (WARN_ON_ONCE(kvm->mm != get_current()->mm)) return -EWOULDBLOCK; @@ -260,7 +530,15 @@ static int __vgic_v5_its_resolve_lpi(struct kvm *kvm, struct vgic_v5_its *its, if (IS_ERR(*irq)) return PTR_ERR(*irq); - return 0; + vgic_get_irq_ref(*irq); + ret = its_cache_add_translation(kvm, its, device_id, event_id, *irq); + if (ret) { + vgic_put_irq(kvm, *irq); + vgic_put_irq(kvm, *irq); + *irq = NULL; + } + + return ret; } static void vgic_v5_its_inject_lpi(struct kvm *kvm, struct vgic_irq *irq) @@ -385,9 +663,17 @@ static void vgic_v5_mmio_write_its_misc(struct kvm *kvm, void *dev, gpa_t addr, size_t offset = addr & (SZ_64K - 1); switch (offset) { - case GICV5_ITS_CR0: - its->enabled = FIELD_GET(GICV5_ITS_CR0_ITSEN, val); + case GICV5_ITS_CR0: { + guard(mutex)(&its->its_lock); + + if (FIELD_GET(GICV5_ITS_CR0_ITSEN, val)) { + its->enabled = true; + } else { + its->enabled = false; + its_cache_clear_translations(its); + } return; + } case GICV5_ITS_CR1: its->cr1.sh = FIELD_GET(GICV5_ITS_CR1_SH, val); its->cr1.oc = FIELD_GET(GICV5_ITS_CR1_OC, val); @@ -403,21 +689,39 @@ static void vgic_v5_mmio_write_its_misc(struct kvm *kvm, void *dev, gpa_t addr, static void vgic_v5_write_its_dt_reg(struct vgic_v5_its *its, size_t offset, unsigned long val) { + lockdep_assert_held(&its->its_lock); + switch (offset) { - case GICV5_ITS_DT_CFGR: - its->dt_cfgr.device_id_bits = - FIELD_GET(GICV5_ITS_DT_CFGR_DEVICEID_BITS, val); - its->dt_cfgr.l2sz = - FIELD_GET(GICV5_ITS_DT_CFGR_L2SZ, val); - its->dt_cfgr.structure = - FIELD_GET(GICV5_ITS_DT_CFGR_STRUCTURE, val); - return; - case GICV5_ITS_DT_BASER: - its->dt_baser.addr = - FIELD_GET(GICV5_ITS_DT_BASER_ADDR_MASK, val) - << GICV5_ITS_DT_BASER_ADDR_SHIFT; + case GICV5_ITS_DT_CFGR: { + u8 device_id_bits = FIELD_GET(GICV5_ITS_DT_CFGR_DEVICEID_BITS, val); + u8 l2sz = FIELD_GET(GICV5_ITS_DT_CFGR_L2SZ, val); + bool structure = FIELD_GET(GICV5_ITS_DT_CFGR_STRUCTURE, val); + + if (its->dt_cfgr.device_id_bits == device_id_bits && + its->dt_cfgr.l2sz == l2sz && + its->dt_cfgr.structure == structure) + return; + + its->dt_cfgr.device_id_bits = device_id_bits; + its->dt_cfgr.l2sz = l2sz; + its->dt_cfgr.structure = structure; + break; + } + case GICV5_ITS_DT_BASER: { + u64 base = FIELD_GET(GICV5_ITS_DT_BASER_ADDR_MASK, val) + << GICV5_ITS_DT_BASER_ADDR_SHIFT; + + if (its->dt_baser.addr == base) + return; + + its->dt_baser.addr = base; + break; + } + default: return; } + + its_cache_clear_translations(its); } static u64 vgic_v5_read_its_dt_baser(struct vgic_v5_its *its) @@ -522,6 +826,8 @@ static void vgic_v5_mmio_write_its_dt_itt(struct kvm *kvm, void *dev, FIELD_GET(GICV5_ITS_INV_DEVICER_EVENTID_BITS, val); its->inv_devicer.i = FIELD_GET(GICV5_ITS_INV_DEVICER_I, val); + if (its->inv_devicer.i) + its_cache_handle_inv_devicer(its); return; case GICV5_ITS_DIDR: case GICV5_ITS_DIDR + 4: @@ -538,6 +844,8 @@ static void vgic_v5_mmio_write_its_dt_itt(struct kvm *kvm, void *dev, its->inv_eventr.itt_l2sz = FIELD_GET(GICV5_ITS_INV_EVENTR_ITT_L2SZ, val); its->inv_eventr.i = FIELD_GET(GICV5_ITS_INV_EVENTR_I, val); + if (its->inv_eventr.i) + its_cache_handle_inv_eventr(its); return; case GICV5_ITS_SYNCR: return; @@ -730,6 +1038,7 @@ static void vgic_v5_its_reset(struct vgic_v5_its *its) its->gen_event_eidr.event_id = 0; its->gen_event_didr.device_id = 0; + its_cache_clear_translations(its); } static int vgic_v5_its_create(struct kvm_device *dev, u32 type) @@ -753,6 +1062,9 @@ static int vgic_v5_its_create(struct kvm_device *dev, u32 type) return -ENODEV; } + hash_init(its->translation_cache); + raw_spin_lock_init(&its->translation_cache_lock); + its->vgic_v5_its_base = VGIC_ADDR_UNDEF; its->dev = dev; @@ -770,6 +1082,7 @@ static void vgic_v5_its_destroy(struct kvm_device *kvm_dev) { struct vgic_v5_its *its = kvm_dev->private; + its_cache_clear_translations(its); kfree(its); kfree(kvm_dev); } diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h index c9eaa7897b57e..bf970275868c9 100644 --- a/include/kvm/arm_vgic.h +++ b/include/kvm/arm_vgic.h @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -590,6 +591,12 @@ struct vgic_v5_its { u32 device_id; } gen_event_didr; + /* A hash table with 2^3 buckets */ + DECLARE_HASHTABLE(translation_cache, 3); + + /* Protects the translation_cache */ + raw_spinlock_t translation_cache_lock; + /* Protects the device in general */ struct mutex its_lock; }; -- 2.34.1 Now that KVM can resolve GICv5 ITS translations from guest memory, allow userspace and irqfd MSI injection to target a virtual GICv5 ITS. Add helpers to validate that an MSI address resolves to a GICv5 ITS IODEV, find the ITS for an MSI, resolve the DeviceID/EventID pair to a vgic_irq, and inject the resulting LPI with the GICv5 VDPEND path. GICv5 MSIs require KVM_MSI_VALID_DEVID, as the DeviceID is part of the ITS translation key. Although MSI data is 32 bits, GICv5 supports only 16-bit EventIDs. Reject a nonzero upper half before narrowing the EventID for cache lookups, so malformed MSI data cannot alias a valid translation. Provide both the slow path, which resolves the translation from guest memory and populates the ITS cache, and the inatomic cached path used by irqfd. If the cached path misses, irqfd retries in a work item. The worker borrows the VM's mm while walking the guest tables, then drops it before injecting the LPI. Wire kvm_set_msi() and kvm_arch_set_irq_inatomic() to use the GICv5 ITS injection helpers for GICv5 VMs, while keeping the existing GICv3 ITS path unchanged for GICv3 VMs. Signed-off-by: Sascha Bischoff --- arch/arm64/kvm/vgic/vgic-irqfd.c | 8 ++ arch/arm64/kvm/vgic/vgic-its-v5.c | 127 +++++++++++++++++++++++++++++- arch/arm64/kvm/vgic/vgic-v5.c | 25 ++++++ arch/arm64/kvm/vgic/vgic.h | 7 ++ 4 files changed, 164 insertions(+), 3 deletions(-) diff --git a/arch/arm64/kvm/vgic/vgic-irqfd.c b/arch/arm64/kvm/vgic/vgic-irqfd.c index ee1a1b214f03c..c5f5ce8b80c1d 100644 --- a/arch/arm64/kvm/vgic/vgic-irqfd.c +++ b/arch/arm64/kvm/vgic/vgic-irqfd.c @@ -114,6 +114,10 @@ int kvm_set_msi(struct kvm_kernel_irq_routing_entry *e, return -1; kvm_populate_msi(e, &msi); + + if (vgic_is_v5(kvm)) + return vgic_v5_its_inject_msi(kvm, &msi); + return vgic_its_inject_msi(kvm, &msi); } @@ -135,6 +139,10 @@ int kvm_arch_set_irq_inatomic(struct kvm_kernel_irq_routing_entry *e, break; kvm_populate_msi(e, &msi); + + if (vgic_is_v5(kvm)) + return vgic_v5_its_inject_cached_translation(kvm, &msi); + return vgic_its_inject_cached_translation(kvm, &msi); } diff --git a/arch/arm64/kvm/vgic/vgic-its-v5.c b/arch/arm64/kvm/vgic/vgic-its-v5.c index e4c0415137605..5920fa1bcb2cf 100644 --- a/arch/arm64/kvm/vgic/vgic-its-v5.c +++ b/arch/arm64/kvm/vgic/vgic-its-v5.c @@ -3,7 +3,9 @@ * Copyright (C) 2025 ARM Limited, All Rights Reserved. */ +#include #include +#include #include #include @@ -475,8 +477,45 @@ static int get_itte(struct kvm *kvm, u32 event_id, gpa_t itt_base, itt_l2_entry); } +struct vgic_v5_its *vgic_v5_msi_to_its(struct kvm *kvm, struct kvm_msi *msi) +{ + struct kvm_io_device *kvm_io_dev; + struct vgic_io_device *iodev; + u64 address; + + if (!vgic_has_its(kvm)) + return ERR_PTR(-ENODEV); + + if (!(msi->flags & KVM_MSI_VALID_DEVID)) + return ERR_PTR(-EINVAL); + + /* + * MSI data is 32 bits, but GICv5 limits EventIDs to 16 bits. + * Match that limit in the vITS. + */ + if (msi->data >> VITS_V5_EVENT_ID_BITS) + return ERR_PTR(-EINVAL); + + address = (u64)msi->address_hi << 32 | msi->address_lo; + + guard(srcu)(&kvm->srcu); + + kvm_io_dev = kvm_io_bus_get_dev(kvm, KVM_MMIO_BUS, address); + if (!kvm_io_dev) + return ERR_PTR(-EINVAL); + + if (kvm_io_dev->ops != &kvm_io_gic_ops) + return ERR_PTR(-EINVAL); + + iodev = container_of(kvm_io_dev, struct vgic_io_device, dev); + if (iodev->iodev_type != IODEV_GICV5_ITS) + return ERR_PTR(-EINVAL); + + return iodev->v5its; +} + static int __vgic_v5_its_resolve_lpi(struct kvm *kvm, struct vgic_v5_its *its, - u32 device_id, u16 event_id, + u32 device_id, u32 event_id, struct vgic_irq **irq) { unsigned int itt_l2sz, event_id_bits; @@ -488,13 +527,15 @@ static int __vgic_v5_its_resolve_lpi(struct kvm *kvm, struct vgic_v5_its *its, int ret; lockdep_assert_held(&its->its_lock); + if (event_id >> VITS_V5_EVENT_ID_BITS) + return -EINVAL; if (!its->enabled) { kvm_debug("Cannot look up LPI; vITS is disabled\n"); return -ENXIO; } - ret = its_cache_look_up_translation(its, device_id, event_id, irq); + ret = its_cache_look_up_translation(its, device_id, (u16)event_id, irq); if (!ret) return 0; @@ -531,7 +572,8 @@ static int __vgic_v5_its_resolve_lpi(struct kvm *kvm, struct vgic_v5_its *its, return PTR_ERR(*irq); vgic_get_irq_ref(*irq); - ret = its_cache_add_translation(kvm, its, device_id, event_id, *irq); + ret = its_cache_add_translation(kvm, its, device_id, (u16)event_id, + *irq); if (ret) { vgic_put_irq(kvm, *irq); vgic_put_irq(kvm, *irq); @@ -541,12 +583,91 @@ static int __vgic_v5_its_resolve_lpi(struct kvm *kvm, struct vgic_v5_its *its, return ret; } +int vgic_v5_its_resolve_lpi(struct kvm *kvm, struct vgic_v5_its *its, + u32 device_id, u32 event_id, + struct vgic_irq **irq) +{ + guard(mutex)(&its->its_lock); + + return __vgic_v5_its_resolve_lpi(kvm, its, device_id, event_id, irq); +} + static void vgic_v5_its_inject_lpi(struct kvm *kvm, struct vgic_irq *irq) { kvm_call_hyp(__vgic_v5_vdpend, irq->intid, true, kvm->arch.vgic.gicv5_vm.vm_id); } +int vgic_v5_its_inject_msi(struct kvm *kvm, struct kvm_msi *msi) +{ + struct vgic_v5_its *its; + struct vgic_irq *irq; + bool borrowed_mm = false; + int ret; + + ret = vgic_v5_check_msi(kvm, msi); + if (ret) + return ret; + + its = vgic_v5_msi_to_its(kvm, msi); + if (IS_ERR(its)) + return PTR_ERR(its); + + /* An irqfd cache miss is resolved by a worker outside the VM's mm. */ + if (current->mm != kvm->mm) { + if (WARN_ON_ONCE(current->mm || !(current->flags & PF_KTHREAD))) + return -EWOULDBLOCK; + + if (!mmget_not_zero(kvm->mm)) + return -EFAULT; + + kthread_use_mm(kvm->mm); + borrowed_mm = true; + } + + ret = vgic_v5_its_resolve_lpi(kvm, its, msi->devid, msi->data, &irq); + if (borrowed_mm) { + kthread_unuse_mm(kvm->mm); + mmput(kvm->mm); + } + + if (ret) + return ret; + + vgic_v5_its_inject_lpi(kvm, irq); + vgic_put_irq(kvm, irq); + + return 1; +} + +int vgic_v5_its_inject_cached_translation(struct kvm *kvm, struct kvm_msi *msi) +{ + struct vgic_v5_its *its; + struct vgic_irq *irq; + int ret; + + ret = vgic_v5_check_msi(kvm, msi); + if (ret) + return ret; + + its = vgic_v5_msi_to_its(kvm, msi); + if (IS_ERR(its)) + return PTR_ERR(its); + + if (!READ_ONCE(its->enabled)) + return -EWOULDBLOCK; + + ret = its_cache_look_up_translation(its, msi->devid, (u16)msi->data, + &irq); + if (ret) + return -EWOULDBLOCK; + + vgic_v5_its_inject_lpi(kvm, irq); + vgic_put_irq(kvm, irq); + + return 0; +} + static void vgic_v5_its_handle_read_event(struct kvm *kvm, struct vgic_v5_its *its) { diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c index 18184020e188b..c90e00f56b298 100644 --- a/arch/arm64/kvm/vgic/vgic-v5.c +++ b/arch/arm64/kvm/vgic/vgic-v5.c @@ -11,6 +11,7 @@ #include #include +#include "vgic-mmio.h" #include "vgic-v5-tables.h" #include "vgic.h" @@ -1635,3 +1636,27 @@ unsigned long vgic_v5_handle_guest_coresight_idreg_read(u32 offset) return 0; } } + +int vgic_v5_check_msi(struct kvm *kvm, struct kvm_msi *msi) +{ + struct kvm_io_device *kvm_io_dev; + struct vgic_io_device *iodev; + u64 address; + + address = (u64)msi->address_hi << 32 | msi->address_lo; + + guard(srcu)(&kvm->srcu); + + kvm_io_dev = kvm_io_bus_get_dev(kvm, KVM_MMIO_BUS, address); + if (!kvm_io_dev) + return -EINVAL; + + if (kvm_io_dev->ops != &kvm_io_gic_ops) + return -EINVAL; + + iodev = container_of(kvm_io_dev, struct vgic_io_device, dev); + if (iodev->iodev_type != IODEV_GICV5_ITS) + return -EINVAL; + + return 0; +} diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h index 5a9aa9bd11dd7..3d06786142c2d 100644 --- a/arch/arm64/kvm/vgic/vgic.h +++ b/arch/arm64/kvm/vgic/vgic.h @@ -362,6 +362,13 @@ void vgic_v3_put(struct kvm_vcpu *vcpu); bool vgic_has_its(struct kvm *kvm); int kvm_vgic_register_its_device(void); int kvm_vgic_v5_register_its_device(void); +int vgic_v5_check_msi(struct kvm *kvm, struct kvm_msi *msi); +struct vgic_v5_its *vgic_v5_msi_to_its(struct kvm *kvm, struct kvm_msi *msi); +int vgic_v5_its_resolve_lpi(struct kvm *kvm, struct vgic_v5_its *its, + u32 device_id, u32 event_id, + struct vgic_irq **irq); +int vgic_v5_its_inject_msi(struct kvm *kvm, struct kvm_msi *msi); +int vgic_v5_its_inject_cached_translation(struct kvm *kvm, struct kvm_msi *msi); void vgic_enable_lpis(struct kvm_vcpu *vcpu); void vgic_flush_pending_lpis(struct kvm_vcpu *vcpu); int vgic_its_inject_msi(struct kvm *kvm, struct kvm_msi *msi); -- 2.34.1 Userspace needs access to the emulated GICv5 ITS MMIO state in order to inspect and restore the device state around VM reset and migration. Expose the GICv5 ITS register frame through KVM_DEV_ARM_VGIC_GRP_ITS_REGS. Reuse the same MMIO register descriptors used by the guest-facing IODEV, but apply the usual userspace access constraints: the ITS base address must have been configured, the register offset must map to an emulated ITS register, and userspace accesses must use the natural 32-bit or 64-bit register alignment. Add control handling for KVM_DEV_ARM_ITS_CTRL_RESET. The reset is performed while all vCPUs are stopped and while holding the ITS lock, so it cannot race with guest MMIO updates or translation lookups. The reset clears guest-programmed state and cached translations while preserving the advertised ITS capability defaults. Do not expose GICv5 ITS SAVE_TABLES or RESTORE_TABLES controls. For GICv5, the guest-owned DT and ITTs are the authoritative translation state, and the KVM translation cache is disposable state that can be rebuilt from guest memory. Signed-off-by: Sascha Bischoff --- arch/arm64/kvm/vgic/vgic-its-v5.c | 270 +++++++++++++++++++++++++----- 1 file changed, 229 insertions(+), 41 deletions(-) diff --git a/arch/arm64/kvm/vgic/vgic-its-v5.c b/arch/arm64/kvm/vgic/vgic-its-v5.c index 5920fa1bcb2cf..62c1ca8004007 100644 --- a/arch/arm64/kvm/vgic/vgic-its-v5.c +++ b/arch/arm64/kvm/vgic/vgic-its-v5.c @@ -851,6 +851,19 @@ static u64 vgic_v5_read_its_dt_baser(struct vgic_v5_its *its) its->dt_baser.addr >> GICV5_ITS_DT_BASER_ADDR_SHIFT); } +static int vgic_v5_uaccess_write_its_dt_itt(struct kvm *kvm, void *dev, + gpa_t addr, unsigned int len, + unsigned long val) +{ + struct vgic_v5_its *its = dev; + size_t offset = addr & (SZ_64K - 1); + + guard(mutex)(&its->its_lock); + + vgic_v5_write_its_dt_reg(its, offset, val); + return 0; +} + static unsigned long vgic_v5_mmio_read_its_dt_itt(struct kvm *kvm, void *dev, gpa_t addr, unsigned int len) { @@ -1018,6 +1031,13 @@ static void vgic_v5_mmio_write_its_wi(struct kvm *kvm, void *dev, gpa_t addr, .its_read = rd, .its_write = wr, \ } +#define REGISTER_ITS_V5_DESC_UACCESS(off, rd, wr, uwr, length, acc) \ + { \ + .reg_offset = off, .len = length, .access_flags = acc, \ + .its_read = rd, .its_write = wr, \ + .uaccess_its_write = uwr, \ + } + static const struct vgic_register_region vgic_v5_its_registers[] = { REGISTER_ITS_V5_DESC(GICV5_ITS_IDR0, vgic_v5_mmio_read_its_misc, vgic_v5_mmio_write_its_wi, 4, VGIC_ACCESS_32bit), @@ -1033,14 +1053,16 @@ static const struct vgic_register_region vgic_v5_its_registers[] = { vgic_v5_mmio_write_its_misc, 4, VGIC_ACCESS_32bit), REGISTER_ITS_V5_DESC(GICV5_ITS_CR1, vgic_v5_mmio_read_its_misc, vgic_v5_mmio_write_its_misc, 4, VGIC_ACCESS_32bit), - REGISTER_ITS_V5_DESC(GICV5_ITS_DT_BASER, - vgic_v5_mmio_read_its_dt_itt, - vgic_v5_mmio_write_its_dt_itt, 8, - VGIC_ACCESS_64bit | VGIC_ACCESS_32bit), - REGISTER_ITS_V5_DESC(GICV5_ITS_DT_CFGR, - vgic_v5_mmio_read_its_dt_itt, - vgic_v5_mmio_write_its_dt_itt, 4, - VGIC_ACCESS_32bit), + REGISTER_ITS_V5_DESC_UACCESS(GICV5_ITS_DT_BASER, + vgic_v5_mmio_read_its_dt_itt, + vgic_v5_mmio_write_its_dt_itt, + vgic_v5_uaccess_write_its_dt_itt, 8, + VGIC_ACCESS_64bit | VGIC_ACCESS_32bit), + REGISTER_ITS_V5_DESC_UACCESS(GICV5_ITS_DT_CFGR, + vgic_v5_mmio_read_its_dt_itt, + vgic_v5_mmio_write_its_dt_itt, + vgic_v5_uaccess_write_its_dt_itt, 4, + VGIC_ACCESS_32bit), REGISTER_ITS_V5_DESC(GICV5_ITS_DIDR, vgic_v5_mmio_read_its_dt_itt, vgic_v5_mmio_write_its_dt_itt, 8, VGIC_ACCESS_64bit | VGIC_ACCESS_32bit), @@ -1243,64 +1265,230 @@ static int vgic_v5_register_its_iodev(struct kvm *kvm, struct vgic_v5_its *its, return ret; } +static int vgic_v5_its_has_attr_regs(struct kvm_device *dev, + struct kvm_device_attr *attr) +{ + struct vgic_v5_its *its = dev->private; + const struct vgic_register_region *region; + gpa_t offset = attr->attr; + int align; + + if (IS_VGIC_ADDR_UNDEF(its->vgic_v5_its_base)) + return -ENXIO; + + region = vgic_find_mmio_region(vgic_v5_its_registers, + ARRAY_SIZE(vgic_v5_its_registers), + offset); + if (!region) + return -ENXIO; + + align = region->access_flags & VGIC_ACCESS_64bit ? 0x7 : 0x3; + if (offset & align) + return -EINVAL; + + return 0; +} + +static int vgic_v5_its_attr_regs_access(struct kvm_device *dev, + struct kvm_device_attr *attr, + u64 *reg, bool is_write) +{ + const struct vgic_register_region *region; + struct vgic_v5_its *its = dev->private; + gpa_t offset = attr->attr; + unsigned int len; + gpa_t addr; + int align; + int ret = 0; + + mutex_lock(&dev->kvm->lock); + + if (kvm_trylock_all_vcpus(dev->kvm)) { + mutex_unlock(&dev->kvm->lock); + return -EBUSY; + } + + mutex_lock(&dev->kvm->arch.config_lock); + + if (IS_VGIC_ADDR_UNDEF(its->vgic_v5_its_base)) { + ret = -ENXIO; + goto out; + } + + region = vgic_find_mmio_region(vgic_v5_its_registers, + ARRAY_SIZE(vgic_v5_its_registers), + offset); + if (!region) { + ret = -ENXIO; + goto out; + } + + /* + * Although the spec supports upper/lower 32-bit accesses to + * 64-bit ITS registers, the userspace ABI requires 64-bit + * accesses to all 64-bit wide registers. + */ + align = region->access_flags & VGIC_ACCESS_64bit ? 0x7 : 0x3; + len = region->access_flags & VGIC_ACCESS_64bit ? 8 : 4; + + if (offset & align) { + ret = -EINVAL; + goto out; + } + + addr = its->vgic_v5_its_base + offset; + + if (is_write) { + if (region->uaccess_its_write) + ret = region->uaccess_its_write(dev->kvm, its, addr, + len, *reg); + else + region->its_write(dev->kvm, its, addr, len, *reg); + } else { + *reg = region->its_read(dev->kvm, its, addr, len); + } +out: + mutex_unlock(&dev->kvm->arch.config_lock); + kvm_unlock_all_vcpus(dev->kvm); + mutex_unlock(&dev->kvm->lock); + + return ret; +} + +static int vgic_v5_its_ctrl(struct kvm *kvm, struct vgic_v5_its *its, u64 attr) +{ + int ret = 0; + + if (attr == KVM_DEV_ARM_VGIC_CTRL_INIT) + return 0; + + mutex_lock(&kvm->lock); + + if (kvm_trylock_all_vcpus(kvm)) { + mutex_unlock(&kvm->lock); + return -EBUSY; + } + + mutex_lock(&kvm->arch.config_lock); + mutex_lock(&its->its_lock); + + switch (attr) { + case KVM_DEV_ARM_ITS_CTRL_RESET: + vgic_v5_its_reset(its); + break; + default: + ret = -ENXIO; + break; + } + + mutex_unlock(&its->its_lock); + mutex_unlock(&kvm->arch.config_lock); + kvm_unlock_all_vcpus(kvm); + mutex_unlock(&kvm->lock); + + return ret; +} + static int vgic_v5_its_set_attr(struct kvm_device *dev, struct kvm_device_attr *attr) { struct vgic_v5_its *its = dev->private; - u64 __user *uaddr = (u64 __user *)(long)attr->addr; - unsigned long type = (unsigned long)attr->attr; - u64 addr; int ret; - if (attr->group != KVM_DEV_ARM_VGIC_GRP_ADDR) - return -ENXIO; + switch (attr->group) { + case KVM_DEV_ARM_VGIC_GRP_ADDR: { + u64 __user *uaddr = (u64 __user *)(long)attr->addr; + unsigned long type = (unsigned long)attr->attr; + u64 addr; - if (type != KVM_VGIC_V5_ADDR_TYPE_ITS) - return -ENODEV; + if (type != KVM_VGIC_V5_ADDR_TYPE_ITS) + return -ENODEV; - if (copy_from_user(&addr, uaddr, sizeof(addr))) - return -EFAULT; + if (copy_from_user(&addr, uaddr, sizeof(addr))) + return -EFAULT; - ret = vgic_check_iorange(dev->kvm, its->vgic_v5_its_base, addr, - SZ_64K, KVM_VGIC_V5_ITS_SIZE); - if (ret) - return ret; + ret = vgic_check_iorange(dev->kvm, its->vgic_v5_its_base, + addr, SZ_64K, KVM_VGIC_V5_ITS_SIZE); + if (ret) + return ret; + + return vgic_v5_register_its_iodev(dev->kvm, its, addr); + } + case KVM_DEV_ARM_VGIC_GRP_CTRL: + return vgic_v5_its_ctrl(dev->kvm, its, attr->attr); + case KVM_DEV_ARM_VGIC_GRP_ITS_REGS: { + u64 __user *uaddr = (u64 __user *)(long)attr->addr; + u64 reg; + + if (get_user(reg, uaddr)) + return -EFAULT; + + return vgic_v5_its_attr_regs_access(dev, attr, ®, true); + } + } - return vgic_v5_register_its_iodev(dev->kvm, its, addr); + return -ENXIO; } static int vgic_v5_its_get_attr(struct kvm_device *dev, struct kvm_device_attr *attr) { - struct vgic_v5_its *its = dev->private; - u64 __user *uaddr = (u64 __user *)(long)attr->addr; - unsigned long type = (unsigned long)attr->attr; - u64 addr = its->vgic_v5_its_base; + switch (attr->group) { + case KVM_DEV_ARM_VGIC_GRP_ADDR: { + struct vgic_v5_its *its = dev->private; + u64 __user *uaddr = (u64 __user *)(long)attr->addr; + unsigned long type = (unsigned long)attr->attr; + u64 addr = its->vgic_v5_its_base; - if (attr->group != KVM_DEV_ARM_VGIC_GRP_ADDR) - return -ENXIO; + if (type != KVM_VGIC_V5_ADDR_TYPE_ITS) + return -ENODEV; - if (type != KVM_VGIC_V5_ADDR_TYPE_ITS) - return -ENODEV; + if (copy_to_user(uaddr, &addr, sizeof(addr))) + return -EFAULT; - if (copy_to_user(uaddr, &addr, sizeof(addr))) - return -EFAULT; + return 0; + } + case KVM_DEV_ARM_VGIC_GRP_ITS_REGS: { + u64 __user *uaddr = (u64 __user *)(long)attr->addr; + u64 reg; + int ret; - return 0; + ret = vgic_v5_its_attr_regs_access(dev, attr, ®, false); + if (ret) + return ret; + + if (put_user(reg, uaddr)) + return -EFAULT; + + return 0; + } + } + + return -ENXIO; } static int vgic_v5_its_has_attr(struct kvm_device *dev, struct kvm_device_attr *attr) { - if (attr->group != KVM_DEV_ARM_VGIC_GRP_ADDR) - return -ENXIO; - - switch (attr->attr) { - case KVM_VGIC_V5_ADDR_TYPE_ITS: - return 0; - default: - return -ENXIO; + switch (attr->group) { + case KVM_DEV_ARM_VGIC_GRP_ADDR: + switch (attr->attr) { + case KVM_VGIC_V5_ADDR_TYPE_ITS: + return 0; + } + break; + case KVM_DEV_ARM_VGIC_GRP_CTRL: + switch (attr->attr) { + case KVM_DEV_ARM_VGIC_CTRL_INIT: + case KVM_DEV_ARM_ITS_CTRL_RESET: + return 0; + } + break; + case KVM_DEV_ARM_VGIC_GRP_ITS_REGS: + return vgic_v5_its_has_attr_regs(dev, attr); } + + return -ENXIO; } static struct kvm_device_ops kvm_arm_vgic_v5_its_ops = { -- 2.34.1 Allow KVM to replace a host ITTE with a virtual translation that directly injects an interrupt into a guest. Add an irq_chip callback to install and remove the virtual translation, and disable lazy masking while the interrupt is forwarded. The KVM caller is added later. The ITS writes the ITTE before invalidating its cache. If invalidation times out, hardware may still use the virtual translation even though setup failed. Allow disable to restore the host ITTE when the forwarded flag is clear, and retain retryable state if restoration also times out. Keep lazy masking disabled until the host mapping is restored. Co-authored-by: Timothy Hayes Signed-off-by: Sascha Bischoff --- drivers/irqchip/irq-gic-v5-its.c | 134 +++++++++++++++++++++++++++++ include/linux/irqchip/arm-gic-v5.h | 8 ++ 2 files changed, 142 insertions(+) diff --git a/drivers/irqchip/irq-gic-v5-its.c b/drivers/irqchip/irq-gic-v5-its.c index 28e39b065de0e..7de40a7cff403 100644 --- a/drivers/irqchip/irq-gic-v5-its.c +++ b/drivers/irqchip/irq-gic-v5-its.c @@ -61,6 +61,48 @@ static void its_writeq_relaxed(struct gicv5_its_chip_data *its_node, const u64 v writeq_relaxed(val, its_node->its_base + reg_offset); } +#ifdef CONFIG_KVM +int gicv5_its_enable_direct_injection(int virq, u16 vm_id, u32 guest_lpi) +{ + struct virt_intr_data virt_intr = { + .vm_id = vm_id, + .guest_lpi = guest_lpi, + }; + int ret; + + /* + * The host will never see that interrupt firing again, so it + * is vital that we don't do any lazy masking. + */ + irq_set_status_flags(virq, IRQ_DISABLE_UNLAZY); + + ret = irq_set_vcpu_affinity(virq, &virt_intr); + if (ret && ret != -ETIMEDOUT && ret != -EBUSY) + irq_clear_status_flags(virq, IRQ_DISABLE_UNLAZY); + + return ret; +} + +int gicv5_its_disable_direct_injection(int virq) +{ + int ret; + + ret = irq_set_vcpu_affinity(virq, NULL); + if (ret) { + pr_err("Failed to restore host interrupt\n"); + return ret; + } + + /* + * In theory the host could see the interrupt again now. Undo this + * again. + */ + irq_clear_status_flags(virq, IRQ_DISABLE_UNLAZY); + + return 0; +} +#endif /* CONFIG_KVM */ + static void gicv5_its_dcache_clean(struct gicv5_its_chip_data *its, void *start, size_t sz) { @@ -723,6 +765,95 @@ static void gicv5_its_compose_msi_msg(struct irq_data *d, struct msi_msg *msg) msi_msg_set_addr(irq_data_get_msi_desc(d), msg, addr); } +#ifdef CONFIG_KVM +static int gicv5_its_set_vcpu_affinity(struct irq_data *d, void *int_data) +{ + struct gicv5_its_dev *its_dev = irq_data_get_irq_chip_data(d); + u16 event_id = FIELD_GET(GICV5_ITS_HWIRQ_EVENT_ID, d->hwirq); + u32 dev_id = FIELD_GET(GICV5_ITS_HWIRQ_DEVICE_ID, d->hwirq); + struct virt_intr_data *data = int_data; + struct gicv5_its_chip_data *its; + __le64 *itte; + u64 itte_val, tmp; + int ret = 0; + + if (IS_ERR(its_dev)) { + pr_err("Failed to find ITS entry for DeviceID 0x%x", dev_id); + return PTR_ERR(its_dev); + } + + its = its_dev->its_node; + + itte = gicv5_its_device_get_itte_ref(its_dev, event_id); + itte_val = le64_to_cpu(*itte); + + /* + * The host entry should be valid, else something strange is going on! + */ + if (!(itte_val & GICV5_ITTL2E_VALID)) { + if (data) + return -EINVAL; + if (!irqd_is_forwarded_to_vcpu(d)) + return 0; + goto invalidate; + } + + if (data) { + if (irqd_is_forwarded_to_vcpu(d)) { + if ((itte_val & GICV5_ITTL2E_VIRTUAL) && + FIELD_GET(GICV5_ITTL2E_LPI_ID, itte_val) == data->guest_lpi && + FIELD_GET(GICV5_ITTL2E_VM_ID, itte_val) == data->vm_id) + return 0; + return -EBUSY; + } + + /* Stale virtual translation without forwarded state. */ + if (itte_val & GICV5_ITTL2E_VIRTUAL) + return -EBUSY; + + tmp = FIELD_PREP(GICV5_ITTL2E_LPI_ID, data->guest_lpi); + tmp |= FIELD_PREP(GICV5_ITTL2E_VM_ID, data->vm_id); + tmp |= GICV5_ITTL2E_VIRTUAL; + tmp |= GICV5_ITTL2E_VALID; + + its_write_table_entry(its, itte, tmp); + } else { + /* + * A failed sync still needs retrying, even if the ITTE is + * host-owned. + */ + if (!(itte_val & GICV5_ITTL2E_VIRTUAL) && + !irqd_is_forwarded_to_vcpu(d)) + return 0; + + if (itte_val & GICV5_ITTL2E_VIRTUAL) { + tmp = FIELD_PREP(GICV5_ITTL2E_LPI_ID, + d->parent_data->hwirq); + tmp |= GICV5_ITTL2E_VALID; + its_write_table_entry(its, itte, tmp); + } + } + +invalidate: + /* Make the change ITS-visible */ + dsb(st); + ret = gicv5_its_itt_cache_inv(its, dev_id, event_id); + if (ret) { + /* Keep a failed restoration distinguishable from a clean host ITTE. */ + if (!data) + irqd_set_forwarded_to_vcpu(d); + return ret; + } + + if (data) + irqd_set_forwarded_to_vcpu(d); + else + irqd_clr_forwarded_to_vcpu(d); + + return 0; +} +#endif /* CONFIG_KVM */ + static const struct irq_chip gicv5_its_irq_chip = { .name = "GICv5-ITS-MSI", .irq_mask = irq_chip_mask_parent, @@ -732,6 +863,9 @@ static const struct irq_chip gicv5_its_irq_chip = { .irq_get_irqchip_state = irq_chip_get_parent_state, .irq_set_irqchip_state = irq_chip_set_parent_state, .irq_compose_msi_msg = gicv5_its_compose_msi_msg, +#ifdef CONFIG_KVM + .irq_set_vcpu_affinity = gicv5_its_set_vcpu_affinity, +#endif /* CONFIG_KVM */ }; static struct gicv5_its_dev *gicv5_its_find_device(struct gicv5_its_chip_data *its, diff --git a/include/linux/irqchip/arm-gic-v5.h b/include/linux/irqchip/arm-gic-v5.h index 7e64eb0436394..791627bdf5436 100644 --- a/include/linux/irqchip/arm-gic-v5.h +++ b/include/linux/irqchip/arm-gic-v5.h @@ -727,4 +727,12 @@ enum gicv5_vcpu_cmd { LPI_VIST_MAKE_INVALID, /* Triggered by a guest */ }; +struct virt_intr_data { + u16 vm_id; + u32 guest_lpi; +}; + +int gicv5_its_enable_direct_injection(int virq, u16 vm_id, u32 guest_lpi); +int gicv5_its_disable_direct_injection(int virq); + #endif -- 2.34.1 Move vgic_supports_direct_msis() from the GICv3 MMIO code to the common VGIC MMIO file without changing its behavior. GICv5 will extend this helper when irq forwarding support is added. Signed-off-by: Sascha Bischoff --- arch/arm64/kvm/vgic/vgic-mmio-v3.c | 13 ------------- arch/arm64/kvm/vgic/vgic-mmio.c | 13 +++++++++++++ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/arch/arm64/kvm/vgic/vgic-mmio-v3.c b/arch/arm64/kvm/vgic/vgic-mmio-v3.c index f5e6c1fe52431..9f16e19c25a23 100644 --- a/arch/arm64/kvm/vgic/vgic-mmio-v3.c +++ b/arch/arm64/kvm/vgic/vgic-mmio-v3.c @@ -38,19 +38,6 @@ u64 update_64bit_reg(u64 reg, unsigned int offset, unsigned int len, return reg | ((u64)val << lower); } -bool vgic_supports_direct_msis(struct kvm *kvm) -{ - /* - * Deliberately conflate vLPI and vSGI support on GICv4.1 hardware, - * indirectly allowing userspace to control whether or not vPEs are - * allocated for the VM. - */ - if (system_supports_direct_sgis() && !vgic_supports_direct_sgis(kvm)) - return false; - - return kvm_vgic_global_state.has_gicv4 && vgic_has_its(kvm); -} - bool system_supports_direct_sgis(void) { return kvm_vgic_global_state.has_gicv4_1 && gic_cpuif_has_vsgi(); diff --git a/arch/arm64/kvm/vgic/vgic-mmio.c b/arch/arm64/kvm/vgic/vgic-mmio.c index 755bc2d74c352..44f4a7f33fa3e 100644 --- a/arch/arm64/kvm/vgic/vgic-mmio.c +++ b/arch/arm64/kvm/vgic/vgic-mmio.c @@ -1142,6 +1142,19 @@ int vgic_register_dist_iodev(struct kvm *kvm, gpa_t dist_base_address, len, &io_device->dev); } +bool vgic_supports_direct_msis(struct kvm *kvm) +{ + /* + * Deliberately conflate vLPI and vSGI support on GICv4.1 hardware, + * indirectly allowing userspace to control whether or not vPEs are + * allocated for the VM. + */ + if (system_supports_direct_sgis() && !vgic_supports_direct_sgis(kvm)) + return false; + + return kvm_vgic_global_state.has_gicv4 && vgic_has_its(kvm); +} + bool vgic_has_its(struct kvm *kvm) { struct vgic_dist *dist = &kvm->arch.vgic; -- 2.34.1 GICv5, like GICv4, supports direct injection of interrupts via the ITS. Move the existing GICv4 interface behind a common shim that selects the appropriate implementation. For GICv5 forwarding, the ITS driver replaces a host ITTE with one targeting a guest. The host no longer receives that interrupt until forwarding is removed and the host ITTE is restored. A guest can make multiple DeviceID/EventID pairs resolve to the same LPI, even though it should not. Track the cache entry responsible for direct injection, and let only that entry tear it down during guest invalidation. Explicitly unforwarding an IRQ clears the owner without removing the cached translation. The ITS driver writes the virtual ITTE before invalidating its cache. If invalidation times out, attempt to restore the host ITTE before falling back to software injection. If the restoration also times out, keep the host IRQ association and a separate LPI reference so unforwarding can be retried even after the guest invalidates the translation. Clear KVM's forwarding state only after the host ITTE has been restored. Forwarding can be set up from another process's address space through IRQ bypass. A cache miss in this path must still walk the guest's DT and ITT. Read the entries from the VM's mm using access_remote_vm() when the caller does not have that mm. Hold an mm reference and protect the memslot lookup with SRCU, and fail the lookup on a short read. Signed-off-by: Sascha Bischoff --- arch/arm64/kvm/arm.c | 6 +- arch/arm64/kvm/vgic/vgic-its-v5.c | 122 +++++++++++++++++++----- arch/arm64/kvm/vgic/vgic-mmio.c | 10 +- arch/arm64/kvm/vgic/vgic-v5.c | 148 ++++++++++++++++++++++++++++++ arch/arm64/kvm/vgic/vgic.c | 17 ++++ arch/arm64/kvm/vgic/vgic.h | 8 ++ include/kvm/arm_vgic.h | 11 +++ 7 files changed, 296 insertions(+), 26 deletions(-) diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c index d593ba9280933..2d9b2bc8f0c45 100644 --- a/arch/arm64/kvm/arm.c +++ b/arch/arm64/kvm/arm.c @@ -3171,7 +3171,7 @@ int kvm_arch_irq_bypass_add_producer(struct irq_bypass_consumer *cons, if (irq_entry->type != KVM_IRQ_ROUTING_MSI) return 0; - return kvm_vgic_v4_set_forwarding(irqfd->kvm, prod->irq, + return kvm_vgic_set_forwarding(irqfd->kvm, prod->irq, &irqfd->irq_entry); } @@ -3185,7 +3185,7 @@ void kvm_arch_irq_bypass_del_producer(struct irq_bypass_consumer *cons, if (irq_entry->type != KVM_IRQ_ROUTING_MSI) return; - kvm_vgic_v4_unset_forwarding(irqfd->kvm, prod->irq); + kvm_vgic_unset_forwarding(irqfd->kvm, prod->irq); } void kvm_arch_update_irqfd_routing(struct kvm_kernel_irqfd *irqfd, @@ -3204,7 +3204,7 @@ void kvm_arch_update_irqfd_routing(struct kvm_kernel_irqfd *irqfd, * * Unmap the vLPI and fall back to software LPI injection. */ - return kvm_vgic_v4_unset_forwarding(irqfd->kvm, irqfd->producer->irq); + kvm_vgic_unset_forwarding(irqfd->kvm, irqfd->producer->irq); } void kvm_arch_irq_bypass_stop(struct irq_bypass_consumer *cons) diff --git a/arch/arm64/kvm/vgic/vgic-its-v5.c b/arch/arm64/kvm/vgic/vgic-its-v5.c index 62c1ca8004007..e71cd67652bae 100644 --- a/arch/arm64/kvm/vgic/vgic-its-v5.c +++ b/arch/arm64/kvm/vgic/vgic-its-v5.c @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -31,7 +32,41 @@ static void its_cache_drop_translation(struct kvm *kvm, struct its_cache_translation_entry *entry) { struct vgic_irq *irq = entry->irq; + unsigned long flags; + int virq = 0; + bool hw = false; + bool drop_forwarding_ref = false; + /* + * Multiple translations can share an LPI as we have no way of stopping + * the guest from doing so. Only the entry associated with direct injection + * may tear down the IRQ's forwarding state, as otherwise we can end up + * accidentally tearing something down. Realistically, this only would + * result in a performance degradation, but we might as well avoid that + * in the first place if we can. + */ + raw_spin_lock_irqsave(&irq->irq_lock, flags); + if (irq->forwarding_owner == entry) { + /* The entry is about to be freed, even if unforwarding fails. */ + irq->forwarding_owner = NULL; + virq = irq->host_irq; + hw = irq->hw; + } + raw_spin_unlock_irqrestore(&irq->irq_lock, flags); + + if (hw && !WARN_ON_ONCE(gicv5_its_disable_direct_injection(virq))) { + raw_spin_lock_irqsave(&irq->irq_lock, flags); + if (irq->hw && irq->host_irq == virq && + !irq->forwarding_owner) { + irq->hw = false; + irq->host_irq = 0; + drop_forwarding_ref = true; + } + raw_spin_unlock_irqrestore(&irq->irq_lock, flags); + } + + if (drop_forwarding_ref) + vgic_put_irq(kvm, irq); vgic_put_irq(kvm, irq); kfree(entry); } @@ -50,7 +85,8 @@ static void its_cache_drop_removed_translations(struct kvm *kvm, static int its_cache_add_translation(struct kvm *kvm, struct vgic_v5_its *its, u32 device_id, u16 event_id, - struct vgic_irq *irq) + struct vgic_irq *irq, + void **owner) { u64 key = ((u64)device_id << 16) | (u64)event_id; struct its_cache_translation_entry *entry; @@ -78,6 +114,8 @@ static int its_cache_add_translation(struct kvm *kvm, struct vgic_v5_its *its, } its_cache_drop_removed_translations(kvm, &removed); + if (owner) + *owner = entry; return 0; } @@ -129,7 +167,8 @@ static void its_cache_clear_translations(struct vgic_v5_its *its) static int its_cache_look_up_translation(struct vgic_v5_its *its, u32 device_id, u16 event_id, - struct vgic_irq **irq) + struct vgic_irq **irq, + void **owner) { u64 key = ((u64)device_id << 16) | (u64)event_id; struct its_cache_translation_entry *entry; @@ -142,6 +181,8 @@ static int its_cache_look_up_translation(struct vgic_v5_its *its, return -ENXIO; *irq = entry->irq; + if (owner) + *owner = entry; return 0; } } @@ -287,6 +328,37 @@ static void its_cache_handle_inv_eventr(struct vgic_v5_its *its) itt_l2sz); } +static int vgic_v5_its_read_entry(struct kvm *kvm, gpa_t gpa, __le64 *entry) +{ + struct mm_struct *mm = kvm->mm; + unsigned long hva; + int srcu_idx, ret; + + if (current->mm == mm) + return kvm_read_guest_lock(kvm, gpa, entry, sizeof(*entry)); + + /* Forwarding setup can run in the IRQ producer's address space. */ + if (!mmget_not_zero(mm)) + return -EFAULT; + + srcu_idx = srcu_read_lock(&kvm->srcu); + hva = gfn_to_hva_prot(kvm, gpa >> PAGE_SHIFT, NULL); + if (kvm_is_error_hva(hva)) { + ret = -EFAULT; + goto out; + } + + /* Table entries are 8-byte aligned, so each read fits in one page. */ + ret = access_remote_vm(mm, hva + offset_in_page(gpa), entry, + sizeof(*entry), 0); + +out: + srcu_read_unlock(&kvm->srcu, srcu_idx); + mmput(mm); + + return ret == sizeof(*entry) ? 0 : -EFAULT; +} + static int parse_linear_dt(struct kvm *kvm, gpa_t dt_base, u32 device_id, gpa_t *itt_base, bool *two_level_itt, unsigned int *event_id_bits, unsigned int *itt_l2sz) @@ -297,8 +369,7 @@ static int parse_linear_dt(struct kvm *kvm, gpa_t dt_base, u32 device_id, int ret; entry_addr = dt_base + device_id * sizeof(__le64); - ret = kvm_read_guest_lock(kvm, entry_addr, &dt_l2_entry, - sizeof(dt_l2_entry)); + ret = vgic_v5_its_read_entry(kvm, entry_addr, &dt_l2_entry); if (ret) { kvm_debug("Failed to read guest memory for L2 DT\n"); return ret; @@ -348,8 +419,7 @@ static int parse_two_level_dt(struct kvm *kvm, gpa_t dt_base, entry_addr = dt_base + (device_id >> device_id_l1_shift) * sizeof(__le64); - ret = kvm_read_guest_lock(kvm, entry_addr, &dt_l1_entry, - sizeof(dt_l1_entry)); + ret = vgic_v5_its_read_entry(kvm, entry_addr, &dt_l1_entry); if (ret) { kvm_debug("Failed to read guest memory for L1 DT\n"); return ret; @@ -405,8 +475,7 @@ static int parse_linear_itt(struct kvm *kvm, u32 event_id, gpa_t itt_base, gpa_t entry_addr = itt_base + event_id * sizeof(*itt_l2_entry); int ret; - ret = kvm_read_guest_lock(kvm, entry_addr, itt_l2_entry, - sizeof(*itt_l2_entry)); + ret = vgic_v5_its_read_entry(kvm, entry_addr, itt_l2_entry); if (ret) kvm_debug("Failed to read guest memory for L2 ITT\n"); @@ -439,8 +508,7 @@ static int parse_two_level_itt(struct kvm *kvm, u32 event_id, gpa_t itt_base, l1_event_id = event_id >> itt_split_bits; entry_addr = itt_base + l1_event_id * sizeof(itt_l1_entry); - ret = kvm_read_guest_lock(kvm, entry_addr, &itt_l1_entry, - sizeof(itt_l1_entry)); + ret = vgic_v5_its_read_entry(kvm, entry_addr, &itt_l1_entry); if (ret) { kvm_debug("Failed to read guest memory for L1 ITT\n"); return ret; @@ -516,7 +584,8 @@ struct vgic_v5_its *vgic_v5_msi_to_its(struct kvm *kvm, struct kvm_msi *msi) static int __vgic_v5_its_resolve_lpi(struct kvm *kvm, struct vgic_v5_its *its, u32 device_id, u32 event_id, - struct vgic_irq **irq) + struct vgic_irq **irq, + void **owner) { unsigned int itt_l2sz, event_id_bits; __le64 itt_l2_entry; @@ -535,13 +604,11 @@ static int __vgic_v5_its_resolve_lpi(struct kvm *kvm, struct vgic_v5_its *its, return -ENXIO; } - ret = its_cache_look_up_translation(its, device_id, (u16)event_id, irq); + ret = its_cache_look_up_translation(its, device_id, (u16)event_id, irq, + owner); if (!ret) return 0; - if (WARN_ON_ONCE(kvm->mm != get_current()->mm)) - return -EWOULDBLOCK; - ret = get_itt(kvm, its, device_id, &itt_base, &two_level_itt, &event_id_bits, &itt_l2sz); if (ret) { @@ -572,8 +639,8 @@ static int __vgic_v5_its_resolve_lpi(struct kvm *kvm, struct vgic_v5_its *its, return PTR_ERR(*irq); vgic_get_irq_ref(*irq); - ret = its_cache_add_translation(kvm, its, device_id, (u16)event_id, - *irq); + ret = its_cache_add_translation(kvm, its, device_id, (u16)event_id, *irq, + owner); if (ret) { vgic_put_irq(kvm, *irq); vgic_put_irq(kvm, *irq); @@ -583,13 +650,24 @@ static int __vgic_v5_its_resolve_lpi(struct kvm *kvm, struct vgic_v5_its *its, return ret; } +int vgic_v5_its_resolve_lpi_locked(struct kvm *kvm, + struct vgic_v5_its *its, + u32 device_id, u32 event_id, + struct vgic_irq **irq, + void **owner) +{ + return __vgic_v5_its_resolve_lpi(kvm, its, device_id, event_id, irq, + owner); +} + int vgic_v5_its_resolve_lpi(struct kvm *kvm, struct vgic_v5_its *its, u32 device_id, u32 event_id, struct vgic_irq **irq) { guard(mutex)(&its->its_lock); - return __vgic_v5_its_resolve_lpi(kvm, its, device_id, event_id, irq); + return __vgic_v5_its_resolve_lpi(kvm, its, device_id, event_id, irq, + NULL); } static void vgic_v5_its_inject_lpi(struct kvm *kvm, struct vgic_irq *irq) @@ -657,8 +735,8 @@ int vgic_v5_its_inject_cached_translation(struct kvm *kvm, struct kvm_msi *msi) if (!READ_ONCE(its->enabled)) return -EWOULDBLOCK; - ret = its_cache_look_up_translation(its, msi->devid, (u16)msi->data, - &irq); + ret = its_cache_look_up_translation(its, msi->devid, (u16)msi->data, &irq, + NULL); if (ret) return -EWOULDBLOCK; @@ -680,7 +758,7 @@ static void vgic_v5_its_handle_read_event(struct kvm *kvm, its->read_event_datar.virt = false; ret = __vgic_v5_its_resolve_lpi(kvm, its, its->didr.device_id, - its->eidr.event_id, &irq); + its->eidr.event_id, &irq, NULL); if (ret) return; @@ -704,7 +782,7 @@ static void vgic_v5_its_handle_gen_event(struct kvm *kvm, ret = __vgic_v5_its_resolve_lpi(kvm, its, its->gen_event_didr.device_id, - its->gen_event_eidr.event_id, &irq); + its->gen_event_eidr.event_id, &irq, NULL); if (ret) return; diff --git a/arch/arm64/kvm/vgic/vgic-mmio.c b/arch/arm64/kvm/vgic/vgic-mmio.c index 44f4a7f33fa3e..ca57275f87f67 100644 --- a/arch/arm64/kvm/vgic/vgic-mmio.c +++ b/arch/arm64/kvm/vgic/vgic-mmio.c @@ -1144,6 +1144,8 @@ int vgic_register_dist_iodev(struct kvm *kvm, gpa_t dist_base_address, bool vgic_supports_direct_msis(struct kvm *kvm) { + const struct vgic_dist *dist = &kvm->arch.vgic; + /* * Deliberately conflate vLPI and vSGI support on GICv4.1 hardware, * indirectly allowing userspace to control whether or not vPEs are @@ -1152,7 +1154,13 @@ bool vgic_supports_direct_msis(struct kvm *kvm) if (system_supports_direct_sgis() && !vgic_supports_direct_sgis(kvm)) return false; - return kvm_vgic_global_state.has_gicv4 && vgic_has_its(kvm); + if (kvm_vgic_global_state.has_gicv4 && vgic_has_its(kvm)) + return true; + if (kvm_vgic_global_state.type == VGIC_V5 && vgic_has_its(kvm) && + dist->vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5) + return true; + + return false; } bool vgic_has_its(struct kvm *kvm) diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c index c90e00f56b298..970df3f41cf24 100644 --- a/arch/arm64/kvm/vgic/vgic-v5.c +++ b/arch/arm64/kvm/vgic/vgic-v5.c @@ -1660,3 +1660,151 @@ int vgic_v5_check_msi(struct kvm *kvm, struct kvm_msi *msi) return 0; } + +static struct vgic_v5_its * +vgic_v5_get_its(struct kvm *kvm, struct kvm_kernel_irq_routing_entry *irq_entry) +{ + struct kvm_msi msi = (struct kvm_msi){ + .address_lo = irq_entry->msi.address_lo, + .address_hi = irq_entry->msi.address_hi, + .data = irq_entry->msi.data, + .flags = irq_entry->msi.flags, + .devid = irq_entry->msi.devid, + }; + + return vgic_v5_msi_to_its(kvm, &msi); +} + +int kvm_vgic_v5_set_forwarding(struct kvm *kvm, int virq, + struct kvm_kernel_irq_routing_entry *irq_entry) +{ + struct vgic_v5_its *its_data; + struct vgic_irq *irq; + void *owner; + bool hw; + int ret; + + if (!vgic_has_its(kvm)) + return 0; + + /* Look up the ITS; bail out if we can't look it up */ + its_data = vgic_v5_get_its(kvm, irq_entry); + if (IS_ERR(its_data)) + return 0; + + /* + * Look up and cache the translation. Bail out if we failed to look up + * the translation at this stage. If the translation becomes valid at a + * later stage, we should fall back to software-based injection. + */ + guard(mutex)(&its_data->its_lock); + + ret = vgic_v5_its_resolve_lpi_locked(kvm, its_data, + irq_entry->msi.devid, + irq_entry->msi.data, &irq, &owner); + if (ret) + return 0; + + scoped_guard(raw_spinlock_irqsave, &irq->irq_lock) { + hw = irq->hw; + } + + if (hw) + goto out_put_irq; + + ret = gicv5_its_enable_direct_injection(virq, vgic_v5_vm_id(kvm), + vgic_v5_get_hwirq_id(irq->intid)); + if (ret) { + if (ret != -ETIMEDOUT) + goto out_put_irq; + + /* The ITTE was written; confirm restoration before falling back. */ + if (!gicv5_its_disable_direct_injection(virq)) + goto out_put_irq; + + /* Keep the host IRQ association so a later unforward can retry. */ + ret = 0; + } + + scoped_guard(raw_spinlock_irqsave, &irq->irq_lock) { + if (!irq->hw) { + /* Keep the LPI available if invalidation must retry unforwarding. */ + vgic_get_irq_ref(irq); + irq->hw = true; + irq->host_irq = virq; + /* + * Record the entry responsible for this host IRQ, even if + * cleanup after a failed sync is still pending. Only this + * entry may unforward on invalidation; another translation + * could map to the same LPI. + */ + irq->forwarding_owner = owner; + } else { + ret = -EBUSY; + } + } + + if (ret) + WARN_ON_ONCE(gicv5_its_disable_direct_injection(virq)); + +out_put_irq: + vgic_put_irq(kvm, irq); + + return 0; +} + +static struct vgic_irq *vgic_v5_host_irq_get_lpi(struct kvm *kvm, int host_irq) +{ + struct vgic_irq *irq; + unsigned long idx; + + guard(rcu)(); + xa_for_each(&kvm->arch.vgic.lpi_xa, idx, irq) { + if (!irq->hw || irq->host_irq != host_irq) + continue; + + if (!vgic_try_get_irq_ref(irq)) + return NULL; + + return irq; + } + + return NULL; +} + +void kvm_vgic_v5_unset_forwarding(struct kvm *kvm, int virq) +{ + struct vgic_irq *irq; + bool drop_forwarding_ref = false; + + if (!vgic_has_its(kvm)) + return; + + /* + * Note: We don't remove the entry from our translation cache here. It + * is up to the guest to invalidate the old translations, and hence the + * removal path is tied into ITS_INV_DEVICER and ITS_INV_EVENTR, rather + * than being explicitly called here. + */ + irq = vgic_v5_host_irq_get_lpi(kvm, virq); + if (!irq) + return; + + if (WARN_ON_ONCE(gicv5_its_disable_direct_injection(virq))) + goto out_put_irq; + + scoped_guard(raw_spinlock_irqsave, &irq->irq_lock) { + if (irq->hw && irq->host_irq == virq) { + irq->hw = false; + irq->host_irq = 0; + irq->forwarding_owner = NULL; + drop_forwarding_ref = true; + } + } + + if (drop_forwarding_ref) + vgic_put_irq(kvm, irq); + +out_put_irq: + vgic_put_irq(kvm, irq); +} diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c index bc0171c479a95..ea5ed49080cd8 100644 --- a/arch/arm64/kvm/vgic/vgic.c +++ b/arch/arm64/kvm/vgic/vgic.c @@ -1439,3 +1439,20 @@ void vgic_irq_handle_resampling(struct vgic_irq *irq, vgic_irq_set_phys_active(irq, false); } } + +int kvm_vgic_set_forwarding(struct kvm *kvm, int irq, + struct kvm_kernel_irq_routing_entry *irq_entry) +{ + if (kvm->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5) + return kvm_vgic_v5_set_forwarding(kvm, irq, irq_entry); + else + return kvm_vgic_v4_set_forwarding(kvm, irq, irq_entry); +} + +void kvm_vgic_unset_forwarding(struct kvm *kvm, int irq) +{ + if (kvm->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5) + kvm_vgic_v5_unset_forwarding(kvm, irq); + else + kvm_vgic_v4_unset_forwarding(kvm, irq); +} diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h index 3d06786142c2d..70ad0641d4f45 100644 --- a/arch/arm64/kvm/vgic/vgic.h +++ b/arch/arm64/kvm/vgic/vgic.h @@ -367,8 +367,16 @@ struct vgic_v5_its *vgic_v5_msi_to_its(struct kvm *kvm, struct kvm_msi *msi); int vgic_v5_its_resolve_lpi(struct kvm *kvm, struct vgic_v5_its *its, u32 device_id, u32 event_id, struct vgic_irq **irq); +int vgic_v5_its_resolve_lpi_locked(struct kvm *kvm, + struct vgic_v5_its *its, + u32 device_id, u32 event_id, + struct vgic_irq **irq, + void **owner); int vgic_v5_its_inject_msi(struct kvm *kvm, struct kvm_msi *msi); int vgic_v5_its_inject_cached_translation(struct kvm *kvm, struct kvm_msi *msi); +int kvm_vgic_v5_set_forwarding(struct kvm *kvm, int irq, + struct kvm_kernel_irq_routing_entry *irq_entry); +void kvm_vgic_v5_unset_forwarding(struct kvm *kvm, int irq); void vgic_enable_lpis(struct kvm_vcpu *vcpu); void vgic_flush_pending_lpis(struct kvm_vcpu *vcpu); int vgic_its_inject_msi(struct kvm *kvm, struct kvm_msi *msi); diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h index bf970275868c9..e3271f9301675 100644 --- a/include/kvm/arm_vgic.h +++ b/include/kvm/arm_vgic.h @@ -296,6 +296,13 @@ struct vgic_irq { refcount_t refcount; /* Used for LPIs */ u32 hwintid; /* HW INTID number */ unsigned int host_irq; /* linux irq corresponding to hwintid */ + /* + * Opaque identity of the mapping responsible for direct injection. On + * invalidation, only the matching mapping may disable forwarding. + * Protected by irq_lock. It can be NULL while an unforward retry is + * pending after the mapping has been invalidated. + */ + void *forwarding_owner; union { u8 targets; /* GICv2 target VCPUs mask */ u32 mpidr; /* GICv3 target VCPU */ @@ -938,6 +945,10 @@ bool vgic_v5_ppi_queue_irq_unlock(struct kvm *kvm, struct vgic_irq *irq, unsigned long flags); void vgic_v5_set_ppi_dvi(struct kvm_vcpu *vcpu, struct vgic_irq *irq, bool dvi); +int kvm_vgic_set_forwarding(struct kvm *kvm, int irq, + struct kvm_kernel_irq_routing_entry *irq_entry); +void kvm_vgic_unset_forwarding(struct kvm *kvm, int irq); + bool vgic_state_is_nested(struct kvm_vcpu *vcpu); /* CPU HP callbacks */ -- 2.34.1 Add coverage for the VGICv5 ITS device address and ITS_REGS attribute groups. Userspace needs to be able to discover and save the virtual ITS register state, while KVM must reject invalid address placement and malformed register accesses. Verify that the ITS device exposes only the ITS address type, reports VGIC_ADDR_UNDEF before configuration, rejects misaligned and out-of-IPA bases, accepts a valid 64K-aligned base, reports it back, and rejects a second base assignment. Exercise ITS_REGS after the ITS base is configured by checking all exposed offsets, read/write-back access, bad offsets, bad alignment, bad userspace pointers and idle status register values. Also initialise the shared VM wrapper so non-ITS tests safely ignore the optional ITS fd during cleanup. Signed-off-by: Sascha Bischoff --- tools/testing/selftests/kvm/arm64/vgic_v5.c | 251 +++++++++++++++++- .../selftests/kvm/include/arm64/gic_v5.h | 60 +++++ 2 files changed, 300 insertions(+), 11 deletions(-) diff --git a/tools/testing/selftests/kvm/arm64/vgic_v5.c b/tools/testing/selftests/kvm/arm64/vgic_v5.c index d5df742b23001..b7fb002a64f6c 100644 --- a/tools/testing/selftests/kvm/arm64/vgic_v5.c +++ b/tools/testing/selftests/kvm/arm64/vgic_v5.c @@ -43,6 +43,7 @@ static u64 max_phys_size; struct vm_gic { struct kvm_vm *vm; int gic_fd; + int its_fd; u32 gic_dev_type; }; @@ -581,9 +582,20 @@ static int run_vcpu(struct kvm_vcpu *vcpu) return __vcpu_run(vcpu) ? -errno : 0; } +static void vm_gic_init(struct vm_gic *v) +{ + v->vm = NULL; + v->gic_fd = -1; + v->its_fd = -1; + v->gic_dev_type = KVM_DEV_TYPE_ARM_VGIC_V5; +} + static void vm_gic_destroy(struct vm_gic *v) { - close(v->gic_fd); + if (v->its_fd >= 0) + close(v->its_fd); + if (v->gic_fd >= 0) + close(v->gic_fd); kvm_vm_free(v->vm); } @@ -623,7 +635,7 @@ static void vgic_v5_spi_line_vm_create(struct vm_gic *v, { u64 attr; - v->gic_dev_type = KVM_DEV_TYPE_ARM_VGIC_V5; + vm_gic_init(v); v->vm = __vm_create(VM_SHAPE_DEFAULT, NR_VCPUS, 0); v->gic_fd = kvm_create_device(v->vm, v->gic_dev_type); *vcpu = vm_vcpu_add(v->vm, 0, guest_spi_line_code); @@ -654,7 +666,7 @@ static void vgic_v5_lpi_ist_vm_create(struct vm_gic *v, u64 attr; int i; - v->gic_dev_type = KVM_DEV_TYPE_ARM_VGIC_V5; + vm_gic_init(v); v->vm = __vm_create(VM_SHAPE_DEFAULT, VGIC_V5_LPI_NR_VCPUS, 0); v->gic_fd = kvm_create_device(v->vm, v->gic_dev_type); @@ -779,6 +791,12 @@ static const struct vgic_region_attr gic_v5_irs_region = { .alignment = GICV5_IRS_ALIGN, }; +static const struct vgic_region_attr gic_v5_its_region = { + .attr = KVM_VGIC_V5_ADDR_TYPE_ITS, + .size = GICV5_ITS_SIZE, + .alignment = GICV5_ITS_ALIGN, +}; + struct vgic_sysreg_attr { const char *name; u32 encoding; @@ -863,6 +881,39 @@ static const struct vgic_irs_reg_attr gic_v5_irs_regs[] = { IRS_REG(GICV5_IRS_SWERR_SYNDROMER1), }; +static const struct vgic_irs_reg_attr gic_v5_its_regs[] = { + IRS_REG(GICV5_ITS_IDR0), + IRS_REG(GICV5_ITS_IDR1), + IRS_REG(GICV5_ITS_IDR2), + IRS_REG(GICV5_ITS_IIDR), + IRS_REG(GICV5_ITS_AIDR), + IRS_REG(GICV5_ITS_CR0), + IRS_REG(GICV5_ITS_CR1), + IRS_REG(GICV5_ITS_DT_BASER), + IRS_REG(GICV5_ITS_DT_CFGR), + IRS_REG(GICV5_ITS_DIDR), + IRS_REG(GICV5_ITS_EIDR), + IRS_REG(GICV5_ITS_INV_EVENTR), + IRS_REG(GICV5_ITS_INV_DEVICER), + IRS_REG(GICV5_ITS_READ_EVENTR), + IRS_REG(GICV5_ITS_READ_EVENT_DATAR), + IRS_REG(GICV5_ITS_STATUSR), + IRS_REG(GICV5_ITS_SYNCR), + IRS_REG(GICV5_ITS_SYNC_STATUSR), + IRS_REG(GICV5_ITS_GEN_EVENT_DIDR), + IRS_REG(GICV5_ITS_GEN_EVENT_EIDR), + IRS_REG(GICV5_ITS_GEN_EVENTR), + IRS_REG(GICV5_ITS_GEN_EVENT_STATUSR), + IRS_REG(GICV5_ITS_MEC_IDR), + IRS_REG(GICV5_ITS_MEC_MECID_R), + IRS_REG(GICV5_ITS_MPAM_IDR), + IRS_REG(GICV5_ITS_MPAM_PARTID_R), + IRS_REG(GICV5_ITS_SWERR_STATUSR), + IRS_REG(GICV5_ITS_SWERR_SYNDROMER0), + IRS_REG(GICV5_ITS_SWERR_SYNDROMER1), + IRS_REG(GICV5_ITS_MPAM_PMGR), +}; + static const struct vgic_sysreg_attr gic_v5_cpu_sysregs[] = { SR(SYS_ICC_ICSR_EL1), SR(SYS_ICC_PPI_ENABLER0_EL1), @@ -945,7 +996,7 @@ static void test_vgic_v5_addr_attrs(void) u64 addr; int ret; - v.gic_dev_type = KVM_DEV_TYPE_ARM_VGIC_V5; + vm_gic_init(&v); v.vm = __vm_create(VM_SHAPE_DEFAULT, NR_VCPUS, 0); v.gic_fd = kvm_create_device(v.vm, v.gic_dev_type); @@ -1001,6 +1052,7 @@ static void test_vgic_v5_addr_attrs(void) vm_gic_destroy(&v); /* Try running a VM without ever setting the IRS base addr */ + vm_gic_init(&v); v.vm = __vm_create(VM_SHAPE_DEFAULT, NR_VCPUS, 0); v.gic_fd = kvm_create_device(v.vm, v.gic_dev_type); vcpu = vm_vcpu_add(v.vm, 0, NULL); @@ -1019,7 +1071,7 @@ static void test_vgic_v5_nr_irqs_attrs(void) u32 nr_irqs; int ret; - v.gic_dev_type = KVM_DEV_TYPE_ARM_VGIC_V5; + vm_gic_init(&v); v.vm = __vm_create(VM_SHAPE_DEFAULT, NR_VCPUS, 0); v.gic_fd = kvm_create_device(v.vm, v.gic_dev_type); @@ -1077,6 +1129,7 @@ static void test_vgic_v5_nr_irqs_attrs(void) vm_gic_destroy(&v); /* If userspace does not set NR_IRQS, init selects the default. */ + vm_gic_init(&v); v.vm = __vm_create(VM_SHAPE_DEFAULT, NR_VCPUS, 0); v.gic_fd = kvm_create_device(v.vm, v.gic_dev_type); vcpu = vm_vcpu_add(v.vm, 0, NULL); @@ -1096,7 +1149,6 @@ static void test_vgic_v5_nr_irqs_attrs(void) TEST_ASSERT(ret && errno == EBUSY, "GICv5 NR_IRQS set after init"); vm_gic_destroy(&v); - } static void test_vgic_v5_irs_regs_attrs(void) @@ -1106,7 +1158,7 @@ static void test_vgic_v5_irs_regs_attrs(void) u64 attr, val; int ret, i; - v.gic_dev_type = KVM_DEV_TYPE_ARM_VGIC_V5; + vm_gic_init(&v); v.vm = __vm_create(VM_SHAPE_DEFAULT, NR_VCPUS, 0); v.gic_fd = kvm_create_device(v.vm, v.gic_dev_type); vcpu = vm_vcpu_add(v.vm, 0, NULL); @@ -1287,6 +1339,177 @@ static void test_vgic_v5_irs_regs_attrs(void) vm_gic_destroy(&v); } +static void test_vgic_v5_its_attrs(void) +{ + struct vm_gic v; + u64 attr, val; + int ret, i; + + vm_gic_init(&v); + v.vm = __vm_create(VM_SHAPE_DEFAULT, NR_VCPUS, 0); + v.gic_fd = kvm_create_device(v.vm, v.gic_dev_type); + v.its_fd = kvm_create_device(v.vm, KVM_DEV_TYPE_ARM_VGIC_V5_ITS); + + kvm_has_device_attr(v.its_fd, KVM_DEV_ARM_VGIC_GRP_ADDR, + gic_v5_its_region.attr); + + /* We should have the ITS Address attr */ + ret = __kvm_has_device_attr(v.its_fd, KVM_DEV_ARM_VGIC_GRP_ADDR, + KVM_VGIC_V5_ADDR_TYPE_ITS); + TEST_ASSERT(!ret, "GICv5 ITS address attr missing"); + + /* But not the IRS one */ + ret = __kvm_has_device_attr(v.its_fd, KVM_DEV_ARM_VGIC_GRP_ADDR, + KVM_VGIC_V5_ADDR_TYPE_IRS); + TEST_ASSERT(ret && errno == ENXIO, "GICv5 ITS accepted IRS address attr"); + + /* Default address is undefined */ + ret = __kvm_device_attr_get(v.its_fd, KVM_DEV_ARM_VGIC_GRP_ADDR, + KVM_VGIC_V5_ADDR_TYPE_ITS, &attr); + TEST_ASSERT(!ret && attr == (-1ULL), + "GICv5 ITS returns VGIC_ADDR_UNDEF"); + + /* Misaligned address */ + attr = gic_v5_its_region.alignment / 0x10; + ret = __kvm_device_attr_set(v.its_fd, KVM_DEV_ARM_VGIC_GRP_ADDR, + KVM_VGIC_V5_ADDR_TYPE_ITS, &attr); + TEST_ASSERT(ret && errno == EINVAL, "GICv5 ITS base not aligned"); + + /* Address out of PA range */ + attr = max_phys_size; + ret = __kvm_device_attr_set(v.its_fd, KVM_DEV_ARM_VGIC_GRP_ADDR, + KVM_VGIC_V5_ADDR_TYPE_ITS, &attr); + TEST_ASSERT(ret && errno == E2BIG, "GICv5 ITS address beyond IPA limit"); + + /* Address + size too big to fit in PA range */ + attr = max_phys_size - gic_v5_its_region.alignment; + ret = __kvm_device_attr_set(v.its_fd, KVM_DEV_ARM_VGIC_GRP_ADDR, + KVM_VGIC_V5_ADDR_TYPE_ITS, &attr); + TEST_ASSERT(ret && errno == E2BIG, + "half of the ITS is beyond IPA limit"); + + /* Set a valid address */ + attr = GICV5_ITS_BASE_GPA; + ret = __kvm_device_attr_set(v.its_fd, KVM_DEV_ARM_VGIC_GRP_ADDR, + KVM_VGIC_V5_ADDR_TYPE_ITS, &attr); + TEST_ASSERT(!ret, "GICv5 ITS base correctly set"); + + /* Read back the set address */ + attr = 0xbad; + ret = __kvm_device_attr_get(v.its_fd, KVM_DEV_ARM_VGIC_GRP_ADDR, + KVM_VGIC_V5_ADDR_TYPE_ITS, &attr); + TEST_ASSERT(!ret && attr == GICV5_ITS_BASE_GPA, + "GICv5 ITS base readback mismatch"); + + /* Attempt to set the address again */ + attr = GICV5_ITS_BASE_GPA + gic_v5_its_region.alignment; + ret = __kvm_device_attr_set(v.its_fd, KVM_DEV_ARM_VGIC_GRP_ADDR, + KVM_VGIC_V5_ADDR_TYPE_ITS, &attr); + TEST_ASSERT(ret && errno == EEXIST, "GICv5 ITS base set again"); + + /* Check existing group/attribute */ + kvm_has_device_attr(v.its_fd, KVM_DEV_ARM_VGIC_GRP_ITS_REGS, + GICV5_ITS_IDR0); + + /* Check MMIO by checking if present, reading, then writing back */ + for (i = 0; i < ARRAY_SIZE(gic_v5_its_regs); i++) { + attr = gic_v5_its_regs[i].attr; + ret = __kvm_has_device_attr(v.its_fd, + KVM_DEV_ARM_VGIC_GRP_ITS_REGS, + attr); + TEST_ASSERT(!ret, "GICv5 ITS_REGS missing %s", + gic_v5_its_regs[i].name); + + val = 0xbad; + ret = __kvm_device_attr_get(v.its_fd, + KVM_DEV_ARM_VGIC_GRP_ITS_REGS, + attr, &val); + TEST_ASSERT(!ret, "GICv5 ITS_REGS get failed for %s", + gic_v5_its_regs[i].name); + + ret = __kvm_device_attr_set(v.its_fd, + KVM_DEV_ARM_VGIC_GRP_ITS_REGS, + attr, &val); + TEST_ASSERT(!ret, "GICv5 ITS_REGS set failed for %s", + gic_v5_its_regs[i].name); + } + + /* Check setting and getting an out-of-range offset */ + attr = GICV5_ITS_SIZE; + ret = __kvm_has_device_attr(v.its_fd, KVM_DEV_ARM_VGIC_GRP_ITS_REGS, + attr); + TEST_ASSERT(ret && errno == ENXIO, "GICv5 ITS_REGS accepted bad offset"); + + ret = __kvm_device_attr_get(v.its_fd, KVM_DEV_ARM_VGIC_GRP_ITS_REGS, + attr, &val); + TEST_ASSERT(ret && errno == ENXIO, "GICv5 ITS_REGS get bad offset"); + + /* Check (mis)alignment */ + attr = GICV5_ITS_IDR0 + 2; + ret = __kvm_has_device_attr(v.its_fd, KVM_DEV_ARM_VGIC_GRP_ITS_REGS, + attr); + TEST_ASSERT(ret && errno == EINVAL, + "GICv5 ITS_REGS accepted unaligned 32-bit attr"); + + attr = GICV5_ITS_DT_BASER + 4; + ret = __kvm_has_device_attr(v.its_fd, KVM_DEV_ARM_VGIC_GRP_ITS_REGS, + attr); + TEST_ASSERT(ret && errno == EINVAL, + "GICv5 ITS_REGS accepted unaligned 64-bit attr"); + + /* Fail to provide attr pointer */ + ret = __kvm_device_attr_get(v.its_fd, KVM_DEV_ARM_VGIC_GRP_ITS_REGS, + GICV5_ITS_IDR0, NULL); + TEST_ASSERT(ret && errno == EFAULT, "GICv5 ITS_REGS get with bad pointer"); + + ret = __kvm_device_attr_set(v.its_fd, KVM_DEV_ARM_VGIC_GRP_ITS_REGS, + GICV5_ITS_IDR0, NULL); + TEST_ASSERT(ret && errno == EFAULT, "GICv5 ITS_REGS set with bad pointer"); + + /* Make sure we can restore the vITS's enable state */ + val = GICV5_ITS_CR0_ITSEN; + ret = __kvm_device_attr_set(v.its_fd, KVM_DEV_ARM_VGIC_GRP_ITS_REGS, + GICV5_ITS_CR0, &val); + TEST_ASSERT(!ret, "GICv5 ITS_REGS failed to enable ITS"); + + /* And DT CFGR/BASER */ + val = 0; + ret = __kvm_device_attr_set(v.its_fd, KVM_DEV_ARM_VGIC_GRP_ITS_REGS, + GICV5_ITS_DT_CFGR, &val); + TEST_ASSERT(!ret, "GICv5 ITS_REGS rejected enabled DT_CFGR update"); + ret = __kvm_device_attr_get(v.its_fd, KVM_DEV_ARM_VGIC_GRP_ITS_REGS, + GICV5_ITS_DT_CFGR, &val); + TEST_ASSERT(!ret && !val, "GICv5 ITS_REGS DT_CFGR restore mismatch"); + + ret = __kvm_device_attr_set(v.its_fd, KVM_DEV_ARM_VGIC_GRP_ITS_REGS, + GICV5_ITS_DT_BASER, &val); + TEST_ASSERT(!ret, "GICv5 ITS_REGS rejected enabled DT_BASER update"); + ret = __kvm_device_attr_get(v.its_fd, KVM_DEV_ARM_VGIC_GRP_ITS_REGS, + GICV5_ITS_DT_BASER, &val); + TEST_ASSERT(!ret && !val, "GICv5 ITS_REGS DT_BASER restore mismatch"); + + vm_gic_destroy(&v); + + vm_gic_init(&v); + v.vm = __vm_create(VM_SHAPE_DEFAULT, NR_VCPUS, 0); + v.gic_fd = kvm_create_device(v.vm, v.gic_dev_type); + v.its_fd = kvm_create_device(v.vm, KVM_DEV_TYPE_ARM_VGIC_V5_ITS); + + /* Try to access MMIO before setting ITS address */ + ret = __kvm_has_device_attr(v.its_fd, KVM_DEV_ARM_VGIC_GRP_ITS_REGS, + GICV5_ITS_IDR0); + TEST_ASSERT(ret && errno == ENXIO, + "GICv5 ITS_REGS accepted without ITS base"); + + val = 0; + ret = __kvm_device_attr_get(v.its_fd, KVM_DEV_ARM_VGIC_GRP_ITS_REGS, + GICV5_ITS_IDR0, &val); + TEST_ASSERT(ret && errno == ENXIO, + "GICv5 ITS_REGS get accepted without ITS base"); + + vm_gic_destroy(&v); +} + static void test_vgic_v5_ist_attrs(void) { struct kvm_vgic_v5_ist ist_attr; @@ -1296,7 +1519,7 @@ static void test_vgic_v5_ist_attrs(void) u64 attr; int ret; - v.gic_dev_type = KVM_DEV_TYPE_ARM_VGIC_V5; + vm_gic_init(&v); v.vm = __vm_create(VM_SHAPE_DEFAULT, NR_VCPUS, 0); v.gic_fd = kvm_create_device(v.vm, v.gic_dev_type); vcpu = vm_vcpu_add(v.vm, 0, NULL); @@ -1383,6 +1606,7 @@ static void test_vgic_v5_ist_attrs(void) vm_gic_destroy(&v); /* IST restore is rejected after the VM has run. */ + vm_gic_init(&v); v.vm = __vm_create(VM_SHAPE_DEFAULT, NR_VCPUS, 0); v.gic_fd = kvm_create_device(v.vm, v.gic_dev_type); vcpu = vm_vcpu_add(v.vm, 0, guest_code); @@ -1411,7 +1635,7 @@ static void test_vgic_v5_userspace_ppis_attrs(void) u64 user_ppis[2]; int ret; - v.gic_dev_type = KVM_DEV_TYPE_ARM_VGIC_V5; + vm_gic_init(&v); v.vm = __vm_create(VM_SHAPE_DEFAULT, NR_VCPUS, 0); v.gic_fd = kvm_create_device(v.vm, v.gic_dev_type); vcpu = vm_vcpu_add(v.vm, 0, NULL); @@ -1452,7 +1676,7 @@ static void test_vgic_v5_cpu_sysreg_attrs(void) u64 attr, val; int ret, i; - v.gic_dev_type = KVM_DEV_TYPE_ARM_VGIC_V5; + vm_gic_init(&v); v.vm = __vm_create(VM_SHAPE_DEFAULT, NR_VCPUS, 0); v.gic_fd = kvm_create_device(v.vm, v.gic_dev_type); vcpu = vm_vcpu_add(v.vm, 0, NULL); @@ -1544,7 +1768,7 @@ static void test_vgic_v5_sparse_vcpu_ids(void) struct vm_gic v; u64 attr; - v.gic_dev_type = KVM_DEV_TYPE_ARM_VGIC_V5; + vm_gic_init(&v); v.vm = __vm_create(VM_SHAPE_DEFAULT, VGIC_V5_SPARSE_NR_VCPUS, 0); v.gic_fd = kvm_create_device(v.vm, v.gic_dev_type); @@ -1588,6 +1812,7 @@ static void test_vgic_v5_ppis(u32 gic_dev_type) uint64_t attr; int ret, i; + vm_gic_init(&v); v.gic_dev_type = gic_dev_type; v.vm = __vm_create(VM_SHAPE_DEFAULT, NR_VCPUS, 0); @@ -1983,6 +2208,7 @@ int test_kvm_device(u32 gic_dev_type) struct vm_gic v; int ret; + vm_gic_init(&v); v.vm = vm_create_with_vcpus(NR_VCPUS, guest_code, vcpus); /* try to create a non existing KVM device */ @@ -2014,6 +2240,9 @@ void run_tests(u32 gic_dev_type) pr_info("Test VGICv5 IRS_REGS attrs\n"); test_vgic_v5_irs_regs_attrs(); + pr_info("Test VGICv5 ITS attrs\n"); + test_vgic_v5_its_attrs(); + pr_info("Test VGICv5 IST attrs\n"); test_vgic_v5_ist_attrs(); diff --git a/tools/testing/selftests/kvm/include/arm64/gic_v5.h b/tools/testing/selftests/kvm/include/arm64/gic_v5.h index 2d455515563e0..22ab78626af5a 100644 --- a/tools/testing/selftests/kvm/include/arm64/gic_v5.h +++ b/tools/testing/selftests/kvm/include/arm64/gic_v5.h @@ -13,6 +13,8 @@ /* GIC component base address is guest PA space */ #define GICV5_IRS_CONFIG_BASE_GPA 0x8000000ULL #define GICV5_IRS_CONFIG_BASE_GVA ((volatile void *)GICV5_IRS_CONFIG_BASE_GPA) +#define GICV5_ITS_BASE_GPA 0x8030000ULL +#define GICV5_ITS_BASE_GVA ((volatile void *)GICV5_ITS_BASE_GPA) #define GICV5_IRS_SIZE 0x20000 #define GICV5_IRS_ALIGN 0x10000 @@ -74,6 +76,64 @@ #define GICV5_IRS_SWERR_SYNDROMER0 0x03c8 #define GICV5_IRS_SWERR_SYNDROMER1 0x03d0 +/* + * ITS register offsets and fields used by the GICv5 ITS selftests. + */ +#define GICV5_ITS_IDR0 0x0000 +#define GICV5_ITS_IDR1 0x0004 +#define GICV5_ITS_IDR2 0x0008 +#define GICV5_ITS_IIDR 0x0040 +#define GICV5_ITS_AIDR 0x0044 +#define GICV5_ITS_CR0 0x0080 +#define GICV5_ITS_CR1 0x0084 +#define GICV5_ITS_DT_BASER 0x00c0 +#define GICV5_ITS_DT_CFGR 0x00d0 +#define GICV5_ITS_DIDR 0x0100 +#define GICV5_ITS_EIDR 0x0108 +#define GICV5_ITS_INV_EVENTR 0x010c +#define GICV5_ITS_INV_DEVICER 0x0110 +#define GICV5_ITS_READ_EVENTR 0x0114 +#define GICV5_ITS_READ_EVENT_DATAR 0x0118 +#define GICV5_ITS_STATUSR 0x0120 +#define GICV5_ITS_SYNCR 0x0140 +#define GICV5_ITS_SYNC_STATUSR 0x0148 +#define GICV5_ITS_GEN_EVENT_DIDR 0x0180 +#define GICV5_ITS_GEN_EVENT_EIDR 0x0188 +#define GICV5_ITS_GEN_EVENTR 0x018c +#define GICV5_ITS_GEN_EVENT_STATUSR 0x0190 +#define GICV5_ITS_MEC_IDR 0x01c0 +#define GICV5_ITS_MEC_MECID_R 0x01c4 +#define GICV5_ITS_MPAM_IDR 0x0200 +#define GICV5_ITS_MPAM_PARTID_R 0x0204 +#define GICV5_ITS_SWERR_STATUSR 0x0240 +#define GICV5_ITS_SWERR_SYNDROMER0 0x0248 +#define GICV5_ITS_SWERR_SYNDROMER1 0x0250 +#define GICV5_ITS_MPAM_PMGR 0x0258 +#define GICV5_ITS_TRANSLATER 0x10000 +#define GICV5_ITS_RL_TRANSLATER 0x10008 + +#define GICV5_ITS_SIZE 0x20000 +#define GICV5_ITS_ALIGN 0x10000 + +#define GICV5_ITS_CR0_IDLE BIT(1) +#define GICV5_ITS_CR0_ITSEN BIT(0) +#define GICV5_ITS_DT_BASER_ADDR_MASK GENMASK_ULL(55, 3) +#define GICV5_ITS_DT_BASER_ADDR_SHIFT 3ULL +#define GICV5_ITS_DT_CFGR_STRUCTURE BIT(16) +#define GICV5_ITS_DT_CFGR_L2SZ GENMASK(7, 6) +#define GICV5_ITS_DT_CFGR_DEVICEID_BITS GENMASK(5, 0) +#define GICV5_ITS_DIDR_DEVICEID GENMASK_ULL(31, 0) +#define GICV5_ITS_EIDR_EVENTID GENMASK(15, 0) +#define GICV5_ITS_INV_EVENTR_I BIT(31) +#define GICV5_ITS_INV_EVENTR_ITT_L2SZ GENMASK(2, 1) +#define GICV5_ITS_INV_EVENTR_L1 BIT(0) +#define GICV5_ITS_INV_DEVICER_I BIT(31) +#define GICV5_ITS_INV_DEVICER_EVENTID_BITS GENMASK(5, 1) +#define GICV5_ITS_INV_DEVICER_L1 BIT(0) +#define GICV5_ITS_STATUSR_IDLE BIT(0) +#define GICV5_ITS_SYNC_STATUSR_IDLE BIT(0) +#define GICV5_ITS_GEN_EVENT_STATUSR_IDLE BIT(0) + #define GICV5_IRS_IDR0_SETLPI BIT(9) #define GICV5_IRS_IDR0_INT_DOM GENMASK(1, 0) #define GICV5_IRS_IDR0_INT_DOM_SECURE 0b00 -- 2.34.1 Add end-to-end coverage for VGICv5 ITS translation and cache invalidation. The ITS attribute test only checks the userspace API surface; this test verifies that KVM can parse guest-provided linear DT and ITT state and use it to deliver LPIs through guest GEN_EVENTR and KVM_SIGNAL_MSI. Build a two-VPE VM with IRS, ITS and an LPI IST. VPE0 programs a linear device table and interrupt translation table, enables the virtual ITS, enables two LPIs targeting VPE1, and sends GEN_EVENTR to populate the translation cache with EventID to LPI A. VPE1 reports the LPI identity it consumed. VPE0 then rewrites the ITT to point at LPI B without invalidation, and a host KVM_SIGNAL_MSI injection verifies that the stale cached LPI A translation is reused. VPE0 invalidates the event with ITS_INV_EVENTR and the host verifies that LPI B is delivered. VPE0 then rewrites the ITT back to LPI A, invalidates the device with ITS_INV_DEVICER, and the host verifies that LPI A is delivered again. This exercises the guest-side event and device invalidation paths rather than invalidating translations directly from the test host. Also cover negative MSI paths by checking that KVM_SIGNAL_MSI rejects a message without KVM_MSI_VALID_DEVID and rejects a message whose address does not target the GICv5 ITS. Finally, inject once before guest-side ITS disable to prove delivery still works, then have VPE0 disable the ITS and verify that a cached KVM_SIGNAL_MSI delivery is rejected with ENXIO. Signed-off-by: Sascha Bischoff --- tools/testing/selftests/kvm/arm64/vgic_v5.c | 419 +++++++++++++++++- .../selftests/kvm/include/arm64/gic_v5.h | 21 + 2 files changed, 437 insertions(+), 3 deletions(-) diff --git a/tools/testing/selftests/kvm/arm64/vgic_v5.c b/tools/testing/selftests/kvm/arm64/vgic_v5.c index b7fb002a64f6c..6ea6aef4f0f46 100644 --- a/tools/testing/selftests/kvm/arm64/vgic_v5.c +++ b/tools/testing/selftests/kvm/arm64/vgic_v5.c @@ -18,6 +18,7 @@ #define VGIC_V5_SPARSE_VCPU0_ID 17 #define VGIC_V5_SPARSE_VCPU1_ID 3 #define VGIC_V5_LPI_MEMSLOT 1 +#define VGIC_V5_ITS_TABLE_MEMSLOT 2 #define VGIC_V5_NR_PRIVATE_IRQS 64 #define VGIC_V5_DEFAULT_NR_SPIS 32 #define VGIC_V5_MAX_NR_SPIS BIT(10) @@ -25,8 +26,17 @@ #define VGIC_V5_IRS_WAIT_RETRIES 1000000 #define VGIC_V5_LPI_IST_BASE_GPA (GICV5_IRS_CONFIG_BASE_GPA + VGIC_V5_IRS_SIZE) #define VGIC_V5_LPI_IST_SIZE 0x10000 +#define VGIC_V5_ITS_DT_BASE_GPA (GICV5_ITS_BASE_GPA + GICV5_ITS_SIZE) +#define VGIC_V5_ITS_ITT_BASE_GPA (VGIC_V5_ITS_DT_BASE_GPA + 0x10000) +#define VGIC_V5_ITS_TABLE_SIZE 0x20000 #define LPI_TEST_TO_VPE1 0 #define LPI_TEST_TO_VPE0 1 +#define ITS_TEST_DEVICE_ID 5 +#define ITS_TEST_EVENT_ID 3 +#define ITS_TEST_LPI_A 2 +#define ITS_TEST_LPI_B 3 +#define ITS_TEST_DEVICE_ID_BITS 6 +#define ITS_TEST_EVENT_ID_BITS 4 #define RESTORE_TEST_LPI_PENDING 0 #define RESTORE_TEST_LPI_ENABLED 1 #define RESTORE_TEST_SPI_PRIO_HIGH 0 @@ -56,6 +66,13 @@ struct vm_gic { #define GUEST_CMD_IST_SOURCE_READY 16 #define GUEST_CMD_IST_SPI_INJECT_READY 17 #define GUEST_CMD_IST_LPI_INJECT_READY 18 +#define GUEST_CMD_ITS_READY 19 +#define GUEST_CMD_ITS_LPI_DELIVERED 20 +#define GUEST_CMD_ITS_GEN_EVENT_SENT 21 +#define GUEST_CMD_ITS_ITT_REWRITTEN 22 +#define GUEST_CMD_ITS_EVENT_INVALIDATED 23 +#define GUEST_CMD_ITS_DEVICE_INVALIDATED 24 +#define GUEST_CMD_ITS_DISABLED 25 static struct kvm_vgic_v5_ist vgic_v5_ist_attr(void *spi_ist, size_t spi_size, void *lpi_ist, size_t lpi_size) @@ -71,6 +88,7 @@ static struct kvm_vgic_v5_ist vgic_v5_ist_attr(void *spi_ist, size_t spi_size, static u32 spi_line_expected; static bool spi_line_level_sensitive; static bool lpi_ist_ready; +static u32 its_expected_lpi; static u32 ist_restore_irq_count[VGIC_V5_LPI_NR_VCPUS]; static u64 gicv5_hwirq(u32 type, u32 intid) @@ -222,6 +240,140 @@ static void gicv5_configure_lpi_ist(void) GICV5_IRS_IST_BASER), val); } +/* We only use a single DeviceID and hence only have a single ITT */ +static void gicv5_write_its_itte(u32 event_id, u32 lpi) +{ + u64 val; + + val = FIELD_PREP(GICV5_ITTL2E_LPI_ID, lpi) | GICV5_ITTL2E_VALID; + WRITE_ONCE(*(u64 *)(VGIC_V5_ITS_ITT_BASE_GPA + + event_id * sizeof(u64)), val); +} + +/* + * Set up the ITS "tables". Point the DTE to the ITT and make valid. Then set up + * the translation in the ITT. We are cheating a bit here, and have the most + * simplistic table setup possible. A Linear DT with one DeviceID in use which + * points to a linear ITT. + */ +static void gicv5_configure_its_tables(u32 lpi) +{ + u64 val; + + val = FIELD_PREP(GICV5_DTL2E_ITT_ADDR_MASK, + VGIC_V5_ITS_ITT_BASE_GPA >> GICV5_ITS_DT_BASER_ADDR_SHIFT) | + FIELD_PREP(GICV5_DTL2E_EVENT_ID_BITS, ITS_TEST_EVENT_ID_BITS) | + GICV5_DTL2E_VALID; + WRITE_ONCE(*(u64 *)(VGIC_V5_ITS_DT_BASE_GPA + + ITS_TEST_DEVICE_ID * sizeof(u64)), val); + + gicv5_write_its_itte(ITS_TEST_EVENT_ID, lpi); +} + +/* Set ITS_DT_CFGR, ITS_DT_BASER, and enable the ITS */ +static void gicv5_configure_its(void) +{ + u64 val; + + val = FIELD_PREP(GICV5_ITS_DT_BASER_ADDR_MASK, + VGIC_V5_ITS_DT_BASE_GPA >> GICV5_ITS_DT_BASER_ADDR_SHIFT); + writeq(val, GICV5_ITS_BASE_GVA + GICV5_ITS_DT_BASER); + + val = FIELD_PREP(GICV5_ITS_DT_CFGR_DEVICEID_BITS, + ITS_TEST_DEVICE_ID_BITS); + writel(val, GICV5_ITS_BASE_GVA + GICV5_ITS_DT_CFGR); + + writel(GICV5_ITS_CR0_ITSEN, GICV5_ITS_BASE_GVA + GICV5_ITS_CR0); + GUEST_ASSERT(readl(GICV5_ITS_BASE_GVA + GICV5_ITS_CR0) & GICV5_ITS_CR0_ITSEN); +} + +/* + * The DT registers are read-only to the guest while the ITS is enabled. Write + * modified versions of the current values and check that the writes are + * ignored. Avoid literal zero here: the arm64 selftest MMIO helpers allow a + * zero-immediate operand, but a store instruction needs a register operand. + */ +static void gicv5_check_enabled_its_dt_regs_ro(void) +{ + const u64 dt_baser = readq(GICV5_ITS_BASE_GVA + GICV5_ITS_DT_BASER); + const u32 dt_cfgr = readl(GICV5_ITS_BASE_GVA + GICV5_ITS_DT_CFGR); + const u64 new_dt_baser = dt_baser ^ BIT_ULL(16); + const u32 new_dt_cfgr = dt_cfgr ^ BIT(0); + + writeq(new_dt_baser, GICV5_ITS_BASE_GVA + GICV5_ITS_DT_BASER); + writel(new_dt_cfgr, GICV5_ITS_BASE_GVA + GICV5_ITS_DT_CFGR); + + GUEST_ASSERT_EQ(readq(GICV5_ITS_BASE_GVA + GICV5_ITS_DT_BASER), + dt_baser); + GUEST_ASSERT_EQ(readl(GICV5_ITS_BASE_GVA + GICV5_ITS_DT_CFGR), + dt_cfgr); +} + +/* + * Read out the Event Data from ITS_READ_EVENT_DATAR and check it to ensure the + * expected translation is returned. + */ +static void gicv5_check_its_read_event(u32 device_id, u32 event_id, u32 lpi) +{ + u64 val; + + val = FIELD_PREP(GICV5_ITS_DIDR_DEVICEID, device_id); + writeq(val, GICV5_ITS_BASE_GVA + GICV5_ITS_DIDR); + + val = FIELD_PREP(GICV5_ITS_EIDR_EVENTID, event_id); + writel(val, GICV5_ITS_BASE_GVA + GICV5_ITS_EIDR); + + writel(GICV5_ITS_READ_EVENTR_R, + GICV5_ITS_BASE_GVA + GICV5_ITS_READ_EVENTR); + + val = readq(GICV5_ITS_BASE_GVA + GICV5_ITS_READ_EVENT_DATAR); + GUEST_ASSERT(val & GICV5_ITS_READ_EVENT_DATAR_VALID); + GUEST_ASSERT_EQ(FIELD_GET(GICV5_ITS_READ_EVENT_DATAR_LPI_ID, val), + lpi); +} + +/* Inject a manually triggered translation via the ITS_GEN_EVENTR */ +static void gicv5_send_its_gen_event(u32 device_id, u32 event_id) +{ + u64 val; + + val = FIELD_PREP(GICV5_ITS_GEN_EVENT_DIDR_DEVICEID, device_id); + writeq(val, GICV5_ITS_BASE_GVA + GICV5_ITS_GEN_EVENT_DIDR); + + val = FIELD_PREP(GICV5_ITS_GEN_EVENT_EIDR_EVENTID, event_id); + writel(val, GICV5_ITS_BASE_GVA + GICV5_ITS_GEN_EVENT_EIDR); + + writel(GICV5_ITS_GEN_EVENTR_R | + FIELD_PREP(GICV5_ITS_GEN_EVENTR_TARGET_DOMAIN, 0), + GICV5_ITS_BASE_GVA + GICV5_ITS_GEN_EVENTR); +} + +static void gicv5_invalidate_its_event(u32 device_id, u32 event_id) +{ + u64 val; + + val = FIELD_PREP(GICV5_ITS_DIDR_DEVICEID, device_id); + writeq(val, GICV5_ITS_BASE_GVA + GICV5_ITS_DIDR); + + val = FIELD_PREP(GICV5_ITS_EIDR_EVENTID, event_id); + writel(val, GICV5_ITS_BASE_GVA + GICV5_ITS_EIDR); + + writel(GICV5_ITS_INV_EVENTR_I, + GICV5_ITS_BASE_GVA + GICV5_ITS_INV_EVENTR); +} + +static void gicv5_invalidate_its_device(u32 device_id, u32 event_id_bits) +{ + u64 val; + + val = FIELD_PREP(GICV5_ITS_DIDR_DEVICEID, device_id); + writeq(val, GICV5_ITS_BASE_GVA + GICV5_ITS_DIDR); + + val = GICV5_ITS_INV_DEVICER_I | + FIELD_PREP(GICV5_ITS_INV_DEVICER_EVENTID_BITS, event_id_bits); + writel(val, GICV5_ITS_BASE_GVA + GICV5_ITS_INV_DEVICER); +} + static void gicv5_enable_irs(void) { writel(GICV5_IRS_CR0_IRSEN, @@ -417,6 +569,80 @@ static void guest_lpi_code(void) wfi(); } +static void guest_its_irq_handler(struct ex_regs *regs) +{ + u64 expected_hwirq = gicv5_lpi_hwirq(READ_ONCE(its_expected_lpi)); + u32 hwirq; + u64 ia; + + ia = gicr_insn(CDIA); + if (!GICV5_GICR_CDIA_VALID(ia)) + return; + + gsb_ack(); + isb(); + + hwirq = FIELD_GET(GICV5_GICR_CDIA_INTID, ia); + GUEST_ASSERT_EQ(hwirq, expected_hwirq); + + gic_insn(hwirq, CDDI); + gic_insn(0, CDEOI); + + GUEST_SYNC(GUEST_CMD_ITS_LPI_DELIVERED); +} + +static void guest_its_code(void) +{ + local_irq_disable(); + + if (!guest_get_vcpuid()) { /* VCPU0 */ + gicv5_enable_irs(); + gicv5_configure_lpi_ist(); + gicv5_enable_lpi(ITS_TEST_LPI_A, 1); + gicv5_enable_lpi(ITS_TEST_LPI_B, 1); + gicv5_configure_its_tables(ITS_TEST_LPI_A); + gicv5_configure_its(); + gicv5_check_enabled_its_dt_regs_ro(); + gicv5_check_its_read_event(ITS_TEST_DEVICE_ID, + ITS_TEST_EVENT_ID, + ITS_TEST_LPI_A); + WRITE_ONCE(lpi_ist_ready, true); + + GUEST_SYNC(GUEST_CMD_ITS_READY); + + gicv5_send_its_gen_event(ITS_TEST_DEVICE_ID, + ITS_TEST_EVENT_ID); + + GUEST_SYNC(GUEST_CMD_ITS_GEN_EVENT_SENT); + + gicv5_write_its_itte(ITS_TEST_EVENT_ID, ITS_TEST_LPI_B); + GUEST_SYNC(GUEST_CMD_ITS_ITT_REWRITTEN); + + gicv5_invalidate_its_event(ITS_TEST_DEVICE_ID, + ITS_TEST_EVENT_ID); + GUEST_SYNC(GUEST_CMD_ITS_EVENT_INVALIDATED); + + gicv5_write_its_itte(ITS_TEST_EVENT_ID, ITS_TEST_LPI_A); + gicv5_invalidate_its_device(ITS_TEST_DEVICE_ID, + ITS_TEST_EVENT_ID_BITS); + GUEST_SYNC(GUEST_CMD_ITS_DEVICE_INVALIDATED); + + writel(0, GICV5_ITS_BASE_GVA + GICV5_ITS_CR0); + GUEST_ASSERT(!(readl(GICV5_ITS_BASE_GVA + GICV5_ITS_CR0) & + GICV5_ITS_CR0_ITSEN)); + GUEST_SYNC(GUEST_CMD_ITS_DISABLED); + } else { /* VCPU1 */ + GUEST_ASSERT(READ_ONCE(lpi_ist_ready)); + gicv5_cpu_enable_interrupts(); + local_irq_enable(); + + GUEST_SYNC(GUEST_CMD_ITS_READY); + } + + while (1) + wfi(); +} + static void restore_test_setup_hwirq(u64 hwirq, u32 target_vpe, u32 priority, bool enable) { @@ -610,6 +836,29 @@ static void vgic_v5_map_irs(struct kvm_vm *vm) GICV5_IRS_CONFIG_BASE_GPA, nr_irs_pages); } +static void vgic_v5_map_its(struct kvm_vm *vm) +{ + unsigned int nr_its_pages; + + nr_its_pages = vm_calc_num_guest_pages(vm->mode, GICV5_ITS_SIZE); + + /* Map the ITS at VA == IPA so guest MMIO writes hit the ITS IODEV. */ + virt_map(vm, GICV5_ITS_BASE_GPA, GICV5_ITS_BASE_GPA, nr_its_pages); +} + +static void vgic_v5_add_its_table_mem(struct kvm_vm *vm) +{ + unsigned int nr_pages; + + nr_pages = vm_calc_num_guest_pages(vm->mode, VGIC_V5_ITS_TABLE_SIZE); + vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, + VGIC_V5_ITS_DT_BASE_GPA, + VGIC_V5_ITS_TABLE_MEMSLOT, + nr_pages, 0); + virt_map(vm, VGIC_V5_ITS_DT_BASE_GPA, VGIC_V5_ITS_DT_BASE_GPA, + nr_pages); +} + static u32 vgic_v5_irq_line_payload(u32 type, u32 num) { return (type << KVM_ARM_IRQ_TYPE_SHIFT) | @@ -660,7 +909,8 @@ static void vgic_v5_spi_line_vm_create(struct vm_gic *v, static void vgic_v5_lpi_ist_vm_create(struct vm_gic *v, struct kvm_vcpu *vcpus[VGIC_V5_LPI_NR_VCPUS], void (*guest_code)(void), - void (*irq_handler)(struct ex_regs *)) + void (*irq_handler)(struct ex_regs *), + bool with_its) { unsigned int nr_lpi_ist_pages; u64 attr; @@ -669,6 +919,9 @@ static void vgic_v5_lpi_ist_vm_create(struct vm_gic *v, vm_gic_init(v); v->vm = __vm_create(VM_SHAPE_DEFAULT, VGIC_V5_LPI_NR_VCPUS, 0); v->gic_fd = kvm_create_device(v->vm, v->gic_dev_type); + if (with_its) + v->its_fd = kvm_create_device(v->vm, + KVM_DEV_TYPE_ARM_VGIC_V5_ITS); for (i = 0; i < VGIC_V5_LPI_NR_VCPUS; i++) { vcpus[i] = vm_vcpu_add(v->vm, i, guest_code); @@ -687,6 +940,16 @@ static void vgic_v5_lpi_ist_vm_create(struct vm_gic *v, KVM_VGIC_V5_ADDR_TYPE_IRS, &attr); vgic_v5_map_irs(v->vm); + if (with_its) { + attr = GICV5_ITS_BASE_GPA; + kvm_device_attr_set(v->its_fd, KVM_DEV_ARM_VGIC_GRP_ADDR, + KVM_VGIC_V5_ADDR_TYPE_ITS, &attr); + kvm_device_attr_set(v->its_fd, KVM_DEV_ARM_VGIC_GRP_CTRL, + KVM_DEV_ARM_VGIC_CTRL_INIT, NULL); + vgic_v5_map_its(v->vm); + vgic_v5_add_its_table_mem(v->vm); + } + nr_lpi_ist_pages = vm_calc_num_guest_pages(v->vm->mode, VGIC_V5_LPI_IST_SIZE); vm_userspace_mem_region_add(v->vm, VM_MEM_SRC_ANONYMOUS, @@ -708,7 +971,14 @@ static void vgic_v5_lpi_vm_create(struct vm_gic *v, struct kvm_vcpu *vcpus[VGIC_V5_LPI_NR_VCPUS]) { vgic_v5_lpi_ist_vm_create(v, vcpus, guest_lpi_code, - guest_lpi_irq_handler); + guest_lpi_irq_handler, false); +} + +static void vgic_v5_its_vm_create(struct vm_gic *v, + struct kvm_vcpu *vcpus[VGIC_V5_LPI_NR_VCPUS]) +{ + vgic_v5_lpi_ist_vm_create(v, vcpus, guest_its_code, + guest_its_irq_handler, true); } static void vgic_v5_ist_restore_vm_create(struct vm_gic *v, @@ -716,7 +986,7 @@ static void vgic_v5_ist_restore_vm_create(struct vm_gic *v, void (*guest_code)(void), void (*irq_handler)(struct ex_regs *)) { - vgic_v5_lpi_ist_vm_create(v, vcpus, guest_code, irq_handler); + vgic_v5_lpi_ist_vm_create(v, vcpus, guest_code, irq_handler, false); } static void vgic_v5_run_spi_line_test(u32 nr_spis, u32 expected_spi, @@ -2199,6 +2469,146 @@ static void test_vgic_v5_ist_save_restore(void) vm_gic_destroy(&dst); } +static int __vgic_v5_signal_msi(struct kvm_vm *vm, u64 address, + u32 device_id, u32 event_id, u32 flags) +{ + struct kvm_msi msi = { + .address_lo = address, + .address_hi = address >> 32, + .data = event_id, + .devid = device_id, + .flags = flags, + }; + + return __vm_ioctl(vm, KVM_SIGNAL_MSI, &msi); +} + +static void vgic_v5_signal_msi(struct kvm_vm *vm, u32 device_id, + u32 event_id) +{ + u64 addr = GICV5_ITS_BASE_GPA + GICV5_ITS_TRANSLATER; + int ret; + + ret = __vgic_v5_signal_msi(vm, addr, device_id, event_id, + KVM_MSI_VALID_DEVID); + TEST_ASSERT(ret == 1, "KVM_SIGNAL_MSI failed for GICv5 ITS"); +} + +static void vgic_v5_expect_its_lpi(struct vm_gic *v, struct kvm_vcpu *vcpu, + u32 lpi) +{ + int ret; + + its_expected_lpi = lpi; + sync_global_to_guest(v->vm, its_expected_lpi); + + vgic_v5_signal_msi(v->vm, ITS_TEST_DEVICE_ID, ITS_TEST_EVENT_ID); + + ret = run_vcpu(vcpu); + TEST_ASSERT(!ret, "Failed to run GICv5 ITS receiver"); + vgic_v5_expect_sync(vcpu, GUEST_CMD_ITS_LPI_DELIVERED, + "GICv5 ITS guest did not consume expected LPI"); +} + +static void test_vgic_v5_its_translation_cache(void) +{ + struct kvm_vcpu *vcpus[VGIC_V5_LPI_NR_VCPUS]; + struct vm_gic v; + u64 wrong_addr; + int ret; + + lpi_ist_ready = false; + its_expected_lpi = ITS_TEST_LPI_A; + vgic_v5_its_vm_create(&v, vcpus); + sync_global_to_guest(v.vm, lpi_ist_ready); + sync_global_to_guest(v.vm, its_expected_lpi); + + ret = run_vcpu(vcpus[0]); + TEST_ASSERT(!ret, "Failed to run GICv5 ITS setup vCPU"); + vgic_v5_expect_sync(vcpus[0], GUEST_CMD_ITS_READY, + "GICv5 ITS setup guest did not become ready"); + + ret = run_vcpu(vcpus[1]); + TEST_ASSERT(!ret, "Failed to run GICv5 ITS receiver vCPU"); + vgic_v5_expect_sync(vcpus[1], GUEST_CMD_ITS_READY, + "GICv5 ITS receiver guest did not become ready"); + + ret = run_vcpu(vcpus[0]); + TEST_ASSERT(!ret, "Failed to run GICv5 ITS GEN_EVENTR sender"); + vgic_v5_expect_sync(vcpus[0], GUEST_CMD_ITS_GEN_EVENT_SENT, + "GICv5 ITS setup guest did not send GEN_EVENTR"); + + ret = run_vcpu(vcpus[1]); + TEST_ASSERT(!ret, "Failed to run GICv5 ITS GEN_EVENTR receiver"); + vgic_v5_expect_sync(vcpus[1], GUEST_CMD_ITS_LPI_DELIVERED, + "GICv5 ITS GEN_EVENTR did not deliver expected LPI"); + + ret = __vgic_v5_signal_msi(v.vm, GICV5_ITS_BASE_GPA + + GICV5_ITS_TRANSLATER, + ITS_TEST_DEVICE_ID, ITS_TEST_EVENT_ID, 0); + TEST_ASSERT(ret < 0 && errno == EINVAL, + "GICv5 ITS accepted MSI without device ID flag"); + + wrong_addr = GICV5_ITS_BASE_GPA + GICV5_ITS_SIZE; + ret = __vgic_v5_signal_msi(v.vm, wrong_addr, ITS_TEST_DEVICE_ID, + ITS_TEST_EVENT_ID, KVM_MSI_VALID_DEVID); + TEST_ASSERT(ret < 0 && errno == EINVAL, + "GICv5 ITS accepted MSI for wrong address"); + + /* + * The earlier GEN_EVENTR has cached EventID -> LPI_A. At this stage, + * VCPU0 rewrites the event, but doesn't invalidate it. The result is + * that the MSI injection reuses the (stale) cache translation. This is + * broken guest behaviour, as it should always issue an invalidation + * when a translation changes. + */ + ret = run_vcpu(vcpus[0]); + TEST_ASSERT(!ret, "Failed to run GICv5 ITS ITT rewrite"); + vgic_v5_expect_sync(vcpus[0], GUEST_CMD_ITS_ITT_REWRITTEN, + "GICv5 ITS guest did not rewrite ITT"); + vgic_v5_expect_its_lpi(&v, vcpus[1], ITS_TEST_LPI_A); + + /* + * Event invalidation must drop the stale EventID -> LPI_A entry. We now + * expect LPI B. + */ + ret = run_vcpu(vcpus[0]); + TEST_ASSERT(!ret, "Failed to run GICv5 ITS event invalidation"); + vgic_v5_expect_sync(vcpus[0], GUEST_CMD_ITS_EVENT_INVALIDATED, + "GICv5 ITS guest did not invalidate event"); + vgic_v5_expect_its_lpi(&v, vcpus[1], ITS_TEST_LPI_B); + + /* + * Device invalidation must cover all cached EventIDs for the + * DeviceID. The guest has again re-written the event to translate to + * LPI A. + */ + ret = run_vcpu(vcpus[0]); + TEST_ASSERT(!ret, "Failed to run GICv5 ITS device invalidation"); + vgic_v5_expect_sync(vcpus[0], GUEST_CMD_ITS_DEVICE_INVALIDATED, + "GICv5 ITS guest did not invalidate device"); + vgic_v5_expect_its_lpi(&v, vcpus[1], ITS_TEST_LPI_A); + + /* Disabling the ITS must also prevent delivery from cached entries. */ + ret = run_vcpu(vcpus[0]); + TEST_ASSERT(!ret, "Failed to run GICv5 ITS disable"); + vgic_v5_expect_sync(vcpus[0], GUEST_CMD_ITS_DISABLED, + "GICv5 ITS guest did not disable ITS"); + + /* + * We should fail to inject this MSI. Use the raw call to ensure we + * catch the return value. + */ + ret = __vgic_v5_signal_msi(v.vm, GICV5_ITS_BASE_GPA + + GICV5_ITS_TRANSLATER, + ITS_TEST_DEVICE_ID, ITS_TEST_EVENT_ID, + KVM_MSI_VALID_DEVID); + TEST_ASSERT(ret < 0 && errno == ENXIO, + "GICv5 ITS accepted cached MSI while disabled"); + + vm_gic_destroy(&v); +} + /* * Returns 0 if it's possible to create GIC device of a given type (V5). */ @@ -2266,6 +2676,9 @@ void run_tests(u32 gic_dev_type) pr_info("Test VGICv5 IST save/restore\n"); test_vgic_v5_ist_save_restore(); + + pr_info("Test VGICv5 ITS translation cache\n"); + test_vgic_v5_its_translation_cache(); } int main(int ac, char **av) diff --git a/tools/testing/selftests/kvm/include/arm64/gic_v5.h b/tools/testing/selftests/kvm/include/arm64/gic_v5.h index 22ab78626af5a..cd3eda8cc77e2 100644 --- a/tools/testing/selftests/kvm/include/arm64/gic_v5.h +++ b/tools/testing/selftests/kvm/include/arm64/gic_v5.h @@ -130,10 +130,31 @@ #define GICV5_ITS_INV_DEVICER_I BIT(31) #define GICV5_ITS_INV_DEVICER_EVENTID_BITS GENMASK(5, 1) #define GICV5_ITS_INV_DEVICER_L1 BIT(0) +#define GICV5_ITS_READ_EVENTR_R BIT(31) +#define GICV5_ITS_READ_EVENT_DATAR_VIRT BIT_ULL(63) +#define GICV5_ITS_READ_EVENT_DATAR_VM_ID GENMASK_ULL(47, 32) +#define GICV5_ITS_READ_EVENT_DATAR_VALID BIT_ULL(31) +#define GICV5_ITS_READ_EVENT_DATAR_LPI_ID GENMASK_ULL(23, 0) +#define GICV5_ITS_GEN_EVENT_DIDR_DEVICEID GENMASK_ULL(31, 0) +#define GICV5_ITS_GEN_EVENT_EIDR_EVENTID GENMASK(15, 0) +#define GICV5_ITS_GEN_EVENTR_R BIT(31) +#define GICV5_ITS_GEN_EVENTR_TARGET_DOMAIN GENMASK(1, 0) #define GICV5_ITS_STATUSR_IDLE BIT(0) #define GICV5_ITS_SYNC_STATUSR_IDLE BIT(0) #define GICV5_ITS_GEN_EVENT_STATUSR_IDLE BIT(0) +#define GICV5_DTL2E_VALID BIT_ULL(0) +#define GICV5_DTL2E_ITT_L2SZ GENMASK_ULL(2, 1) +#define GICV5_DTL2E_ITT_ADDR_MASK GENMASK_ULL(55, 3) +#define GICV5_DTL2E_ITT_DSWE BIT_ULL(57) +#define GICV5_DTL2E_ITT_STRUCTURE BIT_ULL(58) +#define GICV5_DTL2E_EVENT_ID_BITS GENMASK_ULL(63, 59) + +#define GICV5_ITTL2E_LPI_ID GENMASK_ULL(23, 0) +#define GICV5_ITTL2E_DAC GENMASK_ULL(29, 28) +#define GICV5_ITTL2E_VIRTUAL BIT_ULL(30) +#define GICV5_ITTL2E_VALID BIT_ULL(31) + #define GICV5_IRS_IDR0_SETLPI BIT(9) #define GICV5_IRS_IDR0_INT_DOM GENMASK(1, 0) #define GICV5_IRS_IDR0_INT_DOM_SECURE 0b00 -- 2.34.1 Document the GICv5 ITS device and its userspace ABI, including the MMIO address, reset control, register attributes, and MSI requirements. State that software should target ITS_TRANSLATER, while KVM uses the MSI address to identify the virtual ITS. Signed-off-by: Sascha Bischoff --- .../virt/kvm/devices/arm-vgic-v5-its.rst | 102 ++++++++++++++++++ Documentation/virt/kvm/devices/index.rst | 1 + 2 files changed, 103 insertions(+) create mode 100644 Documentation/virt/kvm/devices/arm-vgic-v5-its.rst diff --git a/Documentation/virt/kvm/devices/arm-vgic-v5-its.rst b/Documentation/virt/kvm/devices/arm-vgic-v5-its.rst new file mode 100644 index 0000000000000..250057e4445d7 --- /dev/null +++ b/Documentation/virt/kvm/devices/arm-vgic-v5-its.rst @@ -0,0 +1,102 @@ +.. SPDX-License-Identifier: GPL-2.0 + +============================================================ +ARM Virtual Generic Interrupt Controller v5 ITS (VGICv5 ITS) +============================================================ + +Device types supported: + KVM_DEV_TYPE_ARM_VGIC_V5_ITS ARM GICv5 Interrupt Translation Service + +The GICv5 ITS translates MSI(-X) interrupts into LPIs. It is optional and +requires a GICv5 host and a KVM_DEV_TYPE_ARM_VGIC_V5 device for the VM. The +ITS and VGIC devices may be created in either order, but their models must +match. Userspace should assign each ITS a separate, non-overlapping MMIO +region. + +MSIs injected through KVM_SIGNAL_MSI must provide a DeviceID using +KVM_MSI_VALID_DEVID. The MSI address should target ITS_TRANSLATER in the +ITS_TRANSLATE_FRAME. KVM uses the address to identify the virtual ITS, and +the MSI data supplies the EventID. The virtual ITS supports 16-bit EventIDs; +the upper 16 bits of the MSI data must be zero. + + +Groups +====== + +KVM_DEV_ARM_VGIC_GRP_ADDR +------------------------- + + Attributes: + KVM_VGIC_V5_ADDR_TYPE_ITS (rw, 64-bit) + Base address of the ITS in the guest physical address space. The address + must be 64K aligned. The region covers 128K: a 64K control frame and a + 64K translation frame. The address must be set before accessing ITS + registers through KVM_DEV_ARM_VGIC_GRP_ITS_REGS. It can only be set once. + + Errors: + + ======= ========================================================= + -E2BIG Address outside the guest's addressable IPA range + -EINVAL Address is not 64K aligned, or the 128K range wraps + -EEXIST Address has already been configured + -EFAULT Invalid user pointer for kvm_device_attr.addr + -ENODEV Unsupported address attribute for GET or SET + -ENXIO Unsupported address attribute for HAS + -EBUSY Concurrent address configuration has already set the base + -ENOMEM Unable to allocate space for the MMIO bus entry + -ENOSPC MMIO bus device limit reached + ======= ========================================================= + + +KVM_DEV_ARM_VGIC_GRP_CTRL +------------------------- + + Attributes: + KVM_DEV_ARM_VGIC_CTRL_INIT + Accepted for consistency with other VGIC devices; no additional ITS + initialization is required. + + KVM_DEV_ARM_ITS_CTRL_RESET + Reset the emulated ITS registers and discard cached translations. The + configured MMIO base address is retained. Guest memory containing the + device and interrupt translation tables is not modified. + + Errors: + + ======= ========================================================= + -EBUSY A vCPU could not be locked for reset + -ENXIO Unsupported control attribute for SET, or any GET + ======= ========================================================= + + +KVM_DEV_ARM_VGIC_GRP_ITS_REGS +----------------------------- + + Attributes: + The attr field of kvm_device_attr contains the ITS register offset from + the control frame base address. kvm_device_attr.addr points to a __u64, + regardless of whether the register is 32 or 64 bits wide. A 64-bit + register must be addressed at its base offset; userspace cannot access + its upper or lower half separately. Guest MMIO may use aligned 32-bit + accesses to DT_BASER, DIDR, READ_EVENT_DATAR, and GEN_EVENT_DIDR. + + The ITS base address must be configured before this group can be used. + Writes to read-only registers are ignored. Writes to DT_CFGR and DT_BASER + through this interface update their values even when the ITS is enabled; + guest MMIO writes to those registers are ignored while it is enabled. + + The registers with readable, writable state are CR0, CR1, DT_CFGR, + DT_BASER, DIDR, EIDR, GEN_EVENT_DIDR, and GEN_EVENT_EIDR. + + Some registers initiate guest-visible operations when written, including + INV_EVENTR, INV_DEVICER, READ_EVENTR, and GEN_EVENTR. READ_EVENT_DATAR is + a read-only result register. + + Errors: + + ======= ========================================================= + -ENXIO ITS base address is unset, or offset is unsupported + -EINVAL Offset is not aligned to the register width + -EFAULT Invalid user pointer for kvm_device_attr.addr + -EBUSY A vCPU could not be locked + ======= ========================================================= diff --git a/Documentation/virt/kvm/devices/index.rst b/Documentation/virt/kvm/devices/index.rst index 70845aba38f45..d6d64b7266e83 100644 --- a/Documentation/virt/kvm/devices/index.rst +++ b/Documentation/virt/kvm/devices/index.rst @@ -11,6 +11,7 @@ Devices arm-vgic arm-vgic-v3 arm-vgic-v5 + arm-vgic-v5-its mpic s390_flic vcpu -- 2.34.1