-
Notifications
You must be signed in to change notification settings - Fork 1.3k
/
psci_common.c
1309 lines (1126 loc) · 45.2 KB
/
psci_common.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Copyright (c) 2013-2024, Arm Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <assert.h>
#include <string.h>
#include <arch.h>
#include <arch_features.h>
#include <arch_helpers.h>
#include <common/bl_common.h>
#include <common/debug.h>
#include <context.h>
#include <drivers/delay_timer.h>
#include <lib/el3_runtime/context_mgmt.h>
#include <lib/extensions/spe.h>
#include <lib/utils.h>
#include <plat/common/platform.h>
#include "psci_private.h"
/*
* SPD power management operations, expected to be supplied by the registered
* SPD on successful SP initialization
*/
const spd_pm_ops_t *psci_spd_pm;
/*
* PSCI requested local power state map. This array is used to store the local
* power states requested by a CPU for power levels from level 1 to
* PLAT_MAX_PWR_LVL. It does not store the requested local power state for power
* level 0 (PSCI_CPU_PWR_LVL) as the requested and the target power state for a
* CPU are the same.
*
* During state coordination, the platform is passed an array containing the
* local states requested for a particular non cpu power domain by each cpu
* within the domain.
*
* TODO: Dense packing of the requested states will cause cache thrashing
* when multiple power domains write to it. If we allocate the requested
* states at each power level in a cache-line aligned per-domain memory,
* the cache thrashing can be avoided.
*/
static plat_local_state_t
psci_req_local_pwr_states[PLAT_MAX_PWR_LVL][PLATFORM_CORE_COUNT];
unsigned int psci_plat_core_count;
/*******************************************************************************
* Arrays that hold the platform's power domain tree information for state
* management of power domains.
* Each node in the array 'psci_non_cpu_pd_nodes' corresponds to a power domain
* which is an ancestor of a CPU power domain.
* Each node in the array 'psci_cpu_pd_nodes' corresponds to a cpu power domain
******************************************************************************/
non_cpu_pd_node_t psci_non_cpu_pd_nodes[PSCI_NUM_NON_CPU_PWR_DOMAINS]
#if USE_COHERENT_MEM
__section(".tzfw_coherent_mem")
#endif
;
/* Lock for PSCI state coordination */
DEFINE_PSCI_LOCK(psci_locks[PSCI_NUM_NON_CPU_PWR_DOMAINS]);
cpu_pd_node_t psci_cpu_pd_nodes[PLATFORM_CORE_COUNT];
/*******************************************************************************
* Pointer to functions exported by the platform to complete power mgmt. ops
******************************************************************************/
const plat_psci_ops_t *psci_plat_pm_ops;
/******************************************************************************
* Check that the maximum power level supported by the platform makes sense
*****************************************************************************/
CASSERT((PLAT_MAX_PWR_LVL <= PSCI_MAX_PWR_LVL) &&
(PLAT_MAX_PWR_LVL >= PSCI_CPU_PWR_LVL),
assert_platform_max_pwrlvl_check);
#if PSCI_OS_INIT_MODE
/*******************************************************************************
* The power state coordination mode used in CPU_SUSPEND.
* Defaults to platform-coordinated mode.
******************************************************************************/
suspend_mode_t psci_suspend_mode = PLAT_COORD;
#endif
/*
* The plat_local_state used by the platform is one of these types: RUN,
* RETENTION and OFF. The platform can define further sub-states for each type
* apart from RUN. This categorization is done to verify the sanity of the
* psci_power_state passed by the platform and to print debug information. The
* categorization is done on the basis of the following conditions:
*
* 1. If (plat_local_state == 0) then the category is STATE_TYPE_RUN.
*
* 2. If (0 < plat_local_state <= PLAT_MAX_RET_STATE), then the category is
* STATE_TYPE_RETN.
*
* 3. If (plat_local_state > PLAT_MAX_RET_STATE), then the category is
* STATE_TYPE_OFF.
*/
typedef enum plat_local_state_type {
STATE_TYPE_RUN = 0,
STATE_TYPE_RETN,
STATE_TYPE_OFF
} plat_local_state_type_t;
/* Function used to categorize plat_local_state. */
static plat_local_state_type_t find_local_state_type(plat_local_state_t state)
{
if (state != 0U) {
if (state > PLAT_MAX_RET_STATE) {
return STATE_TYPE_OFF;
} else {
return STATE_TYPE_RETN;
}
} else {
return STATE_TYPE_RUN;
}
}
/******************************************************************************
* Check that the maximum retention level supported by the platform is less
* than the maximum off level.
*****************************************************************************/
CASSERT(PLAT_MAX_RET_STATE < PLAT_MAX_OFF_STATE,
assert_platform_max_off_and_retn_state_check);
/******************************************************************************
* This function ensures that the power state parameter in a CPU_SUSPEND request
* is valid. If so, it returns the requested states for each power level.
*****************************************************************************/
int psci_validate_power_state(unsigned int power_state,
psci_power_state_t *state_info)
{
/* Check SBZ bits in power state are zero */
if (psci_check_power_state(power_state) != 0U)
return PSCI_E_INVALID_PARAMS;
assert(psci_plat_pm_ops->validate_power_state != NULL);
/* Validate the power_state using platform pm_ops */
return psci_plat_pm_ops->validate_power_state(power_state, state_info);
}
/******************************************************************************
* This function retrieves the `psci_power_state_t` for system suspend from
* the platform.
*****************************************************************************/
void psci_query_sys_suspend_pwrstate(psci_power_state_t *state_info)
{
/*
* Assert that the required pm_ops hook is implemented to ensure that
* the capability detected during psci_setup() is valid.
*/
assert(psci_plat_pm_ops->get_sys_suspend_power_state != NULL);
/*
* Query the platform for the power_state required for system suspend
*/
psci_plat_pm_ops->get_sys_suspend_power_state(state_info);
}
#if PSCI_OS_INIT_MODE
/*******************************************************************************
* This function verifies that all the other cores at the 'end_pwrlvl' have been
* idled and the current CPU is the last running CPU at the 'end_pwrlvl'.
* Returns 1 (true) if the current CPU is the last ON CPU or 0 (false)
* otherwise.
******************************************************************************/
static bool psci_is_last_cpu_to_idle_at_pwrlvl(unsigned int end_pwrlvl)
{
unsigned int my_idx, lvl;
unsigned int parent_idx = 0;
unsigned int cpu_start_idx, ncpus, cpu_idx;
plat_local_state_t local_state;
if (end_pwrlvl == PSCI_CPU_PWR_LVL) {
return true;
}
my_idx = plat_my_core_pos();
for (lvl = PSCI_CPU_PWR_LVL; lvl <= end_pwrlvl; lvl++) {
parent_idx = psci_cpu_pd_nodes[my_idx].parent_node;
}
cpu_start_idx = psci_non_cpu_pd_nodes[parent_idx].cpu_start_idx;
ncpus = psci_non_cpu_pd_nodes[parent_idx].ncpus;
for (cpu_idx = cpu_start_idx; cpu_idx < cpu_start_idx + ncpus;
cpu_idx++) {
local_state = psci_get_cpu_local_state_by_idx(cpu_idx);
if (cpu_idx == my_idx) {
assert(is_local_state_run(local_state) != 0);
continue;
}
if (is_local_state_run(local_state) != 0) {
return false;
}
}
return true;
}
#endif
/*******************************************************************************
* This function verifies that all the other cores in the system have been
* turned OFF and the current CPU is the last running CPU in the system.
* Returns true, if the current CPU is the last ON CPU or false otherwise.
******************************************************************************/
bool psci_is_last_on_cpu(void)
{
unsigned int cpu_idx, my_idx = plat_my_core_pos();
for (cpu_idx = 0; cpu_idx < psci_plat_core_count; cpu_idx++) {
if (cpu_idx == my_idx) {
assert(psci_get_aff_info_state() == AFF_STATE_ON);
continue;
}
if (psci_get_aff_info_state_by_idx(cpu_idx) != AFF_STATE_OFF) {
VERBOSE("core=%u other than current core=%u %s\n",
cpu_idx, my_idx, "running in the system");
return false;
}
}
return true;
}
/*******************************************************************************
* This function verifies that all cores in the system have been turned ON.
* Returns true, if all CPUs are ON or false otherwise.
******************************************************************************/
static bool psci_are_all_cpus_on(void)
{
unsigned int cpu_idx;
for (cpu_idx = 0; cpu_idx < psci_plat_core_count; cpu_idx++) {
if (psci_get_aff_info_state_by_idx(cpu_idx) == AFF_STATE_OFF) {
return false;
}
}
return true;
}
/*******************************************************************************
* Routine to return the maximum power level to traverse to after a cpu has
* been physically powered up. It is expected to be called immediately after
* reset from assembler code.
******************************************************************************/
static unsigned int get_power_on_target_pwrlvl(void)
{
unsigned int pwrlvl;
/*
* Assume that this cpu was suspended and retrieve its target power
* level. If it is invalid then it could only have been turned off
* earlier. PLAT_MAX_PWR_LVL will be the highest power level a
* cpu can be turned off to.
*/
pwrlvl = psci_get_suspend_pwrlvl();
if (pwrlvl == PSCI_INVALID_PWR_LVL)
pwrlvl = PLAT_MAX_PWR_LVL;
assert(pwrlvl < PSCI_INVALID_PWR_LVL);
return pwrlvl;
}
/******************************************************************************
* Helper function to update the requested local power state array. This array
* does not store the requested state for the CPU power level. Hence an
* assertion is added to prevent us from accessing the CPU power level.
*****************************************************************************/
static void psci_set_req_local_pwr_state(unsigned int pwrlvl,
unsigned int cpu_idx,
plat_local_state_t req_pwr_state)
{
assert(pwrlvl > PSCI_CPU_PWR_LVL);
if ((pwrlvl > PSCI_CPU_PWR_LVL) && (pwrlvl <= PLAT_MAX_PWR_LVL) &&
(cpu_idx < psci_plat_core_count)) {
psci_req_local_pwr_states[pwrlvl - 1U][cpu_idx] = req_pwr_state;
}
}
/******************************************************************************
* This function initializes the psci_req_local_pwr_states.
*****************************************************************************/
void __init psci_init_req_local_pwr_states(void)
{
/* Initialize the requested state of all non CPU power domains as OFF */
unsigned int pwrlvl;
unsigned int core;
for (pwrlvl = 0U; pwrlvl < PLAT_MAX_PWR_LVL; pwrlvl++) {
for (core = 0; core < psci_plat_core_count; core++) {
psci_req_local_pwr_states[pwrlvl][core] =
PLAT_MAX_OFF_STATE;
}
}
}
/******************************************************************************
* Helper function to return a reference to an array containing the local power
* states requested by each cpu for a power domain at 'pwrlvl'. The size of the
* array will be the number of cpu power domains of which this power domain is
* an ancestor. These requested states will be used to determine a suitable
* target state for this power domain during psci state coordination. An
* assertion is added to prevent us from accessing the CPU power level.
*****************************************************************************/
static plat_local_state_t *psci_get_req_local_pwr_states(unsigned int pwrlvl,
unsigned int cpu_idx)
{
assert(pwrlvl > PSCI_CPU_PWR_LVL);
if ((pwrlvl > PSCI_CPU_PWR_LVL) && (pwrlvl <= PLAT_MAX_PWR_LVL) &&
(cpu_idx < psci_plat_core_count)) {
return &psci_req_local_pwr_states[pwrlvl - 1U][cpu_idx];
} else
return NULL;
}
#if PSCI_OS_INIT_MODE
/******************************************************************************
* Helper function to save a copy of the psci_req_local_pwr_states (prev) for a
* CPU (cpu_idx), and update psci_req_local_pwr_states with the new requested
* local power states (state_info).
*****************************************************************************/
void psci_update_req_local_pwr_states(unsigned int end_pwrlvl,
unsigned int cpu_idx,
psci_power_state_t *state_info,
plat_local_state_t *prev)
{
unsigned int lvl;
#ifdef PLAT_MAX_CPU_SUSPEND_PWR_LVL
unsigned int max_pwrlvl = PLAT_MAX_CPU_SUSPEND_PWR_LVL;
#else
unsigned int max_pwrlvl = PLAT_MAX_PWR_LVL;
#endif
plat_local_state_t req_state;
for (lvl = PSCI_CPU_PWR_LVL + 1U; lvl <= max_pwrlvl; lvl++) {
/* Save the previous requested local power state */
prev[lvl - 1U] = *psci_get_req_local_pwr_states(lvl, cpu_idx);
/* Update the new requested local power state */
if (lvl <= end_pwrlvl) {
req_state = state_info->pwr_domain_state[lvl];
} else {
req_state = state_info->pwr_domain_state[end_pwrlvl];
}
psci_set_req_local_pwr_state(lvl, cpu_idx, req_state);
}
}
/******************************************************************************
* Helper function to restore the previously saved requested local power states
* (prev) for a CPU (cpu_idx) to psci_req_local_pwr_states.
*****************************************************************************/
void psci_restore_req_local_pwr_states(unsigned int cpu_idx,
plat_local_state_t *prev)
{
unsigned int lvl;
#ifdef PLAT_MAX_CPU_SUSPEND_PWR_LVL
unsigned int max_pwrlvl = PLAT_MAX_CPU_SUSPEND_PWR_LVL;
#else
unsigned int max_pwrlvl = PLAT_MAX_PWR_LVL;
#endif
for (lvl = PSCI_CPU_PWR_LVL + 1U; lvl <= max_pwrlvl; lvl++) {
/* Restore the previous requested local power state */
psci_set_req_local_pwr_state(lvl, cpu_idx, prev[lvl - 1U]);
}
}
#endif
/*
* psci_non_cpu_pd_nodes can be placed either in normal memory or coherent
* memory.
*
* With !USE_COHERENT_MEM, psci_non_cpu_pd_nodes is placed in normal memory,
* it's accessed by both cached and non-cached participants. To serve the common
* minimum, perform a cache flush before read and after write so that non-cached
* participants operate on latest data in main memory.
*
* When USE_COHERENT_MEM is used, psci_non_cpu_pd_nodes is placed in coherent
* memory. With HW_ASSISTED_COHERENCY, all PSCI participants are cache-coherent.
* In both cases, no cache operations are required.
*/
/*
* Retrieve local state of non-CPU power domain node from a non-cached CPU,
* after any required cache maintenance operation.
*/
static plat_local_state_t get_non_cpu_pd_node_local_state(
unsigned int parent_idx)
{
#if !(USE_COHERENT_MEM || HW_ASSISTED_COHERENCY || WARMBOOT_ENABLE_DCACHE_EARLY)
flush_dcache_range(
(uintptr_t) &psci_non_cpu_pd_nodes[parent_idx],
sizeof(psci_non_cpu_pd_nodes[parent_idx]));
#endif
return psci_non_cpu_pd_nodes[parent_idx].local_state;
}
/*
* Update local state of non-CPU power domain node from a cached CPU; perform
* any required cache maintenance operation afterwards.
*/
static void set_non_cpu_pd_node_local_state(unsigned int parent_idx,
plat_local_state_t state)
{
psci_non_cpu_pd_nodes[parent_idx].local_state = state;
#if !(USE_COHERENT_MEM || HW_ASSISTED_COHERENCY || WARMBOOT_ENABLE_DCACHE_EARLY)
flush_dcache_range(
(uintptr_t) &psci_non_cpu_pd_nodes[parent_idx],
sizeof(psci_non_cpu_pd_nodes[parent_idx]));
#endif
}
/******************************************************************************
* Helper function to return the current local power state of each power domain
* from the current cpu power domain to its ancestor at the 'end_pwrlvl'. This
* function will be called after a cpu is powered on to find the local state
* each power domain has emerged from.
*****************************************************************************/
void psci_get_target_local_pwr_states(unsigned int end_pwrlvl,
psci_power_state_t *target_state)
{
unsigned int parent_idx, lvl;
plat_local_state_t *pd_state = target_state->pwr_domain_state;
pd_state[PSCI_CPU_PWR_LVL] = psci_get_cpu_local_state();
parent_idx = psci_cpu_pd_nodes[plat_my_core_pos()].parent_node;
/* Copy the local power state from node to state_info */
for (lvl = PSCI_CPU_PWR_LVL + 1U; lvl <= end_pwrlvl; lvl++) {
pd_state[lvl] = get_non_cpu_pd_node_local_state(parent_idx);
parent_idx = psci_non_cpu_pd_nodes[parent_idx].parent_node;
}
/* Set the the higher levels to RUN */
for (; lvl <= PLAT_MAX_PWR_LVL; lvl++)
target_state->pwr_domain_state[lvl] = PSCI_LOCAL_STATE_RUN;
}
/******************************************************************************
* Helper function to set the target local power state that each power domain
* from the current cpu power domain to its ancestor at the 'end_pwrlvl' will
* enter. This function will be called after coordination of requested power
* states has been done for each power level.
*****************************************************************************/
void psci_set_target_local_pwr_states(unsigned int end_pwrlvl,
const psci_power_state_t *target_state)
{
unsigned int parent_idx, lvl;
const plat_local_state_t *pd_state = target_state->pwr_domain_state;
psci_set_cpu_local_state(pd_state[PSCI_CPU_PWR_LVL]);
/*
* Need to flush as local_state might be accessed with Data Cache
* disabled during power on
*/
psci_flush_cpu_data(psci_svc_cpu_data.local_state);
parent_idx = psci_cpu_pd_nodes[plat_my_core_pos()].parent_node;
/* Copy the local_state from state_info */
for (lvl = 1U; lvl <= end_pwrlvl; lvl++) {
set_non_cpu_pd_node_local_state(parent_idx, pd_state[lvl]);
parent_idx = psci_non_cpu_pd_nodes[parent_idx].parent_node;
}
}
/*******************************************************************************
* PSCI helper function to get the parent nodes corresponding to a cpu_index.
******************************************************************************/
void psci_get_parent_pwr_domain_nodes(unsigned int cpu_idx,
unsigned int end_lvl,
unsigned int *node_index)
{
unsigned int parent_node = psci_cpu_pd_nodes[cpu_idx].parent_node;
unsigned int i;
unsigned int *node = node_index;
for (i = PSCI_CPU_PWR_LVL + 1U; i <= end_lvl; i++) {
*node = parent_node;
node++;
parent_node = psci_non_cpu_pd_nodes[parent_node].parent_node;
}
}
/******************************************************************************
* This function is invoked post CPU power up and initialization. It sets the
* affinity info state, target power state and requested power state for the
* current CPU and all its ancestor power domains to RUN.
*****************************************************************************/
void psci_set_pwr_domains_to_run(unsigned int end_pwrlvl)
{
unsigned int parent_idx, cpu_idx = plat_my_core_pos(), lvl;
parent_idx = psci_cpu_pd_nodes[cpu_idx].parent_node;
/* Reset the local_state to RUN for the non cpu power domains. */
for (lvl = PSCI_CPU_PWR_LVL + 1U; lvl <= end_pwrlvl; lvl++) {
set_non_cpu_pd_node_local_state(parent_idx,
PSCI_LOCAL_STATE_RUN);
psci_set_req_local_pwr_state(lvl,
cpu_idx,
PSCI_LOCAL_STATE_RUN);
parent_idx = psci_non_cpu_pd_nodes[parent_idx].parent_node;
}
/* Set the affinity info state to ON */
psci_set_aff_info_state(AFF_STATE_ON);
psci_set_cpu_local_state(PSCI_LOCAL_STATE_RUN);
psci_flush_cpu_data(psci_svc_cpu_data);
}
/******************************************************************************
* This function is used in platform-coordinated mode.
*
* This function is passed the local power states requested for each power
* domain (state_info) between the current CPU domain and its ancestors until
* the target power level (end_pwrlvl). It updates the array of requested power
* states with this information.
*
* Then, for each level (apart from the CPU level) until the 'end_pwrlvl', it
* retrieves the states requested by all the cpus of which the power domain at
* that level is an ancestor. It passes this information to the platform to
* coordinate and return the target power state. If the target state for a level
* is RUN then subsequent levels are not considered. At the CPU level, state
* coordination is not required. Hence, the requested and the target states are
* the same.
*
* The 'state_info' is updated with the target state for each level between the
* CPU and the 'end_pwrlvl' and returned to the caller.
*
* This function will only be invoked with data cache enabled and while
* powering down a core.
*****************************************************************************/
void psci_do_state_coordination(unsigned int end_pwrlvl,
psci_power_state_t *state_info)
{
unsigned int lvl, parent_idx, cpu_idx = plat_my_core_pos();
unsigned int start_idx;
unsigned int ncpus;
plat_local_state_t target_state, *req_states;
assert(end_pwrlvl <= PLAT_MAX_PWR_LVL);
parent_idx = psci_cpu_pd_nodes[cpu_idx].parent_node;
/* For level 0, the requested state will be equivalent
to target state */
for (lvl = PSCI_CPU_PWR_LVL + 1U; lvl <= end_pwrlvl; lvl++) {
/* First update the requested power state */
psci_set_req_local_pwr_state(lvl, cpu_idx,
state_info->pwr_domain_state[lvl]);
/* Get the requested power states for this power level */
start_idx = psci_non_cpu_pd_nodes[parent_idx].cpu_start_idx;
req_states = psci_get_req_local_pwr_states(lvl, start_idx);
/*
* Let the platform coordinate amongst the requested states at
* this power level and return the target local power state.
*/
ncpus = psci_non_cpu_pd_nodes[parent_idx].ncpus;
target_state = plat_get_target_pwr_state(lvl,
req_states,
ncpus);
state_info->pwr_domain_state[lvl] = target_state;
/* Break early if the negotiated target power state is RUN */
if (is_local_state_run(state_info->pwr_domain_state[lvl]) != 0)
break;
parent_idx = psci_non_cpu_pd_nodes[parent_idx].parent_node;
}
/*
* This is for cases when we break out of the above loop early because
* the target power state is RUN at a power level < end_pwlvl.
* We update the requested power state from state_info and then
* set the target state as RUN.
*/
for (lvl = lvl + 1U; lvl <= end_pwrlvl; lvl++) {
psci_set_req_local_pwr_state(lvl, cpu_idx,
state_info->pwr_domain_state[lvl]);
state_info->pwr_domain_state[lvl] = PSCI_LOCAL_STATE_RUN;
}
}
#if PSCI_OS_INIT_MODE
/******************************************************************************
* This function is used in OS-initiated mode.
*
* This function is passed the local power states requested for each power
* domain (state_info) between the current CPU domain and its ancestors until
* the target power level (end_pwrlvl), and ensures the requested power states
* are valid. It updates the array of requested power states with this
* information.
*
* Then, for each level (apart from the CPU level) until the 'end_pwrlvl', it
* retrieves the states requested by all the cpus of which the power domain at
* that level is an ancestor. It passes this information to the platform to
* coordinate and return the target power state. If the requested state does
* not match the target state, the request is denied.
*
* The 'state_info' is not modified.
*
* This function will only be invoked with data cache enabled and while
* powering down a core.
*****************************************************************************/
int psci_validate_state_coordination(unsigned int end_pwrlvl,
psci_power_state_t *state_info)
{
int rc = PSCI_E_SUCCESS;
unsigned int lvl, parent_idx, cpu_idx = plat_my_core_pos();
unsigned int start_idx;
unsigned int ncpus;
plat_local_state_t target_state, *req_states;
plat_local_state_t prev[PLAT_MAX_PWR_LVL];
assert(end_pwrlvl <= PLAT_MAX_PWR_LVL);
parent_idx = psci_cpu_pd_nodes[cpu_idx].parent_node;
/*
* Save a copy of the previous requested local power states and update
* the new requested local power states.
*/
psci_update_req_local_pwr_states(end_pwrlvl, cpu_idx, state_info, prev);
for (lvl = PSCI_CPU_PWR_LVL + 1U; lvl <= end_pwrlvl; lvl++) {
/* Get the requested power states for this power level */
start_idx = psci_non_cpu_pd_nodes[parent_idx].cpu_start_idx;
req_states = psci_get_req_local_pwr_states(lvl, start_idx);
/*
* Let the platform coordinate amongst the requested states at
* this power level and return the target local power state.
*/
ncpus = psci_non_cpu_pd_nodes[parent_idx].ncpus;
target_state = plat_get_target_pwr_state(lvl,
req_states,
ncpus);
/*
* Verify that the requested power state matches the target
* local power state.
*/
if (state_info->pwr_domain_state[lvl] != target_state) {
if (target_state == PSCI_LOCAL_STATE_RUN) {
rc = PSCI_E_DENIED;
} else {
rc = PSCI_E_INVALID_PARAMS;
}
goto exit;
}
parent_idx = psci_non_cpu_pd_nodes[parent_idx].parent_node;
}
/*
* Verify that the current core is the last running core at the
* specified power level.
*/
lvl = state_info->last_at_pwrlvl;
if (!psci_is_last_cpu_to_idle_at_pwrlvl(lvl)) {
rc = PSCI_E_DENIED;
}
exit:
if (rc != PSCI_E_SUCCESS) {
/* Restore the previous requested local power states. */
psci_restore_req_local_pwr_states(cpu_idx, prev);
return rc;
}
return rc;
}
#endif
/******************************************************************************
* This function validates a suspend request by making sure that if a standby
* state is requested then no power level is turned off and the highest power
* level is placed in a standby/retention state.
*
* It also ensures that the state level X will enter is not shallower than the
* state level X + 1 will enter.
*
* This validation will be enabled only for DEBUG builds as the platform is
* expected to perform these validations as well.
*****************************************************************************/
int psci_validate_suspend_req(const psci_power_state_t *state_info,
unsigned int is_power_down_state)
{
unsigned int max_off_lvl, target_lvl, max_retn_lvl;
plat_local_state_t state;
plat_local_state_type_t req_state_type, deepest_state_type;
int i;
/* Find the target suspend power level */
target_lvl = psci_find_target_suspend_lvl(state_info);
if (target_lvl == PSCI_INVALID_PWR_LVL)
return PSCI_E_INVALID_PARAMS;
/* All power domain levels are in a RUN state to begin with */
deepest_state_type = STATE_TYPE_RUN;
for (i = (int) target_lvl; i >= (int) PSCI_CPU_PWR_LVL; i--) {
state = state_info->pwr_domain_state[i];
req_state_type = find_local_state_type(state);
/*
* While traversing from the highest power level to the lowest,
* the state requested for lower levels has to be the same or
* deeper i.e. equal to or greater than the state at the higher
* levels. If this condition is true, then the requested state
* becomes the deepest state encountered so far.
*/
if (req_state_type < deepest_state_type)
return PSCI_E_INVALID_PARAMS;
deepest_state_type = req_state_type;
}
/* Find the highest off power level */
max_off_lvl = psci_find_max_off_lvl(state_info);
/* The target_lvl is either equal to the max_off_lvl or max_retn_lvl */
max_retn_lvl = PSCI_INVALID_PWR_LVL;
if (target_lvl != max_off_lvl)
max_retn_lvl = target_lvl;
/*
* If this is not a request for a power down state then max off level
* has to be invalid and max retention level has to be a valid power
* level.
*/
if ((is_power_down_state == 0U) &&
((max_off_lvl != PSCI_INVALID_PWR_LVL) ||
(max_retn_lvl == PSCI_INVALID_PWR_LVL)))
return PSCI_E_INVALID_PARAMS;
return PSCI_E_SUCCESS;
}
/******************************************************************************
* This function finds the highest power level which will be powered down
* amongst all the power levels specified in the 'state_info' structure
*****************************************************************************/
unsigned int psci_find_max_off_lvl(const psci_power_state_t *state_info)
{
int i;
for (i = (int) PLAT_MAX_PWR_LVL; i >= (int) PSCI_CPU_PWR_LVL; i--) {
if (is_local_state_off(state_info->pwr_domain_state[i]) != 0)
return (unsigned int) i;
}
return PSCI_INVALID_PWR_LVL;
}
/******************************************************************************
* This functions finds the level of the highest power domain which will be
* placed in a low power state during a suspend operation.
*****************************************************************************/
unsigned int psci_find_target_suspend_lvl(const psci_power_state_t *state_info)
{
int i;
for (i = (int) PLAT_MAX_PWR_LVL; i >= (int) PSCI_CPU_PWR_LVL; i--) {
if (is_local_state_run(state_info->pwr_domain_state[i]) == 0)
return (unsigned int) i;
}
return PSCI_INVALID_PWR_LVL;
}
/*******************************************************************************
* This function is passed the highest level in the topology tree that the
* operation should be applied to and a list of node indexes. It picks up locks
* from the node index list in order of increasing power domain level in the
* range specified.
******************************************************************************/
void psci_acquire_pwr_domain_locks(unsigned int end_pwrlvl,
const unsigned int *parent_nodes)
{
unsigned int parent_idx;
unsigned int level;
/* No locking required for level 0. Hence start locking from level 1 */
for (level = PSCI_CPU_PWR_LVL + 1U; level <= end_pwrlvl; level++) {
parent_idx = parent_nodes[level - 1U];
psci_lock_get(&psci_non_cpu_pd_nodes[parent_idx]);
}
}
/*******************************************************************************
* This function is passed the highest level in the topology tree that the
* operation should be applied to and a list of node indexes. It releases the
* locks in order of decreasing power domain level in the range specified.
******************************************************************************/
void psci_release_pwr_domain_locks(unsigned int end_pwrlvl,
const unsigned int *parent_nodes)
{
unsigned int parent_idx;
unsigned int level;
/* Unlock top down. No unlocking required for level 0. */
for (level = end_pwrlvl; level >= (PSCI_CPU_PWR_LVL + 1U); level--) {
parent_idx = parent_nodes[level - 1U];
psci_lock_release(&psci_non_cpu_pd_nodes[parent_idx]);
}
}
/*******************************************************************************
* This function determines the full entrypoint information for the requested
* PSCI entrypoint on power on/resume and returns it.
******************************************************************************/
#ifdef __aarch64__
static int psci_get_ns_ep_info(entry_point_info_t *ep,
uintptr_t entrypoint,
u_register_t context_id)
{
u_register_t ep_attr, sctlr;
unsigned int daif, ee, mode;
u_register_t ns_scr_el3 = read_scr_el3();
u_register_t ns_sctlr_el1 = read_sctlr_el1();
sctlr = ((ns_scr_el3 & SCR_HCE_BIT) != 0U) ?
read_sctlr_el2() : ns_sctlr_el1;
ee = 0;
ep_attr = NON_SECURE | EP_ST_DISABLE;
if ((sctlr & SCTLR_EE_BIT) != 0U) {
ep_attr |= EP_EE_BIG;
ee = 1;
}
SET_PARAM_HEAD(ep, PARAM_EP, VERSION_1, ep_attr);
ep->pc = entrypoint;
zeromem(&ep->args, sizeof(ep->args));
ep->args.arg0 = context_id;
/*
* Figure out whether the cpu enters the non-secure address space
* in aarch32 or aarch64
*/
if ((ns_scr_el3 & SCR_RW_BIT) != 0U) {
/*
* Check whether a Thumb entry point has been provided for an
* aarch64 EL
*/
if ((entrypoint & 0x1UL) != 0UL)
return PSCI_E_INVALID_ADDRESS;
mode = ((ns_scr_el3 & SCR_HCE_BIT) != 0U) ? MODE_EL2 : MODE_EL1;
ep->spsr = SPSR_64((uint64_t)mode, MODE_SP_ELX,
DISABLE_ALL_EXCEPTIONS);
} else {
mode = ((ns_scr_el3 & SCR_HCE_BIT) != 0U) ?
MODE32_hyp : MODE32_svc;
/*
* TODO: Choose async. exception bits if HYP mode is not
* implemented according to the values of SCR.{AW, FW} bits
*/
daif = DAIF_ABT_BIT | DAIF_IRQ_BIT | DAIF_FIQ_BIT;
ep->spsr = SPSR_MODE32((uint64_t)mode, entrypoint & 0x1, ee,
daif);
}
return PSCI_E_SUCCESS;
}
#else /* !__aarch64__ */
static int psci_get_ns_ep_info(entry_point_info_t *ep,
uintptr_t entrypoint,
u_register_t context_id)
{
u_register_t ep_attr;
unsigned int aif, ee, mode;
u_register_t scr = read_scr();
u_register_t ns_sctlr, sctlr;
/* Switch to non secure state */
write_scr(scr | SCR_NS_BIT);
isb();
ns_sctlr = read_sctlr();
sctlr = scr & SCR_HCE_BIT ? read_hsctlr() : ns_sctlr;
/* Return to original state */
write_scr(scr);
isb();
ee = 0;
ep_attr = NON_SECURE | EP_ST_DISABLE;
if (sctlr & SCTLR_EE_BIT) {
ep_attr |= EP_EE_BIG;
ee = 1;
}
SET_PARAM_HEAD(ep, PARAM_EP, VERSION_1, ep_attr);
ep->pc = entrypoint;
zeromem(&ep->args, sizeof(ep->args));
ep->args.arg0 = context_id;
mode = scr & SCR_HCE_BIT ? MODE32_hyp : MODE32_svc;
/*
* TODO: Choose async. exception bits if HYP mode is not
* implemented according to the values of SCR.{AW, FW} bits
*/
aif = SPSR_ABT_BIT | SPSR_IRQ_BIT | SPSR_FIQ_BIT;
ep->spsr = SPSR_MODE32(mode, entrypoint & 0x1, ee, aif);
return PSCI_E_SUCCESS;
}
#endif /* __aarch64__ */
/*******************************************************************************
* This function validates the entrypoint with the platform layer if the
* appropriate pm_ops hook is exported by the platform and returns the
* 'entry_point_info'.
******************************************************************************/
int psci_validate_entry_point(entry_point_info_t *ep,
uintptr_t entrypoint,
u_register_t context_id)
{
int rc;
/* Validate the entrypoint using platform psci_ops */
if (psci_plat_pm_ops->validate_ns_entrypoint != NULL) {
rc = psci_plat_pm_ops->validate_ns_entrypoint(entrypoint);
if (rc != PSCI_E_SUCCESS)
return PSCI_E_INVALID_ADDRESS;
}
/*
* Verify and derive the re-entry information for
* the non-secure world from the non-secure state from
* where this call originated.
*/
rc = psci_get_ns_ep_info(ep, entrypoint, context_id);
return rc;
}
/*******************************************************************************
* Generic handler which is called when a cpu is physically powered on. It
* traverses the node information and finds the highest power level powered
* off and performs generic, architectural, platform setup and state management
* to power on that power level and power levels below it.
* e.g. For a cpu that's been powered on, it will call the platform specific
* code to enable the gic cpu interface and for a cluster it will enable
* coherency at the interconnect level in addition to gic cpu interface.
******************************************************************************/
void psci_warmboot_entrypoint(void)
{
unsigned int end_pwrlvl;
unsigned int cpu_idx = plat_my_core_pos();
unsigned int parent_nodes[PLAT_MAX_PWR_LVL] = {0};
psci_power_state_t state_info = { {PSCI_LOCAL_STATE_RUN} };
/* Init registers that never change for the lifetime of TF-A */
cm_manage_extensions_el3();
/*
* Verify that we have been explicitly turned ON or resumed from
* suspend.
*/
if (psci_get_aff_info_state() == AFF_STATE_OFF) {
ERROR("Unexpected affinity info state.\n");
panic();
}
/*
* Get the maximum power domain level to traverse to after this cpu
* has been physically powered up.
*/
end_pwrlvl = get_power_on_target_pwrlvl();
/* Get the parent nodes */
psci_get_parent_pwr_domain_nodes(cpu_idx, end_pwrlvl, parent_nodes);
/*