irqchip/gic-v3: Add INTID range and convertion primitives

In the beginning, life was simple. The GIC driver mostly cared about
PPIs, SPIs and LPIs, all with nicely layed out ranges.

We're about to change all that, with new ranges such as EPPI and ESPI
interleaved in the middle of the no-irq-land between the "special IDs"
and the LPI range. Boo.

In order to make our life less hellish, let's introduce a set of primitives
that will allow ranges to be identified easily and offsets to be remapped.

So far, there is no functionnal change.

Signed-off-by: Marc Zyngier <maz@kernel.org>
This commit is contained in:
Marc Zyngier 2019-07-16 14:41:40 +01:00
parent 13d22e2e1f
commit e91b036e1c
1 changed files with 83 additions and 29 deletions

View File

@ -97,6 +97,32 @@ static DEFINE_PER_CPU(bool, has_rss);
/* Our default, arbitrary priority value. Linux only uses one anyway. */
#define DEFAULT_PMR_VALUE 0xf0
enum gic_intid_range {
PPI_RANGE,
SPI_RANGE,
LPI_RANGE,
__INVALID_RANGE__
};
static enum gic_intid_range __get_intid_range(irq_hw_number_t hwirq)
{
switch (hwirq) {
case 16 ... 31:
return PPI_RANGE;
case 32 ... 1019:
return SPI_RANGE;
case 8192 ... GENMASK(23, 0):
return LPI_RANGE;
default:
return __INVALID_RANGE__;
}
}
static enum gic_intid_range get_intid_range(struct irq_data *d)
{
return __get_intid_range(d->hwirq);
}
static inline unsigned int gic_irq(struct irq_data *d)
{
return d->hwirq;
@ -104,18 +130,23 @@ static inline unsigned int gic_irq(struct irq_data *d)
static inline int gic_irq_in_rdist(struct irq_data *d)
{
return gic_irq(d) < 32;
return get_intid_range(d) == PPI_RANGE;
}
static inline void __iomem *gic_dist_base(struct irq_data *d)
{
if (gic_irq_in_rdist(d)) /* SGI+PPI -> SGI_base for this CPU */
switch (get_intid_range(d)) {
case PPI_RANGE:
/* SGI+PPI -> SGI_base for this CPU */
return gic_data_rdist_sgi_base();
if (d->hwirq <= 1023) /* SPI -> dist_base */
case SPI_RANGE:
/* SPI -> dist_base */
return gic_data.dist_base;
return NULL;
default:
return NULL;
}
}
static void gic_do_wait_for_rwp(void __iomem *base)
@ -196,24 +227,46 @@ static void gic_enable_redist(bool enable)
/*
* Routines to disable, enable, EOI and route interrupts
*/
static u32 convert_offset_index(struct irq_data *d, u32 offset, u32 *index)
{
switch (get_intid_range(d)) {
case PPI_RANGE:
case SPI_RANGE:
*index = d->hwirq;
return offset;
default:
break;
}
WARN_ON(1);
*index = d->hwirq;
return offset;
}
static int gic_peek_irq(struct irq_data *d, u32 offset)
{
u32 mask = 1 << (gic_irq(d) % 32);
void __iomem *base;
u32 index, mask;
offset = convert_offset_index(d, offset, &index);
mask = 1 << (index % 32);
if (gic_irq_in_rdist(d))
base = gic_data_rdist_sgi_base();
else
base = gic_data.dist_base;
return !!(readl_relaxed(base + offset + (gic_irq(d) / 32) * 4) & mask);
return !!(readl_relaxed(base + offset + (index / 32) * 4) & mask);
}
static void gic_poke_irq(struct irq_data *d, u32 offset)
{
u32 mask = 1 << (gic_irq(d) % 32);
void (*rwp_wait)(void);
void __iomem *base;
u32 index, mask;
offset = convert_offset_index(d, offset, &index);
mask = 1 << (index % 32);
if (gic_irq_in_rdist(d)) {
base = gic_data_rdist_sgi_base();
@ -223,7 +276,7 @@ static void gic_poke_irq(struct irq_data *d, u32 offset)
rwp_wait = gic_dist_wait_for_rwp;
}
writel_relaxed(mask, base + offset + (gic_irq(d) / 32) * 4);
writel_relaxed(mask, base + offset + (index / 32) * 4);
rwp_wait();
}
@ -316,8 +369,11 @@ static int gic_irq_get_irqchip_state(struct irq_data *d,
static void gic_irq_set_prio(struct irq_data *d, u8 prio)
{
void __iomem *base = gic_dist_base(d);
u32 offset, index;
writeb_relaxed(prio, base + GICD_IPRIORITYR + gic_irq(d));
offset = convert_offset_index(d, GICD_IPRIORITYR, &index);
writeb_relaxed(prio, base + offset + index);
}
static int gic_irq_nmi_setup(struct irq_data *d)
@ -407,6 +463,7 @@ static int gic_set_type(struct irq_data *d, unsigned int type)
unsigned int irq = gic_irq(d);
void (*rwp_wait)(void);
void __iomem *base;
u32 offset, index;
int ret;
/* Interrupt configuration for SGIs can't be changed */
@ -426,8 +483,9 @@ static int gic_set_type(struct irq_data *d, unsigned int type)
rwp_wait = gic_dist_wait_for_rwp;
}
offset = convert_offset_index(d, GICD_ICFGR, &index);
ret = gic_configure_irq(irq, type, base + GICD_ICFGR, rwp_wait);
ret = gic_configure_irq(index, type, base + offset, rwp_wait);
if (ret && irq < 32) {
/* Misconfigured PPIs are usually not fatal */
pr_warn("GIC: PPI%d is secure or misconfigured\n", irq - 16);
@ -970,6 +1028,7 @@ static int gic_set_affinity(struct irq_data *d, const struct cpumask *mask_val,
bool force)
{
unsigned int cpu;
u32 offset, index;
void __iomem *reg;
int enabled;
u64 val;
@ -990,7 +1049,8 @@ static int gic_set_affinity(struct irq_data *d, const struct cpumask *mask_val,
if (enabled)
gic_mask_irq(d);
reg = gic_dist_base(d) + GICD_IROUTER + (gic_irq(d) * 8);
offset = convert_offset_index(d, GICD_IROUTER, &index);
reg = gic_dist_base(d) + offset + (index * 8);
val = gic_mpidr_to_affinity(cpu_logical_map(cpu));
gic_write_irouter(val, reg);
@ -1084,36 +1144,30 @@ static int gic_irq_domain_map(struct irq_domain *d, unsigned int irq,
if (static_branch_likely(&supports_deactivate_key))
chip = &gic_eoimode1_chip;
/* SGIs are private to the core kernel */
if (hw < 16)
return -EPERM;
/* Nothing here */
if (hw >= gic_data.irq_nr && hw < 8192)
return -EPERM;
/* Off limits */
if (hw >= GIC_ID_NR)
return -EPERM;
/* PPIs */
if (hw < 32) {
switch (__get_intid_range(hw)) {
case PPI_RANGE:
irq_set_percpu_devid(irq);
irq_domain_set_info(d, irq, hw, chip, d->host_data,
handle_percpu_devid_irq, NULL, NULL);
irq_set_status_flags(irq, IRQ_NOAUTOEN);
}
/* SPIs */
if (hw >= 32 && hw < gic_data.irq_nr) {
break;
case SPI_RANGE:
irq_domain_set_info(d, irq, hw, chip, d->host_data,
handle_fasteoi_irq, NULL, NULL);
irq_set_probe(irq);
irqd_set_single_target(irq_desc_get_irq_data(irq_to_desc(irq)));
}
/* LPIs */
if (hw >= 8192 && hw < GIC_ID_NR) {
break;
case LPI_RANGE:
if (!gic_dist_supports_lpis())
return -EPERM;
irq_domain_set_info(d, irq, hw, chip, d->host_data,
handle_fasteoi_irq, NULL, NULL);
break;
default:
return -EPERM;
}
return 0;