Current ITS driver implementation limits the device ID to a few
number of bits depending on memory that has been allocated to a
flat DEV-ITT table. Some of the devices are not usable when device
ID is spread out across a large range of 32 bit values.

This patch covers more DEVID bits by implementing the GIC-ITS indirect
device table. This feature is enabled automatically during driver
probe if the flat table is not adequate to hold all DEVID bits.

Signed-off-by: Shanker Donthineni <[email protected]>
---
[v1]->[v2]:
  -Removed the line "Change-Id: I9c6d005dexxx" from commit message.
  -Fixed the its->max_devid field initialization. 

 drivers/irqchip/irq-gic-v3-its.c   | 79 +++++++++++++++++++++++++++++++++++---
 include/linux/irqchip/arm-gic-v3.h |  1 +
 2 files changed, 75 insertions(+), 5 deletions(-)

diff --git a/drivers/irqchip/irq-gic-v3-its.c b/drivers/irqchip/irq-gic-v3-its.c
index e23d1d1..408a358 100644
--- a/drivers/irqchip/irq-gic-v3-its.c
+++ b/drivers/irqchip/irq-gic-v3-its.c
@@ -41,6 +41,8 @@
 
 #define ITS_FLAGS_CMDQ_NEEDS_FLUSHING          (1ULL << 0)
 #define ITS_FLAGS_WORKAROUND_CAVIUM_22375      (1ULL << 1)
+#define ITS_FLAGS_DEVICE_NEEDS_FLUSHING                (1ULL << 2)
+#define ITS_FLAGS_INDIRECT_DEVICE_TABLE                (1ULL << 3)
 
 #define RDIST_FLAGS_PROPBASE_NEEDS_FLUSHING    (1 << 0)
 
@@ -71,6 +73,10 @@ struct its_node {
        struct list_head        its_device_list;
        u64                     flags;
        u32                     ite_size;
+       u32                     dev_table_idx;
+       u32                     dev_table_shift;
+       u32                     max_devid;
+       u32                     order;
 };
 
 #define ITS_ITT_ALIGN          SZ_256
@@ -824,6 +830,8 @@ static int its_alloc_tables(const char *node_name, struct 
its_node *its)
        u64 typer;
        u32 ids;
 
+#define ITS_DEVICE_MAX_ORDER   min(MAX_ORDER, get_order(SZ_8M))
+
        if (its->flags & ITS_FLAGS_WORKAROUND_CAVIUM_22375) {
                /*
                 * erratum 22375: only alloc 8MB table size
@@ -867,11 +875,12 @@ static int its_alloc_tables(const char *node_name, struct 
its_node *its)
                         */
                        order = max(get_order((1UL << ids) * entry_size),
                                    order);
-                       if (order >= MAX_ORDER) {
-                               order = MAX_ORDER - 1;
-                               pr_warn("%s: Device Table too large, reduce its 
page order to %u\n",
-                                       node_name, order);
-                       }
+                       if (order >= ITS_DEVICE_MAX_ORDER) {
+                               /* Update flags for two-level setup */
+                               its->flags |= ITS_FLAGS_INDIRECT_DEVICE_TABLE;
+                               order = ITS_DEVICE_MAX_ORDER - 1;
+                       } else
+                               its->max_devid =  (1 << ids) - 1;
                }
 
                alloc_size = (1 << order) * PAGE_SIZE;
@@ -911,6 +920,26 @@ retry_baser:
                        break;
                }
 
+               /* Enable two-level (indirect) device table if it is required */
+               if ((type == GITS_BASER_TYPE_DEVICE) &&
+                   (its->flags & ITS_FLAGS_INDIRECT_DEVICE_TABLE)) {
+                       u32 shift = ilog2(psz / entry_size);
+                       u32 max_ids = ilog2(alloc_size >> 3) + shift;
+
+                       if (ids > max_ids) {
+                               pr_warn(
+                                       "ITS: @%pa DEVID too large reduce 
%u->%u\n",
+                                       &its->phys_base, ids, max_ids);
+                       }
+                       its->max_devid =  (1UL << max_ids) - 1;
+                       its->order = get_order(psz);
+                       its->dev_table_idx = i;
+                       its->dev_table_shift = shift;
+                       if (!(val & GITS_BASER_SHAREABILITY_MASK))
+                               its->flags |= ITS_FLAGS_DEVICE_NEEDS_FLUSHING;
+                       val |= GITS_BASER_INDIRECT;
+               }
+
                val |= alloc_pages - 1;
 
                writeq_relaxed(val, its->base + GITS_BASER + i * 8);
@@ -1134,6 +1163,32 @@ static struct its_device *its_find_device(struct 
its_node *its, u32 dev_id)
        return its_dev;
 }
 
+static int its_alloc_device_table(struct its_node *its, u32 dev_id)
+{
+       u64 *devtbl = its->tables[its->dev_table_idx];
+       u32 alloc_size = (1 << its->order) * PAGE_SIZE;
+       u32 idx = dev_id >> its->dev_table_shift;
+       struct page *page;
+
+       /* Do nothing if the level-2 DEV-ITT entry was mapped earlier */
+       if (devtbl[idx])
+               return 0;
+
+       /* Allocate memory for level-2 device table & map to level-1 table */
+       page = alloc_pages(GFP_KERNEL | __GFP_ZERO, its->order);
+       if (!page)
+               return -ENOMEM;
+
+       devtbl[idx] = page_to_phys(page) | GITS_BASER_VALID;
+
+       if (its->flags & ITS_FLAGS_DEVICE_NEEDS_FLUSHING) {
+               __flush_dcache_area(page_address(page), alloc_size);
+               __flush_dcache_area(devtbl + idx, sizeof(*devtbl));
+       }
+
+       return 0;
+}
+
 static struct its_device *its_create_device(struct its_node *its, u32 dev_id,
                                            int nvecs)
 {
@@ -1147,6 +1202,20 @@ static struct its_device *its_create_device(struct 
its_node *its, u32 dev_id,
        int nr_ites;
        int sz;
 
+       if (dev_id > its->max_devid) {
+               pr_err("ITS: dev_id too large %d\n", dev_id);
+               return NULL;
+       }
+
+       /* Ensure memory for level-2 table is allocated for dev_id */
+       if (its->flags & ITS_FLAGS_INDIRECT_DEVICE_TABLE) {
+               int ret;
+
+               ret = its_alloc_device_table(its, dev_id);
+               if (ret)
+                       return NULL;
+       }
+
        dev = kzalloc(sizeof(*dev), GFP_KERNEL);
        /*
         * At least one bit of EventID is being used, hence a minimum
diff --git a/include/linux/irqchip/arm-gic-v3.h 
b/include/linux/irqchip/arm-gic-v3.h
index d5d798b..04dfe09 100644
--- a/include/linux/irqchip/arm-gic-v3.h
+++ b/include/linux/irqchip/arm-gic-v3.h
@@ -206,6 +206,7 @@
 #define GITS_BASER_NR_REGS             8
 
 #define GITS_BASER_VALID               (1UL << 63)
+#define GITS_BASER_INDIRECT            (1UL << 62)
 #define GITS_BASER_nCnB                        (0UL << 59)
 #define GITS_BASER_nC                  (1UL << 59)
 #define GITS_BASER_RaWt                        (2UL << 59)
-- 
Qualcomm Technologies, Inc. on behalf
of the Qualcomm Innovation Center, Inc.  The Qualcomm Innovation Center, Inc. 
is a member of the Code Aurora Forum, a Linux Foundation Collaborative Project.

Reply via email to