diff --git a/xen/arch/arm/gic-v3-its.c b/xen/arch/arm/gic-v3-its.c index 38f084017a..8864e0b018 100644 --- a/xen/arch/arm/gic-v3-its.c +++ b/xen/arch/arm/gic-v3-its.c @@ -859,6 +859,23 @@ struct pending_irq *gicv3_its_get_event_pending_irq(struct domain *d, return get_event_pending_irq(d, vdoorbell_address, vdevid, eventid, NULL); } +int gicv3_remove_guest_event(struct domain *d, paddr_t vdoorbell_address, + uint32_t vdevid, uint32_t eventid) +{ + uint32_t host_lpi = INVALID_LPI; + + if ( !get_event_pending_irq(d, vdoorbell_address, vdevid, eventid, + &host_lpi) ) + return -EINVAL; + + if ( host_lpi == INVALID_LPI ) + return -EINVAL; + + gicv3_lpi_update_host_entry(host_lpi, d->domain_id, INVALID_LPI); + + return 0; +} + /* Scan the DT for any ITS nodes and create a list of host ITSes out of it. */ void gicv3_its_dt_init(const struct dt_device_node *node) { diff --git a/xen/arch/arm/gic-v3-lpi.c b/xen/arch/arm/gic-v3-lpi.c index dc936fae72..c3474f5434 100644 --- a/xen/arch/arm/gic-v3-lpi.c +++ b/xen/arch/arm/gic-v3-lpi.c @@ -215,6 +215,23 @@ out: irq_exit(); } +void gicv3_lpi_update_host_entry(uint32_t host_lpi, int domain_id, + uint32_t virt_lpi) +{ + union host_lpi *hlpip, hlpi; + + ASSERT(host_lpi >= LPI_OFFSET); + + host_lpi -= LPI_OFFSET; + + hlpip = &lpi_data.host_lpis[host_lpi / HOST_LPIS_PER_PAGE][host_lpi % HOST_LPIS_PER_PAGE]; + + hlpi.virt_lpi = virt_lpi; + hlpi.dom_id = domain_id; + + write_u64_atomic(&hlpip->data, hlpi.data); +} + static int gicv3_lpi_allocate_pendtable(uint64_t *reg) { uint64_t val; diff --git a/xen/arch/arm/vgic-v3-its.c b/xen/arch/arm/vgic-v3-its.c index 4552bc9fc5..d236bbebf6 100644 --- a/xen/arch/arm/vgic-v3-its.c +++ b/xen/arch/arm/vgic-v3-its.c @@ -159,6 +159,21 @@ static struct vcpu *get_vcpu_from_collection(struct virt_its *its, return its->d->vcpu[vcpu_id]; } +/* Set the address of an ITT for a given device ID. */ +static int its_set_itt_address(struct virt_its *its, uint32_t devid, + paddr_t itt_address, uint32_t nr_bits) +{ + paddr_t addr = get_baser_phys_addr(its->baser_dev); + dev_table_entry_t itt_entry = DEV_TABLE_ENTRY(itt_address, nr_bits); + + if ( devid >= its->max_devices ) + return -ENOENT; + + return vgic_access_guest_memory(its->d, + addr + devid * sizeof(dev_table_entry_t), + &itt_entry, sizeof(itt_entry), true); +} + /* * Lookup the address of the Interrupt Translation Table associated with * that device ID. @@ -375,6 +390,130 @@ out_unlock: return ret; } +/* Must be called with the ITS lock held. */ +static int its_discard_event(struct virt_its *its, + uint32_t vdevid, uint32_t vevid) +{ + struct pending_irq *p; + unsigned long flags; + struct vcpu *vcpu; + uint32_t vlpi; + + ASSERT(spin_is_locked(&its->its_lock)); + + if ( !read_itte(its, vdevid, vevid, &vcpu, &vlpi) ) + return -ENOENT; + + if ( vlpi == INVALID_LPI ) + return -ENOENT; + + /* + * TODO: This relies on the VCPU being correct in the ITS tables. + * This can be fixed by either using a per-IRQ lock or by using + * the VCPU ID from the pending_irq instead. + */ + spin_lock_irqsave(&vcpu->arch.vgic.lock, flags); + + /* Remove the pending_irq from the tree. */ + write_lock(&its->d->arch.vgic.pend_lpi_tree_lock); + p = radix_tree_delete(&its->d->arch.vgic.pend_lpi_tree, vlpi); + write_unlock(&its->d->arch.vgic.pend_lpi_tree_lock); + + if ( !p ) + { + spin_unlock_irqrestore(&vcpu->arch.vgic.lock, flags); + + return -ENOENT; + } + + /* Cleanup the pending_irq and disconnect it from the LPI. */ + gic_remove_irq_from_queues(vcpu, p); + vgic_init_pending_irq(p, INVALID_LPI); + + spin_unlock_irqrestore(&vcpu->arch.vgic.lock, flags); + + /* Remove the corresponding host LPI entry */ + return gicv3_remove_guest_event(its->d, its->doorbell_address, + vdevid, vevid); +} + +static void its_unmap_device(struct virt_its *its, uint32_t devid) +{ + dev_table_entry_t itt; + uint64_t evid; + + spin_lock(&its->its_lock); + + if ( its_get_itt(its, devid, &itt) ) + goto out; + + /* + * For DomUs we need to check that the number of events per device + * is really limited, otherwise looping over all events can take too + * long for a guest. This ASSERT can then be removed if that is + * covered. + */ + ASSERT(is_hardware_domain(its->d)); + + for ( evid = 0; evid < DEV_TABLE_ITT_SIZE(itt); evid++ ) + /* Don't care about errors here, clean up as much as possible. */ + its_discard_event(its, devid, evid); + +out: + spin_unlock(&its->its_lock); +} + +static int its_handle_mapd(struct virt_its *its, uint64_t *cmdptr) +{ + /* size and devid get validated by the functions called below. */ + uint32_t devid = its_cmd_get_deviceid(cmdptr); + unsigned int size = its_cmd_get_size(cmdptr) + 1; + bool valid = its_cmd_get_validbit(cmdptr); + paddr_t itt_addr = its_cmd_get_ittaddr(cmdptr); + int ret; + + /* Sanitize the number of events. */ + if ( valid && (size > its->evid_bits) ) + return -1; + + if ( !valid ) + /* Discard all events and remove pending LPIs. */ + its_unmap_device(its, devid); + + /* + * There is no easy and clean way for Xen to know the ITS device ID of a + * particular (PCI) device, so we have to rely on the guest telling + * us about it. For *now* we are just using the device ID *Dom0* uses, + * because the driver there has the actual knowledge. + * Eventually this will be replaced with a dedicated hypercall to + * announce pass-through of devices. + */ + if ( is_hardware_domain(its->d) ) + { + + /* + * Dom0's ITSes are mapped 1:1, so both addresses are the same. + * Also the device IDs are equal. + */ + ret = gicv3_its_map_guest_device(its->d, its->doorbell_address, devid, + its->doorbell_address, devid, + BIT(size), valid); + if ( ret && valid ) + return ret; + } + + spin_lock(&its->its_lock); + + if ( valid ) + ret = its_set_itt_address(its, devid, itt_addr, size); + else + ret = its_set_itt_address(its, devid, INVALID_PADDR, 1); + + spin_unlock(&its->its_lock); + + return ret; +} + #define ITS_CMD_BUFFER_SIZE(baser) ((((baser) & 0xff) + 1) << 12) #define ITS_CMD_OFFSET(reg) ((reg) & GENMASK(19, 5)) @@ -420,6 +559,9 @@ static int vgic_its_handle_cmds(struct domain *d, struct virt_its *its) case GITS_CMD_MAPC: ret = its_handle_mapc(its, command); break; + case GITS_CMD_MAPD: + ret = its_handle_mapd(its, command); + break; case GITS_CMD_SYNC: /* We handle ITS commands synchronously, so we ignore SYNC. */ break; diff --git a/xen/include/asm-arm/gic_v3_its.h b/xen/include/asm-arm/gic_v3_its.h index be67726723..0089ac2f94 100644 --- a/xen/include/asm-arm/gic_v3_its.h +++ b/xen/include/asm-arm/gic_v3_its.h @@ -175,6 +175,11 @@ struct pending_irq *gicv3_its_get_event_pending_irq(struct domain *d, paddr_t vdoorbell_address, uint32_t vdevid, uint32_t eventid); +int gicv3_remove_guest_event(struct domain *d, paddr_t vdoorbell_address, + uint32_t vdevid, uint32_t eventid); +void gicv3_lpi_update_host_entry(uint32_t host_lpi, int domain_id, + uint32_t virt_lpi); + #else static inline void gicv3_its_dt_init(const struct dt_device_node *node)