summaryrefslogtreecommitdiff
path: root/usr/src/uts/common/os/cpu_event.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/uts/common/os/cpu_event.c')
-rw-r--r--usr/src/uts/common/os/cpu_event.c65
1 files changed, 64 insertions, 1 deletions
diff --git a/usr/src/uts/common/os/cpu_event.c b/usr/src/uts/common/os/cpu_event.c
index 11162ccfc9..b3ed323f92 100644
--- a/usr/src/uts/common/os/cpu_event.c
+++ b/usr/src/uts/common/os/cpu_event.c
@@ -19,7 +19,7 @@
* CDDL HEADER END
*/
/*
- * Copyright (c) 2009, Intel Corporation.
+ * Copyright (c) 2009-2010, Intel Corporation.
* All rights reserved.
*/
@@ -152,8 +152,13 @@ typedef struct cpu_idle_cb_item {
/* Per-CPU state aligned to CPU_CACHE_COHERENCE_SIZE to avoid false sharing. */
typedef union cpu_idle_cb_state {
struct {
+ /* Index of already invoked callbacks. */
int index;
+ /* Invoke registered callbacks if true. */
+ boolean_t enabled;
+ /* Property values are valid if true. */
boolean_t ready;
+ /* Pointers to per-CPU properties. */
cpu_idle_prop_value_t *idle_state;
cpu_idle_prop_value_t *enter_ts;
cpu_idle_prop_value_t *exit_ts;
@@ -182,6 +187,17 @@ static int cpu_idle_cb_max = 0;
static cpu_idle_cb_state_t *cpu_idle_cb_state;
+#ifdef __x86
+/*
+ * cpuset used to intercept CPUs before powering them off.
+ * The control CPU sets the bit corresponding to the target CPU and waits
+ * until the bit is cleared.
+ * The target CPU disables interrupts before clearing corresponding bit and
+ * then loops for ever.
+ */
+static cpuset_t cpu_idle_intercept_set;
+#endif
+
static int cpu_idle_prop_update_intr_cnt(void *arg, uint64_t seqnum,
cpu_idle_prop_value_t *valp);
@@ -326,17 +342,26 @@ cpu_event_init(void)
#endif
}
+/*
+ * This function is called to initialize per CPU state when starting CPUs.
+ */
void
cpu_event_init_cpu(cpu_t *cp)
{
ASSERT(cp->cpu_seqid < max_ncpus);
+ cpu_idle_cb_state[cp->cpu_seqid].v.index = 0;
cpu_idle_cb_state[cp->cpu_seqid].v.ready = B_FALSE;
+ cpu_idle_cb_state[cp->cpu_seqid].v.enabled = B_TRUE;
}
+/*
+ * This function is called to clean up per CPU state when stopping CPUs.
+ */
void
cpu_event_fini_cpu(cpu_t *cp)
{
ASSERT(cp->cpu_seqid < max_ncpus);
+ cpu_idle_cb_state[cp->cpu_seqid].v.enabled = B_FALSE;
cpu_idle_cb_state[cp->cpu_seqid].v.ready = B_FALSE;
}
@@ -618,6 +643,21 @@ cpu_idle_enter(int state, int flag,
ASSERT(CPU->cpu_seqid < max_ncpus);
sp = &cpu_idle_cb_state[CPU->cpu_seqid];
ASSERT(sp->v.index == 0);
+ if (sp->v.enabled == B_FALSE) {
+#if defined(__x86)
+ /* Intercept CPU at a safe point before powering off it. */
+ if (CPU_IN_SET(cpu_idle_intercept_set, CPU->cpu_id)) {
+ iflags = intr_clear();
+ CPUSET_ATOMIC_DEL(cpu_idle_intercept_set, CPU->cpu_id);
+ /*CONSTCOND*/
+ while (1) {
+ SMT_PAUSE();
+ }
+ }
+#endif
+
+ return (0);
+ }
/*
* On x86, cpu_idle_enter can be called from idle thread with either
@@ -1091,3 +1131,26 @@ cpu_idle_get_cpu_state(cpu_t *cp)
cpu_idle_prop_array[CPU_IDLE_PROP_IDX_IDLE_STATE].handle,
CPU_IDLE_GET_CTX(cp)));
}
+
+#if defined(__x86)
+/*
+ * Intercept CPU at a safe point in idle() before powering it off.
+ */
+void
+cpu_idle_intercept_cpu(cpu_t *cp)
+{
+ ASSERT(cp->cpu_seqid < max_ncpus);
+ ASSERT(cpu_idle_cb_state[cp->cpu_seqid].v.enabled == B_FALSE);
+
+ /* Set flag to intercept CPU. */
+ CPUSET_ATOMIC_ADD(cpu_idle_intercept_set, cp->cpu_id);
+ /* Wake up CPU from possible sleep state. */
+ poke_cpu(cp->cpu_id);
+ while (CPU_IN_SET(cpu_idle_intercept_set, cp->cpu_id)) {
+ DELAY(1);
+ }
+ /*
+ * Now target CPU is spinning in a pause loop with interrupts disabled.
+ */
+}
+#endif