Message ID | 20220525121030.16054-7-Dragan.Mladjenovic@syrmia.com (mailing list archive) |
---|---|
State | Superseded |
Headers | show |
Series | MIPS: Support I6500 multi-cluster configuration | expand |
On Wed, 25 May 2022 13:10:24 +0100, Dragan Mladjenovic <Dragan.Mladjenovic@syrmia.com> wrote: > > From: Paul Burton <paulburton@kernel.org> > > The MIPS I6500 CPU & CM (Coherence Manager) 3.5 introduce the concept of > multiple clusters to the system. In these systems each cluster contains > its own GIC, so the GIC isn't truly global any longer. We do have the > ability to access registers in the GICs of remote clusters using a > redirect register block much like the redirect register blocks provided > by the CM & CPC, and configured through the same GCR_REDIRECT register > that we our mips_cm_lock_other() abstraction builds upon. > > It is expected that external interrupts are connected identically to all > clusters. That is, if we have a device providing an interrupt connected > to GIC interrupt pin 0 then it should be connected to pin 0 of every GIC > in the system. This simplifies things somewhat by allowing us for the > most part to treat the GIC as though it is still truly global, so long > as we take care to configure interrupts in the cluster that we want them > affine to. I can see how this can work for level interrupts, but how does this work for edge interrupts? Is there any guarantee that the interrupt will be discarded if routed to a cluster where it isn't configured? Otherwise, I can imagine plenty of spurious interrupts on affinity change. Thanks, M.
在 2022/6/6 12:47, Marc Zyngier 写道: > On Wed, 25 May 2022 13:10:24 +0100, > Dragan Mladjenovic <Dragan.Mladjenovic@syrmia.com> wrote: >> From: Paul Burton <paulburton@kernel.org> >> >> The MIPS I6500 CPU & CM (Coherence Manager) 3.5 introduce the concept of >> multiple clusters to the system. In these systems each cluster contains >> its own GIC, so the GIC isn't truly global any longer. We do have the >> ability to access registers in the GICs of remote clusters using a >> redirect register block much like the redirect register blocks provided >> by the CM & CPC, and configured through the same GCR_REDIRECT register >> that we our mips_cm_lock_other() abstraction builds upon. >> >> It is expected that external interrupts are connected identically to all >> clusters. That is, if we have a device providing an interrupt connected >> to GIC interrupt pin 0 then it should be connected to pin 0 of every GIC >> in the system. This simplifies things somewhat by allowing us for the >> most part to treat the GIC as though it is still truly global, so long >> as we take care to configure interrupts in the cluster that we want them >> affine to. > I can see how this can work for level interrupts, but how does this > work for edge interrupts? Is there any guarantee that the interrupt > will be discarded if routed to a cluster where it isn't configured? It is supposed to mask the interrupt out on the GIC which belongs to the cluster that the interrupt is not routed to. When it's masked out GIC simply won't sense any level change. I guess it's sort of guarantee? Thanks - Jiaxun > > Otherwise, I can imagine plenty of spurious interrupts on affinity > change. > > Thanks, > > M. >
On Tue, 07 Jun 2022 19:23:02 +0100, Jiaxun Yang <jiaxun.yang@flygoat.com> wrote: > > > > 在 2022/6/6 12:47, Marc Zyngier 写道: > > On Wed, 25 May 2022 13:10:24 +0100, > > Dragan Mladjenovic <Dragan.Mladjenovic@syrmia.com> wrote: > >> From: Paul Burton <paulburton@kernel.org> > >> > >> The MIPS I6500 CPU & CM (Coherence Manager) 3.5 introduce the concept of > >> multiple clusters to the system. In these systems each cluster contains > >> its own GIC, so the GIC isn't truly global any longer. We do have the > >> ability to access registers in the GICs of remote clusters using a > >> redirect register block much like the redirect register blocks provided > >> by the CM & CPC, and configured through the same GCR_REDIRECT register > >> that we our mips_cm_lock_other() abstraction builds upon. > >> > >> It is expected that external interrupts are connected identically to all > >> clusters. That is, if we have a device providing an interrupt connected > >> to GIC interrupt pin 0 then it should be connected to pin 0 of every GIC > >> in the system. This simplifies things somewhat by allowing us for the > >> most part to treat the GIC as though it is still truly global, so long > >> as we take care to configure interrupts in the cluster that we want them > >> affine to. > > I can see how this can work for level interrupts, but how does this > > work for edge interrupts? Is there any guarantee that the interrupt > > will be discarded if routed to a cluster where it isn't configured? > It is supposed to mask the interrupt out on the GIC which belongs to the > cluster that the interrupt is not routed to. > > When it's masked out GIC simply won't sense any level change. > > I guess it's sort of guarantee? Pretty much the opposite. There is a *strong* requirement that a masked interrupt can still detect interrupts, so that on unmask the interrupt fires (you'd otherwise lose edge interrupts pretty often). What does the MIPS GIC arch spec says about this? Thanks, M.
在2022年6月8日六月 上午7:05,Marc Zyngier写道: > On Tue, 07 Jun 2022 19:23:02 +0100, > Jiaxun Yang <jiaxun.yang@flygoat.com> wrote: >> >> >> >> 在 2022/6/6 12:47, Marc Zyngier 写道: >> > On Wed, 25 May 2022 13:10:24 +0100, >> > Dragan Mladjenovic <Dragan.Mladjenovic@syrmia.com> wrote: >> >> From: Paul Burton <paulburton@kernel.org> >> >> >> >> The MIPS I6500 CPU & CM (Coherence Manager) 3.5 introduce the concept of >> >> multiple clusters to the system. In these systems each cluster contains >> >> its own GIC, so the GIC isn't truly global any longer. We do have the >> >> ability to access registers in the GICs of remote clusters using a >> >> redirect register block much like the redirect register blocks provided >> >> by the CM & CPC, and configured through the same GCR_REDIRECT register >> >> that we our mips_cm_lock_other() abstraction builds upon. >> >> >> >> It is expected that external interrupts are connected identically to all >> >> clusters. That is, if we have a device providing an interrupt connected >> >> to GIC interrupt pin 0 then it should be connected to pin 0 of every GIC >> >> in the system. This simplifies things somewhat by allowing us for the >> >> most part to treat the GIC as though it is still truly global, so long >> >> as we take care to configure interrupts in the cluster that we want them >> >> affine to. >> > I can see how this can work for level interrupts, but how does this >> > work for edge interrupts? Is there any guarantee that the interrupt >> > will be discarded if routed to a cluster where it isn't configured? >> It is supposed to mask the interrupt out on the GIC which belongs to the >> cluster that the interrupt is not routed to. >> >> When it's masked out GIC simply won't sense any level change. >> >> I guess it's sort of guarantee? > > Pretty much the opposite. There is a *strong* requirement that a > masked interrupt can still detect interrupts, so that on unmask the > interrupt fires (you'd otherwise lose edge interrupts pretty often). Oops, sorry there is a terminology issue. On MIPS Coherent Manager manual it uses terminology of “Masked” when vector register of a interrupt is cleared. It means implementation will guarantee interrupt will be dropped when it’s routed to nowhere. > > What does the MIPS GIC arch spec says about this? Unfortunately GIC is not a arch spec. It’s just a implementation spec of MIPS Coherent Manager. Thanks. - Jiaxun > > Thanks, > > M. > > -- > Without deviation from the norm, progress is not possible.
On Thu, 09 Jun 2022 11:14:01 +0100, "Jiaxun Yang" <jiaxun.yang@flygoat.com> wrote: > > > > 在2022年6月8日六月 上午7:05,Marc Zyngier写道: > > On Tue, 07 Jun 2022 19:23:02 +0100, > > Jiaxun Yang <jiaxun.yang@flygoat.com> wrote: > >> > >> > >> > >> 在 2022/6/6 12:47, Marc Zyngier 写道: > >> > On Wed, 25 May 2022 13:10:24 +0100, > >> > Dragan Mladjenovic <Dragan.Mladjenovic@syrmia.com> wrote: > >> >> From: Paul Burton <paulburton@kernel.org> > >> >> > >> >> The MIPS I6500 CPU & CM (Coherence Manager) 3.5 introduce the concept of > >> >> multiple clusters to the system. In these systems each cluster contains > >> >> its own GIC, so the GIC isn't truly global any longer. We do have the > >> >> ability to access registers in the GICs of remote clusters using a > >> >> redirect register block much like the redirect register blocks provided > >> >> by the CM & CPC, and configured through the same GCR_REDIRECT register > >> >> that we our mips_cm_lock_other() abstraction builds upon. > >> >> > >> >> It is expected that external interrupts are connected identically to all > >> >> clusters. That is, if we have a device providing an interrupt connected > >> >> to GIC interrupt pin 0 then it should be connected to pin 0 of every GIC > >> >> in the system. This simplifies things somewhat by allowing us for the > >> >> most part to treat the GIC as though it is still truly global, so long > >> >> as we take care to configure interrupts in the cluster that we want them > >> >> affine to. > >> > I can see how this can work for level interrupts, but how does this > >> > work for edge interrupts? Is there any guarantee that the interrupt > >> > will be discarded if routed to a cluster where it isn't configured? > >> It is supposed to mask the interrupt out on the GIC which belongs to the > >> cluster that the interrupt is not routed to. > >> > >> When it's masked out GIC simply won't sense any level change. > >> > >> I guess it's sort of guarantee? > > > > Pretty much the opposite. There is a *strong* requirement that a > > masked interrupt can still detect interrupts, so that on unmask the > > interrupt fires (you'd otherwise lose edge interrupts pretty often). > Oops, sorry there is a terminology issue. On MIPS Coherent Manager > manual it uses terminology of “Masked” when vector register of > a interrupt is cleared. > > It means implementation will guarantee interrupt will be dropped > when it’s routed to nowhere. Ah, right, that makes more sense. Thanks, M.
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index 15edb9a6fcae..5f706b3a27aa 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -310,6 +310,7 @@ config KEYSTONE_IRQ config MIPS_GIC bool + select GENERIC_IRQ_EFFECTIVE_AFF_MASK select GENERIC_IRQ_IPI select MIPS_CM diff --git a/drivers/irqchip/irq-mips-gic.c b/drivers/irqchip/irq-mips-gic.c index f692392666a2..45c9c660f2ef 100644 --- a/drivers/irqchip/irq-mips-gic.c +++ b/drivers/irqchip/irq-mips-gic.c @@ -124,6 +124,41 @@ static int __gic_with_next_online_cpu(int prev) (cpu) = __gic_with_next_online_cpu(cpu), \ (cpu) < nr_cpu_ids;) +/** + * gic_irq_lock_cluster() - Lock redirect block access to IRQ's cluster + * @d: struct irq_data corresponding to the interrupt we're interested in + * + * Locks redirect register block access to the global register block of the GIC + * within the remote cluster that the IRQ corresponding to @d is affine to, + * returning true when this redirect block setup & locking has been performed. + * + * If @d is affine to the local cluster then no locking is performed and this + * function will return false, indicating to the caller that it should access + * the local clusters registers without the overhead of indirection through the + * redirect block. + * + * In summary, if this function returns true then the caller should access GIC + * registers using redirect register block accessors & then call + * mips_cm_unlock_other() when done. If this function returns false then the + * caller should trivially access GIC registers in the local cluster. + * + * Returns true if locking performed, else false. + */ +static bool gic_irq_lock_cluster(struct irq_data *d) +{ + unsigned int cpu, cl; + + cpu = cpumask_first(irq_data_get_effective_affinity_mask(d)); + BUG_ON(cpu >= NR_CPUS); + + cl = cpu_cluster(&cpu_data[cpu]); + if (cl == cpu_cluster(¤t_cpu_data)) + return false; + + mips_cm_lock_other(cl, 0, 0, CM_GCR_Cx_OTHER_BLOCK_GLOBAL); + return true; +} + static void gic_clear_pcpu_masks(unsigned int intr) { unsigned int i; @@ -170,7 +205,12 @@ static void gic_send_ipi(struct irq_data *d, unsigned int cpu) { irq_hw_number_t hwirq = GIC_HWIRQ_TO_SHARED(irqd_to_hwirq(d)); - write_gic_wedge(GIC_WEDGE_RW | hwirq); + if (gic_irq_lock_cluster(d)) { + write_gic_redir_wedge(GIC_WEDGE_RW | hwirq); + mips_cm_unlock_other(); + } else { + write_gic_wedge(GIC_WEDGE_RW | hwirq); + } } int gic_get_c0_compare_int(void) @@ -238,7 +278,13 @@ static void gic_mask_irq(struct irq_data *d) { unsigned int intr = GIC_HWIRQ_TO_SHARED(d->hwirq); - write_gic_rmask(intr); + if (gic_irq_lock_cluster(d)) { + write_gic_redir_rmask(intr); + mips_cm_unlock_other(); + } else { + write_gic_rmask(intr); + } + gic_clear_pcpu_masks(intr); } @@ -247,7 +293,12 @@ static void gic_unmask_irq(struct irq_data *d) unsigned int intr = GIC_HWIRQ_TO_SHARED(d->hwirq); unsigned int cpu; - write_gic_smask(intr); + if (gic_irq_lock_cluster(d)) { + write_gic_redir_smask(intr); + mips_cm_unlock_other(); + } else { + write_gic_smask(intr); + } gic_clear_pcpu_masks(intr); cpu = cpumask_first(irq_data_get_effective_affinity_mask(d)); @@ -258,7 +309,12 @@ static void gic_ack_irq(struct irq_data *d) { unsigned int irq = GIC_HWIRQ_TO_SHARED(d->hwirq); - write_gic_wedge(irq); + if (gic_irq_lock_cluster(d)) { + write_gic_redir_wedge(irq); + mips_cm_unlock_other(); + } else { + write_gic_wedge(irq); + } } static int gic_set_type(struct irq_data *d, unsigned int type) @@ -298,9 +354,16 @@ static int gic_set_type(struct irq_data *d, unsigned int type) break; } - change_gic_pol(irq, pol); - change_gic_trig(irq, trig); - change_gic_dual(irq, dual); + if (gic_irq_lock_cluster(d)) { + change_gic_redir_pol(irq, pol); + change_gic_redir_trig(irq, trig); + change_gic_redir_dual(irq, dual); + mips_cm_unlock_other(); + } else { + change_gic_pol(irq, pol); + change_gic_trig(irq, trig); + change_gic_dual(irq, dual); + } if (trig == GIC_TRIG_EDGE) irq_set_chip_handler_name_locked(d, &gic_edge_irq_controller, @@ -318,25 +381,72 @@ static int gic_set_affinity(struct irq_data *d, const struct cpumask *cpumask, bool force) { unsigned int irq = GIC_HWIRQ_TO_SHARED(d->hwirq); + unsigned int cpu, cl, old_cpu, old_cl; unsigned long flags; - unsigned int cpu; + /* + * The GIC specifies that we can only route an interrupt to one VP(E), + * ie. CPU in Linux parlance, at a time. Therefore we always route to + * the first online CPU in the mask. + */ cpu = cpumask_first_and(cpumask, cpu_online_mask); if (cpu >= NR_CPUS) return -EINVAL; - /* Assumption : cpumask refers to a single CPU */ - spin_lock_irqsave(&gic_lock, flags); + old_cpu = cpumask_first(irq_data_get_effective_affinity_mask(d)); + old_cl = cpu_cluster(&cpu_data[old_cpu]); + cl = cpu_cluster(&cpu_data[cpu]); - /* Re-route this IRQ */ - write_gic_map_vp(irq, BIT(mips_cm_vp_id(cpu))); + spin_lock_irqsave(&gic_lock, flags); - /* Update the pcpu_masks */ - gic_clear_pcpu_masks(irq); - if (read_gic_mask(irq)) - set_bit(irq, per_cpu_ptr(pcpu_masks, cpu)); + /* + * If we're moving affinity between clusters, stop routing the + * interrupt to any VP(E) in the old cluster. + */ + if (cl != old_cl) { + if (gic_irq_lock_cluster(d)) { + write_gic_redir_map_vp(irq, 0); + mips_cm_unlock_other(); + } else { + write_gic_map_vp(irq, 0); + } + } + /* + * Update effective affinity - after this gic_irq_lock_cluster() will + * begin operating on the new cluster. + */ irq_data_update_effective_affinity(d, cpumask_of(cpu)); + + /* + * If we're moving affinity between clusters, configure the interrupt + * trigger type in the new cluster. + */ + if (cl != old_cl) + gic_set_type(d, irqd_get_trigger_type(d)); + + /* Route the interrupt to its new VP(E) */ + if (gic_irq_lock_cluster(d)) { + write_gic_redir_map_pin(irq, + GIC_MAP_PIN_MAP_TO_PIN | gic_cpu_pin); + write_gic_redir_map_vp(irq, BIT(mips_cm_vp_id(cpu))); + + /* Update the pcpu_masks */ + gic_clear_pcpu_masks(irq); + if (read_gic_redir_mask(irq)) + set_bit(irq, per_cpu_ptr(pcpu_masks, cpu)); + + mips_cm_unlock_other(); + } else { + write_gic_map_pin(irq, GIC_MAP_PIN_MAP_TO_PIN | gic_cpu_pin); + write_gic_map_vp(irq, BIT(mips_cm_vp_id(cpu))); + + /* Update the pcpu_masks */ + gic_clear_pcpu_masks(irq); + if (read_gic_mask(irq)) + set_bit(irq, per_cpu_ptr(pcpu_masks, cpu)); + } + spin_unlock_irqrestore(&gic_lock, flags); return IRQ_SET_MASK_OK; @@ -488,11 +598,21 @@ static int gic_shared_irq_domain_map(struct irq_domain *d, unsigned int virq, unsigned long flags; data = irq_get_irq_data(virq); + irq_data_update_effective_affinity(data, cpumask_of(cpu)); spin_lock_irqsave(&gic_lock, flags); - write_gic_map_pin(intr, GIC_MAP_PIN_MAP_TO_PIN | gic_cpu_pin); - write_gic_map_vp(intr, BIT(mips_cm_vp_id(cpu))); - irq_data_update_effective_affinity(data, cpumask_of(cpu)); + + /* Route the interrupt to its VP(E) */ + if (gic_irq_lock_cluster(data)) { + write_gic_redir_map_pin(intr, + GIC_MAP_PIN_MAP_TO_PIN | gic_cpu_pin); + write_gic_redir_map_vp(intr, BIT(mips_cm_vp_id(cpu))); + mips_cm_unlock_other(); + } else { + write_gic_map_pin(intr, GIC_MAP_PIN_MAP_TO_PIN | gic_cpu_pin); + write_gic_map_vp(intr, BIT(mips_cm_vp_id(cpu))); + } + spin_unlock_irqrestore(&gic_lock, flags); return 0; @@ -670,6 +790,9 @@ static int gic_ipi_domain_alloc(struct irq_domain *d, unsigned int virq, if (ret) goto error; + /* Set affinity to cpu. */ + irq_data_update_effective_affinity(irq_get_irq_data(virq + i), + cpumask_of(cpu)); ret = irq_set_irq_type(virq + i, IRQ_TYPE_EDGE_RISING); if (ret) goto error;