[Skiboot] [PATCH 29/34] cpu: Add support for nap mode on P8
Benjamin Herrenschmidt
benh at kernel.crashing.org
Sun Jul 24 09:27:23 AEST 2016
This allows us to send threads to nap mode when either idle (waiting
for a job) or when in a sleep delay (time_wait*).
We only enable the functionality after the 0x100 vector has been
patched, and we disable it before transferring control to Linux.
Signed-off-by: Benjamin Herrenschmidt <benh at kernel.crashing.org>
---
core/cpu.c | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
core/init.c | 10 ++++++-
include/cpu.h | 4 +++
3 files changed, 106 insertions(+), 1 deletion(-)
diff --git a/core/cpu.c b/core/cpu.c
index 3f2ebd9..6cda5d0 100644
--- a/core/cpu.c
+++ b/core/cpu.c
@@ -28,6 +28,7 @@
#include <affinity.h>
#include <chip.h>
#include <timebase.h>
+#include <interrupts.h>
#include <ccan/str/str.h>
#include <ccan/container_of/container_of.h>
@@ -49,6 +50,7 @@ static struct lock reinit_lock = LOCK_UNLOCKED;
static bool hile_supported;
static unsigned long hid0_hile;
static unsigned long hid0_attn;
+static bool pm_enabled;
unsigned long cpu_secondary_start __force_data = 0;
@@ -79,6 +81,17 @@ unsigned long __attrconst cpu_stack_top(unsigned int pir)
NORMAL_STACK_SIZE - STACK_TOP_GAP;
}
+static void cpu_wake(struct cpu_thread *cpu)
+{
+ /* Is it idle ? If not, no need to wake */
+ sync();
+ if (!cpu->in_idle)
+ return;
+
+ /* Poke IPI */
+ icp_kick_cpu(cpu);
+}
+
static struct cpu_thread *cpu_find_job_target(void)
{
struct cpu_thread *cpu, *best, *me = this_cpu();
@@ -194,6 +207,8 @@ struct cpu_job *__cpu_queue_job(struct cpu_thread *cpu,
cpu->job_has_no_return = true;
else
cpu->job_count++;
+ if (pm_enabled)
+ cpu_wake(cpu);
unlock(&cpu->job_lock);
return job;
@@ -279,9 +294,87 @@ static void cpu_idle_default(enum cpu_wake_cause wake_on __unused)
cpu_relax();
}
+static void cpu_idle_p8(enum cpu_wake_cause wake_on)
+{
+ uint64_t lpcr = mfspr(SPR_LPCR) & ~SPR_LPCR_P8_PECE;
+ struct cpu_thread *cpu = this_cpu();
+
+ if (!pm_enabled) {
+ cpu_idle_default(wake_on);
+ return;
+ }
+
+ /* If we are waking on job, whack DEC to highest value */
+ if (wake_on == cpu_wake_on_job)
+ mtspr(SPR_DEC, 0x7fffffff);
+
+ /* Clean up ICP, be ready for IPIs */
+ icp_prep_for_pm();
+
+ /* Setup wakup cause in LPCR */
+ lpcr |= SPR_LPCR_P8_PECE2 | SPR_LPCR_P8_PECE3;
+ mtspr(SPR_LPCR, lpcr);
+
+ /* Synchronize with wakers */
+ if (wake_on == cpu_wake_on_job) {
+ /* Mark ourselves in idle so other CPUs know to send an IPI */
+ cpu->in_idle = true;
+ sync();
+
+ /* Check for jobs again */
+ if (cpu_check_jobs(cpu) || !pm_enabled)
+ goto skip_sleep;
+ } else {
+ /* Mark outselves sleeping so cpu_set_pm_enable knows to
+ * send an IPI
+ */
+ cpu->in_sleep = true;
+ sync();
+
+ /* Check if PM got disabled */
+ if (!pm_enabled)
+ goto skip_sleep;
+ }
+
+ /* Enter nap */
+ enter_pm_state(false);
+
+skip_sleep:
+ /* Restore */
+ sync();
+ cpu->in_idle = false;
+ cpu->in_sleep = false;
+ reset_cpu_icp();
+}
+
+void cpu_set_pm_enable(bool enabled)
+{
+ struct cpu_thread *cpu;
+
+ prlog(PR_INFO, "CPU: %sing power management\n",
+ enabled ? "enabl" : "disabl");
+
+ pm_enabled = enabled;
+
+ if (enabled)
+ return;
+
+ /* If disabling, take everybody out of PM */
+ sync();
+ for_each_available_cpu(cpu) {
+ while (cpu->in_sleep || cpu->in_idle) {
+ icp_kick_cpu(cpu);
+ cpu_relax();
+ }
+ }
+}
+
void cpu_idle(enum cpu_wake_cause wake_on)
{
switch(proc_gen) {
+ case proc_gen_p8:
+ cpu_idle_p8(wake_on);
+ break;
default:
cpu_idle_default(wake_on);
break;
diff --git a/core/init.c b/core/init.c
index 237ccfb..1cf4cc2 100644
--- a/core/init.c
+++ b/core/init.c
@@ -360,8 +360,10 @@ static bool load_kernel(void)
* If the kernel is at 0, restore it as it was overwritten
* by our vectors.
*/
- if (kernel_entry < 0x2000)
+ if (kernel_entry < 0x2000) {
+ cpu_set_pm_enable(false);
memcpy(NULL, old_vectors, 0x2000);
+ }
} else {
if (!kernel_size)
printf("INIT: Assuming kernel at %p\n",
@@ -478,6 +480,9 @@ void __noreturn load_and_boot_kernel(bool is_reboot)
mem_dump_free();
+ /* Take processours out of nap */
+ cpu_set_pm_enable(false);
+
printf("INIT: Starting kernel at 0x%llx, fdt at %p (size 0x%x)\n",
kernel_entry, fdt, fdt_totalsize(fdt));
@@ -765,6 +770,9 @@ void __noreturn __nomcount main_cpu_entry(const void *fdt, u32 master_cpu)
*/
setup_reset_vector();
+ /* We can now do NAP mode */
+ cpu_set_pm_enable(true);
+
/*
* Sycnhronize time bases. Thi resets all the TB values to a small
* value (so they appear to go backward at this point), and synchronize
diff --git a/include/cpu.h b/include/cpu.h
index 4164151..341e73d 100644
--- a/include/cpu.h
+++ b/include/cpu.h
@@ -66,6 +66,8 @@ struct cpu_thread {
bool in_mcount;
bool in_poller;
bool in_reinit;
+ bool in_sleep;
+ bool in_idle;
uint32_t hbrt_spec_wakeup; /* primary only */
uint64_t save_l2_fir_action1;
uint64_t current_token;
@@ -251,6 +253,8 @@ extern void cpu_process_jobs(void);
extern void cpu_process_local_jobs(void);
/* Check if there's any job pending */
bool cpu_check_jobs(struct cpu_thread *cpu);
+/* Enable/disable PM */
+void cpu_set_pm_enable(bool pm_enabled);
static inline void cpu_give_self_os(void)
{
--
2.7.4
More information about the Skiboot
mailing list