diff options
| author | Boris Ostrovsky <boris.ostrovsky@amd.com> | 2012-03-13 14:55:09 -0400 |
|---|---|---|
| committer | Len Brown <len.brown@intel.com> | 2012-03-30 03:23:01 -0400 |
| commit | 1a022e3f1be11730bd8747b1af96a0274bf6356e (patch) | |
| tree | d3c95b68626ac7d963ac3a85a07dae1cfc011906 | |
| parent | e07510585a88c0f6c6c728e2e006aa913496d4ae (diff) | |
idle, x86: Allow off-lined CPU to enter deeper C states
Currently when a CPU is off-lined it enters either MWAIT-based idle or,
if MWAIT is not desired or supported, HLT-based idle (which places the
processor in C1 state). This patch allows processors without MWAIT
support to stay in states deeper than C1.
Signed-off-by: Boris Ostrovsky <boris.ostrovsky@amd.com>
Signed-off-by: Len Brown <len.brown@intel.com>
| -rw-r--r-- | arch/x86/kernel/smpboot.c | 4 | ||||
| -rw-r--r-- | drivers/acpi/processor_idle.c | 31 | ||||
| -rw-r--r-- | drivers/cpuidle/cpuidle.c | 28 | ||||
| -rw-r--r-- | include/linux/cpuidle.h | 5 |
4 files changed, 67 insertions, 1 deletions
diff --git a/arch/x86/kernel/smpboot.c b/arch/x86/kernel/smpboot.c index 66d250c00d11..93a2a0932b51 100644 --- a/arch/x86/kernel/smpboot.c +++ b/arch/x86/kernel/smpboot.c | |||
| @@ -50,6 +50,7 @@ | |||
| 50 | #include <linux/tboot.h> | 50 | #include <linux/tboot.h> |
| 51 | #include <linux/stackprotector.h> | 51 | #include <linux/stackprotector.h> |
| 52 | #include <linux/gfp.h> | 52 | #include <linux/gfp.h> |
| 53 | #include <linux/cpuidle.h> | ||
| 53 | 54 | ||
| 54 | #include <asm/acpi.h> | 55 | #include <asm/acpi.h> |
| 55 | #include <asm/desc.h> | 56 | #include <asm/desc.h> |
| @@ -1422,7 +1423,8 @@ void native_play_dead(void) | |||
| 1422 | tboot_shutdown(TB_SHUTDOWN_WFS); | 1423 | tboot_shutdown(TB_SHUTDOWN_WFS); |
| 1423 | 1424 | ||
| 1424 | mwait_play_dead(); /* Only returns on failure */ | 1425 | mwait_play_dead(); /* Only returns on failure */ |
| 1425 | hlt_play_dead(); | 1426 | if (cpuidle_play_dead()) |
| 1427 | hlt_play_dead(); | ||
| 1426 | } | 1428 | } |
| 1427 | 1429 | ||
| 1428 | #else /* ... !CONFIG_HOTPLUG_CPU */ | 1430 | #else /* ... !CONFIG_HOTPLUG_CPU */ |
diff --git a/drivers/acpi/processor_idle.c b/drivers/acpi/processor_idle.c index 0e8e2de2ed3e..6b1d32a161ae 100644 --- a/drivers/acpi/processor_idle.c +++ b/drivers/acpi/processor_idle.c | |||
| @@ -770,6 +770,35 @@ static int acpi_idle_enter_c1(struct cpuidle_device *dev, | |||
| 770 | return index; | 770 | return index; |
| 771 | } | 771 | } |
| 772 | 772 | ||
| 773 | |||
| 774 | /** | ||
| 775 | * acpi_idle_play_dead - enters an ACPI state for long-term idle (i.e. off-lining) | ||
| 776 | * @dev: the target CPU | ||
| 777 | * @index: the index of suggested state | ||
| 778 | */ | ||
| 779 | static int acpi_idle_play_dead(struct cpuidle_device *dev, int index) | ||
| 780 | { | ||
| 781 | struct cpuidle_state_usage *state_usage = &dev->states_usage[index]; | ||
| 782 | struct acpi_processor_cx *cx = cpuidle_get_statedata(state_usage); | ||
| 783 | |||
| 784 | ACPI_FLUSH_CPU_CACHE(); | ||
| 785 | |||
| 786 | while (1) { | ||
| 787 | |||
| 788 | if (cx->entry_method == ACPI_CSTATE_HALT) | ||
| 789 | halt(); | ||
| 790 | else if (cx->entry_method == ACPI_CSTATE_SYSTEMIO) { | ||
| 791 | inb(cx->address); | ||
| 792 | /* See comment in acpi_idle_do_entry() */ | ||
| 793 | inl(acpi_gbl_FADT.xpm_timer_block.address); | ||
| 794 | } else | ||
| 795 | return -ENODEV; | ||
| 796 | } | ||
| 797 | |||
| 798 | /* Never reached */ | ||
| 799 | return 0; | ||
| 800 | } | ||
| 801 | |||
| 773 | /** | 802 | /** |
| 774 | * acpi_idle_enter_simple - enters an ACPI state without BM handling | 803 | * acpi_idle_enter_simple - enters an ACPI state without BM handling |
| 775 | * @dev: the target CPU | 804 | * @dev: the target CPU |
| @@ -1077,12 +1106,14 @@ static int acpi_processor_setup_cpuidle_states(struct acpi_processor *pr) | |||
| 1077 | state->flags |= CPUIDLE_FLAG_TIME_VALID; | 1106 | state->flags |= CPUIDLE_FLAG_TIME_VALID; |
| 1078 | 1107 | ||
| 1079 | state->enter = acpi_idle_enter_c1; | 1108 | state->enter = acpi_idle_enter_c1; |
| 1109 | state->enter_dead = acpi_idle_play_dead; | ||
| 1080 | drv->safe_state_index = count; | 1110 | drv->safe_state_index = count; |
| 1081 | break; | 1111 | break; |
| 1082 | 1112 | ||
| 1083 | case ACPI_STATE_C2: | 1113 | case ACPI_STATE_C2: |
| 1084 | state->flags |= CPUIDLE_FLAG_TIME_VALID; | 1114 | state->flags |= CPUIDLE_FLAG_TIME_VALID; |
| 1085 | state->enter = acpi_idle_enter_simple; | 1115 | state->enter = acpi_idle_enter_simple; |
| 1116 | state->enter_dead = acpi_idle_play_dead; | ||
| 1086 | drv->safe_state_index = count; | 1117 | drv->safe_state_index = count; |
| 1087 | break; | 1118 | break; |
| 1088 | 1119 | ||
diff --git a/drivers/cpuidle/cpuidle.c b/drivers/cpuidle/cpuidle.c index f7cab5e9c4d6..3e146b2ada4a 100644 --- a/drivers/cpuidle/cpuidle.c +++ b/drivers/cpuidle/cpuidle.c | |||
| @@ -72,6 +72,34 @@ typedef int (*cpuidle_enter_t)(struct cpuidle_device *dev, | |||
| 72 | static cpuidle_enter_t cpuidle_enter_ops; | 72 | static cpuidle_enter_t cpuidle_enter_ops; |
| 73 | 73 | ||
| 74 | /** | 74 | /** |
| 75 | * cpuidle_play_dead - cpu off-lining | ||
| 76 | * | ||
| 77 | * Only returns in case of an error | ||
| 78 | */ | ||
| 79 | int cpuidle_play_dead(void) | ||
| 80 | { | ||
| 81 | struct cpuidle_device *dev = __this_cpu_read(cpuidle_devices); | ||
| 82 | struct cpuidle_driver *drv = cpuidle_get_driver(); | ||
| 83 | int i, dead_state = -1; | ||
| 84 | int power_usage = -1; | ||
| 85 | |||
| 86 | /* Find lowest-power state that supports long-term idle */ | ||
| 87 | for (i = CPUIDLE_DRIVER_STATE_START; i < drv->state_count; i++) { | ||
| 88 | struct cpuidle_state *s = &drv->states[i]; | ||
| 89 | |||
| 90 | if (s->power_usage < power_usage && s->enter_dead) { | ||
| 91 | power_usage = s->power_usage; | ||
| 92 | dead_state = i; | ||
| 93 | } | ||
| 94 | } | ||
| 95 | |||
| 96 | if (dead_state != -1) | ||
| 97 | return drv->states[dead_state].enter_dead(dev, dead_state); | ||
| 98 | |||
| 99 | return -ENODEV; | ||
| 100 | } | ||
| 101 | |||
| 102 | /** | ||
| 75 | * cpuidle_idle_call - the main idle loop | 103 | * cpuidle_idle_call - the main idle loop |
| 76 | * | 104 | * |
| 77 | * NOTE: no locks or semaphores should be used here | 105 | * NOTE: no locks or semaphores should be used here |
diff --git a/include/linux/cpuidle.h b/include/linux/cpuidle.h index f3ebbba368b3..d557bcd0ada7 100644 --- a/include/linux/cpuidle.h +++ b/include/linux/cpuidle.h | |||
| @@ -51,6 +51,8 @@ struct cpuidle_state { | |||
| 51 | int (*enter) (struct cpuidle_device *dev, | 51 | int (*enter) (struct cpuidle_device *dev, |
| 52 | struct cpuidle_driver *drv, | 52 | struct cpuidle_driver *drv, |
| 53 | int index); | 53 | int index); |
| 54 | |||
| 55 | int (*enter_dead) (struct cpuidle_device *dev, int index); | ||
| 54 | }; | 56 | }; |
| 55 | 57 | ||
| 56 | /* Idle State Flags */ | 58 | /* Idle State Flags */ |
| @@ -147,6 +149,8 @@ extern int cpuidle_wrap_enter(struct cpuidle_device *dev, | |||
| 147 | struct cpuidle_driver *drv, int index, | 149 | struct cpuidle_driver *drv, int index, |
| 148 | int (*enter)(struct cpuidle_device *dev, | 150 | int (*enter)(struct cpuidle_device *dev, |
| 149 | struct cpuidle_driver *drv, int index)); | 151 | struct cpuidle_driver *drv, int index)); |
| 152 | extern int cpuidle_play_dead(void); | ||
| 153 | |||
| 150 | #else | 154 | #else |
| 151 | static inline void disable_cpuidle(void) { } | 155 | static inline void disable_cpuidle(void) { } |
| 152 | static inline int cpuidle_idle_call(void) { return -ENODEV; } | 156 | static inline int cpuidle_idle_call(void) { return -ENODEV; } |
| @@ -168,6 +172,7 @@ static inline int cpuidle_wrap_enter(struct cpuidle_device *dev, | |||
| 168 | int (*enter)(struct cpuidle_device *dev, | 172 | int (*enter)(struct cpuidle_device *dev, |
| 169 | struct cpuidle_driver *drv, int index)) | 173 | struct cpuidle_driver *drv, int index)) |
| 170 | { return -ENODEV; } | 174 | { return -ENODEV; } |
| 175 | static inline int cpuidle_play_dead(void) {return -ENODEV; } | ||
| 171 | 176 | ||
| 172 | #endif | 177 | #endif |
| 173 | 178 | ||
