/* * Use our own MPIDR accessors as the generic ones in asm/cputype.h have * __attribute_const__ and we don't want the compiler to assume any * constness here as the value _does_ change along some code paths.
*/
/* * Our state has been saved at this point. Let's release our * inbound CPU.
*/
mcpm_set_entry_vector(ib_cpu, ib_cluster, cpu_resume);
sev();
/* * From this point, we must assume that our counterpart CPU might * have taken over in its parallel world already, as if execution * just returned from cpu_suspend(). It is therefore important to * be very careful not to make any change the other guy is not * expecting. This is why we need stack isolation. * * Fancy under cover tasks could be performed here. For now * we have none.
*/
/* * Let's wait until our inbound is alive.
*/ while (!handshake) {
wfe();
smp_mb();
}
/* Let's put ourself down. */
mcpm_cpu_power_down();
/* should never get here */
BUG();
}
/* * Stack isolation. To ensure 'current' remains valid, we just use another * piece of our thread's stack space which should be fairly lightly used. * The selected area starts just above the thread_info structure located * at the very bottom of the stack, aligned to a cache line, and indexed * with the cluster number.
*/ #define STACK_SIZE 512 externvoid call_with_stack(void (*fn)(void *), void *arg, void *sp); staticint bL_switchpoint(unsignedlong _arg)
{ unsignedint mpidr = read_mpidr(); unsignedint clusterid = MPIDR_AFFINITY_LEVEL(mpidr, 1); void *stack = current_thread_info() + 1;
stack = PTR_ALIGN(stack, L1_CACHE_BYTES);
stack += clusterid * STACK_SIZE + STACK_SIZE;
call_with_stack(bL_do_switch, (void *)_arg, stack);
BUG();
}
/* * bL_switch_to - Switch to a specific cluster for the current CPU * @new_cluster_id: the ID of the cluster to switch to. * * This function must be called on the CPU to be switched. * Returns 0 on success, else a negative status code.
*/ staticint bL_switch_to(unsignedint new_cluster_id)
{ unsignedint mpidr, this_cpu, that_cpu; unsignedint ob_mpidr, ob_cpu, ob_cluster, ib_mpidr, ib_cpu, ib_cluster; struct completion inbound_alive; longvolatile *handshake_ptr; int ipi_nr, ret;
/* * Let's wake up the inbound CPU now in case it requires some delay * to come online, but leave it gated in our entry vector code.
*/
ret = mcpm_cpu_power_up(ib_cpu, ib_cluster); if (ret) {
pr_err("%s: mcpm_cpu_power_up() returned %d\n", __func__, ret); return ret;
}
/* * Raise a SGI on the inbound CPU to make sure it doesn't stall * in a possible WFI, such as in bL_power_down().
*/
gic_send_sgi(bL_gic_id[ib_cpu][ib_cluster], 0);
/* * Wait for the inbound to come up. This allows for other * tasks to be scheduled in the mean time.
*/
wait_for_completion(&inbound_alive);
mcpm_set_early_poke(ib_cpu, ib_cluster, 0, 0);
/* * From this point we are entering the switch critical zone * and can't take any interrupts anymore.
*/
local_irq_disable();
local_fiq_disable();
trace_cpu_migrate_begin(ktime_get_real_ns(), ob_mpidr);
/* redirect GIC's SGIs to our counterpart */
gic_migrate_target(bL_gic_id[ib_cpu][ib_cluster]);
tick_suspend_local();
ret = cpu_pm_enter();
/* we can not tolerate errors at this point */ if (ret)
panic("%s: cpu_pm_enter() returned %d\n", __func__, ret);
/* Swap the physical CPUs in the logical map for this logical CPU. */
cpu_logical_map(this_cpu) = ib_mpidr;
cpu_logical_map(that_cpu) = ob_mpidr;
/* Let's do the actual CPU switch. */
ret = cpu_suspend((unsignedlong)&handshake_ptr, bL_switchpoint); if (ret > 0)
panic("%s: cpu_suspend() returned %d\n", __func__, ret);
/* We are executing on the inbound CPU at this point */
mpidr = read_mpidr();
pr_debug("after switch: CPU %d MPIDR %#x\n", this_cpu, mpidr);
BUG_ON(mpidr != ib_mpidr);
task = kthread_run_on_cpu(bL_switcher_thread, arg,
cpu, "kswitcher_%d"); if (IS_ERR(task))
pr_err("%s failed for CPU %d\n", __func__, cpu);
return task;
}
/* * bL_switch_request_cb - Switch to a specific cluster for the given CPU, * with completion notification via a callback * * @cpu: the CPU to switch * @new_cluster_id: the ID of the cluster to switch to. * @completer: switch completion callback. if non-NULL, * @completer(@completer_cookie) will be called on completion of * the switch, in non-atomic context. * @completer_cookie: opaque context argument for @completer. * * This function causes a cluster switch on the given CPU by waking up * the appropriate switcher thread. This function may or may not return * before the switch has occurred. * * If a @completer callback function is supplied, it will be called when * the switch is complete. This can be used to determine asynchronously * when the switch is complete, regardless of when bL_switch_request() * returns. When @completer is supplied, no new switch request is permitted * for the affected CPU until after the switch is complete, and @completer * has returned.
*/ int bL_switch_request_cb(unsignedint cpu, unsignedint new_cluster_id,
bL_switch_completion_handler completer, void *completer_cookie)
{ struct bL_thread *t;
if (cpu >= ARRAY_SIZE(bL_threads)) {
pr_err("%s: cpu %d out of bounds\n", __func__, cpu); return -EINVAL;
}
t = &bL_threads[cpu];
if (IS_ERR(t->task)) return PTR_ERR(t->task); if (!t->task) return -ESRCH;
int bL_switcher_register_notifier(struct notifier_block *nb)
{ return blocking_notifier_chain_register(&bL_activation_notifier, nb);
}
EXPORT_SYMBOL_GPL(bL_switcher_register_notifier);
int bL_switcher_unregister_notifier(struct notifier_block *nb)
{ return blocking_notifier_chain_unregister(&bL_activation_notifier, nb);
}
EXPORT_SYMBOL_GPL(bL_switcher_unregister_notifier);
staticint bL_activation_notify(unsignedlong val)
{ int ret;
ret = blocking_notifier_call_chain(&bL_activation_notifier, val, NULL); if (ret & NOTIFY_STOP_MASK)
pr_err("%s: notifier chain failed with status 0x%x\n",
__func__, ret); return notifier_to_errno(ret);
}
staticvoid bL_switcher_restore_cpus(void)
{ int i;
for_each_cpu(i, &bL_switcher_removed_logical_cpus) { struct device *cpu_dev = get_cpu_device(i); int ret = device_online(cpu_dev); if (ret)
dev_err(cpu_dev, "switcher: unable to restore CPU\n");
}
}
staticint bL_switcher_halve_cpus(void)
{ int i, j, cluster_0, gic_id, ret; unsignedint cpu, cluster, mask;
cpumask_t available_cpus;
/* First pass to validate what we have */
mask = 0;
for_each_online_cpu(i) {
cpu = MPIDR_AFFINITY_LEVEL(cpu_logical_map(i), 0);
cluster = MPIDR_AFFINITY_LEVEL(cpu_logical_map(i), 1); if (cluster >= 2) {
pr_err("%s: only dual cluster systems are supported\n", __func__); return -EINVAL;
} if (WARN_ON(cpu >= MAX_CPUS_PER_CLUSTER)) return -EINVAL;
mask |= (1 << cluster);
} if (mask != 3) {
pr_err("%s: no CPU pairing possible\n", __func__); return -EINVAL;
}
/* * Now let's do the pairing. We match each CPU with another CPU * from a different cluster. To get a uniform scheduling behavior * without fiddling with CPU topology and compute capacity data, * we'll use logical CPUs initially belonging to the same cluster.
*/
memset(bL_switcher_cpu_pairing, -1, sizeof(bL_switcher_cpu_pairing));
cpumask_copy(&available_cpus, cpu_online_mask);
cluster_0 = -1;
for_each_cpu(i, &available_cpus) { int match = -1;
cluster = MPIDR_AFFINITY_LEVEL(cpu_logical_map(i), 1); if (cluster_0 == -1)
cluster_0 = cluster; if (cluster != cluster_0) continue;
cpumask_clear_cpu(i, &available_cpus);
for_each_cpu(j, &available_cpus) {
cluster = MPIDR_AFFINITY_LEVEL(cpu_logical_map(j), 1); /* * Let's remember the last match to create "odd" * pairings on purpose in order for other code not * to assume any relation between physical and * logical CPU numbers.
*/ if (cluster != cluster_0)
match = j;
} if (match != -1) {
bL_switcher_cpu_pairing[i] = match;
cpumask_clear_cpu(match, &available_cpus);
pr_info("CPU%d paired with CPU%d\n", i, match);
}
}
/* * Now we disable the unwanted CPUs i.e. everything that has no * pairing information (that includes the pairing counterparts).
*/
cpumask_clear(&bL_switcher_removed_logical_cpus);
for_each_online_cpu(i) {
cpu = MPIDR_AFFINITY_LEVEL(cpu_logical_map(i), 0);
cluster = MPIDR_AFFINITY_LEVEL(cpu_logical_map(i), 1);
/* Let's take note of the GIC ID for this CPU */
gic_id = gic_get_cpu_id(i); if (gic_id < 0) {
pr_err("%s: bad GIC ID for CPU %d\n", __func__, i);
bL_switcher_restore_cpus(); return -EINVAL;
}
bL_gic_id[cpu][cluster] = gic_id;
pr_info("GIC ID for CPU %u cluster %u is %u\n",
cpu, cluster, gic_id);
if (bL_switcher_cpu_pairing[i] != -1) {
bL_switcher_cpu_original_cluster[i] = cluster; continue;
}
ret = device_offline(get_cpu_device(i)); if (ret) {
bL_switcher_restore_cpus(); return ret;
}
cpumask_set_cpu(i, &bL_switcher_removed_logical_cpus);
}
return 0;
}
/* Determine the logical CPU a given physical CPU is grouped on. */ int bL_switcher_get_logical_index(u32 mpidr)
{ int cpu;
if (!bL_switcher_active) return -EUNATCH;
mpidr &= MPIDR_HWID_BITMASK;
for_each_online_cpu(cpu) { int pairing = bL_switcher_cpu_pairing[cpu]; if (pairing == -1) continue; if ((mpidr == cpu_logical_map(cpu)) ||
(mpidr == cpu_logical_map(pairing))) return cpu;
} return -EINVAL;
}
if (bL_activation_notify(BL_NOTIFY_PRE_DISABLE) != 0) {
bL_activation_notify(BL_NOTIFY_POST_ENABLE); goto out;
}
bL_switcher_active = 0;
/* * To deactivate the switcher, we must shut down the switcher * threads to prevent any other requests from being accepted. * Then, if the final cluster for given logical CPU is not the * same as the original one, we'll recreate a switcher thread * just for the purpose of switching the CPU back without any * possibility for interference from external requests.
*/
for_each_online_cpu(cpu) {
t = &bL_threads[cpu];
task = t->task;
t->task = NULL; if (!task || IS_ERR(task)) continue;
kthread_stop(task); /* no more switch may happen on this CPU at this point */
cluster = MPIDR_AFFINITY_LEVEL(cpu_logical_map(cpu), 1); if (cluster == bL_switcher_cpu_original_cluster[cpu]) continue;
init_completion(&t->started);
t->wanted_cluster = bL_switcher_cpu_original_cluster[cpu];
task = bL_switcher_thread_create(cpu, t); if (!IS_ERR(task)) {
wait_for_completion(&t->started);
kthread_stop(task);
cluster = MPIDR_AFFINITY_LEVEL(cpu_logical_map(cpu), 1); if (cluster == bL_switcher_cpu_original_cluster[cpu]) continue;
} /* If execution gets here, we're in trouble. */
pr_crit("%s: unable to restore original cluster for CPU %d\n",
__func__, cpu);
pr_crit("%s: CPU %d can't be restored\n",
__func__, bL_switcher_cpu_pairing[cpu]);
cpumask_clear_cpu(bL_switcher_cpu_pairing[cpu],
&bL_switcher_removed_logical_cpus);
}
/* * Veto any CPU hotplug operation on those CPUs we've removed * while the switcher is active. * We're just not ready to deal with that given the trickery involved.
*/ staticint bL_switcher_cpu_pre(unsignedint cpu)
{ int pairing;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.