@@ -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, (unsigned long)256));
+ 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;
}
@@ -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,7 +238,8 @@ 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;
@@ -272,9 +274,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 +302,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 +315,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 +326,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);
@@ -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