| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Copyright (C) 2024-2025 ARM Limited, All Rights Reserved. |
| */ |
| |
| #define pr_fmt(fmt) "GICv5 IRS: " fmt |
| |
| #include <linux/kmemleak.h> |
| #include <linux/log2.h> |
| #include <linux/of.h> |
| #include <linux/of_address.h> |
| |
| #include <linux/irqchip.h> |
| #include <linux/irqchip/arm-gic-v5.h> |
| |
| /* |
| * Hardcoded ID_BITS limit for systems supporting only a 1-level IST |
| * table. Systems supporting only a 1-level IST table aren't expected |
| * to require more than 2^12 LPIs. Tweak as required. |
| */ |
| #define LPI_ID_BITS_LINEAR 12 |
| |
| #define IRS_FLAGS_NON_COHERENT BIT(0) |
| |
| static DEFINE_PER_CPU_READ_MOSTLY(struct gicv5_irs_chip_data *, per_cpu_irs_data); |
| static LIST_HEAD(irs_nodes); |
| |
| static u32 irs_readl_relaxed(struct gicv5_irs_chip_data *irs_data, |
| const u32 reg_offset) |
| { |
| return readl_relaxed(irs_data->irs_base + reg_offset); |
| } |
| |
| static void irs_writel_relaxed(struct gicv5_irs_chip_data *irs_data, |
| const u32 val, const u32 reg_offset) |
| { |
| writel_relaxed(val, irs_data->irs_base + reg_offset); |
| } |
| |
| static u64 irs_readq_relaxed(struct gicv5_irs_chip_data *irs_data, |
| const u32 reg_offset) |
| { |
| return readq_relaxed(irs_data->irs_base + reg_offset); |
| } |
| |
| static void irs_writeq_relaxed(struct gicv5_irs_chip_data *irs_data, |
| const u64 val, const u32 reg_offset) |
| { |
| writeq_relaxed(val, irs_data->irs_base + reg_offset); |
| } |
| |
| /* |
| * The polling wait (in gicv5_wait_for_op_s_atomic()) on a GIC register |
| * provides the memory barriers (through MMIO accessors) |
| * required to synchronize CPU and GIC access to IST memory. |
| */ |
| static int gicv5_irs_ist_synchronise(struct gicv5_irs_chip_data *irs_data) |
| { |
| return gicv5_wait_for_op_atomic(irs_data->irs_base, GICV5_IRS_IST_STATUSR, |
| GICV5_IRS_IST_STATUSR_IDLE, NULL); |
| } |
| |
| static int __init gicv5_irs_init_ist_linear(struct gicv5_irs_chip_data *irs_data, |
| unsigned int lpi_id_bits, |
| unsigned int istsz) |
| { |
| size_t l2istsz; |
| u32 n, cfgr; |
| void *ist; |
| u64 baser; |
| int ret; |
| |
| /* Taken from GICv5 specifications 10.2.1.13 IRS_IST_BASER */ |
| n = max(5, lpi_id_bits + 1 + istsz); |
| |
| l2istsz = BIT(n + 1); |
| /* |
| * Check memory requirements. For a linear IST we cap the |
| * number of ID bits to a value that should never exceed |
| * kmalloc interface memory allocation limits, so this |
| * check is really belt and braces. |
| */ |
| if (l2istsz > KMALLOC_MAX_SIZE) { |
| u8 lpi_id_cap = ilog2(KMALLOC_MAX_SIZE) - 2 + istsz; |
| |
| pr_warn("Limiting LPI ID bits from %u to %u\n", |
| lpi_id_bits, lpi_id_cap); |
| lpi_id_bits = lpi_id_cap; |
| l2istsz = KMALLOC_MAX_SIZE; |
| } |
| |
| ist = kzalloc(l2istsz, GFP_KERNEL); |
| if (!ist) |
| return -ENOMEM; |
| |
| if (irs_data->flags & IRS_FLAGS_NON_COHERENT) |
| dcache_clean_inval_poc((unsigned long)ist, |
| (unsigned long)ist + l2istsz); |
| else |
| dsb(ishst); |
| |
| cfgr = FIELD_PREP(GICV5_IRS_IST_CFGR_STRUCTURE, |
| GICV5_IRS_IST_CFGR_STRUCTURE_LINEAR) | |
| FIELD_PREP(GICV5_IRS_IST_CFGR_ISTSZ, istsz) | |
| FIELD_PREP(GICV5_IRS_IST_CFGR_L2SZ, |
| GICV5_IRS_IST_CFGR_L2SZ_4K) | |
| FIELD_PREP(GICV5_IRS_IST_CFGR_LPI_ID_BITS, lpi_id_bits); |
| irs_writel_relaxed(irs_data, cfgr, GICV5_IRS_IST_CFGR); |
| |
| gicv5_global_data.ist.l2 = false; |
| |
| baser = (virt_to_phys(ist) & GICV5_IRS_IST_BASER_ADDR_MASK) | |
| FIELD_PREP(GICV5_IRS_IST_BASER_VALID, 0x1); |
| irs_writeq_relaxed(irs_data, baser, GICV5_IRS_IST_BASER); |
| |
| ret = gicv5_irs_ist_synchronise(irs_data); |
| if (ret) { |
| kfree(ist); |
| return ret; |
| } |
| kmemleak_ignore(ist); |
| |
| return 0; |
| } |
| |
| static int __init gicv5_irs_init_ist_two_level(struct gicv5_irs_chip_data *irs_data, |
| unsigned int lpi_id_bits, |
| unsigned int istsz, |
| unsigned int l2sz) |
| { |
| __le64 *l1ist; |
| u32 cfgr, n; |
| size_t l1sz; |
| u64 baser; |
| int ret; |
| |
| /* Taken from GICv5 specifications 10.2.1.13 IRS_IST_BASER */ |
| n = max(5, lpi_id_bits - ((10 - istsz) + (2 * l2sz)) + 2); |
| |
| l1sz = BIT(n + 1); |
| |
| l1ist = kzalloc(l1sz, GFP_KERNEL); |
| if (!l1ist) |
| return -ENOMEM; |
| |
| if (irs_data->flags & IRS_FLAGS_NON_COHERENT) |
| dcache_clean_inval_poc((unsigned long)l1ist, |
| (unsigned long)l1ist + l1sz); |
| else |
| dsb(ishst); |
| |
| cfgr = FIELD_PREP(GICV5_IRS_IST_CFGR_STRUCTURE, |
| GICV5_IRS_IST_CFGR_STRUCTURE_TWO_LEVEL) | |
| FIELD_PREP(GICV5_IRS_IST_CFGR_ISTSZ, istsz) | |
| FIELD_PREP(GICV5_IRS_IST_CFGR_L2SZ, l2sz) | |
| FIELD_PREP(GICV5_IRS_IST_CFGR_LPI_ID_BITS, lpi_id_bits); |
| irs_writel_relaxed(irs_data, cfgr, GICV5_IRS_IST_CFGR); |
| |
| /* |
| * The L2SZ determine bits required at L2 level. Number of bytes |
| * required by metadata is reported through istsz - the number of bits |
| * covered by L2 entries scales accordingly. |
| */ |
| gicv5_global_data.ist.l2_size = BIT(11 + (2 * l2sz) + 1); |
| gicv5_global_data.ist.l2_bits = (10 - istsz) + (2 * l2sz); |
| gicv5_global_data.ist.l1ist_addr = l1ist; |
| gicv5_global_data.ist.l2 = true; |
| |
| baser = (virt_to_phys(l1ist) & GICV5_IRS_IST_BASER_ADDR_MASK) | |
| FIELD_PREP(GICV5_IRS_IST_BASER_VALID, 0x1); |
| irs_writeq_relaxed(irs_data, baser, GICV5_IRS_IST_BASER); |
| |
| ret = gicv5_irs_ist_synchronise(irs_data); |
| if (ret) { |
| kfree(l1ist); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * Alloc L2 IST entries on demand. |
| * |
| * Locking/serialization is guaranteed by irqdomain core code by |
| * taking the hierarchical domain struct irq_domain.root->mutex. |
| */ |
| int gicv5_irs_iste_alloc(const u32 lpi) |
| { |
| struct gicv5_irs_chip_data *irs_data; |
| unsigned int index; |
| u32 l2istr, l2bits; |
| __le64 *l1ist; |
| size_t l2size; |
| void *l2ist; |
| int ret; |
| |
| if (!gicv5_global_data.ist.l2) |
| return 0; |
| |
| irs_data = per_cpu(per_cpu_irs_data, smp_processor_id()); |
| if (!irs_data) |
| return -ENOENT; |
| |
| l2size = gicv5_global_data.ist.l2_size; |
| l2bits = gicv5_global_data.ist.l2_bits; |
| l1ist = gicv5_global_data.ist.l1ist_addr; |
| index = lpi >> l2bits; |
| |
| if (FIELD_GET(GICV5_ISTL1E_VALID, le64_to_cpu(l1ist[index]))) |
| return 0; |
| |
| l2ist = kzalloc(l2size, GFP_KERNEL); |
| if (!l2ist) |
| return -ENOMEM; |
| |
| l1ist[index] = cpu_to_le64(virt_to_phys(l2ist) & GICV5_ISTL1E_L2_ADDR_MASK); |
| |
| if (irs_data->flags & IRS_FLAGS_NON_COHERENT) { |
| dcache_clean_inval_poc((unsigned long)l2ist, |
| (unsigned long)l2ist + l2size); |
| dcache_clean_poc((unsigned long)(l1ist + index), |
| (unsigned long)(l1ist + index) + sizeof(*l1ist)); |
| } else { |
| dsb(ishst); |
| } |
| |
| l2istr = FIELD_PREP(GICV5_IRS_MAP_L2_ISTR_ID, lpi); |
| irs_writel_relaxed(irs_data, l2istr, GICV5_IRS_MAP_L2_ISTR); |
| |
| ret = gicv5_irs_ist_synchronise(irs_data); |
| if (ret) { |
| l1ist[index] = 0; |
| kfree(l2ist); |
| return ret; |
| } |
| kmemleak_ignore(l2ist); |
| |
| /* |
| * Make sure we invalidate the cache line pulled before the IRS |
| * had a chance to update the L1 entry and mark it valid. |
| */ |
| if (irs_data->flags & IRS_FLAGS_NON_COHERENT) { |
| /* |
| * gicv5_irs_ist_synchronise() includes memory |
| * barriers (MMIO accessors) required to guarantee that the |
| * following dcache invalidation is not executed before the |
| * IST mapping operation has completed. |
| */ |
| dcache_inval_poc((unsigned long)(l1ist + index), |
| (unsigned long)(l1ist + index) + sizeof(*l1ist)); |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * Try to match the L2 IST size to the pagesize, and if this is not possible |
| * pick the smallest supported L2 size in order to minimise the requirement for |
| * physically contiguous blocks of memory as page-sized allocations are |
| * guaranteed to be physically contiguous, and are by definition the easiest to |
| * find. |
| * |
| * Fall back to the smallest supported size (in the event that the pagesize |
| * itself is not supported) again serves to make it easier to find physically |
| * contiguous blocks of memory. |
| */ |
| static unsigned int gicv5_irs_l2_sz(u32 idr2) |
| { |
| switch (PAGE_SIZE) { |
| case SZ_64K: |
| if (GICV5_IRS_IST_L2SZ_SUPPORT_64KB(idr2)) |
| return GICV5_IRS_IST_CFGR_L2SZ_64K; |
| fallthrough; |
| case SZ_4K: |
| if (GICV5_IRS_IST_L2SZ_SUPPORT_4KB(idr2)) |
| return GICV5_IRS_IST_CFGR_L2SZ_4K; |
| fallthrough; |
| case SZ_16K: |
| if (GICV5_IRS_IST_L2SZ_SUPPORT_16KB(idr2)) |
| return GICV5_IRS_IST_CFGR_L2SZ_16K; |
| break; |
| } |
| |
| if (GICV5_IRS_IST_L2SZ_SUPPORT_4KB(idr2)) |
| return GICV5_IRS_IST_CFGR_L2SZ_4K; |
| |
| return GICV5_IRS_IST_CFGR_L2SZ_64K; |
| } |
| |
| static int __init gicv5_irs_init_ist(struct gicv5_irs_chip_data *irs_data) |
| { |
| u32 lpi_id_bits, idr2_id_bits, idr2_min_lpi_id_bits, l2_iste_sz, l2sz; |
| u32 l2_iste_sz_split, idr2; |
| bool two_levels, istmd; |
| u64 baser; |
| int ret; |
| |
| baser = irs_readq_relaxed(irs_data, GICV5_IRS_IST_BASER); |
| if (FIELD_GET(GICV5_IRS_IST_BASER_VALID, baser)) { |
| pr_err("IST is marked as valid already; cannot allocate\n"); |
| return -EPERM; |
| } |
| |
| idr2 = irs_readl_relaxed(irs_data, GICV5_IRS_IDR2); |
| two_levels = !!FIELD_GET(GICV5_IRS_IDR2_IST_LEVELS, idr2); |
| |
| idr2_id_bits = FIELD_GET(GICV5_IRS_IDR2_ID_BITS, idr2); |
| idr2_min_lpi_id_bits = FIELD_GET(GICV5_IRS_IDR2_MIN_LPI_ID_BITS, idr2); |
| |
| /* |
| * For two level tables we are always supporting the maximum allowed |
| * number of IDs. |
| * |
| * For 1-level tables, we should support a number of bits that |
| * is >= min_lpi_id_bits but cap it to LPI_ID_BITS_LINEAR lest |
| * the level 1-table gets too large and its memory allocation |
| * may fail. |
| */ |
| if (two_levels) { |
| lpi_id_bits = idr2_id_bits; |
| } else { |
| lpi_id_bits = max(LPI_ID_BITS_LINEAR, idr2_min_lpi_id_bits); |
| lpi_id_bits = min(lpi_id_bits, idr2_id_bits); |
| } |
| |
| /* |
| * Cap the ID bits according to the CPUIF supported ID bits |
| */ |
| lpi_id_bits = min(lpi_id_bits, gicv5_global_data.cpuif_id_bits); |
| |
| if (two_levels) |
| l2sz = gicv5_irs_l2_sz(idr2); |
| |
| istmd = !!FIELD_GET(GICV5_IRS_IDR2_ISTMD, idr2); |
| |
| l2_iste_sz = GICV5_IRS_IST_CFGR_ISTSZ_4; |
| |
| if (istmd) { |
| l2_iste_sz_split = FIELD_GET(GICV5_IRS_IDR2_ISTMD_SZ, idr2); |
| |
| if (lpi_id_bits < l2_iste_sz_split) |
| l2_iste_sz = GICV5_IRS_IST_CFGR_ISTSZ_8; |
| else |
| l2_iste_sz = GICV5_IRS_IST_CFGR_ISTSZ_16; |
| } |
| |
| /* |
| * Follow GICv5 specification recommendation to opt in for two |
| * level tables (ref: 10.2.1.14 IRS_IST_CFGR). |
| */ |
| if (two_levels && (lpi_id_bits > ((10 - l2_iste_sz) + (2 * l2sz)))) { |
| ret = gicv5_irs_init_ist_two_level(irs_data, lpi_id_bits, |
| l2_iste_sz, l2sz); |
| } else { |
| ret = gicv5_irs_init_ist_linear(irs_data, lpi_id_bits, |
| l2_iste_sz); |
| } |
| if (ret) |
| return ret; |
| |
| gicv5_init_lpis(BIT(lpi_id_bits)); |
| |
| return 0; |
| } |
| |
| struct iaffid_entry { |
| u16 iaffid; |
| bool valid; |
| }; |
| |
| static DEFINE_PER_CPU(struct iaffid_entry, cpu_iaffid); |
| |
| int gicv5_irs_cpu_to_iaffid(int cpuid, u16 *iaffid) |
| { |
| if (!per_cpu(cpu_iaffid, cpuid).valid) { |
| pr_err("IAFFID for CPU %d has not been initialised\n", cpuid); |
| return -ENODEV; |
| } |
| |
| *iaffid = per_cpu(cpu_iaffid, cpuid).iaffid; |
| |
| return 0; |
| } |
| |
| struct gicv5_irs_chip_data *gicv5_irs_lookup_by_spi_id(u32 spi_id) |
| { |
| struct gicv5_irs_chip_data *irs_data; |
| u32 min, max; |
| |
| list_for_each_entry(irs_data, &irs_nodes, entry) { |
| if (!irs_data->spi_range) |
| continue; |
| |
| min = irs_data->spi_min; |
| max = irs_data->spi_min + irs_data->spi_range - 1; |
| if (spi_id >= min && spi_id <= max) |
| return irs_data; |
| } |
| |
| return NULL; |
| } |
| |
| static int gicv5_irs_wait_for_spi_op(struct gicv5_irs_chip_data *irs_data) |
| { |
| u32 statusr; |
| int ret; |
| |
| ret = gicv5_wait_for_op_atomic(irs_data->irs_base, GICV5_IRS_SPI_STATUSR, |
| GICV5_IRS_SPI_STATUSR_IDLE, &statusr); |
| if (ret) |
| return ret; |
| |
| return !!FIELD_GET(GICV5_IRS_SPI_STATUSR_V, statusr) ? 0 : -EIO; |
| } |
| |
| static int gicv5_irs_wait_for_irs_pe(struct gicv5_irs_chip_data *irs_data, |
| bool selr) |
| { |
| bool valid = true; |
| u32 statusr; |
| int ret; |
| |
| ret = gicv5_wait_for_op_atomic(irs_data->irs_base, GICV5_IRS_PE_STATUSR, |
| GICV5_IRS_PE_STATUSR_IDLE, &statusr); |
| if (ret) |
| return ret; |
| |
| if (selr) |
| valid = !!FIELD_GET(GICV5_IRS_PE_STATUSR_V, statusr); |
| |
| return valid ? 0 : -EIO; |
| } |
| |
| static int gicv5_irs_wait_for_pe_selr(struct gicv5_irs_chip_data *irs_data) |
| { |
| return gicv5_irs_wait_for_irs_pe(irs_data, true); |
| } |
| |
| static int gicv5_irs_wait_for_pe_cr0(struct gicv5_irs_chip_data *irs_data) |
| { |
| return gicv5_irs_wait_for_irs_pe(irs_data, false); |
| } |
| |
| int gicv5_spi_irq_set_type(struct irq_data *d, unsigned int type) |
| { |
| struct gicv5_irs_chip_data *irs_data = d->chip_data; |
| u32 selr, cfgr; |
| bool level; |
| int ret; |
| |
| /* |
| * There is no distinction between HIGH/LOW for level IRQs |
| * and RISING/FALLING for edge IRQs in the architecture, |
| * hence consider them equivalent. |
| */ |
| switch (type) { |
| case IRQ_TYPE_EDGE_RISING: |
| case IRQ_TYPE_EDGE_FALLING: |
| level = false; |
| break; |
| case IRQ_TYPE_LEVEL_HIGH: |
| case IRQ_TYPE_LEVEL_LOW: |
| level = true; |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| guard(raw_spinlock)(&irs_data->spi_config_lock); |
| |
| selr = FIELD_PREP(GICV5_IRS_SPI_SELR_ID, d->hwirq); |
| irs_writel_relaxed(irs_data, selr, GICV5_IRS_SPI_SELR); |
| ret = gicv5_irs_wait_for_spi_op(irs_data); |
| if (ret) |
| return ret; |
| |
| cfgr = FIELD_PREP(GICV5_IRS_SPI_CFGR_TM, level); |
| irs_writel_relaxed(irs_data, cfgr, GICV5_IRS_SPI_CFGR); |
| |
| return gicv5_irs_wait_for_spi_op(irs_data); |
| } |
| |
| static int gicv5_irs_wait_for_idle(struct gicv5_irs_chip_data *irs_data) |
| { |
| return gicv5_wait_for_op_atomic(irs_data->irs_base, GICV5_IRS_CR0, |
| GICV5_IRS_CR0_IDLE, NULL); |
| } |
| |
| void gicv5_irs_syncr(void) |
| { |
| struct gicv5_irs_chip_data *irs_data; |
| u32 syncr; |
| |
| irs_data = list_first_entry_or_null(&irs_nodes, struct gicv5_irs_chip_data, entry); |
| if (WARN_ON_ONCE(!irs_data)) |
| return; |
| |
| syncr = FIELD_PREP(GICV5_IRS_SYNCR_SYNC, 1); |
| irs_writel_relaxed(irs_data, syncr, GICV5_IRS_SYNCR); |
| |
| gicv5_wait_for_op(irs_data->irs_base, GICV5_IRS_SYNC_STATUSR, |
| GICV5_IRS_SYNC_STATUSR_IDLE); |
| } |
| |
| int gicv5_irs_register_cpu(int cpuid) |
| { |
| struct gicv5_irs_chip_data *irs_data; |
| u32 selr, cr0; |
| u16 iaffid; |
| int ret; |
| |
| ret = gicv5_irs_cpu_to_iaffid(cpuid, &iaffid); |
| if (ret) { |
| pr_err("IAFFID for CPU %d has not been initialised\n", cpuid); |
| return ret; |
| } |
| |
| irs_data = per_cpu(per_cpu_irs_data, cpuid); |
| if (!irs_data) { |
| pr_err("No IRS associated with CPU %u\n", cpuid); |
| return -ENXIO; |
| } |
| |
| selr = FIELD_PREP(GICV5_IRS_PE_SELR_IAFFID, iaffid); |
| irs_writel_relaxed(irs_data, selr, GICV5_IRS_PE_SELR); |
| |
| ret = gicv5_irs_wait_for_pe_selr(irs_data); |
| if (ret) { |
| pr_err("IAFFID 0x%x used in IRS_PE_SELR is invalid\n", iaffid); |
| return -ENXIO; |
| } |
| |
| cr0 = FIELD_PREP(GICV5_IRS_PE_CR0_DPS, 0x1); |
| irs_writel_relaxed(irs_data, cr0, GICV5_IRS_PE_CR0); |
| |
| ret = gicv5_irs_wait_for_pe_cr0(irs_data); |
| if (ret) |
| return ret; |
| |
| pr_debug("CPU %d enabled PE IAFFID 0x%x\n", cpuid, iaffid); |
| |
| return 0; |
| } |
| |
| static void __init gicv5_irs_init_bases(struct gicv5_irs_chip_data *irs_data, |
| void __iomem *irs_base, |
| struct fwnode_handle *handle) |
| { |
| struct device_node *np = to_of_node(handle); |
| u32 cr0, cr1; |
| |
| irs_data->fwnode = handle; |
| irs_data->irs_base = irs_base; |
| |
| if (of_property_read_bool(np, "dma-noncoherent")) { |
| /* |
| * A non-coherent IRS implies that some cache levels cannot be |
| * used coherently by the cores and GIC. Our only option is to mark |
| * memory attributes for the GIC as non-cacheable; by default, |
| * non-cacheable memory attributes imply outer-shareable |
| * shareability, the value written into IRS_CR1_SH is ignored. |
| */ |
| cr1 = FIELD_PREP(GICV5_IRS_CR1_VPED_WA, GICV5_NO_WRITE_ALLOC) | |
| FIELD_PREP(GICV5_IRS_CR1_VPED_RA, GICV5_NO_READ_ALLOC) | |
| FIELD_PREP(GICV5_IRS_CR1_VMD_WA, GICV5_NO_WRITE_ALLOC) | |
| FIELD_PREP(GICV5_IRS_CR1_VMD_RA, GICV5_NO_READ_ALLOC) | |
| FIELD_PREP(GICV5_IRS_CR1_VPET_RA, GICV5_NO_READ_ALLOC) | |
| FIELD_PREP(GICV5_IRS_CR1_VMT_RA, GICV5_NO_READ_ALLOC) | |
| FIELD_PREP(GICV5_IRS_CR1_IST_WA, GICV5_NO_WRITE_ALLOC) | |
| FIELD_PREP(GICV5_IRS_CR1_IST_RA, GICV5_NO_READ_ALLOC) | |
| FIELD_PREP(GICV5_IRS_CR1_IC, GICV5_NON_CACHE) | |
| FIELD_PREP(GICV5_IRS_CR1_OC, GICV5_NON_CACHE); |
| irs_data->flags |= IRS_FLAGS_NON_COHERENT; |
| } else { |
| cr1 = FIELD_PREP(GICV5_IRS_CR1_VPED_WA, GICV5_WRITE_ALLOC) | |
| FIELD_PREP(GICV5_IRS_CR1_VPED_RA, GICV5_READ_ALLOC) | |
| FIELD_PREP(GICV5_IRS_CR1_VMD_WA, GICV5_WRITE_ALLOC) | |
| FIELD_PREP(GICV5_IRS_CR1_VMD_RA, GICV5_READ_ALLOC) | |
| FIELD_PREP(GICV5_IRS_CR1_VPET_RA, GICV5_READ_ALLOC) | |
| FIELD_PREP(GICV5_IRS_CR1_VMT_RA, GICV5_READ_ALLOC) | |
| FIELD_PREP(GICV5_IRS_CR1_IST_WA, GICV5_WRITE_ALLOC) | |
| FIELD_PREP(GICV5_IRS_CR1_IST_RA, GICV5_READ_ALLOC) | |
| FIELD_PREP(GICV5_IRS_CR1_IC, GICV5_WB_CACHE) | |
| FIELD_PREP(GICV5_IRS_CR1_OC, GICV5_WB_CACHE) | |
| FIELD_PREP(GICV5_IRS_CR1_SH, GICV5_INNER_SHARE); |
| } |
| |
| irs_writel_relaxed(irs_data, cr1, GICV5_IRS_CR1); |
| |
| cr0 = FIELD_PREP(GICV5_IRS_CR0_IRSEN, 0x1); |
| irs_writel_relaxed(irs_data, cr0, GICV5_IRS_CR0); |
| gicv5_irs_wait_for_idle(irs_data); |
| } |
| |
| static int __init gicv5_irs_of_init_affinity(struct device_node *node, |
| struct gicv5_irs_chip_data *irs_data, |
| u8 iaffid_bits) |
| { |
| /* |
| * Detect IAFFID<->CPU mappings from the device tree and |
| * record IRS<->CPU topology information. |
| */ |
| u16 iaffid_mask = GENMASK(iaffid_bits - 1, 0); |
| int ret, i, ncpus, niaffids; |
| |
| ncpus = of_count_phandle_with_args(node, "cpus", NULL); |
| if (ncpus < 0) |
| return -EINVAL; |
| |
| niaffids = of_property_count_elems_of_size(node, "arm,iaffids", |
| sizeof(u16)); |
| if (niaffids != ncpus) |
| return -EINVAL; |
| |
| u16 *iaffids __free(kfree) = kcalloc(niaffids, sizeof(*iaffids), GFP_KERNEL); |
| if (!iaffids) |
| return -ENOMEM; |
| |
| ret = of_property_read_u16_array(node, "arm,iaffids", iaffids, niaffids); |
| if (ret) |
| return ret; |
| |
| for (i = 0; i < ncpus; i++) { |
| struct device_node *cpu_node; |
| int cpu; |
| |
| cpu_node = of_parse_phandle(node, "cpus", i); |
| if (!cpu_node) { |
| pr_warn(FW_BUG "Erroneous CPU node phandle\n"); |
| continue; |
| } |
| |
| cpu = of_cpu_node_to_id(cpu_node); |
| of_node_put(cpu_node); |
| if (cpu < 0) |
| continue; |
| |
| if (iaffids[i] & ~iaffid_mask) { |
| pr_warn("CPU %d iaffid 0x%x exceeds IRS iaffid bits\n", |
| cpu, iaffids[i]); |
| continue; |
| } |
| |
| per_cpu(cpu_iaffid, cpu).iaffid = iaffids[i]; |
| per_cpu(cpu_iaffid, cpu).valid = true; |
| |
| /* We also know that the CPU is connected to this IRS */ |
| per_cpu(per_cpu_irs_data, cpu) = irs_data; |
| } |
| |
| return ret; |
| } |
| |
| static void irs_setup_pri_bits(u32 idr1) |
| { |
| switch (FIELD_GET(GICV5_IRS_IDR1_PRIORITY_BITS, idr1)) { |
| case GICV5_IRS_IDR1_PRIORITY_BITS_1BITS: |
| gicv5_global_data.irs_pri_bits = 1; |
| break; |
| case GICV5_IRS_IDR1_PRIORITY_BITS_2BITS: |
| gicv5_global_data.irs_pri_bits = 2; |
| break; |
| case GICV5_IRS_IDR1_PRIORITY_BITS_3BITS: |
| gicv5_global_data.irs_pri_bits = 3; |
| break; |
| case GICV5_IRS_IDR1_PRIORITY_BITS_4BITS: |
| gicv5_global_data.irs_pri_bits = 4; |
| break; |
| case GICV5_IRS_IDR1_PRIORITY_BITS_5BITS: |
| gicv5_global_data.irs_pri_bits = 5; |
| break; |
| default: |
| pr_warn("Detected wrong IDR priority bits value 0x%lx\n", |
| FIELD_GET(GICV5_IRS_IDR1_PRIORITY_BITS, idr1)); |
| gicv5_global_data.irs_pri_bits = 1; |
| break; |
| } |
| } |
| |
| static int __init gicv5_irs_init(struct device_node *node) |
| { |
| struct gicv5_irs_chip_data *irs_data; |
| void __iomem *irs_base; |
| u32 idr, spi_count; |
| u8 iaffid_bits; |
| int ret; |
| |
| irs_data = kzalloc(sizeof(*irs_data), GFP_KERNEL); |
| if (!irs_data) |
| return -ENOMEM; |
| |
| raw_spin_lock_init(&irs_data->spi_config_lock); |
| |
| ret = of_property_match_string(node, "reg-names", "ns-config"); |
| if (ret < 0) { |
| pr_err("%pOF: ns-config reg-name not present\n", node); |
| goto out_err; |
| } |
| |
| irs_base = of_io_request_and_map(node, ret, of_node_full_name(node)); |
| if (IS_ERR(irs_base)) { |
| pr_err("%pOF: unable to map GICv5 IRS registers\n", node); |
| ret = PTR_ERR(irs_base); |
| goto out_err; |
| } |
| |
| gicv5_irs_init_bases(irs_data, irs_base, &node->fwnode); |
| |
| idr = irs_readl_relaxed(irs_data, GICV5_IRS_IDR1); |
| iaffid_bits = FIELD_GET(GICV5_IRS_IDR1_IAFFID_BITS, idr) + 1; |
| |
| ret = gicv5_irs_of_init_affinity(node, irs_data, iaffid_bits); |
| if (ret) { |
| pr_err("Failed to parse CPU IAFFIDs from the device tree!\n"); |
| goto out_iomem; |
| } |
| |
| idr = irs_readl_relaxed(irs_data, GICV5_IRS_IDR2); |
| if (WARN(!FIELD_GET(GICV5_IRS_IDR2_LPI, idr), |
| "LPI support not available - no IPIs, can't proceed\n")) { |
| ret = -ENODEV; |
| goto out_iomem; |
| } |
| |
| idr = irs_readl_relaxed(irs_data, GICV5_IRS_IDR7); |
| irs_data->spi_min = FIELD_GET(GICV5_IRS_IDR7_SPI_BASE, idr); |
| |
| idr = irs_readl_relaxed(irs_data, GICV5_IRS_IDR6); |
| irs_data->spi_range = FIELD_GET(GICV5_IRS_IDR6_SPI_IRS_RANGE, idr); |
| |
| if (irs_data->spi_range) { |
| pr_info("%s detected SPI range [%u-%u]\n", |
| of_node_full_name(node), |
| irs_data->spi_min, |
| irs_data->spi_min + |
| irs_data->spi_range - 1); |
| } |
| |
| /* |
| * Do the global setting only on the first IRS. |
| * Global properties (iaffid_bits, global spi count) are guaranteed to |
| * be consistent across IRSes by the architecture. |
| */ |
| if (list_empty(&irs_nodes)) { |
| |
| idr = irs_readl_relaxed(irs_data, GICV5_IRS_IDR1); |
| irs_setup_pri_bits(idr); |
| |
| idr = irs_readl_relaxed(irs_data, GICV5_IRS_IDR5); |
| |
| spi_count = FIELD_GET(GICV5_IRS_IDR5_SPI_RANGE, idr); |
| gicv5_global_data.global_spi_count = spi_count; |
| |
| gicv5_init_lpi_domain(); |
| |
| pr_debug("Detected %u SPIs globally\n", spi_count); |
| } |
| |
| list_add_tail(&irs_data->entry, &irs_nodes); |
| |
| return 0; |
| |
| out_iomem: |
| iounmap(irs_base); |
| out_err: |
| kfree(irs_data); |
| return ret; |
| } |
| |
| void __init gicv5_irs_remove(void) |
| { |
| struct gicv5_irs_chip_data *irs_data, *tmp_data; |
| |
| gicv5_free_lpi_domain(); |
| gicv5_deinit_lpis(); |
| |
| list_for_each_entry_safe(irs_data, tmp_data, &irs_nodes, entry) { |
| iounmap(irs_data->irs_base); |
| list_del(&irs_data->entry); |
| kfree(irs_data); |
| } |
| } |
| |
| int __init gicv5_irs_enable(void) |
| { |
| struct gicv5_irs_chip_data *irs_data; |
| int ret; |
| |
| irs_data = list_first_entry_or_null(&irs_nodes, |
| struct gicv5_irs_chip_data, entry); |
| if (!irs_data) |
| return -ENODEV; |
| |
| ret = gicv5_irs_init_ist(irs_data); |
| if (ret) { |
| pr_err("Failed to init IST\n"); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| void __init gicv5_irs_its_probe(void) |
| { |
| struct gicv5_irs_chip_data *irs_data; |
| |
| list_for_each_entry(irs_data, &irs_nodes, entry) |
| gicv5_its_of_probe(to_of_node(irs_data->fwnode)); |
| } |
| |
| int __init gicv5_irs_of_probe(struct device_node *parent) |
| { |
| struct device_node *np; |
| int ret; |
| |
| for_each_available_child_of_node(parent, np) { |
| if (!of_device_is_compatible(np, "arm,gic-v5-irs")) |
| continue; |
| |
| ret = gicv5_irs_init(np); |
| if (ret) |
| pr_err("Failed to init IRS %s\n", np->full_name); |
| } |
| |
| return list_empty(&irs_nodes) ? -ENODEV : 0; |
| } |