Current PSCI code handles idle state entry through the psci_cpu_suspend_enter() API, that takes an idle state index as a parameter and convert the index into a previously initialized power_state parameter before calling the PSCI.CPU_SUSPEND() with it. This is unwieldly, since it forces the PSCI firmware layer to keep track of power_state parameter for every idle state so that the index->power_state conversion can be made in the PSCI firmware layer instead of the CPUidle driver implementations. Move the power_state handling out of drivers/firmware/psci into the respective ACPI/DT PSCI CPUidle backends and convert the psci_cpu_suspend_enter() API to get the power_state parameter as input, which makes it closer to its firmware interface PSCI.CPU_SUSPEND() API. A notable side effect is that the PSCI ACPI/DT CPUidle backends now can directly handle (and if needed update) power_state parameters before handing them over to the PSCI firmware interface to trigger PSCI.CPU_SUSPEND() calls.
Unified · Split
| @@ -47,17 +47,58 @@ int arm_cpuidle_suspend(int index) · 46 unmodified lines | ||
| 47 | 47 | |
| 48 | 48 | #define ARM64_LPI_IS_RETENTION_STATE(arch_flags) (!(arch_flags)) |
| 49 | 49 | |
| 50 | +static int psci_acpi_cpu_init_idle(unsigned int cpu) | |
| 51 | +{ | |
| 52 | + int i, count; | |
| 53 | + struct acpi_lpi_state *lpi; | |
| 54 | + struct acpi_processor *pr = per_cpu(processors, cpu); | |
| 55 | + | |
| 56 | + /* | |
| 57 | + * If the PSCI cpu_suspend function hook has not been initialized | |
| 58 | + * idle states must not be enabled, so bail out | |
| 59 | + */ | |
| 60 | + if (!psci_ops.cpu_suspend) | |
| 61 | + return -EOPNOTSUPP; | |
| 62 | + | |
| 63 | + if (unlikely(!pr || !pr->flags.has_lpi)) | |
| 64 | + return -EINVAL; | |
| 65 | + | |
| 66 | + count = pr->power.count - 1; | |
| 67 | + if (count <= 0) | |
| 68 | + return -ENODEV; | |
| 69 | + | |
| 70 | + for (i = 0; i < count; i++) { | |
| 71 | + u32 state; | |
| 72 | + | |
| 73 | + lpi = &pr->power.lpi_states[i + 1]; | |
| 74 | + /* | |
| 75 | + * Only bits[31:0] represent a PSCI power_state while | |
| 76 | + * bits[63:32] must be 0x0 as per ARM ACPI FFH Specification | |
| 77 | + */ | |
| 78 | + state = lpi->address; | |
| 79 | + if (!psci_power_state_is_valid(state)) { | |
| 80 | + pr_warn("Invalid PSCI power state %#x\n", state); | |
| 81 | + return -EINVAL; | |
| 82 | + } | |
| 83 | + } | |
| 84 | + | |
| 85 | + return 0; | |
| 86 | +} | |
| 87 | + | |
| 50 | 88 | int acpi_processor_ffh_lpi_probe(unsigned int cpu) |
| 51 | 89 | { |
| 52 | − return psci_cpu_init_idle(cpu); | |
| 90 | + return psci_acpi_cpu_init_idle(cpu); | |
| 53 | 91 | } |
| 54 | 92 | |
| 55 | 93 | int acpi_processor_ffh_lpi_enter(struct acpi_lpi_state *lpi) |
| 56 | 94 | { |
| 95 | + u32 state = lpi->address; | |
| 96 | + | |
| 57 | 97 | if (ARM64_LPI_IS_RETENTION_STATE(lpi->arch_flags)) |
| 58 | − return CPU_PM_CPU_IDLE_ENTER_RETENTION(psci_cpu_suspend_enter, | |
| 59 | − lpi->index); | |
| 98 | + return CPU_PM_CPU_IDLE_ENTER_RETENTION_PARAM(psci_cpu_suspend_enter, | |
| 99 | + lpi->index, state); | |
| 60 | 100 | else |
| 61 | − return CPU_PM_CPU_IDLE_ENTER(psci_cpu_suspend_enter, lpi->index); | |
| 101 | + return CPU_PM_CPU_IDLE_ENTER_PARAM(psci_cpu_suspend_enter, | |
| 102 | + lpi->index, state); | |
| 62 | 103 | } |
| 63 | 104 | #endif |
| @@ -22,10 +22,15 @@ · 21 unmodified lines | ||
| 22 | 22 | |
| 23 | 23 | #include "dt_idle_states.h" |
| 24 | 24 | |
| 25 | +static DEFINE_PER_CPU_READ_MOSTLY(u32 *, psci_power_state); | |
| 26 | + | |
| 25 | 27 | static int psci_enter_idle_state(struct cpuidle_device *dev, |
| 26 | 28 | struct cpuidle_driver *drv, int idx) |
| 27 | 29 | { |
| 28 | − return CPU_PM_CPU_IDLE_ENTER(psci_cpu_suspend_enter, idx); | |
| 30 | + u32 *state = __this_cpu_read(psci_power_state); | |
| 31 | + | |
| 32 | + return CPU_PM_CPU_IDLE_ENTER_PARAM(psci_cpu_suspend_enter, | |
| 33 | + idx, state[idx - 1]); | |
| 29 | 34 | } |
| 30 | 35 | |
| 31 | 36 | static struct cpuidle_driver psci_idle_driver __initdata = { |
| @@ -51,6 +56,86 @@ static const struct of_device_id psci_idle_state_match[] __initconst = { · 19 unmodified lines | ||
| 51 | 56 | { }, |
| 52 | 57 | }; |
| 53 | 58 | |
| 59 | +static int __init psci_dt_parse_state_node(struct device_node *np, u32 *state) | |
| 60 | +{ | |
| 61 | + int err = of_property_read_u32(np, "arm,psci-suspend-param", state); | |
| 62 | + | |
| 63 | + if (err) { | |
| 64 | + pr_warn("%pOF missing arm,psci-suspend-param property\n", np); | |
| 65 | + return err; | |
| 66 | + } | |
| 67 | + | |
| 68 | + if (!psci_power_state_is_valid(*state)) { | |
| 69 | + pr_warn("Invalid PSCI power state %#x\n", *state); | |
| 70 | + return -EINVAL; | |
| 71 | + } | |
| 72 | + | |
| 73 | + return 0; | |
| 74 | +} | |
| 75 | + | |
| 76 | +static int __init psci_dt_cpu_init_idle(struct device_node *cpu_node, int cpu) | |
| 77 | +{ | |
| 78 | + int i, ret = 0, count = 0; | |
| 79 | + u32 *psci_states; | |
| 80 | + struct device_node *state_node; | |
| 81 | + | |
| 82 | + /* Count idle states */ | |
| 83 | + while ((state_node = of_parse_phandle(cpu_node, "cpu-idle-states", | |
| 84 | + count))) { | |
| 85 | + count++; | |
| 86 | + of_node_put(state_node); | |
| 87 | + } | |
| 88 | + | |
| 89 | + if (!count) | |
| 90 | + return -ENODEV; | |
| 91 | + | |
| 92 | + psci_states = kcalloc(count, sizeof(*psci_states), GFP_KERNEL); | |
| 93 | + if (!psci_states) | |
| 94 | + return -ENOMEM; | |
| 95 | + | |
| 96 | + for (i = 0; i < count; i++) { | |
| 97 | + state_node = of_parse_phandle(cpu_node, "cpu-idle-states", i); | |
| 98 | + ret = psci_dt_parse_state_node(state_node, &psci_states[i]); | |
| 99 | + of_node_put(state_node); | |
| 100 | + | |
| 101 | + if (ret) | |
| 102 | + goto free_mem; | |
| 103 | + | |
| 104 | + pr_debug("psci-power-state %#x index %d\n", psci_states[i], i); | |
| 105 | + } | |
| 106 | + | |
| 107 | + /* Idle states parsed correctly, initialize per-cpu pointer */ | |
| 108 | + per_cpu(psci_power_state, cpu) = psci_states; | |
| 109 | + return 0; | |
| 110 | + | |
| 111 | +free_mem: | |
| 112 | + kfree(psci_states); | |
| 113 | + return ret; | |
| 114 | +} | |
| 115 | + | |
| 116 | +static __init int psci_cpu_init_idle(unsigned int cpu) | |
| 117 | +{ | |
| 118 | + struct device_node *cpu_node; | |
| 119 | + int ret; | |
| 120 | + | |
| 121 | + /* | |
| 122 | + * If the PSCI cpu_suspend function hook has not been initialized | |
| 123 | + * idle states must not be enabled, so bail out | |
| 124 | + */ | |
| 125 | + if (!psci_ops.cpu_suspend) | |
| 126 | + return -EOPNOTSUPP; | |
| 127 | + | |
| 128 | + cpu_node = of_cpu_device_node_get(cpu); | |
| 129 | + if (!cpu_node) | |
| 130 | + return -ENODEV; | |
| 131 | + | |
| 132 | + ret = psci_dt_cpu_init_idle(cpu_node, cpu); | |
| 133 | + | |
| 134 | + of_node_put(cpu_node); | |
| 135 | + | |
| 136 | + return ret; | |
| 137 | +} | |
| 138 | + | |
| 54 | 139 | static int __init psci_idle_init_cpu(int cpu) |
| 55 | 140 | { |
| 56 | 141 | struct cpuidle_driver *drv; |
| @@ -103,7 +103,7 @@ static inline bool psci_power_state_loses_context(u32 state) · 102 unmodified lines | ||
| 103 | 103 | return state & mask; |
| 104 | 104 | } |
| 105 | 105 | |
| 106 | −static inline bool psci_power_state_is_valid(u32 state) | |
| 106 | +bool psci_power_state_is_valid(u32 state) | |
| 107 | 107 | { |
| 108 | 108 | const u32 valid_mask = psci_has_ext_power_state() ? |
| 109 | 109 | PSCI_1_0_EXT_POWER_STATE_MASK : |
| @@ -277,162 +277,21 @@ static int __init psci_features(u32 psci_func_id) · 167 unmodified lines | ||
| 277 | 277 | } |
| 278 | 278 | |
| 279 | 279 | #ifdef CONFIG_CPU_IDLE |
| 280 | −static DEFINE_PER_CPU_READ_MOSTLY(u32 *, psci_power_state); | |
| 281 | − | |
| 282 | −static int psci_dt_parse_state_node(struct device_node *np, u32 *state) | |
| 283 | −{ | |
| 284 | − int err = of_property_read_u32(np, "arm,psci-suspend-param", state); | |
| 285 | − | |
| 286 | − if (err) { | |
| 287 | − pr_warn("%pOF missing arm,psci-suspend-param property\n", np); | |
| 288 | − return err; | |
| 289 | − } | |
| 290 | − | |
| 291 | − if (!psci_power_state_is_valid(*state)) { | |
| 292 | − pr_warn("Invalid PSCI power state %#x\n", *state); | |
| 293 | − return -EINVAL; | |
| 294 | − } | |
| 295 | − | |
| 296 | − return 0; | |
| 297 | −} | |
| 298 | − | |
| 299 | −static int psci_dt_cpu_init_idle(struct device_node *cpu_node, int cpu) | |
| 300 | −{ | |
| 301 | − int i, ret = 0, count = 0; | |
| 302 | − u32 *psci_states; | |
| 303 | − struct device_node *state_node; | |
| 304 | − | |
| 305 | − /* Count idle states */ | |
| 306 | − while ((state_node = of_parse_phandle(cpu_node, "cpu-idle-states", | |
| 307 | − count))) { | |
| 308 | − count++; | |
| 309 | − of_node_put(state_node); | |
| 310 | − } | |
| 311 | − | |
| 312 | − if (!count) | |
| 313 | − return -ENODEV; | |
| 314 | − | |
| 315 | − psci_states = kcalloc(count, sizeof(*psci_states), GFP_KERNEL); | |
| 316 | − if (!psci_states) | |
| 317 | − return -ENOMEM; | |
| 318 | − | |
| 319 | − for (i = 0; i < count; i++) { | |
| 320 | − state_node = of_parse_phandle(cpu_node, "cpu-idle-states", i); | |
| 321 | − ret = psci_dt_parse_state_node(state_node, &psci_states[i]); | |
| 322 | − of_node_put(state_node); | |
| 323 | − | |
| 324 | − if (ret) | |
| 325 | − goto free_mem; | |
| 326 | − | |
| 327 | − pr_debug("psci-power-state %#x index %d\n", psci_states[i], i); | |
| 328 | − } | |
| 329 | − | |
| 330 | − /* Idle states parsed correctly, initialize per-cpu pointer */ | |
| 331 | − per_cpu(psci_power_state, cpu) = psci_states; | |
| 332 | − return 0; | |
| 333 | − | |
| 334 | −free_mem: | |
| 335 | − kfree(psci_states); | |
| 336 | − return ret; | |
| 337 | −} | |
| 338 | − | |
| 339 | −#ifdef CONFIG_ACPI | |
| 340 | −#include <acpi/processor.h> | |
| 341 | − | |
| 342 | −static int __maybe_unused psci_acpi_cpu_init_idle(unsigned int cpu) | |
| 343 | −{ | |
| 344 | − int i, count; | |
| 345 | − u32 *psci_states; | |
| 346 | − struct acpi_lpi_state *lpi; | |
| 347 | − struct acpi_processor *pr = per_cpu(processors, cpu); | |
| 348 | − | |
| 349 | − if (unlikely(!pr || !pr->flags.has_lpi)) | |
| 350 | − return -EINVAL; | |
| 351 | − | |
| 352 | − count = pr->power.count - 1; | |
| 353 | − if (count <= 0) | |
| 354 | − return -ENODEV; | |
| 355 | − | |
| 356 | − psci_states = kcalloc(count, sizeof(*psci_states), GFP_KERNEL); | |
| 357 | − if (!psci_states) | |
| 358 | − return -ENOMEM; | |
| 359 | − | |
| 360 | − for (i = 0; i < count; i++) { | |
| 361 | − u32 state; | |
| 362 | − | |
| 363 | − lpi = &pr->power.lpi_states[i + 1]; | |
| 364 | − /* | |
| 365 | − * Only bits[31:0] represent a PSCI power_state while | |
| 366 | − * bits[63:32] must be 0x0 as per ARM ACPI FFH Specification | |
| 367 | − */ | |
| 368 | − state = lpi->address; | |
| 369 | − if (!psci_power_state_is_valid(state)) { | |
| 370 | − pr_warn("Invalid PSCI power state %#x\n", state); | |
| 371 | − kfree(psci_states); | |
| 372 | − return -EINVAL; | |
| 373 | − } | |
| 374 | − psci_states[i] = state; | |
| 375 | − } | |
| 376 | − /* Idle states parsed correctly, initialize per-cpu pointer */ | |
| 377 | − per_cpu(psci_power_state, cpu) = psci_states; | |
| 378 | − return 0; | |
| 379 | −} | |
| 380 | −#else | |
| 381 | −static int __maybe_unused psci_acpi_cpu_init_idle(unsigned int cpu) | |
| 382 | −{ | |
| 383 | − return -EINVAL; | |
| 384 | −} | |
| 385 | −#endif | |
| 386 | − | |
| 387 | −int psci_cpu_init_idle(unsigned int cpu) | |
| 388 | −{ | |
| 389 | − struct device_node *cpu_node; | |
| 390 | − int ret; | |
| 391 | − | |
| 392 | − /* | |
| 393 | − * If the PSCI cpu_suspend function hook has not been initialized | |
| 394 | − * idle states must not be enabled, so bail out | |
| 395 | − */ | |
| 396 | − if (!psci_ops.cpu_suspend) | |
| 397 | − return -EOPNOTSUPP; | |
| 398 | − | |
| 399 | − if (!acpi_disabled) | |
| 400 | − return psci_acpi_cpu_init_idle(cpu); | |
| 401 | − | |
| 402 | − cpu_node = of_get_cpu_node(cpu, NULL); | |
| 403 | − if (!cpu_node) | |
| 404 | − return -ENODEV; | |
| 405 | − | |
| 406 | − ret = psci_dt_cpu_init_idle(cpu_node, cpu); | |
| 407 | − | |
| 408 | − of_node_put(cpu_node); | |
| 409 | − | |
| 410 | − return ret; | |
| 411 | −} | |
| 412 | − | |
| 413 | −static int psci_suspend_finisher(unsigned long index) | |
| 280 | +static int psci_suspend_finisher(unsigned long state) | |
| 414 | 281 | { |
| 415 | − u32 *state = __this_cpu_read(psci_power_state); | |
| 282 | + u32 power_state = state; | |
| 416 | 283 | |
| 417 | − return psci_ops.cpu_suspend(state[index - 1], | |
| 418 | − __pa_symbol(cpu_resume)); | |
| 284 | + return psci_ops.cpu_suspend(power_state, __pa_symbol(cpu_resume)); | |
| 419 | 285 | } |
| 420 | 286 | |
| 421 | −int psci_cpu_suspend_enter(unsigned long index) | |
| 287 | +int psci_cpu_suspend_enter(u32 state) | |
| 422 | 288 | { |
| 423 | 289 | int ret; |
| 424 | − u32 *state = __this_cpu_read(psci_power_state); | |
| 425 | − /* | |
| 426 | − * idle state index 0 corresponds to wfi, should never be called | |
| 427 | − * from the cpu_suspend operations | |
| 428 | − */ | |
| 429 | − if (WARN_ON_ONCE(!index)) | |
| 430 | − return -EINVAL; | |
| 431 | 290 | |
| 432 | − if (!psci_power_state_loses_context(state[index - 1])) | |
| 433 | − ret = psci_ops.cpu_suspend(state[index - 1], 0); | |
| 291 | + if (!psci_power_state_loses_context(state)) | |
| 292 | + ret = psci_ops.cpu_suspend(state, 0); | |
| 434 | 293 | else |
| 435 | − ret = cpu_suspend(index, psci_suspend_finisher); | |
| 294 | + ret = cpu_suspend(state, psci_suspend_finisher); | |
| 436 | 295 | |
| 437 | 296 | return ret; |
| 438 | 297 | } |
| @@ -256,7 +256,10 @@ static inline int cpuidle_register_governor(struct cpuidle_governor *gov) · 255 unmodified lines | ||
| 256 | 256 | {return 0;} |
| 257 | 257 | #endif |
| 258 | 258 | |
| 259 | −#define __CPU_PM_CPU_IDLE_ENTER(low_level_idle_enter, idx, is_retention) \ | |
| 259 | +#define __CPU_PM_CPU_IDLE_ENTER(low_level_idle_enter, \ | |
| 260 | + idx, \ | |
| 261 | + state, \ | |
| 262 | + is_retention) \ | |
| 260 | 263 | ({ \ |
| 261 | 264 | int __ret = 0; \ |
| 262 | 265 | \ |
| @@ -268,7 +271,7 @@ static inline int cpuidle_register_governor(struct cpuidle_governor *gov) · 5 unmodified lines | ||
| 268 | 271 | if (!is_retention) \ |
| 269 | 272 | __ret = cpu_pm_enter(); \ |
| 270 | 273 | if (!__ret) { \ |
| 271 | − __ret = low_level_idle_enter(idx); \ | |
| 274 | + __ret = low_level_idle_enter(state); \ | |
| 272 | 275 | if (!is_retention) \ |
| 273 | 276 | cpu_pm_exit(); \ |
| 274 | 277 | } \ |
| @@ -277,9 +280,15 @@ static inline int cpuidle_register_governor(struct cpuidle_governor *gov) · 2 unmodified lines | ||
| 277 | 280 | }) |
| 278 | 281 | |
| 279 | 282 | #define CPU_PM_CPU_IDLE_ENTER(low_level_idle_enter, idx) \ |
| 280 | − __CPU_PM_CPU_IDLE_ENTER(low_level_idle_enter, idx, 0) | |
| 283 | + __CPU_PM_CPU_IDLE_ENTER(low_level_idle_enter, idx, idx, 0) | |
| 281 | 284 | |
| 282 | 285 | #define CPU_PM_CPU_IDLE_ENTER_RETENTION(low_level_idle_enter, idx) \ |
| 283 | − __CPU_PM_CPU_IDLE_ENTER(low_level_idle_enter, idx, 1) | |
| 286 | + __CPU_PM_CPU_IDLE_ENTER(low_level_idle_enter, idx, idx, 1) | |
| 287 | + | |
| 288 | +#define CPU_PM_CPU_IDLE_ENTER_PARAM(low_level_idle_enter, idx, state) \ | |
| 289 | + __CPU_PM_CPU_IDLE_ENTER(low_level_idle_enter, idx, state, 0) | |
| 290 | + | |
| 291 | +#define CPU_PM_CPU_IDLE_ENTER_RETENTION_PARAM(low_level_idle_enter, idx, state) \ | |
| 292 | + __CPU_PM_CPU_IDLE_ENTER(low_level_idle_enter, idx, state, 1) | |
| 284 | 293 | |
| 285 | 294 | #endif /* _LINUX_CPUIDLE_H */ |
| @@ -15,8 +15,8 @@ · 14 unmodified lines | ||
| 15 | 15 | |
| 16 | 16 | bool psci_tos_resident_on(int cpu); |
| 17 | 17 | |
| 18 | −int psci_cpu_init_idle(unsigned int cpu); | |
| 19 | −int psci_cpu_suspend_enter(unsigned long index); | |
| 18 | +int psci_cpu_suspend_enter(u32 state); | |
| 19 | +bool psci_power_state_is_valid(u32 state); | |
| 20 | 20 | |
| 21 | 21 | enum psci_conduit { |
| 22 | 22 | PSCI_CONDUIT_NONE, |