diff --git a/xen/arch/arm/gic-v3-its.c b/xen/arch/arm/gic-v3-its.c index 5fd83af25a..34833166ad 100644 --- a/xen/arch/arm/gic-v3-its.c +++ b/xen/arch/arm/gic-v3-its.c @@ -42,6 +42,7 @@ struct its_device { struct rb_node rbnode; struct host_its *hw_its; void *itt_addr; + unsigned int itt_order; paddr_t guest_doorbell; /* Identifies the virtual ITS */ uint32_t host_devid; uint32_t guest_devid; @@ -50,6 +51,104 @@ struct its_device { struct pending_irq *pend_irqs; /* One struct per event */ }; +/* + * It is unlikely that a platform implements ITSes with different quirks, + * so assume they all share the same. + */ +struct its_quirk { + const char *desc; + bool (*init)(struct host_its *hw_its); + uint32_t iidr; + uint32_t mask; +}; + +static uint32_t __ro_after_init its_quirk_flags; + +static bool gicv3_its_enable_quirk_gen4(struct host_its *hw_its) +{ + its_quirk_flags |= HOST_ITS_WORKAROUND_NC_NS | + HOST_ITS_WORKAROUND_32BIT_ADDR; + + return true; +} + +static const struct its_quirk its_quirks[] = { + { + .desc = "R-Car Gen4", + .iidr = 0x0201743b, + .mask = 0xffffffff, + .init = gicv3_its_enable_quirk_gen4, + }, + { + /* Sentinel. */ + } +}; + +static struct its_quirk* gicv3_its_find_quirk(uint32_t iidr) +{ + const struct its_quirk *quirks = its_quirks; + + for ( ; quirks->desc; quirks++ ) + { + if ( quirks->iidr == (quirks->mask & iidr) ) + return (struct its_quirk *)quirks; + } + + return NULL; +} + +static void gicv3_its_enable_quirks(struct host_its *hw_its) +{ + uint32_t iidr = readl_relaxed(hw_its->its_base + GITS_IIDR); + const struct its_quirk *quirk = gicv3_its_find_quirk(iidr); + + if ( quirk && quirk->init(hw_its) ) + printk("GICv3: enabling workaround for ITS: %s\n", quirk->desc); +} + +static void gicv3_its_validate_quirks(void) +{ + const struct its_quirk *quirk = NULL, *prev = NULL; + const struct host_its *hw_its; + + if ( list_empty(&host_its_list) ) + return; + + hw_its = list_first_entry(&host_its_list, struct host_its, entry); + prev = gicv3_its_find_quirk(readl_relaxed(hw_its->its_base + GITS_IIDR)); + + list_for_each_entry(hw_its, &host_its_list, entry) + { + quirk = gicv3_its_find_quirk(readl_relaxed(hw_its->its_base + GITS_IIDR)); + BUG_ON(quirk != prev); + prev = quirk; + } +} + +uint64_t gicv3_its_get_cacheability(void) +{ + if ( its_quirk_flags & HOST_ITS_WORKAROUND_NC_NS ) + return GIC_BASER_CACHE_nC; + + return GIC_BASER_CACHE_RaWaWb; +} + +uint64_t gicv3_its_get_shareability(void) +{ + if ( its_quirk_flags & HOST_ITS_WORKAROUND_NC_NS ) + return GIC_BASER_NonShareable; + + return GIC_BASER_InnerShareable; +} + +unsigned int gicv3_its_get_memflags(void) +{ + if ( its_quirk_flags & HOST_ITS_WORKAROUND_32BIT_ADDR ) + return MEMF_bits(32); + + return 0; +} + bool gicv3_its_host_has_its(void) { return !list_empty(&host_its_list); @@ -289,19 +388,23 @@ static void *its_map_cbaser(struct host_its *its) { void __iomem *cbasereg = its->its_base + GITS_CBASER; uint64_t reg; + unsigned int order; void *buffer; - reg = GIC_BASER_InnerShareable << GITS_BASER_SHAREABILITY_SHIFT; + reg = gicv3_its_get_shareability() << GITS_BASER_SHAREABILITY_SHIFT; reg |= GIC_BASER_CACHE_SameAsInner << GITS_BASER_OUTER_CACHEABILITY_SHIFT; - reg |= GIC_BASER_CACHE_RaWaWb << GITS_BASER_INNER_CACHEABILITY_SHIFT; + reg |= gicv3_its_get_cacheability() << GITS_BASER_INNER_CACHEABILITY_SHIFT; - buffer = _xzalloc(ITS_CMD_QUEUE_SZ, SZ_64K); + order = get_order_from_bytes(max(ITS_CMD_QUEUE_SZ, SZ_64K)); + buffer = alloc_xenheap_pages(order, gicv3_its_get_memflags()); if ( !buffer ) return NULL; + memset(buffer, 0, PAGE_SIZE << order); + if ( virt_to_maddr(buffer) & ~GENMASK(51, 12) ) { - xfree(buffer); + free_xenheap_pages(buffer, order); return NULL; } @@ -340,11 +443,12 @@ static int its_map_baser(void __iomem *basereg, uint64_t regc, unsigned int entry_size = GITS_BASER_ENTRY_SIZE(regc); unsigned int pagesz = 2; /* try 64K pages first, then go down. */ unsigned int table_size; + unsigned int order; void *buffer; - attr = GIC_BASER_InnerShareable << GITS_BASER_SHAREABILITY_SHIFT; + attr = gicv3_its_get_shareability() << GITS_BASER_SHAREABILITY_SHIFT; attr |= GIC_BASER_CACHE_SameAsInner << GITS_BASER_OUTER_CACHEABILITY_SHIFT; - attr |= GIC_BASER_CACHE_RaWaWb << GITS_BASER_INNER_CACHEABILITY_SHIFT; + attr |= gicv3_its_get_cacheability() << GITS_BASER_INNER_CACHEABILITY_SHIFT; /* * Setup the BASE register with the attributes that we like. Then read @@ -357,13 +461,16 @@ retry: /* The BASE registers support at most 256 pages. */ table_size = min(table_size, 256U << BASER_PAGE_BITS(pagesz)); - buffer = _xzalloc(table_size, BIT(BASER_PAGE_BITS(pagesz), UL)); + order = get_order_from_bytes(max(table_size, BIT(BASER_PAGE_BITS(pagesz), U))); + buffer = alloc_xenheap_pages(order, gicv3_its_get_memflags()); if ( !buffer ) return -ENOMEM; + memset(buffer, 0, PAGE_SIZE << order); + if ( !check_baser_phys_addr(buffer, BASER_PAGE_BITS(pagesz)) ) { - xfree(buffer); + free_xenheap_pages(buffer, order); return -ERANGE; } @@ -396,7 +503,7 @@ retry: if ( ((regc >> GITS_BASER_PAGE_SIZE_SHIFT) & 0x3UL) == pagesz ) return 0; - xfree(buffer); + free_xenheap_pages(buffer, order); if ( pagesz-- > 0 ) goto retry; @@ -453,6 +560,8 @@ static int gicv3_its_init_single_its(struct host_its *hw_its) if ( ret ) return ret; + gicv3_its_enable_quirks(hw_its); + reg = readq_relaxed(hw_its->its_base + GITS_TYPER); hw_its->devid_bits = GITS_TYPER_DEVICE_ID_BITS(reg); hw_its->evid_bits = GITS_TYPER_EVENT_ID_BITS(reg); @@ -530,7 +639,7 @@ static int remove_mapped_guest_device(struct its_device *dev) printk(XENLOG_WARNING "Can't unmap host ITS device 0x%x\n", dev->host_devid); - xfree(dev->itt_addr); + free_xenheap_pages(dev->itt_addr, dev->itt_order); xfree(dev->pend_irqs); xfree(dev->host_lpi_blocks); xfree(dev); @@ -619,6 +728,7 @@ int gicv3_its_map_guest_device(struct domain *d, struct its_device *dev = NULL; struct rb_node **new = &d->arch.vgic.its_devices.rb_node, *parent = NULL; int i, ret = -ENOENT; /* "i" must be signed to check for >= 0 below. */ + unsigned int order; hw_its = gicv3_its_find_by_doorbell(host_doorbell); if ( !hw_its ) @@ -681,10 +791,13 @@ int gicv3_its_map_guest_device(struct domain *d, ret = -ENOMEM; /* An Interrupt Translation Table needs to be 256-byte aligned. */ - itt_addr = _xzalloc(nr_events * hw_its->itte_size, 256); + order = get_order_from_bytes(max(nr_events * hw_its->itte_size, 256UL)); + itt_addr = alloc_xenheap_pages(order, gicv3_its_get_memflags()); if ( !itt_addr ) goto out_unlock; + memset(itt_addr, 0, PAGE_SIZE << order); + clean_and_invalidate_dcache_va_range(itt_addr, nr_events * hw_its->itte_size); @@ -718,6 +831,7 @@ int gicv3_its_map_guest_device(struct domain *d, goto out_unlock; dev->itt_addr = itt_addr; + dev->itt_order = order; dev->hw_its = hw_its; dev->guest_doorbell = guest_doorbell; dev->guest_devid = guest_devid; @@ -775,7 +889,8 @@ out: xfree(dev->pend_irqs); xfree(dev->host_lpi_blocks); } - xfree(itt_addr); + if ( itt_addr ) + free_xenheap_pages(itt_addr, order); xfree(dev); return ret; @@ -1089,6 +1204,8 @@ int gicv3_its_init(void) return ret; } + gicv3_its_validate_quirks(); + return 0; } diff --git a/xen/arch/arm/gic-v3-lpi.c b/xen/arch/arm/gic-v3-lpi.c index de4b0eb4a4..de5052e5cf 100644 --- a/xen/arch/arm/gic-v3-lpi.c +++ b/xen/arch/arm/gic-v3-lpi.c @@ -227,6 +227,7 @@ void gicv3_lpi_update_host_entry(uint32_t host_lpi, int domain_id, static int gicv3_lpi_allocate_pendtable(unsigned int cpu) { void *pendtable; + unsigned int order; if ( per_cpu(lpi_redist, cpu).pending_table ) return -EBUSY; @@ -237,14 +238,16 @@ static int gicv3_lpi_allocate_pendtable(unsigned int cpu) * The GICv3 imposes a 64KB alignment requirement, also requires * physically contiguous memory. */ - pendtable = _xzalloc(lpi_data.max_host_lpi_ids / 8, SZ_64K); + order = get_order_from_bytes(max(lpi_data.max_host_lpi_ids / 8, (unsigned long)SZ_64K)); + pendtable = alloc_xenheap_pages(order, gicv3_its_get_memflags()); if ( !pendtable ) return -ENOMEM; + memset(pendtable, 0, PAGE_SIZE << order); /* Make sure the physical address can be encoded in the register. */ if ( virt_to_maddr(pendtable) & ~GENMASK(51, 16) ) { - xfree(pendtable); + free_xenheap_pages(pendtable, order); return -ERANGE; } clean_and_invalidate_dcache_va_range(pendtable, @@ -272,9 +275,9 @@ static int gicv3_lpi_set_pendtable(void __iomem *rdist_base) ASSERT(!(virt_to_maddr(pendtable) & ~GENMASK(51, 16))); - val = GIC_BASER_CACHE_RaWaWb << GICR_PENDBASER_INNER_CACHEABILITY_SHIFT; + val = gicv3_its_get_cacheability() << GICR_PENDBASER_INNER_CACHEABILITY_SHIFT; val |= GIC_BASER_CACHE_SameAsInner << GICR_PENDBASER_OUTER_CACHEABILITY_SHIFT; - val |= GIC_BASER_InnerShareable << GICR_PENDBASER_SHAREABILITY_SHIFT; + val |= gicv3_its_get_shareability() << GICR_PENDBASER_SHAREABILITY_SHIFT; val |= GICR_PENDBASER_PTZ; val |= virt_to_maddr(pendtable); @@ -300,10 +303,11 @@ static int gicv3_lpi_set_pendtable(void __iomem *rdist_base) static int gicv3_lpi_set_proptable(void __iomem * rdist_base) { uint64_t reg; + unsigned int order; - reg = GIC_BASER_CACHE_RaWaWb << GICR_PROPBASER_INNER_CACHEABILITY_SHIFT; + reg = gicv3_its_get_cacheability() << GICR_PROPBASER_INNER_CACHEABILITY_SHIFT; reg |= GIC_BASER_CACHE_SameAsInner << GICR_PROPBASER_OUTER_CACHEABILITY_SHIFT; - reg |= GIC_BASER_InnerShareable << GICR_PROPBASER_SHAREABILITY_SHIFT; + reg |= gicv3_its_get_shareability() << GICR_PROPBASER_SHAREABILITY_SHIFT; /* * The property table is shared across all redistributors, so allocate @@ -312,7 +316,10 @@ static int gicv3_lpi_set_proptable(void __iomem * rdist_base) if ( !lpi_data.lpi_property ) { /* The property table holds one byte per LPI. */ - void *table = _xmalloc(lpi_data.max_host_lpi_ids, SZ_4K); + void *table; + + order = get_order_from_bytes(max(lpi_data.max_host_lpi_ids, (unsigned long)SZ_4K)); + table = alloc_xenheap_pages(order, gicv3_its_get_memflags()); if ( !table ) return -ENOMEM; @@ -320,7 +327,7 @@ static int gicv3_lpi_set_proptable(void __iomem * rdist_base) /* Make sure the physical address can be encoded in the register. */ if ( (virt_to_maddr(table) & ~GENMASK(51, 12)) ) { - xfree(table); + free_xenheap_pages(table, order); return -ERANGE; } memset(table, GIC_PRI_IRQ | LPI_PROP_RES1, MAX_NR_HOST_LPIS); diff --git a/xen/arch/arm/include/asm/gic_v3_its.h b/xen/arch/arm/include/asm/gic_v3_its.h index c24d4752d0..0737e67aa6 100644 --- a/xen/arch/arm/include/asm/gic_v3_its.h +++ b/xen/arch/arm/include/asm/gic_v3_its.h @@ -110,6 +110,9 @@ #define HOST_ITS_FLUSH_CMD_QUEUE (1U << 0) #define HOST_ITS_USES_PTA (1U << 1) +#define HOST_ITS_WORKAROUND_NC_NS (1U << 0) +#define HOST_ITS_WORKAROUND_32BIT_ADDR (1U << 1) + /* We allocate LPIs on the hosts in chunks of 32 to reduce handling overhead. */ #define LPI_BLOCK 32U @@ -197,6 +200,11 @@ struct pending_irq *gicv3_assign_guest_event(struct domain *d, void gicv3_lpi_update_host_entry(uint32_t host_lpi, int domain_id, uint32_t virt_lpi); +/* ITS quirks handling. */ +uint64_t gicv3_its_get_cacheability(void); +uint64_t gicv3_its_get_shareability(void); +unsigned int gicv3_its_get_memflags(void); + #else #ifdef CONFIG_ACPI