Taken from Paul Burton MIPS repo with minor changes from Chao-ying Fu.
Tested with 64r6el_defconfig on Boston board in 2 cluster/2 VPU and
1 cluster/4 VPU configurations.
v2:
Apply correct Signed-off-by to avoid confusion.
Chao-ying Fu (1):
irqchip: mips-gic: Setup defaults in each cluster
Paul Burton (11):
MIPS: CPS: Add a couple of multi-cluster utility functions
MIPS: GIC: Generate redirect block accessors
irqchip: mips-gic: Introduce gic_with_each_online_cpu()
irqchip: mips-gic: Support multi-cluster in gic_with_each_online_cpu()
irqchip: mips-gic: Multi-cluster support
clocksource: mips-gic-timer: Always use cluster 0 counter as clocksource
clocksource: mips-gic-timer: Enable counter when CPUs start
MIPS: pm-cps: Use per-CPU variables as per-CPU, not per-core
MIPS: CPS: Introduce struct cluster_boot_config
MIPS: Report cluster in /proc/cpuinfo
MIPS: CPS: Boot CPUs in secondary clusters
arch/mips/include/asm/mips-cm.h | 18 ++
arch/mips/include/asm/mips-cps.h | 38 ++++
arch/mips/include/asm/mips-gic.h | 50 +++--
arch/mips/include/asm/smp-cps.h | 7 +-
arch/mips/kernel/asm-offsets.c | 3 +
arch/mips/kernel/cps-vec.S | 19 +-
arch/mips/kernel/mips-cm.c | 41 +++-
arch/mips/kernel/pm-cps.c | 35 ++--
arch/mips/kernel/proc.c | 3 +
arch/mips/kernel/smp-cps.c | 297 ++++++++++++++++++++++-----
drivers/clocksource/mips-gic-timer.c | 45 +++-
drivers/irqchip/Kconfig | 1 +
drivers/irqchip/irq-mips-gic.c | 263 +++++++++++++++++++++---
13 files changed, 692 insertions(+), 128 deletions(-)
--
2.17.1
From: Paul Burton <[email protected]>
In preparation for supporting multi-cluster systems, introduce a struct
cluster_boot_config as an extra layer in the boot configuration
maintained by the MIPS Coherent Processing System (CPS) SMP
implementation. For now only one struct cluster_boot_config will be
allocated & we'll simply defererence its core_config field to find the
struct core_boot_config array which we use to boot as usual.
Signed-off-by: Paul Burton <[email protected]>
Signed-off-by: Dragan Mladjenovic <[email protected]>
diff --git a/arch/mips/include/asm/smp-cps.h b/arch/mips/include/asm/smp-cps.h
index 7e5b9411faee..84a713667be2 100644
--- a/arch/mips/include/asm/smp-cps.h
+++ b/arch/mips/include/asm/smp-cps.h
@@ -20,7 +20,11 @@ struct core_boot_config {
struct vpe_boot_config *vpe_config;
};
-extern struct core_boot_config *mips_cps_core_bootcfg;
+struct cluster_boot_config {
+ struct core_boot_config *core_config;
+};
+
+extern struct cluster_boot_config *mips_cps_cluster_bootcfg;
extern void mips_cps_core_entry(void);
extern void mips_cps_core_init(void);
diff --git a/arch/mips/kernel/asm-offsets.c b/arch/mips/kernel/asm-offsets.c
index 04ca75278f02..7d1f032e8275 100644
--- a/arch/mips/kernel/asm-offsets.c
+++ b/arch/mips/kernel/asm-offsets.c
@@ -391,6 +391,9 @@ void output_cps_defines(void)
{
COMMENT(" MIPS CPS offsets. ");
+ OFFSET(CLUSTERBOOTCFG_CORECONFIG, cluster_boot_config, core_config);
+ DEFINE(CLUSTERBOOTCFG_SIZE, sizeof(struct cluster_boot_config));
+
OFFSET(COREBOOTCFG_VPEMASK, core_boot_config, vpe_mask);
OFFSET(COREBOOTCFG_VPECONFIG, core_boot_config, vpe_config);
DEFINE(COREBOOTCFG_SIZE, sizeof(struct core_boot_config));
diff --git a/arch/mips/kernel/cps-vec.S b/arch/mips/kernel/cps-vec.S
index 975343240148..f80466f810d4 100644
--- a/arch/mips/kernel/cps-vec.S
+++ b/arch/mips/kernel/cps-vec.S
@@ -17,6 +17,10 @@
#define GCR_CPC_BASE_OFS 0x0088
#define GCR_CL_COHERENCE_OFS 0x2008
#define GCR_CL_ID_OFS 0x2028
+#define CM3_GCR_Cx_ID_CLUSTER_SHF 8
+#define CM3_GCR_Cx_ID_CLUSTER_MSK (0xff << 8)
+#define CM3_GCR_Cx_ID_CORENUM_SHF 0
+#define CM3_GCR_Cx_ID_CORENUM_MSK (0xff << 0)
#define CPC_CL_VC_STOP_OFS 0x2020
#define CPC_CL_VC_RUN_OFS 0x2028
@@ -305,13 +309,22 @@ LEAF(mips_cps_core_init)
*/
LEAF(mips_cps_get_bootcfg)
/* Calculate a pointer to this cores struct core_boot_config */
+ PTR_LA v0, mips_cps_cluster_bootcfg
+ PTR_L v0, 0(v0)
cmgcrb t0
lw t0, GCR_CL_ID_OFS(t0)
+#ifdef CONFIG_CPU_MIPSR6
+ ext t1, t0, CM3_GCR_Cx_ID_CLUSTER_SHF, 8
+ li t2, CLUSTERBOOTCFG_SIZE
+ mul t1, t1, t2
+ PTR_ADDU \
+ v0, v0, t1
+#endif
+ PTR_L v0, CLUSTERBOOTCFG_CORECONFIG(v0)
+ andi t0, t0, CM3_GCR_Cx_ID_CORENUM_MSK
li t1, COREBOOTCFG_SIZE
mul t0, t0, t1
- PTR_LA t1, mips_cps_core_bootcfg
- PTR_L t1, 0(t1)
- PTR_ADDU v0, t0, t1
+ PTR_ADDU v0, v0, t0
/* Calculate this VPEs ID. If the core doesn't support MT use 0 */
li t9, 0
diff --git a/arch/mips/kernel/pm-cps.c b/arch/mips/kernel/pm-cps.c
index a7bcf2b814c8..84e79bd84971 100644
--- a/arch/mips/kernel/pm-cps.c
+++ b/arch/mips/kernel/pm-cps.c
@@ -107,12 +107,14 @@ static void coupled_barrier(atomic_t *a, unsigned online)
int cps_pm_enter_state(enum cps_pm_state state)
{
unsigned cpu = smp_processor_id();
+ unsigned int cluster = cpu_cluster(¤t_cpu_data);
unsigned core = cpu_core(¤t_cpu_data);
unsigned online, left;
cpumask_t *coupled_mask = this_cpu_ptr(&online_coupled);
u32 *core_ready_count, *nc_core_ready_count;
void *nc_addr;
cps_nc_entry_fn entry;
+ struct cluster_boot_config *cluster_cfg;
struct core_boot_config *core_cfg;
struct vpe_boot_config *vpe_cfg;
atomic_t *barrier;
@@ -142,7 +144,8 @@ int cps_pm_enter_state(enum cps_pm_state state)
if (!mips_cps_smp_in_use())
return -EINVAL;
- core_cfg = &mips_cps_core_bootcfg[core];
+ cluster_cfg = &mips_cps_cluster_bootcfg[cluster];
+ core_cfg = &cluster_cfg->core_config[core];
vpe_cfg = &core_cfg->vpe_config[cpu_vpe_id(¤t_cpu_data)];
vpe_cfg->pc = (unsigned long)mips_cps_pm_restore;
vpe_cfg->gp = (unsigned long)current_thread_info();
diff --git a/arch/mips/kernel/smp-cps.c b/arch/mips/kernel/smp-cps.c
index bcd6a944b839..360d132bee71 100644
--- a/arch/mips/kernel/smp-cps.c
+++ b/arch/mips/kernel/smp-cps.c
@@ -27,7 +27,7 @@
static bool threads_disabled;
static DECLARE_BITMAP(core_power, NR_CPUS);
-struct core_boot_config *mips_cps_core_bootcfg;
+struct cluster_boot_config *mips_cps_cluster_bootcfg;
static int __init setup_nothreads(char *s)
{
@@ -118,9 +118,11 @@ static void __init cps_smp_setup(void)
static void __init cps_prepare_cpus(unsigned int max_cpus)
{
- unsigned ncores, core_vpes, c, cca;
+ unsigned int nclusters, ncores, core_vpes, c, cl, cca;
bool cca_unsuitable, cores_limited;
u32 *entry_code;
+ struct cluster_boot_config *cluster_bootcfg;
+ struct core_boot_config *core_bootcfg;
mips_mt_set_cpuoptions();
@@ -168,40 +170,54 @@ static void __init cps_prepare_cpus(unsigned int max_cpus)
(void *)entry_code - (void *)&mips_cps_core_entry);
__sync();
- /* Allocate core boot configuration structs */
- ncores = mips_cps_numcores(0);
- mips_cps_core_bootcfg = kcalloc(ncores, sizeof(*mips_cps_core_bootcfg),
- GFP_KERNEL);
- if (!mips_cps_core_bootcfg) {
- pr_err("Failed to allocate boot config for %u cores\n", ncores);
- goto err_out;
- }
+ /* Allocate cluster boot configuration structs */
+ nclusters = mips_cps_numclusters();
+ mips_cps_cluster_bootcfg = kcalloc(nclusters,
+ sizeof(*mips_cps_cluster_bootcfg),
+ GFP_KERNEL);
- /* Allocate VPE boot configuration structs */
- for (c = 0; c < ncores; c++) {
- core_vpes = core_vpe_count(0, c);
- mips_cps_core_bootcfg[c].vpe_config = kcalloc(core_vpes,
- sizeof(*mips_cps_core_bootcfg[c].vpe_config),
- GFP_KERNEL);
- if (!mips_cps_core_bootcfg[c].vpe_config) {
- pr_err("Failed to allocate %u VPE boot configs\n",
- core_vpes);
+ for (cl = 0; cl < nclusters; cl++) {
+ /* Allocate core boot configuration structs */
+ ncores = mips_cps_numcores(cl);
+ core_bootcfg = kcalloc(ncores, sizeof(*core_bootcfg),
+ GFP_KERNEL);
+ if (!core_bootcfg)
goto err_out;
+ mips_cps_cluster_bootcfg[cl].core_config = core_bootcfg;
+
+ /* Allocate VPE boot configuration structs */
+ for (c = 0; c < ncores; c++) {
+ core_vpes = core_vpe_count(cl, c);
+ core_bootcfg[c].vpe_config = kcalloc(core_vpes,
+ sizeof(*core_bootcfg[c].vpe_config),
+ GFP_KERNEL);
+ if (!core_bootcfg[c].vpe_config)
+ goto err_out;
}
}
/* Mark this CPU as booted */
- atomic_set(&mips_cps_core_bootcfg[cpu_core(¤t_cpu_data)].vpe_mask,
- 1 << cpu_vpe_id(¤t_cpu_data));
+ cl = cpu_cluster(¤t_cpu_data);
+ c = cpu_core(¤t_cpu_data);
+ cluster_bootcfg = &mips_cps_cluster_bootcfg[cl];
+ core_bootcfg = &cluster_bootcfg->core_config[c];
+ atomic_set(&core_bootcfg->vpe_mask, 1 << cpu_vpe_id(¤t_cpu_data));
return;
err_out:
/* Clean up allocations */
- if (mips_cps_core_bootcfg) {
- for (c = 0; c < ncores; c++)
- kfree(mips_cps_core_bootcfg[c].vpe_config);
- kfree(mips_cps_core_bootcfg);
- mips_cps_core_bootcfg = NULL;
+ if (mips_cps_cluster_bootcfg) {
+ for (cl = 0; cl < nclusters; cl++) {
+ cluster_bootcfg = &mips_cps_cluster_bootcfg[cl];
+ ncores = mips_cps_numcores(cl);
+ for (c = 0; c < ncores; c++) {
+ core_bootcfg = &cluster_bootcfg->core_config[c];
+ kfree(core_bootcfg->vpe_config);
+ }
+ kfree(mips_cps_cluster_bootcfg[c].core_config);
+ }
+ kfree(mips_cps_cluster_bootcfg);
+ mips_cps_cluster_bootcfg = NULL;
}
/* Effectively disable SMP by declaring CPUs not present */
@@ -286,17 +302,23 @@ static void boot_core(unsigned int core, unsigned int vpe_id)
static void remote_vpe_boot(void *dummy)
{
+ unsigned int cluster = cpu_cluster(¤t_cpu_data);
unsigned core = cpu_core(¤t_cpu_data);
- struct core_boot_config *core_cfg = &mips_cps_core_bootcfg[core];
+ struct cluster_boot_config *cluster_cfg =
+ &mips_cps_cluster_bootcfg[cluster];
+ struct core_boot_config *core_cfg = &cluster_cfg->core_config[core];
mips_cps_boot_vpes(core_cfg, cpu_vpe_id(¤t_cpu_data));
}
static int cps_boot_secondary(int cpu, struct task_struct *idle)
{
+ unsigned int cluster = cpu_cluster(&cpu_data[cpu]);
unsigned core = cpu_core(&cpu_data[cpu]);
unsigned vpe_id = cpu_vpe_id(&cpu_data[cpu]);
- struct core_boot_config *core_cfg = &mips_cps_core_bootcfg[core];
+ struct cluster_boot_config *cluster_cfg =
+ &mips_cps_cluster_bootcfg[cluster];
+ struct core_boot_config *core_cfg = &cluster_cfg->core_config[core];
struct vpe_boot_config *vpe_cfg = &core_cfg->vpe_config[vpe_id];
unsigned long core_entry;
unsigned int remote;
@@ -449,12 +471,14 @@ static void cps_kexec_nonboot_cpu(void)
static int cps_cpu_disable(void)
{
unsigned cpu = smp_processor_id();
+ struct cluster_boot_config *cluster_cfg;
struct core_boot_config *core_cfg;
if (!cps_pm_support_state(CPS_PM_POWER_GATED))
return -EINVAL;
- core_cfg = &mips_cps_core_bootcfg[cpu_core(¤t_cpu_data)];
+ cluster_cfg = &mips_cps_cluster_bootcfg[cpu_cluster(¤t_cpu_data)];
+ core_cfg = &cluster_cfg->core_config[cpu_core(¤t_cpu_data)];
atomic_sub(1 << cpu_vpe_id(¤t_cpu_data), &core_cfg->vpe_mask);
smp_mb__after_atomic();
set_cpu_online(cpu, false);
--
2.17.1
From: Paul Burton <[email protected]>
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.
This patch introduces support for such multi-cluster systems in the MIPS
GIC irqchip driver. We introduce a new gic_irq_lock_cluster() function
which allows us to either:
1) Configure access to a GIC in a remote cluster via the redirect
register block, using mips_cm_lock_other().
Or:
2) Detect that the interrupt in question is affine to the local
cluster and we should use plain old GIC register access to the GIC
in the local cluster.
It is possible to access the local cluster's GIC registers via the
redirect block, but keeping the special case for them is both good for
performance (because we avoid the locking & indirection overhead of
using the redirect block) and necessary to maintain compatibility with
systems using CM revisions prior to 3.5 which don't support the redirect
block.
The gic_irq_lock_cluster() function relies upon an IRQs effective
affinity in order to discover which cluster the IRQ is affine to. In
order to track this & allow it to be updated at an appropriate point
during gic_set_affinity() we select the generic support for effective
affinity using CONFIG_GENERIC_IRQ_EFFECTIVE_AFF_MASK.
gic_set_affinity() is the one function which gains much complexity. It
now deconfigures routing to any VP(E), ie. CPU, on the old cluster when
moving affinity to a new cluster. Because we only configure an
interrupts trigger type in the cluster which it is affine to we call
gic_set_type() to configure that in the new cluster, after having
updated the effective affinity mask such that gic_irq_lock_cluster()
begins operating on the new cluster. Finally we map the interrupt to the
appropriate pin & VP(E) in the new cluster.
gic_shared_irq_domain_map() moves its update of the IRQs effective
affinity to before its use of gic_irq_lock_cluster(), in order to ensure
we operate on the cluster the IRQ is affine to.
The remaining changes are straightforward use of the
gic_irq_lock_cluster() function to select between local cluster & remote
cluster code-paths when configuring interrupts.
Signed-off-by: Paul Burton <[email protected]>
Signed-off-by: Chao-ying Fu <[email protected]>
Signed-off-by: Dragan Mladjenovic <[email protected]>
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;
--
2.17.1
From: Paul Burton <[email protected]>
The pm-cps code has up until now used per-CPU variables indexed by core,
rather than CPU number, in order to share data amongst sibling CPUs (ie.
VPs/threads in a core). This works fine for single cluster systems, but
with multi-cluster systems a core number is no longer unique in the
system, leading to sharing between CPUs that are not actually siblings.
Avoid this issue by using per-CPU variables as they are more generally
used - ie. access them using CPU numbers rather than core numbers.
Sharing between siblings is then accomplished by:
- Assigning the same pointer to entries for each sibling CPU for the
nc_asm_enter & ready_count variables, which allow this by virtue of
being per-CPU pointers.
- Indexing by the first CPU set in a CPUs cpu_sibling_map in the case
of pm_barrier, for which we can't use the previous approach because
the per-CPU variable is not a pointer.
Signed-off-by: Paul Burton <[email protected]>
Signed-off-by: Dragan Mladjenovic <[email protected]>
diff --git a/arch/mips/kernel/pm-cps.c b/arch/mips/kernel/pm-cps.c
index 9bf60d7d44d3..a7bcf2b814c8 100644
--- a/arch/mips/kernel/pm-cps.c
+++ b/arch/mips/kernel/pm-cps.c
@@ -56,10 +56,7 @@ static DEFINE_PER_CPU_ALIGNED(u32*, ready_count);
/* Indicates online CPUs coupled with the current CPU */
static DEFINE_PER_CPU_ALIGNED(cpumask_t, online_coupled);
-/*
- * Used to synchronize entry to deep idle states. Actually per-core rather
- * than per-CPU.
- */
+/* Used to synchronize entry to deep idle states */
static DEFINE_PER_CPU_ALIGNED(atomic_t, pm_barrier);
/* Saved CPU state across the CPS_PM_POWER_GATED state */
@@ -118,9 +115,10 @@ int cps_pm_enter_state(enum cps_pm_state state)
cps_nc_entry_fn entry;
struct core_boot_config *core_cfg;
struct vpe_boot_config *vpe_cfg;
+ atomic_t *barrier;
/* Check that there is an entry function for this state */
- entry = per_cpu(nc_asm_enter, core)[state];
+ entry = per_cpu(nc_asm_enter, cpu)[state];
if (!entry)
return -EINVAL;
@@ -156,7 +154,7 @@ int cps_pm_enter_state(enum cps_pm_state state)
smp_mb__after_atomic();
/* Create a non-coherent mapping of the core ready_count */
- core_ready_count = per_cpu(ready_count, core);
+ core_ready_count = per_cpu(ready_count, cpu);
nc_addr = kmap_noncoherent(virt_to_page(core_ready_count),
(unsigned long)core_ready_count);
nc_addr += ((unsigned long)core_ready_count & ~PAGE_MASK);
@@ -164,7 +162,8 @@ int cps_pm_enter_state(enum cps_pm_state state)
/* Ensure ready_count is zero-initialised before the assembly runs */
WRITE_ONCE(*nc_core_ready_count, 0);
- coupled_barrier(&per_cpu(pm_barrier, core), online);
+ barrier = &per_cpu(pm_barrier, cpumask_first(&cpu_sibling_map[cpu]));
+ coupled_barrier(barrier, online);
/* Run the generated entry code */
left = entry(online, nc_core_ready_count);
@@ -635,12 +634,14 @@ static void *cps_gen_entry_code(unsigned cpu, enum cps_pm_state state)
static int cps_pm_online_cpu(unsigned int cpu)
{
- enum cps_pm_state state;
- unsigned core = cpu_core(&cpu_data[cpu]);
+ unsigned int sibling, core;
void *entry_fn, *core_rc;
+ enum cps_pm_state state;
+
+ core = cpu_core(&cpu_data[cpu]);
for (state = CPS_PM_NC_WAIT; state < CPS_PM_STATE_COUNT; state++) {
- if (per_cpu(nc_asm_enter, core)[state])
+ if (per_cpu(nc_asm_enter, cpu)[state])
continue;
if (!test_bit(state, state_support))
continue;
@@ -652,16 +653,19 @@ static int cps_pm_online_cpu(unsigned int cpu)
clear_bit(state, state_support);
}
- per_cpu(nc_asm_enter, core)[state] = entry_fn;
+ for_each_cpu(sibling, &cpu_sibling_map[cpu])
+ per_cpu(nc_asm_enter, sibling)[state] = entry_fn;
}
- if (!per_cpu(ready_count, core)) {
+ if (!per_cpu(ready_count, cpu)) {
core_rc = kmalloc(sizeof(u32), GFP_KERNEL);
if (!core_rc) {
pr_err("Failed allocate core %u ready_count\n", core);
return -ENOMEM;
}
- per_cpu(ready_count, core) = core_rc;
+
+ for_each_cpu(sibling, &cpu_sibling_map[cpu])
+ per_cpu(ready_count, sibling) = core_rc;
}
return 0;
--
2.17.1
From: Paul Burton <[email protected]>
Probe for & boot CPUs (cores & VPs) in secondary clusters (ie. not the
cluster that began booting Linux) when they are present in systems with
CM 3.5 or higher.
Signed-off-by: Paul Burton <[email protected]>
Signed-off-by: Chao-ying Fu <[email protected]>
Signed-off-by: Dragan Mladjenovic <[email protected]>
diff --git a/arch/mips/include/asm/mips-cm.h b/arch/mips/include/asm/mips-cm.h
index 23c67c0871b1..435049907e44 100644
--- a/arch/mips/include/asm/mips-cm.h
+++ b/arch/mips/include/asm/mips-cm.h
@@ -239,6 +239,12 @@ GCR_ACCESSOR_RW(32, 0x130, l2_config)
GCR_ACCESSOR_RO(32, 0x150, sys_config2)
#define CM_GCR_SYS_CONFIG2_MAXVPW GENMASK(3, 0)
+/* GCR_L2-RAM_CONFIG - Configuration & status of L2 cache RAMs */
+GCR_ACCESSOR_RW(64, 0x240, l2_ram_config)
+#define CM_GCR_L2_RAM_CONFIG_PRESENT BIT(31)
+#define CM_GCR_L2_RAM_CONFIG_HCI_DONE BIT(30)
+#define CM_GCR_L2_RAM_CONFIG_HCI_SUPPORTED BIT(29)
+
/* GCR_L2_PFT_CONTROL - Controls hardware L2 prefetching */
GCR_ACCESSOR_RW(32, 0x300, l2_pft_control)
#define CM_GCR_L2_PFT_CONTROL_PAGEMASK GENMASK(31, 12)
@@ -250,6 +256,18 @@ GCR_ACCESSOR_RW(32, 0x308, l2_pft_control_b)
#define CM_GCR_L2_PFT_CONTROL_B_CEN BIT(8)
#define CM_GCR_L2_PFT_CONTROL_B_PORTID GENMASK(7, 0)
+/* GCR_L2_TAG_ADDR - Access addresses in L2 cache tags */
+GCR_ACCESSOR_RW(64, 0x600, l2_tag_addr)
+
+/* GCR_L2_TAG_STATE - Access L2 cache tag state */
+GCR_ACCESSOR_RW(64, 0x608, l2_tag_state)
+
+/* GCR_L2_DATA - Access data in L2 cache lines */
+GCR_ACCESSOR_RW(64, 0x610, l2_data)
+
+/* GCR_L2_ECC - Access ECC information from L2 cache lines */
+GCR_ACCESSOR_RW(64, 0x618, l2_ecc)
+
/* GCR_L2SM_COP - L2 cache op state machine control */
GCR_ACCESSOR_RW(32, 0x620, l2sm_cop)
#define CM_GCR_L2SM_COP_PRESENT BIT(31)
diff --git a/arch/mips/include/asm/smp-cps.h b/arch/mips/include/asm/smp-cps.h
index 84a713667be2..7bcad119ceb1 100644
--- a/arch/mips/include/asm/smp-cps.h
+++ b/arch/mips/include/asm/smp-cps.h
@@ -21,6 +21,7 @@ struct core_boot_config {
};
struct cluster_boot_config {
+ unsigned long *core_power;
struct core_boot_config *core_config;
};
diff --git a/arch/mips/kernel/mips-cm.c b/arch/mips/kernel/mips-cm.c
index 5b843c2ff077..f4c23bcab417 100644
--- a/arch/mips/kernel/mips-cm.c
+++ b/arch/mips/kernel/mips-cm.c
@@ -309,7 +309,9 @@ void mips_cm_lock_other(unsigned int cluster, unsigned int core,
FIELD_PREP(CM3_GCR_Cx_OTHER_VP, vp);
if (cm_rev >= CM_REV_CM3_5) {
- val |= CM_GCR_Cx_OTHER_CLUSTER_EN;
+ if (cluster != cpu_cluster(¤t_cpu_data))
+ val |= CM_GCR_Cx_OTHER_CLUSTER_EN;
+ val |= CM_GCR_Cx_OTHER_GIC_EN;
val |= FIELD_PREP(CM_GCR_Cx_OTHER_CLUSTER, cluster);
val |= FIELD_PREP(CM_GCR_Cx_OTHER_BLOCK, block);
} else {
diff --git a/arch/mips/kernel/smp-cps.c b/arch/mips/kernel/smp-cps.c
index 360d132bee71..311e9e40e5c7 100644
--- a/arch/mips/kernel/smp-cps.c
+++ b/arch/mips/kernel/smp-cps.c
@@ -25,7 +25,6 @@
#include <asm/uasm.h>
static bool threads_disabled;
-static DECLARE_BITMAP(core_power, NR_CPUS);
struct cluster_boot_config *mips_cps_cluster_bootcfg;
@@ -36,6 +35,51 @@ static int __init setup_nothreads(char *s)
}
early_param("nothreads", setup_nothreads);
+static void power_up_other_cluster(unsigned int cluster)
+{
+ u32 stat, seq_state;
+ unsigned int timeout;
+
+ mips_cm_lock_other(cluster, CM_GCR_Cx_OTHER_CORE_CM, 0,
+ CM_GCR_Cx_OTHER_BLOCK_LOCAL);
+ stat = read_cpc_co_stat_conf();
+ mips_cm_unlock_other();
+
+ seq_state = stat & CPC_Cx_STAT_CONF_SEQSTATE;
+ seq_state >>= __ffs(CPC_Cx_STAT_CONF_SEQSTATE);
+ if (seq_state == CPC_Cx_STAT_CONF_SEQSTATE_U5)
+ return;
+
+ /* Set endianness & power up the CM */
+ mips_cm_lock_other(cluster, 0, 0, CM_GCR_Cx_OTHER_BLOCK_GLOBAL);
+ write_cpc_redir_sys_config(IS_ENABLED(CONFIG_CPU_BIG_ENDIAN));
+ write_cpc_redir_pwrup_ctl(1);
+ mips_cm_unlock_other();
+
+ /* Wait for the CM to start up */
+ timeout = 1000;
+ mips_cm_lock_other(cluster, CM_GCR_Cx_OTHER_CORE_CM, 0,
+ CM_GCR_Cx_OTHER_BLOCK_LOCAL);
+ while (1) {
+ stat = read_cpc_co_stat_conf();
+ seq_state = stat & CPC_Cx_STAT_CONF_SEQSTATE;
+ seq_state >>= __ffs(CPC_Cx_STAT_CONF_SEQSTATE);
+ if (seq_state == CPC_Cx_STAT_CONF_SEQSTATE_U5)
+ break;
+
+ if (timeout) {
+ mdelay(1);
+ timeout--;
+ } else {
+ pr_warn("Waiting for cluster %u CM to power up... STAT_CONF=0x%x\n",
+ cluster, stat);
+ mdelay(1000);
+ }
+ }
+
+ mips_cm_unlock_other();
+}
+
static unsigned core_vpe_count(unsigned int cluster, unsigned core)
{
if (threads_disabled)
@@ -59,6 +103,9 @@ static void __init cps_smp_setup(void)
pr_cont(",");
pr_cont("{");
+ if (mips_cm_revision() >= CM_REV_CM3_5)
+ power_up_other_cluster(cl);
+
ncores = mips_cps_numcores(cl);
for (c = 0; c < ncores; c++) {
core_vpes = core_vpe_count(cl, c);
@@ -86,8 +133,8 @@ static void __init cps_smp_setup(void)
/* Indicate present CPUs (CPU being synonymous with VPE) */
for (v = 0; v < min_t(unsigned, nvpes, NR_CPUS); v++) {
- set_cpu_possible(v, cpu_cluster(&cpu_data[v]) == 0);
- set_cpu_present(v, cpu_cluster(&cpu_data[v]) == 0);
+ set_cpu_possible(v, true);
+ set_cpu_present(v, true);
__cpu_number_map[v] = v;
__cpu_logical_map[v] = v;
}
@@ -95,19 +142,15 @@ static void __init cps_smp_setup(void)
/* Set a coherent default CCA (CWB) */
change_c0_config(CONF_CM_CMASK, 0x5);
- /* Core 0 is powered up (we're running on it) */
- bitmap_set(core_power, 0, 1);
-
/* Initialise core 0 */
mips_cps_core_init();
/* Make core 0 coherent with everything */
write_gcr_cl_coherence(0xff);
- if (mips_cm_revision() >= CM_REV_CM3) {
- core_entry = CKSEG1ADDR((unsigned long)mips_cps_core_entry);
+ core_entry = CKSEG1ADDR((unsigned long)mips_cps_core_entry);
+ if (mips_cm_revision() >= CM_REV_CM3)
write_gcr_bev_base(core_entry);
- }
#ifdef CONFIG_MIPS_MT_FPAFF
/* If we have an FPU, enroll ourselves in the FPU-full mask */
@@ -185,6 +228,10 @@ static void __init cps_prepare_cpus(unsigned int max_cpus)
goto err_out;
mips_cps_cluster_bootcfg[cl].core_config = core_bootcfg;
+ mips_cps_cluster_bootcfg[cl].core_power =
+ kcalloc(BITS_TO_LONGS(ncores), sizeof(unsigned long),
+ GFP_KERNEL);
+
/* Allocate VPE boot configuration structs */
for (c = 0; c < ncores; c++) {
core_vpes = core_vpe_count(cl, c);
@@ -196,11 +243,12 @@ static void __init cps_prepare_cpus(unsigned int max_cpus)
}
}
- /* Mark this CPU as booted */
+ /* Mark this CPU as powered up & booted */
cl = cpu_cluster(¤t_cpu_data);
c = cpu_core(¤t_cpu_data);
cluster_bootcfg = &mips_cps_cluster_bootcfg[cl];
core_bootcfg = &cluster_bootcfg->core_config[c];
+ bitmap_set(cluster_bootcfg->core_power, cpu_core(¤t_cpu_data), 1);
atomic_set(&core_bootcfg->vpe_mask, 1 << cpu_vpe_id(¤t_cpu_data));
return;
@@ -228,16 +276,123 @@ static void __init cps_prepare_cpus(unsigned int max_cpus)
}
}
-static void boot_core(unsigned int core, unsigned int vpe_id)
+static void init_cluster_l2(void)
{
- u32 stat, seq_state;
- unsigned timeout;
+ u32 l2_cfg, l2sm_cop, result;
+
+ while (1) {
+ l2_cfg = read_gcr_redir_l2_ram_config();
+
+ /* If HCI is not supported, use the state machine below */
+ if (!(l2_cfg & CM_GCR_L2_RAM_CONFIG_PRESENT))
+ break;
+ if (!(l2_cfg & CM_GCR_L2_RAM_CONFIG_HCI_SUPPORTED))
+ break;
+
+ /* If the HCI_DONE bit is set, we're finished */
+ if (l2_cfg & CM_GCR_L2_RAM_CONFIG_HCI_DONE)
+ return;
+ }
+
+ l2sm_cop = read_gcr_redir_l2sm_cop();
+ if (WARN(!(l2sm_cop & CM_GCR_L2SM_COP_PRESENT),
+ "L2 init not supported on this system yet"))
+ return;
+
+ /* Clear L2 tag registers */
+ write_gcr_redir_l2_tag_state(0);
+ write_gcr_redir_l2_ecc(0);
+
+ /* Ensure the L2 tag writes complete before the state machine starts */
+ mb();
+
+ /* Wait for the L2 state machine to be idle */
+ do {
+ l2sm_cop = read_gcr_redir_l2sm_cop();
+ } while (l2sm_cop & CM_GCR_L2SM_COP_RUNNING);
+
+ /* Start a store tag operation */
+ l2sm_cop = CM_GCR_L2SM_COP_TYPE_IDX_STORETAG;
+ l2sm_cop <<= __ffs(CM_GCR_L2SM_COP_TYPE);
+ l2sm_cop |= CM_GCR_L2SM_COP_CMD_START;
+ write_gcr_redir_l2sm_cop(l2sm_cop);
+
+ /* Ensure the state machine starts before we poll for completion */
+ mb();
+
+ /* Wait for the operation to be complete */
+ do {
+ l2sm_cop = read_gcr_redir_l2sm_cop();
+ result = l2sm_cop & CM_GCR_L2SM_COP_RESULT;
+ result >>= __ffs(CM_GCR_L2SM_COP_RESULT);
+ } while (!result);
+
+ WARN(result != CM_GCR_L2SM_COP_RESULT_DONE_OK,
+ "L2 state machine failed cache init with error %u\n", result);
+}
+
+static void boot_core(unsigned int cluster, unsigned int core,
+ unsigned int vpe_id)
+{
+ struct cluster_boot_config *cluster_cfg;
+ u32 access, stat, seq_state;
+ unsigned int timeout, ncores;
+ unsigned long core_entry;
+
+ cluster_cfg = &mips_cps_cluster_bootcfg[cluster];
+ ncores = mips_cps_numcores(cluster);
+ core_entry = CKSEG1ADDR((unsigned long)mips_cps_core_entry);
+
+ if ((cluster != cpu_cluster(¤t_cpu_data)) &&
+ bitmap_empty(cluster_cfg->core_power, ncores)) {
+ power_up_other_cluster(cluster);
+
+ mips_cm_lock_other(cluster, core, 0,
+ CM_GCR_Cx_OTHER_BLOCK_GLOBAL);
+
+ /* Ensure cluster GCRs are where we expect */
+ write_gcr_redir_base(read_gcr_base());
+ write_gcr_redir_cpc_base(read_gcr_cpc_base());
+ write_gcr_redir_gic_base(read_gcr_gic_base());
+
+ init_cluster_l2();
+
+ /* Mirror L2 configuration */
+ write_gcr_redir_l2_only_sync_base(read_gcr_l2_only_sync_base());
+ write_gcr_redir_l2_pft_control(read_gcr_l2_pft_control());
+ write_gcr_redir_l2_pft_control_b(read_gcr_l2_pft_control_b());
+
+ /* Mirror ECC/parity setup */
+ write_gcr_redir_err_control(read_gcr_err_control());
+
+ /* Set BEV base */
+ write_gcr_redir_bev_base(core_entry);
+
+ mips_cm_unlock_other();
+ }
+
+ if (cluster != cpu_cluster(¤t_cpu_data)) {
+ mips_cm_lock_other(cluster, core, 0,
+ CM_GCR_Cx_OTHER_BLOCK_GLOBAL);
+
+ /* Ensure the core can access the GCRs */
+ access = read_gcr_redir_access();
+ access |= BIT(core);
+ write_gcr_redir_access(access);
+
+ mips_cm_unlock_other();
+ } else {
+ /* Ensure the core can access the GCRs */
+ access = read_gcr_access();
+ access |= BIT(core);
+ write_gcr_access(access);
+ }
/* Select the appropriate core */
- mips_cm_lock_other(0, core, 0, CM_GCR_Cx_OTHER_BLOCK_LOCAL);
+ mips_cm_lock_other(cluster, core, 0, CM_GCR_Cx_OTHER_BLOCK_LOCAL);
/* Set its reset vector */
- write_gcr_co_reset_base(CKSEG1ADDR((unsigned long)mips_cps_core_entry));
+ write_gcr_co_reset_base(core_entry);
/* Ensure its coherency is disabled */
write_gcr_co_coherence(0);
@@ -245,9 +400,6 @@ static void boot_core(unsigned int core, unsigned int vpe_id)
/* Start it with the legacy memory map and exception base */
write_gcr_co_reset_ext_base(CM_GCR_Cx_RESET_EXT_BASE_UEB);
- /* Ensure the core can access the GCRs */
- set_gcr_access(1 << core);
-
if (mips_cpc_present()) {
/* Reset the core */
mips_cpc_lock_other(core);
@@ -297,7 +449,17 @@ static void boot_core(unsigned int core, unsigned int vpe_id)
mips_cm_unlock_other();
/* The core is now powered up */
- bitmap_set(core_power, core, 1);
+ bitmap_set(cluster_cfg->core_power, core, 1);
+
+ /*
+ * Restore CM_PWRUP=0 so that the CM can power down if all the cores in
+ * the cluster do (eg. if they're all removed via hotplug.
+ */
+ if (mips_cm_revision() >= CM_REV_CM3_5) {
+ mips_cm_lock_other(cluster, 0, 0, CM_GCR_Cx_OTHER_BLOCK_GLOBAL);
+ write_cpc_redir_pwrup_ctl(0);
+ mips_cm_unlock_other();
+ }
}
static void remote_vpe_boot(void *dummy)
@@ -324,10 +486,6 @@ static int cps_boot_secondary(int cpu, struct task_struct *idle)
unsigned int remote;
int err;
- /* We don't yet support booting CPUs in other clusters */
- if (cpu_cluster(&cpu_data[cpu]) != cpu_cluster(&raw_current_cpu_data))
- return -ENOSYS;
-
vpe_cfg->pc = (unsigned long)&smp_bootstrap;
vpe_cfg->sp = __KSTK_TOS(idle);
vpe_cfg->gp = (unsigned long)task_thread_info(idle);
@@ -336,14 +494,15 @@ static int cps_boot_secondary(int cpu, struct task_struct *idle)
preempt_disable();
- if (!test_bit(core, core_power)) {
+ if (!test_bit(core, cluster_cfg->core_power)) {
/* Boot a VPE on a powered down core */
- boot_core(core, vpe_id);
+ boot_core(cluster, core, vpe_id);
goto out;
}
if (cpu_has_vp) {
- mips_cm_lock_other(0, core, vpe_id, CM_GCR_Cx_OTHER_BLOCK_LOCAL);
+ mips_cm_lock_other(cluster, core, vpe_id,
+ CM_GCR_Cx_OTHER_BLOCK_LOCAL);
core_entry = CKSEG1ADDR((unsigned long)mips_cps_core_entry);
write_gcr_co_reset_base(core_entry);
mips_cm_unlock_other();
@@ -543,11 +702,15 @@ static void wait_for_sibling_halt(void *ptr_cpu)
static void cps_cpu_die(unsigned int cpu)
{
+ unsigned int cluster = cpu_cluster(&cpu_data[cpu]);
unsigned core = cpu_core(&cpu_data[cpu]);
unsigned int vpe_id = cpu_vpe_id(&cpu_data[cpu]);
ktime_t fail_time;
unsigned stat;
int err;
+ struct cluster_boot_config *cluster_cfg;
+
+ cluster_cfg = &mips_cps_cluster_bootcfg[cluster];
/* Wait for the cpu to choose its way out */
if (!cpu_wait_death(cpu, 5)) {
@@ -605,7 +768,7 @@ static void cps_cpu_die(unsigned int cpu)
} while (1);
/* Indicate the core is powered off */
- bitmap_clear(core_power, core, 1);
+ bitmap_clear(cluster_cfg->core_power, core, 1);
} else if (cpu_has_mipsmt) {
/*
* Have a CPU with access to the offlined CPUs registers wait
--
2.17.1
From: Paul Burton <[email protected]>
In a multi-cluster MIPS system we have multiple GICs - one in each
cluster - each of which has its own independent counter. The counters in
each GIC are not synchronised in any way, so they can drift relative to
one another through the lifetime of the system. This is problematic for
a clocksource which ought to be global.
Avoid problems by always accessing cluster 0's counter, using
cross-cluster register access. This adds overhead so we only do so on
systems where we actually have CPUs present in multiple clusters.
For now, be extra conservative and don't use gic counter for vdso or
sched_clock in this case.
Signed-off-by: Paul Burton <[email protected]>
Signed-off-by: Chao-ying Fu <[email protected]>
Signed-off-by: Dragan Mladjenovic <[email protected]>
diff --git a/drivers/clocksource/mips-gic-timer.c b/drivers/clocksource/mips-gic-timer.c
index be4175f415ba..6632d314a2c0 100644
--- a/drivers/clocksource/mips-gic-timer.c
+++ b/drivers/clocksource/mips-gic-timer.c
@@ -170,6 +170,37 @@ static u64 gic_hpt_read(struct clocksource *cs)
return gic_read_count();
}
+static u64 gic_hpt_read_multicluster(struct clocksource *cs)
+{
+ unsigned int hi, hi2, lo;
+ u64 count;
+
+ mips_cm_lock_other(0, 0, 0, CM_GCR_Cx_OTHER_BLOCK_GLOBAL);
+
+ if (mips_cm_is64) {
+ count = read_gic_redir_counter();
+ goto out;
+ }
+
+ hi = read_gic_redir_counter_32h();
+ while (true) {
+ lo = read_gic_redir_counter_32l();
+
+ /* If hi didn't change then lo didn't wrap & we're done */
+ hi2 = read_gic_redir_counter_32h();
+ if (hi2 == hi)
+ break;
+
+ /* Otherwise, repeat with the latest hi value */
+ hi = hi2;
+ }
+
+ count = (((u64)hi) << 32) + lo;
+out:
+ mips_cm_unlock_other();
+ return count;
+}
+
static struct clocksource gic_clocksource = {
.name = "GIC",
.read = gic_hpt_read,
@@ -204,6 +235,11 @@ static int __init __gic_clocksource_init(void)
/* Calculate a somewhat reasonable rating value. */
gic_clocksource.rating = 200 + gic_frequency / 10000000;
+ if (mips_cps_multicluster_cpus()) {
+ gic_clocksource.read = &gic_hpt_read_multicluster;
+ gic_clocksource.vdso_clock_mode = VDSO_CLOCKMODE_NONE;
+ }
+
ret = clocksource_register_hz(&gic_clocksource, gic_frequency);
if (ret < 0)
pr_warn("Unable to register clocksource\n");
@@ -262,7 +298,8 @@ static int __init gic_clocksource_of_init(struct device_node *node)
* stable CPU frequency or on the platforms with CM3 and CPU frequency
* change performed by the CPC core clocks divider.
*/
- if (mips_cm_revision() >= CM_REV_CM3 || !IS_ENABLED(CONFIG_CPU_FREQ)) {
+ if ((mips_cm_revision() >= CM_REV_CM3 || !IS_ENABLED(CONFIG_CPU_FREQ)) &&
+ !mips_cps_multicluster_cpus()) {
sched_clock_register(mips_cm_is64 ?
gic_read_count_64 : gic_read_count_2x32,
64, gic_frequency);
--
2.17.1
From: Paul Burton <[email protected]>
A few pieces of code in the MIPS GIC driver operate on the GIC local
register block for each online CPU, accessing each via the GIC's
other/redirect register block. This patch abstracts the process of
iterating over online CPUs & configuring the other/redirect region to
access their registers through a new gic_with_each_online_cpu() macro.
This simplifies users of the new macro slightly, and more importantly
prepares us for handling multi-cluster systems where the register
configuration will be done via the CM's GCR_CL_REDIRECT register. By
abstracting all other/redirect block configuration through this macro,
and the __gic_with_next_online_cpu() function which backs it, users will
trivially gain support for multi-cluster when it is implemented in
__gic_with_next_online_cpu().
Signed-off-by: Paul Burton <[email protected]>
Signed-off-by: Chao-ying Fu <[email protected]>
Signed-off-by: Dragan Mladjenovic <[email protected]>
diff --git a/drivers/irqchip/irq-mips-gic.c b/drivers/irqchip/irq-mips-gic.c
index ff89b36267dd..4872bebe24cf 100644
--- a/drivers/irqchip/irq-mips-gic.c
+++ b/drivers/irqchip/irq-mips-gic.c
@@ -65,6 +65,45 @@ static struct gic_all_vpes_chip_data {
bool mask;
} gic_all_vpes_chip_data[GIC_NUM_LOCAL_INTRS];
+static int __gic_with_next_online_cpu(int prev)
+{
+ unsigned int cpu;
+
+ /* Discover the next online CPU */
+ cpu = cpumask_next(prev, cpu_online_mask);
+
+ /* If there isn't one, we're done */
+ if (cpu >= nr_cpu_ids)
+ return cpu;
+
+ /*
+ * Lock access to the next CPU's GIC local register block.
+ *
+ * In the single cluster case we simply set GIC_VL_OTHER. The caller
+ * holds gic_lock so nothing can clobber the value we write.
+ */
+ write_gic_vl_other(mips_cm_vp_id(cpu));
+
+ return cpu;
+}
+
+/**
+ * gic_with_each_online_cpu() - Iterate over online CPUs, access local registers
+ * @cpu: An integer variable to hold the current CPU number
+ *
+ * Iterate over online CPUs & configure the other/redirect register region to
+ * access each CPUs GIC local register block, which can be accessed from the
+ * loop body using read_gic_vo_*() or write_gic_vo_*() accessor functions or
+ * their derivatives.
+ *
+ * The caller must hold gic_lock throughout the loop, such that GIC_VL_OTHER
+ * cannot be clobbered.
+ */
+#define gic_with_each_online_cpu(cpu) \
+ for ((cpu) = __gic_with_next_online_cpu(-1); \
+ (cpu) = __gic_with_next_online_cpu(cpu), \
+ (cpu) < nr_cpu_ids;)
+
static void gic_clear_pcpu_masks(unsigned int intr)
{
unsigned int i;
@@ -357,10 +396,8 @@ static void gic_mask_local_irq_all_vpes(struct irq_data *d)
cd->mask = false;
spin_lock_irqsave(&gic_lock, flags);
- for_each_online_cpu(cpu) {
- write_gic_vl_other(mips_cm_vp_id(cpu));
+ gic_with_each_online_cpu(cpu)
write_gic_vo_rmask(BIT(intr));
- }
spin_unlock_irqrestore(&gic_lock, flags);
}
@@ -375,10 +412,8 @@ static void gic_unmask_local_irq_all_vpes(struct irq_data *d)
cd->mask = true;
spin_lock_irqsave(&gic_lock, flags);
- for_each_online_cpu(cpu) {
- write_gic_vl_other(mips_cm_vp_id(cpu));
+ gic_with_each_online_cpu(cpu)
write_gic_vo_smask(BIT(intr));
- }
spin_unlock_irqrestore(&gic_lock, flags);
}
@@ -532,10 +567,8 @@ static int gic_irq_domain_map(struct irq_domain *d, unsigned int virq,
return -EPERM;
spin_lock_irqsave(&gic_lock, flags);
- for_each_online_cpu(cpu) {
- write_gic_vl_other(mips_cm_vp_id(cpu));
+ gic_with_each_online_cpu(cpu)
write_gic_vo_map(mips_gic_vx_map_reg(intr), map);
- }
spin_unlock_irqrestore(&gic_lock, flags);
return 0;
--
2.17.1
From: Paul Burton <[email protected]>
When >= CM3.5 output cluster number.
Signed-off-by: Paul Burton <[email protected]>
Signed-off-by: Dragan Mladjenovic <[email protected]>
diff --git a/arch/mips/kernel/proc.c b/arch/mips/kernel/proc.c
index bb43bf850314..a66e7705315d 100644
--- a/arch/mips/kernel/proc.c
+++ b/arch/mips/kernel/proc.c
@@ -12,6 +12,7 @@
#include <asm/cpu.h>
#include <asm/cpu-features.h>
#include <asm/idle.h>
+#include <asm/mips-cps.h>
#include <asm/mipsregs.h>
#include <asm/processor.h>
#include <asm/prom.h>
@@ -282,6 +283,8 @@ static int show_cpuinfo(struct seq_file *m, void *v)
seq_printf(m, "kscratch registers\t: %d\n",
hweight8(cpu_data[n].kscratch_mask));
seq_printf(m, "package\t\t\t: %d\n", cpu_data[n].package);
+ if (mips_cm_revision() >= CM_REV_CM3_5)
+ seq_printf(m, "cluster\t\t\t: %d\n", cpu_cluster(&cpu_data[n]));
seq_printf(m, "core\t\t\t: %d\n", cpu_core(&cpu_data[n]));
#if defined(CONFIG_MIPS_MT_SMP) || defined(CONFIG_CPU_MIPSR6)
--
2.17.1
From: Paul Burton <[email protected]>
In multi-cluster MIPS I6500 systems we have a GIC in each cluster, each
with its own counter. When a cluster powers up the counter will be
stopped, with the COUNTSTOP bit set in the GIC_CONFIG register.
In single cluster systems it has been fine for us to clear COUNTSTOP
once in gic_clocksource_of_init() in order to start the counter, since
with only one cluster we know that we won't be resetting that cluster's
GIC at any point (ignoring suspend/resume cycles which would need to
handle clearing COUNTSTOP in the resume path). Once we support
multi-cluster systems this will only have started the counter in the
boot cluster, and any CPUs in other clusters will find their counter
stopped which will break the GIC clock_event_device.
Resolve this by having CPUs clear the COUNTSTOP bit when they come
online, using the existing gic_starting_cpu() CPU hotplug callback. This
will allow CPUs in secondary clusters to ensure that the cluster's GIC
counter is running as expected.
Signed-off-by: Paul Burton <[email protected]>
Signed-off-by: Chao-ying Fu <[email protected]>
Signed-off-by: Dragan Mladjenovic <[email protected]>
diff --git a/drivers/clocksource/mips-gic-timer.c b/drivers/clocksource/mips-gic-timer.c
index 6632d314a2c0..90a736a13115 100644
--- a/drivers/clocksource/mips-gic-timer.c
+++ b/drivers/clocksource/mips-gic-timer.c
@@ -119,6 +119,9 @@ static void gic_update_frequency(void *data)
static int gic_starting_cpu(unsigned int cpu)
{
+ /* Ensure the GIC counter is running */
+ clear_gic_config(GIC_CONFIG_COUNTSTOP);
+
gic_clockevent_cpu_init(cpu, this_cpu_ptr(&gic_clockevent_device));
return 0;
}
@@ -289,9 +292,6 @@ static int __init gic_clocksource_of_init(struct device_node *node)
pr_warn("Unable to register clock notifier\n");
}
- /* And finally start the counter */
- clear_gic_config(GIC_CONFIG_COUNTSTOP);
-
/*
* It's safe to use the MIPS GIC timer as a sched clock source only if
* its ticks are stable, which is true on either the platforms with
--
2.17.1
From: Chao-ying Fu <[email protected]>
In multi-cluster MIPS I6500 systems we have a GIC per cluster. The
default shared interrupt setup that we configure in gic_of_init() will
only apply to the GIC in the cluster containing the boot CPU, leaving
the GICs of other clusters unconfigured. Similarly configure other
clusters here.
Signed-off-by: Chao-ying Fu <[email protected]>
Signed-off-by: Dragan Mladjenovic <[email protected]>
diff --git a/drivers/irqchip/irq-mips-gic.c b/drivers/irqchip/irq-mips-gic.c
index 89a3c6d04e09..f692392666a2 100644
--- a/drivers/irqchip/irq-mips-gic.c
+++ b/drivers/irqchip/irq-mips-gic.c
@@ -741,7 +741,7 @@ static int gic_cpu_startup(unsigned int cpu)
static int __init gic_of_init(struct device_node *node,
struct device_node *parent)
{
- unsigned int cpu_vec, i, gicconfig, v[2], num_ipis;
+ unsigned int cpu_vec, i, gicconfig, v[2], num_ipis, cl, nclusters;
unsigned long reserved;
phys_addr_t gic_base;
struct resource res;
@@ -860,11 +860,30 @@ static int __init gic_of_init(struct device_node *node,
board_bind_eic_interrupt = &gic_bind_eic_interrupt;
- /* Setup defaults */
- for (i = 0; i < gic_shared_intrs; i++) {
- change_gic_pol(i, GIC_POL_ACTIVE_HIGH);
- change_gic_trig(i, GIC_TRIG_LEVEL);
- write_gic_rmask(i);
+ /*
+ * Initialise each cluster's GIC shared registers to sane default
+ * values.
+ * Otherwise, the IPI set up will be erased if we move code
+ * to gic_cpu_startup for each cpu.
+ */
+ nclusters = mips_cps_numclusters();
+ for (cl = 0; cl < nclusters; cl++) {
+ if (cl == cpu_cluster(¤t_cpu_data)) {
+ for (i = 0; i < gic_shared_intrs; i++) {
+ change_gic_pol(i, GIC_POL_ACTIVE_HIGH);
+ change_gic_trig(i, GIC_TRIG_LEVEL);
+ write_gic_rmask(i);
+ }
+ } else {
+ mips_cm_lock_other(cl, 0, 0,
+ CM_GCR_Cx_OTHER_BLOCK_GLOBAL);
+ for (i = 0; i < gic_shared_intrs; i++) {
+ change_gic_redir_pol(i, GIC_POL_ACTIVE_HIGH);
+ change_gic_redir_trig(i, GIC_TRIG_LEVEL);
+ write_gic_redir_rmask(i);
+ }
+ mips_cm_unlock_other();
+ }
}
return cpuhp_setup_state(CPUHP_AP_IRQ_MIPS_GIC_STARTING,
--
2.17.1
On Wed, 25 May 2022 13:10:24 +0100,
Dragan Mladjenovic <[email protected]> wrote:
>
> From: Paul Burton <[email protected]>
>
> 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.
--
Without deviation from the norm, progress is not possible.
On Wed, 25 May 2022 13:10:21 +0100,
Dragan Mladjenovic <[email protected]> wrote:
>
> From: Paul Burton <[email protected]>
>
> A few pieces of code in the MIPS GIC driver operate on the GIC local
> register block for each online CPU, accessing each via the GIC's
> other/redirect register block. This patch abstracts the process of
> iterating over online CPUs & configuring the other/redirect region to
> access their registers through a new gic_with_each_online_cpu() macro.
>
> This simplifies users of the new macro slightly, and more importantly
> prepares us for handling multi-cluster systems where the register
> configuration will be done via the CM's GCR_CL_REDIRECT register. By
> abstracting all other/redirect block configuration through this macro,
> and the __gic_with_next_online_cpu() function which backs it, users will
> trivially gain support for multi-cluster when it is implemented in
> __gic_with_next_online_cpu().
>
> Signed-off-by: Paul Burton <[email protected]>
> Signed-off-by: Chao-ying Fu <[email protected]>
> Signed-off-by: Dragan Mladjenovic <[email protected]>
>
> diff --git a/drivers/irqchip/irq-mips-gic.c b/drivers/irqchip/irq-mips-gic.c
> index ff89b36267dd..4872bebe24cf 100644
> --- a/drivers/irqchip/irq-mips-gic.c
> +++ b/drivers/irqchip/irq-mips-gic.c
> @@ -65,6 +65,45 @@ static struct gic_all_vpes_chip_data {
> bool mask;
> } gic_all_vpes_chip_data[GIC_NUM_LOCAL_INTRS];
>
> +static int __gic_with_next_online_cpu(int prev)
> +{
> + unsigned int cpu;
> +
> + /* Discover the next online CPU */
> + cpu = cpumask_next(prev, cpu_online_mask);
> +
> + /* If there isn't one, we're done */
> + if (cpu >= nr_cpu_ids)
> + return cpu;
> +
> + /*
> + * Lock access to the next CPU's GIC local register block.
> + *
> + * In the single cluster case we simply set GIC_VL_OTHER. The caller
> + * holds gic_lock so nothing can clobber the value we write.
> + */
> + write_gic_vl_other(mips_cm_vp_id(cpu));
> +
> + return cpu;
> +}
> +
> +/**
> + * gic_with_each_online_cpu() - Iterate over online CPUs, access local registers
> + * @cpu: An integer variable to hold the current CPU number
> + *
> + * Iterate over online CPUs & configure the other/redirect register region to
> + * access each CPUs GIC local register block, which can be accessed from the
> + * loop body using read_gic_vo_*() or write_gic_vo_*() accessor functions or
> + * their derivatives.
> + *
> + * The caller must hold gic_lock throughout the loop, such that GIC_VL_OTHER
> + * cannot be clobbered.
> + */
> +#define gic_with_each_online_cpu(cpu) \
nit: please keep the kernel convention of using 'for_each'. This makes
it far easier to grep for such iterators when doing bulk refactoring.
Also, since there is a requirement to hold the gic_lock, please add a
lockdep_assert_held() in the loop so that it can be checked with a
lockdep kernel.
> + for ((cpu) = __gic_with_next_online_cpu(-1); \
> + (cpu) = __gic_with_next_online_cpu(cpu), \
> + (cpu) < nr_cpu_ids;)
> +
> static void gic_clear_pcpu_masks(unsigned int intr)
> {
> unsigned int i;
> @@ -357,10 +396,8 @@ static void gic_mask_local_irq_all_vpes(struct irq_data *d)
> cd->mask = false;
>
> spin_lock_irqsave(&gic_lock, flags);
> - for_each_online_cpu(cpu) {
> - write_gic_vl_other(mips_cm_vp_id(cpu));
> + gic_with_each_online_cpu(cpu)
> write_gic_vo_rmask(BIT(intr));
> - }
> spin_unlock_irqrestore(&gic_lock, flags);
> }
>
> @@ -375,10 +412,8 @@ static void gic_unmask_local_irq_all_vpes(struct irq_data *d)
> cd->mask = true;
>
> spin_lock_irqsave(&gic_lock, flags);
> - for_each_online_cpu(cpu) {
> - write_gic_vl_other(mips_cm_vp_id(cpu));
> + gic_with_each_online_cpu(cpu)
> write_gic_vo_smask(BIT(intr));
> - }
> spin_unlock_irqrestore(&gic_lock, flags);
> }
>
> @@ -532,10 +567,8 @@ static int gic_irq_domain_map(struct irq_domain *d, unsigned int virq,
> return -EPERM;
>
> spin_lock_irqsave(&gic_lock, flags);
> - for_each_online_cpu(cpu) {
> - write_gic_vl_other(mips_cm_vp_id(cpu));
> + gic_with_each_online_cpu(cpu)
> write_gic_vo_map(mips_gic_vx_map_reg(intr), map);
> - }
> spin_unlock_irqrestore(&gic_lock, flags);
>
> return 0;
Thanks,
M.
--
Without deviation from the norm, progress is not possible.
On Wed, 25 May 2022 13:10:29 +0100,
Dragan Mladjenovic <[email protected]> wrote:
>
> From: Paul Burton <[email protected]>
>
> When >= CM3.5 output cluster number.
>
> Signed-off-by: Paul Burton <[email protected]>
> Signed-off-by: Dragan Mladjenovic <[email protected]>
>
> diff --git a/arch/mips/kernel/proc.c b/arch/mips/kernel/proc.c
> index bb43bf850314..a66e7705315d 100644
> --- a/arch/mips/kernel/proc.c
> +++ b/arch/mips/kernel/proc.c
> @@ -12,6 +12,7 @@
> #include <asm/cpu.h>
> #include <asm/cpu-features.h>
> #include <asm/idle.h>
> +#include <asm/mips-cps.h>
> #include <asm/mipsregs.h>
> #include <asm/processor.h>
> #include <asm/prom.h>
> @@ -282,6 +283,8 @@ static int show_cpuinfo(struct seq_file *m, void *v)
> seq_printf(m, "kscratch registers\t: %d\n",
> hweight8(cpu_data[n].kscratch_mask));
> seq_printf(m, "package\t\t\t: %d\n", cpu_data[n].package);
> + if (mips_cm_revision() >= CM_REV_CM3_5)
> + seq_printf(m, "cluster\t\t\t: %d\n", cpu_cluster(&cpu_data[n]));
> seq_printf(m, "core\t\t\t: %d\n", cpu_core(&cpu_data[n]));
>
> #if defined(CONFIG_MIPS_MT_SMP) || defined(CONFIG_CPU_MIPSR6)
This will break userspace. Please don't do that.
M.
--
Without deviation from the norm, progress is not possible.
在 2022/6/6 12:47, Marc Zyngier 写道:
> On Wed, 25 May 2022 13:10:24 +0100,
> Dragan Mladjenovic <[email protected]> wrote:
>> From: Paul Burton <[email protected]>
>>
>> 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.
>
在 2022/6/6 14:14, Marc Zyngier 写道:
> On Wed, 25 May 2022 13:10:29 +0100,
> Dragan Mladjenovic <[email protected]> wrote:
>> From: Paul Burton <[email protected]>
>>
>> When >= CM3.5 output cluster number.
>>
>> Signed-off-by: Paul Burton <[email protected]>
>> Signed-off-by: Dragan Mladjenovic <[email protected]>
>>
>> diff --git a/arch/mips/kernel/proc.c b/arch/mips/kernel/proc.c
>> index bb43bf850314..a66e7705315d 100644
>> --- a/arch/mips/kernel/proc.c
>> +++ b/arch/mips/kernel/proc.c
>> @@ -12,6 +12,7 @@
>> #include <asm/cpu.h>
>> #include <asm/cpu-features.h>
>> #include <asm/idle.h>
>> +#include <asm/mips-cps.h>
>> #include <asm/mipsregs.h>
>> #include <asm/processor.h>
>> #include <asm/prom.h>
>> @@ -282,6 +283,8 @@ static int show_cpuinfo(struct seq_file *m, void *v)
>> seq_printf(m, "kscratch registers\t: %d\n",
>> hweight8(cpu_data[n].kscratch_mask));
>> seq_printf(m, "package\t\t\t: %d\n", cpu_data[n].package);
>> + if (mips_cm_revision() >= CM_REV_CM3_5)
>> + seq_printf(m, "cluster\t\t\t: %d\n", cpu_cluster(&cpu_data[n]));
>> seq_printf(m, "core\t\t\t: %d\n", cpu_core(&cpu_data[n]));
>>
>> #if defined(CONFIG_MIPS_MT_SMP) || defined(CONFIG_CPU_MIPSR6)
> This will break userspace. Please don't do that.
Hmm, userspace always parse cpuinfo line by line so I guess it won't be
a problem if we add a new line here.
We accumulated some new additions to cpuinfo in past years and there
was never a single complain for that.
Though I don't like the idea of using CM version to switch on the cluster
display....
Thanks
- Jiaxun
>
> M.
>
On Tue, 07 Jun 2022 19:23:02 +0100,
Jiaxun Yang <[email protected]> wrote:
>
>
>
> 在 2022/6/6 12:47, Marc Zyngier 写道:
> > On Wed, 25 May 2022 13:10:24 +0100,
> > Dragan Mladjenovic <[email protected]> wrote:
> >> From: Paul Burton <[email protected]>
> >>
> >> 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.
--
Without deviation from the norm, progress is not possible.
On Tue, 07 Jun 2022 19:27:36 +0100,
Jiaxun Yang <[email protected]> wrote:
>
>
>
> 在 2022/6/6 14:14, Marc Zyngier 写道:
> > On Wed, 25 May 2022 13:10:29 +0100,
> > Dragan Mladjenovic <[email protected]> wrote:
> >> From: Paul Burton <[email protected]>
> >>
> >> When >= CM3.5 output cluster number.
> >>
> >> Signed-off-by: Paul Burton <[email protected]>
> >> Signed-off-by: Dragan Mladjenovic <[email protected]>
> >>
> >> diff --git a/arch/mips/kernel/proc.c b/arch/mips/kernel/proc.c
> >> index bb43bf850314..a66e7705315d 100644
> >> --- a/arch/mips/kernel/proc.c
> >> +++ b/arch/mips/kernel/proc.c
> >> @@ -12,6 +12,7 @@
> >> #include <asm/cpu.h>
> >> #include <asm/cpu-features.h>
> >> #include <asm/idle.h>
> >> +#include <asm/mips-cps.h>
> >> #include <asm/mipsregs.h>
> >> #include <asm/processor.h>
> >> #include <asm/prom.h>
> >> @@ -282,6 +283,8 @@ static int show_cpuinfo(struct seq_file *m, void *v)
> >> seq_printf(m, "kscratch registers\t: %d\n",
> >> hweight8(cpu_data[n].kscratch_mask));
> >> seq_printf(m, "package\t\t\t: %d\n", cpu_data[n].package);
> >> + if (mips_cm_revision() >= CM_REV_CM3_5)
> >> + seq_printf(m, "cluster\t\t\t: %d\n", cpu_cluster(&cpu_data[n]));
> >> seq_printf(m, "core\t\t\t: %d\n", cpu_core(&cpu_data[n]));
> >> #if defined(CONFIG_MIPS_MT_SMP) || defined(CONFIG_CPU_MIPSR6)
> > This will break userspace. Please don't do that.
> Hmm, userspace always parse cpuinfo line by line so I guess it won't be
> a problem if we add a new line here.
Given that you cannot audit all userspace, this is a pretty bold
statement ;-).
> We accumulated some new additions to cpuinfo in past years and there
> was never a single complain for that.
Consider yourself lucky. Other architectures have see tons of
regressions as soon as a /proc file was updated.
> Though I don't like the idea of using CM version to switch on the
> cluster display....
But the other question is *what information* does this bring to a
user? Very little, if at all. And if you want to this to be exhaustive
and usable by SW that runs across architectures, then providing
accurate and parseable topology information is the way to do it, using
a standard interface:
maz@valley-girl:~$ ls -1 /sys/devices/system/cpu/cpu0/topology/
cluster_cpus
cluster_cpus_list
cluster_id
core_cpus
core_cpus_list
core_id
core_siblings
core_siblings_list
package_cpus
package_cpus_list
physical_package_id
thread_siblings
thread_siblings_list
Thanks,
M.
--
Without deviation from the norm, progress is not possible.
在2022年6月8日六月 上午7:05,Marc Zyngier写道:
> On Tue, 07 Jun 2022 19:23:02 +0100,
> Jiaxun Yang <[email protected]> wrote:
>>
>>
>>
>> 在 2022/6/6 12:47, Marc Zyngier 写道:
>> > On Wed, 25 May 2022 13:10:24 +0100,
>> > Dragan Mladjenovic <[email protected]> wrote:
>> >> From: Paul Burton <[email protected]>
>> >>
>> >> 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.
--
- Jiaxun
On Thu, 09 Jun 2022 11:14:01 +0100,
"Jiaxun Yang" <[email protected]> wrote:
>
>
>
> 在2022年6月8日六月 上午7:05,Marc Zyngier写道:
> > On Tue, 07 Jun 2022 19:23:02 +0100,
> > Jiaxun Yang <[email protected]> wrote:
> >>
> >>
> >>
> >> 在 2022/6/6 12:47, Marc Zyngier 写道:
> >> > On Wed, 25 May 2022 13:10:24 +0100,
> >> > Dragan Mladjenovic <[email protected]> wrote:
> >> >> From: Paul Burton <[email protected]>
> >> >>
> >> >> 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.
--
Without deviation from the norm, progress is not possible.
On 25-May-22 14:10, Dragan Mladjenovic wrote:
> From: Paul Burton <[email protected]>
>
> In a multi-cluster MIPS system we have multiple GICs - one in each
> cluster - each of which has its own independent counter. The counters in
> each GIC are not synchronised in any way, so they can drift relative to
> one another through the lifetime of the system. This is problematic for
> a clocksource which ought to be global.
>
> Avoid problems by always accessing cluster 0's counter, using
> cross-cluster register access. This adds overhead so we only do so on
> systems where we actually have CPUs present in multiple clusters.
> For now, be extra conservative and don't use gic counter for vdso or
> sched_clock in this case.
>
> Signed-off-by: Paul Burton <[email protected]>
> Signed-off-by: Chao-ying Fu <[email protected]>
> Signed-off-by: Dragan Mladjenovic <[email protected]>
>
> diff --git a/drivers/clocksource/mips-gic-timer.c b/drivers/clocksource/mips-gic-timer.c
> index be4175f415ba..6632d314a2c0 100644
> --- a/drivers/clocksource/mips-gic-timer.c
> +++ b/drivers/clocksource/mips-gic-timer.c
> @@ -170,6 +170,37 @@ static u64 gic_hpt_read(struct clocksource *cs)
> return gic_read_count();
> }
>
> +static u64 gic_hpt_read_multicluster(struct clocksource *cs)
> +{
> + unsigned int hi, hi2, lo;
> + u64 count;
> +
> + mips_cm_lock_other(0, 0, 0, CM_GCR_Cx_OTHER_BLOCK_GLOBAL);
> +
> + if (mips_cm_is64) {
> + count = read_gic_redir_counter();
> + goto out;
> + }
> +
> + hi = read_gic_redir_counter_32h();
> + while (true) {
> + lo = read_gic_redir_counter_32l();
> +
> + /* If hi didn't change then lo didn't wrap & we're done */
> + hi2 = read_gic_redir_counter_32h();
> + if (hi2 == hi)
> + break;
> +
> + /* Otherwise, repeat with the latest hi value */
> + hi = hi2;
> + }
> +
> + count = (((u64)hi) << 32) + lo;
> +out:
> + mips_cm_unlock_other();
> + return count;
> +}
> +
> static struct clocksource gic_clocksource = {
> .name = "GIC",
> .read = gic_hpt_read,
> @@ -204,6 +235,11 @@ static int __init __gic_clocksource_init(void)
> /* Calculate a somewhat reasonable rating value. */
> gic_clocksource.rating = 200 + gic_frequency / 10000000;
>
> + if (mips_cps_multicluster_cpus()) {
> + gic_clocksource.read = &gic_hpt_read_multicluster;
> + gic_clocksource.vdso_clock_mode = VDSO_CLOCKMODE_NONE;
> + }
> +
> ret = clocksource_register_hz(&gic_clocksource, gic_frequency);
> if (ret < 0)
> pr_warn("Unable to register clocksource\n");
> @@ -262,7 +298,8 @@ static int __init gic_clocksource_of_init(struct device_node *node)
> * stable CPU frequency or on the platforms with CM3 and CPU frequency
> * change performed by the CPC core clocks divider.
> */
> - if (mips_cm_revision() >= CM_REV_CM3 || !IS_ENABLED(CONFIG_CPU_FREQ)) {
> + if ((mips_cm_revision() >= CM_REV_CM3 || !IS_ENABLED(CONFIG_CPU_FREQ)) &&
> + !mips_cps_multicluster_cpus()) {
> sched_clock_register(mips_cm_is64 ?
> gic_read_count_64 : gic_read_count_2x32,
> 64, gic_frequency);
Hi,
I was expecting some comments on this, but I'll ask first. We now taking
a conservative approach of not using gic as sched_clock in multicluster
case. Is this necessary or can sched_clock tolerate a fixed delta
between clocks on different cpu clusters?
Best regards,
Dragan
On 2022-06-27 15:17, Dragan Mladjenovic wrote:
> On 25-May-22 14:10, Dragan Mladjenovic wrote:
>> From: Paul Burton <[email protected]>
>>
>> In a multi-cluster MIPS system we have multiple GICs - one in each
>> cluster - each of which has its own independent counter. The counters
>> in
>> each GIC are not synchronised in any way, so they can drift relative
>> to
>> one another through the lifetime of the system. This is problematic
>> for
>> a clocksource which ought to be global.
>>
>> Avoid problems by always accessing cluster 0's counter, using
>> cross-cluster register access. This adds overhead so we only do so on
>> systems where we actually have CPUs present in multiple clusters.
>> For now, be extra conservative and don't use gic counter for vdso or
>> sched_clock in this case.
>>
>> Signed-off-by: Paul Burton <[email protected]>
>> Signed-off-by: Chao-ying Fu <[email protected]>
>> Signed-off-by: Dragan Mladjenovic <[email protected]>
>>
>> diff --git a/drivers/clocksource/mips-gic-timer.c
>> b/drivers/clocksource/mips-gic-timer.c
>> index be4175f415ba..6632d314a2c0 100644
>> --- a/drivers/clocksource/mips-gic-timer.c
>> +++ b/drivers/clocksource/mips-gic-timer.c
>> @@ -170,6 +170,37 @@ static u64 gic_hpt_read(struct clocksource *cs)
>> return gic_read_count();
>> }
>> +static u64 gic_hpt_read_multicluster(struct clocksource *cs)
>> +{
>> + unsigned int hi, hi2, lo;
>> + u64 count;
>> +
>> + mips_cm_lock_other(0, 0, 0, CM_GCR_Cx_OTHER_BLOCK_GLOBAL);
>> +
>> + if (mips_cm_is64) {
>> + count = read_gic_redir_counter();
>> + goto out;
>> + }
>> +
>> + hi = read_gic_redir_counter_32h();
>> + while (true) {
>> + lo = read_gic_redir_counter_32l();
>> +
>> + /* If hi didn't change then lo didn't wrap & we're done */
>> + hi2 = read_gic_redir_counter_32h();
>> + if (hi2 == hi)
>> + break;
>> +
>> + /* Otherwise, repeat with the latest hi value */
>> + hi = hi2;
>> + }
>> +
>> + count = (((u64)hi) << 32) + lo;
>> +out:
>> + mips_cm_unlock_other();
>> + return count;
>> +}
>> +
>> static struct clocksource gic_clocksource = {
>> .name = "GIC",
>> .read = gic_hpt_read,
>> @@ -204,6 +235,11 @@ static int __init __gic_clocksource_init(void)
>> /* Calculate a somewhat reasonable rating value. */
>> gic_clocksource.rating = 200 + gic_frequency / 10000000;
>> + if (mips_cps_multicluster_cpus()) {
>> + gic_clocksource.read = &gic_hpt_read_multicluster;
>> + gic_clocksource.vdso_clock_mode = VDSO_CLOCKMODE_NONE;
>> + }
>> +
>> ret = clocksource_register_hz(&gic_clocksource, gic_frequency);
>> if (ret < 0)
>> pr_warn("Unable to register clocksource\n");
>> @@ -262,7 +298,8 @@ static int __init gic_clocksource_of_init(struct
>> device_node *node)
>> * stable CPU frequency or on the platforms with CM3 and CPU
>> frequency
>> * change performed by the CPC core clocks divider.
>> */
>> - if (mips_cm_revision() >= CM_REV_CM3 ||
>> !IS_ENABLED(CONFIG_CPU_FREQ)) {
>> + if ((mips_cm_revision() >= CM_REV_CM3 ||
>> !IS_ENABLED(CONFIG_CPU_FREQ)) &&
>> + !mips_cps_multicluster_cpus()) {
>> sched_clock_register(mips_cm_is64 ?
>> gic_read_count_64 : gic_read_count_2x32,
>> 64, gic_frequency);
>
> Hi,
>
> I was expecting some comments on this, but I'll ask first. We now
> taking a conservative approach of not using gic as sched_clock in
> multicluster case. Is this necessary or can sched_clock tolerate a
> fixed delta between clocks on different cpu clusters?
I don't think that's wise. We generally go into all sort of
troubles to keep sched_clock() strictly identical between CPUs,
and there are tons of things that rely on this (the scheduler
itself, but any sort of tracing...). You just have to grep
for the various use cases.
A consequence of the above is that the kernel can (and will)
snapshot a sched_clock value, and compare it to the value on
the current CPU. Imagine what happens if the difference is
negative...
So I don't know what the deal is with the MIPS GIC, but if any
of the above can happen, you're doomed.
M.
--
Jazz is not dead. It just smells funny...