xref: /openbmc/qemu/target/arm/arm-powerctl.c (revision 7a1dc45af581d2b643cdbf33c01fd96271616fbd)
1fcf5ef2aSThomas Huth /*
2fcf5ef2aSThomas Huth  * QEMU support -- ARM Power Control specific functions.
3fcf5ef2aSThomas Huth  *
4fcf5ef2aSThomas Huth  * Copyright (c) 2016 Jean-Christophe Dubois
5fcf5ef2aSThomas Huth  *
6fcf5ef2aSThomas Huth  * This work is licensed under the terms of the GNU GPL, version 2 or later.
7fcf5ef2aSThomas Huth  * See the COPYING file in the top-level directory.
8fcf5ef2aSThomas Huth  *
9fcf5ef2aSThomas Huth  */
10fcf5ef2aSThomas Huth 
11fcf5ef2aSThomas Huth #include "qemu/osdep.h"
12fcf5ef2aSThomas Huth #include "cpu.h"
13fcf5ef2aSThomas Huth #include "cpu-qom.h"
14fcf5ef2aSThomas Huth #include "internals.h"
15fcf5ef2aSThomas Huth #include "arm-powerctl.h"
16fcf5ef2aSThomas Huth #include "qemu/log.h"
17062ba099SAlex Bennée #include "qemu/main-loop.h"
182b77ad4dSFabiano Rosas #include "sysemu/tcg.h"
19*e2d8cf9bSPhilippe Mathieu-Daudé #include "target/arm/multiprocessing.h"
20fcf5ef2aSThomas Huth 
21fcf5ef2aSThomas Huth #ifndef DEBUG_ARM_POWERCTL
22fcf5ef2aSThomas Huth #define DEBUG_ARM_POWERCTL 0
23fcf5ef2aSThomas Huth #endif
24fcf5ef2aSThomas Huth 
25fcf5ef2aSThomas Huth #define DPRINTF(fmt, args...) \
26fcf5ef2aSThomas Huth     do { \
27fcf5ef2aSThomas Huth         if (DEBUG_ARM_POWERCTL) { \
28fcf5ef2aSThomas Huth             fprintf(stderr, "[ARM]%s: " fmt , __func__, ##args); \
29fcf5ef2aSThomas Huth         } \
30fcf5ef2aSThomas Huth     } while (0)
31fcf5ef2aSThomas Huth 
arm_get_cpu_by_id(uint64_t id)32fcf5ef2aSThomas Huth CPUState *arm_get_cpu_by_id(uint64_t id)
33fcf5ef2aSThomas Huth {
34fcf5ef2aSThomas Huth     CPUState *cpu;
35fcf5ef2aSThomas Huth 
36fcf5ef2aSThomas Huth     DPRINTF("cpu %" PRId64 "\n", id);
37fcf5ef2aSThomas Huth 
38fcf5ef2aSThomas Huth     CPU_FOREACH(cpu) {
39fcf5ef2aSThomas Huth         ARMCPU *armcpu = ARM_CPU(cpu);
40fcf5ef2aSThomas Huth 
41c4380f7bSRichard Henderson         if (arm_cpu_mp_affinity(armcpu) == id) {
42fcf5ef2aSThomas Huth             return cpu;
43fcf5ef2aSThomas Huth         }
44fcf5ef2aSThomas Huth     }
45fcf5ef2aSThomas Huth 
46fcf5ef2aSThomas Huth     qemu_log_mask(LOG_GUEST_ERROR,
47fcf5ef2aSThomas Huth                   "[ARM]%s: Requesting unknown CPU %" PRId64 "\n",
48fcf5ef2aSThomas Huth                   __func__, id);
49fcf5ef2aSThomas Huth 
50fcf5ef2aSThomas Huth     return NULL;
51fcf5ef2aSThomas Huth }
52fcf5ef2aSThomas Huth 
53062ba099SAlex Bennée struct CpuOnInfo {
54062ba099SAlex Bennée     uint64_t entry;
55062ba099SAlex Bennée     uint64_t context_id;
56062ba099SAlex Bennée     uint32_t target_el;
57062ba099SAlex Bennée     bool target_aa64;
58062ba099SAlex Bennée };
59062ba099SAlex Bennée 
60062ba099SAlex Bennée 
arm_set_cpu_on_async_work(CPUState * target_cpu_state,run_on_cpu_data data)61062ba099SAlex Bennée static void arm_set_cpu_on_async_work(CPUState *target_cpu_state,
62062ba099SAlex Bennée                                       run_on_cpu_data data)
63062ba099SAlex Bennée {
64062ba099SAlex Bennée     ARMCPU *target_cpu = ARM_CPU(target_cpu_state);
65062ba099SAlex Bennée     struct CpuOnInfo *info = (struct CpuOnInfo *) data.host_ptr;
66062ba099SAlex Bennée 
67062ba099SAlex Bennée     /* Initialize the cpu we are turning on */
68062ba099SAlex Bennée     cpu_reset(target_cpu_state);
693a45f4f5SPeter Maydell     arm_emulate_firmware_reset(target_cpu_state, info->target_el);
70062ba099SAlex Bennée     target_cpu_state->halted = 0;
71062ba099SAlex Bennée 
72062ba099SAlex Bennée     /* We check if the started CPU is now at the correct level */
73062ba099SAlex Bennée     assert(info->target_el == arm_current_el(&target_cpu->env));
74062ba099SAlex Bennée 
75062ba099SAlex Bennée     if (info->target_aa64) {
76062ba099SAlex Bennée         target_cpu->env.xregs[0] = info->context_id;
77062ba099SAlex Bennée     } else {
78062ba099SAlex Bennée         target_cpu->env.regs[0] = info->context_id;
79062ba099SAlex Bennée     }
80062ba099SAlex Bennée 
812b77ad4dSFabiano Rosas     if (tcg_enabled()) {
82c8fa6079SNiek Linnenbank         /* CP15 update requires rebuilding hflags */
83c8fa6079SNiek Linnenbank         arm_rebuild_hflags(&target_cpu->env);
842b77ad4dSFabiano Rosas     }
85c8fa6079SNiek Linnenbank 
86062ba099SAlex Bennée     /* Start the new CPU at the requested address */
87062ba099SAlex Bennée     cpu_set_pc(target_cpu_state, info->entry);
88062ba099SAlex Bennée 
89062ba099SAlex Bennée     g_free(info);
90062ba099SAlex Bennée 
91062ba099SAlex Bennée     /* Finally set the power status */
92195801d7SStefan Hajnoczi     assert(bql_locked());
93062ba099SAlex Bennée     target_cpu->power_state = PSCI_ON;
94062ba099SAlex Bennée }
95062ba099SAlex Bennée 
arm_set_cpu_on(uint64_t cpuid,uint64_t entry,uint64_t context_id,uint32_t target_el,bool target_aa64)96fcf5ef2aSThomas Huth int arm_set_cpu_on(uint64_t cpuid, uint64_t entry, uint64_t context_id,
97fcf5ef2aSThomas Huth                    uint32_t target_el, bool target_aa64)
98fcf5ef2aSThomas Huth {
99fcf5ef2aSThomas Huth     CPUState *target_cpu_state;
100fcf5ef2aSThomas Huth     ARMCPU *target_cpu;
101062ba099SAlex Bennée     struct CpuOnInfo *info;
102062ba099SAlex Bennée 
103195801d7SStefan Hajnoczi     assert(bql_locked());
104fcf5ef2aSThomas Huth 
105fcf5ef2aSThomas Huth     DPRINTF("cpu %" PRId64 " (EL %d, %s) @ 0x%" PRIx64 " with R0 = 0x%" PRIx64
106fcf5ef2aSThomas Huth             "\n", cpuid, target_el, target_aa64 ? "aarch64" : "aarch32", entry,
107fcf5ef2aSThomas Huth             context_id);
108fcf5ef2aSThomas Huth 
109fcf5ef2aSThomas Huth     /* requested EL level need to be in the 1 to 3 range */
110fcf5ef2aSThomas Huth     assert((target_el > 0) && (target_el < 4));
111fcf5ef2aSThomas Huth 
112fcf5ef2aSThomas Huth     if (target_aa64 && (entry & 3)) {
113fcf5ef2aSThomas Huth         /*
114fcf5ef2aSThomas Huth          * if we are booting in AArch64 mode then "entry" needs to be 4 bytes
115fcf5ef2aSThomas Huth          * aligned.
116fcf5ef2aSThomas Huth          */
117fcf5ef2aSThomas Huth         return QEMU_ARM_POWERCTL_INVALID_PARAM;
118fcf5ef2aSThomas Huth     }
119fcf5ef2aSThomas Huth 
120fcf5ef2aSThomas Huth     /* Retrieve the cpu we are powering up */
121fcf5ef2aSThomas Huth     target_cpu_state = arm_get_cpu_by_id(cpuid);
122fcf5ef2aSThomas Huth     if (!target_cpu_state) {
123fcf5ef2aSThomas Huth         /* The cpu was not found */
124fcf5ef2aSThomas Huth         return QEMU_ARM_POWERCTL_INVALID_PARAM;
125fcf5ef2aSThomas Huth     }
126fcf5ef2aSThomas Huth 
127fcf5ef2aSThomas Huth     target_cpu = ARM_CPU(target_cpu_state);
128062ba099SAlex Bennée     if (target_cpu->power_state == PSCI_ON) {
129fcf5ef2aSThomas Huth         qemu_log_mask(LOG_GUEST_ERROR,
130fcf5ef2aSThomas Huth                       "[ARM]%s: CPU %" PRId64 " is already on\n",
131fcf5ef2aSThomas Huth                       __func__, cpuid);
132fcf5ef2aSThomas Huth         return QEMU_ARM_POWERCTL_ALREADY_ON;
133fcf5ef2aSThomas Huth     }
134fcf5ef2aSThomas Huth 
135fcf5ef2aSThomas Huth     /*
136fcf5ef2aSThomas Huth      * The newly brought CPU is requested to enter the exception level
137fcf5ef2aSThomas Huth      * "target_el" and be in the requested mode (AArch64 or AArch32).
138fcf5ef2aSThomas Huth      */
139fcf5ef2aSThomas Huth 
140fcf5ef2aSThomas Huth     if (((target_el == 3) && !arm_feature(&target_cpu->env, ARM_FEATURE_EL3)) ||
141fcf5ef2aSThomas Huth         ((target_el == 2) && !arm_feature(&target_cpu->env, ARM_FEATURE_EL2))) {
142fcf5ef2aSThomas Huth         /*
143fcf5ef2aSThomas Huth          * The CPU does not support requested level
144fcf5ef2aSThomas Huth          */
145fcf5ef2aSThomas Huth         return QEMU_ARM_POWERCTL_INVALID_PARAM;
146fcf5ef2aSThomas Huth     }
147fcf5ef2aSThomas Huth 
148fcf5ef2aSThomas Huth     if (!target_aa64 && arm_feature(&target_cpu->env, ARM_FEATURE_AARCH64)) {
149fcf5ef2aSThomas Huth         /*
150fcf5ef2aSThomas Huth          * For now we don't support booting an AArch64 CPU in AArch32 mode
151fcf5ef2aSThomas Huth          * TODO: We should add this support later
152fcf5ef2aSThomas Huth          */
153fcf5ef2aSThomas Huth         qemu_log_mask(LOG_UNIMP,
154fcf5ef2aSThomas Huth                       "[ARM]%s: Starting AArch64 CPU %" PRId64
155fcf5ef2aSThomas Huth                       " in AArch32 mode is not supported yet\n",
156fcf5ef2aSThomas Huth                       __func__, cpuid);
157fcf5ef2aSThomas Huth         return QEMU_ARM_POWERCTL_INVALID_PARAM;
158fcf5ef2aSThomas Huth     }
159fcf5ef2aSThomas Huth 
160fcf5ef2aSThomas Huth     /*
161062ba099SAlex Bennée      * If another CPU has powered the target on we are in the state
162062ba099SAlex Bennée      * ON_PENDING and additional attempts to power on the CPU should
163062ba099SAlex Bennée      * fail (see 6.6 Implementation CPU_ON/CPU_OFF races in the PSCI
164062ba099SAlex Bennée      * spec)
165fcf5ef2aSThomas Huth      */
166062ba099SAlex Bennée     if (target_cpu->power_state == PSCI_ON_PENDING) {
167062ba099SAlex Bennée         qemu_log_mask(LOG_GUEST_ERROR,
168062ba099SAlex Bennée                       "[ARM]%s: CPU %" PRId64 " is already powering on\n",
169062ba099SAlex Bennée                       __func__, cpuid);
170062ba099SAlex Bennée         return QEMU_ARM_POWERCTL_ON_PENDING;
171fcf5ef2aSThomas Huth     }
172fcf5ef2aSThomas Huth 
173062ba099SAlex Bennée     /* To avoid racing with a CPU we are just kicking off we do the
174062ba099SAlex Bennée      * final bit of preparation for the work in the target CPUs
175062ba099SAlex Bennée      * context.
176fcf5ef2aSThomas Huth      */
177062ba099SAlex Bennée     info = g_new(struct CpuOnInfo, 1);
178062ba099SAlex Bennée     info->entry = entry;
179062ba099SAlex Bennée     info->context_id = context_id;
180062ba099SAlex Bennée     info->target_el = target_el;
181062ba099SAlex Bennée     info->target_aa64 = target_aa64;
182fcf5ef2aSThomas Huth 
183062ba099SAlex Bennée     async_run_on_cpu(target_cpu_state, arm_set_cpu_on_async_work,
184062ba099SAlex Bennée                      RUN_ON_CPU_HOST_PTR(info));
185fcf5ef2aSThomas Huth 
186fcf5ef2aSThomas Huth     /* We are good to go */
187fcf5ef2aSThomas Huth     return QEMU_ARM_POWERCTL_RET_SUCCESS;
188fcf5ef2aSThomas Huth }
189fcf5ef2aSThomas Huth 
arm_set_cpu_on_and_reset_async_work(CPUState * target_cpu_state,run_on_cpu_data data)190ea824b97SPeter Maydell static void arm_set_cpu_on_and_reset_async_work(CPUState *target_cpu_state,
191ea824b97SPeter Maydell                                                 run_on_cpu_data data)
192ea824b97SPeter Maydell {
193ea824b97SPeter Maydell     ARMCPU *target_cpu = ARM_CPU(target_cpu_state);
194ea824b97SPeter Maydell 
195ea824b97SPeter Maydell     /* Initialize the cpu we are turning on */
196ea824b97SPeter Maydell     cpu_reset(target_cpu_state);
197ea824b97SPeter Maydell     target_cpu_state->halted = 0;
198ea824b97SPeter Maydell 
199ea824b97SPeter Maydell     /* Finally set the power status */
200195801d7SStefan Hajnoczi     assert(bql_locked());
201ea824b97SPeter Maydell     target_cpu->power_state = PSCI_ON;
202ea824b97SPeter Maydell }
203ea824b97SPeter Maydell 
arm_set_cpu_on_and_reset(uint64_t cpuid)204ea824b97SPeter Maydell int arm_set_cpu_on_and_reset(uint64_t cpuid)
205ea824b97SPeter Maydell {
206ea824b97SPeter Maydell     CPUState *target_cpu_state;
207ea824b97SPeter Maydell     ARMCPU *target_cpu;
208ea824b97SPeter Maydell 
209195801d7SStefan Hajnoczi     assert(bql_locked());
210ea824b97SPeter Maydell 
211ea824b97SPeter Maydell     /* Retrieve the cpu we are powering up */
212ea824b97SPeter Maydell     target_cpu_state = arm_get_cpu_by_id(cpuid);
213ea824b97SPeter Maydell     if (!target_cpu_state) {
214ea824b97SPeter Maydell         /* The cpu was not found */
215ea824b97SPeter Maydell         return QEMU_ARM_POWERCTL_INVALID_PARAM;
216ea824b97SPeter Maydell     }
217ea824b97SPeter Maydell 
218ea824b97SPeter Maydell     target_cpu = ARM_CPU(target_cpu_state);
219ea824b97SPeter Maydell     if (target_cpu->power_state == PSCI_ON) {
220ea824b97SPeter Maydell         qemu_log_mask(LOG_GUEST_ERROR,
221ea824b97SPeter Maydell                       "[ARM]%s: CPU %" PRId64 " is already on\n",
222ea824b97SPeter Maydell                       __func__, cpuid);
223ea824b97SPeter Maydell         return QEMU_ARM_POWERCTL_ALREADY_ON;
224ea824b97SPeter Maydell     }
225ea824b97SPeter Maydell 
226ea824b97SPeter Maydell     /*
227ea824b97SPeter Maydell      * If another CPU has powered the target on we are in the state
228ea824b97SPeter Maydell      * ON_PENDING and additional attempts to power on the CPU should
229ea824b97SPeter Maydell      * fail (see 6.6 Implementation CPU_ON/CPU_OFF races in the PSCI
230ea824b97SPeter Maydell      * spec)
231ea824b97SPeter Maydell      */
232ea824b97SPeter Maydell     if (target_cpu->power_state == PSCI_ON_PENDING) {
233ea824b97SPeter Maydell         qemu_log_mask(LOG_GUEST_ERROR,
234ea824b97SPeter Maydell                       "[ARM]%s: CPU %" PRId64 " is already powering on\n",
235ea824b97SPeter Maydell                       __func__, cpuid);
236ea824b97SPeter Maydell         return QEMU_ARM_POWERCTL_ON_PENDING;
237ea824b97SPeter Maydell     }
238ea824b97SPeter Maydell 
239ea824b97SPeter Maydell     async_run_on_cpu(target_cpu_state, arm_set_cpu_on_and_reset_async_work,
240ea824b97SPeter Maydell                      RUN_ON_CPU_NULL);
241ea824b97SPeter Maydell 
242ea824b97SPeter Maydell     /* We are good to go */
243ea824b97SPeter Maydell     return QEMU_ARM_POWERCTL_RET_SUCCESS;
244ea824b97SPeter Maydell }
245ea824b97SPeter Maydell 
arm_set_cpu_off_async_work(CPUState * target_cpu_state,run_on_cpu_data data)246062ba099SAlex Bennée static void arm_set_cpu_off_async_work(CPUState *target_cpu_state,
247062ba099SAlex Bennée                                        run_on_cpu_data data)
248062ba099SAlex Bennée {
249062ba099SAlex Bennée     ARMCPU *target_cpu = ARM_CPU(target_cpu_state);
250062ba099SAlex Bennée 
251195801d7SStefan Hajnoczi     assert(bql_locked());
252062ba099SAlex Bennée     target_cpu->power_state = PSCI_OFF;
253062ba099SAlex Bennée     target_cpu_state->halted = 1;
254062ba099SAlex Bennée     target_cpu_state->exception_index = EXCP_HLT;
255062ba099SAlex Bennée }
256062ba099SAlex Bennée 
arm_set_cpu_off(uint64_t cpuid)257fcf5ef2aSThomas Huth int arm_set_cpu_off(uint64_t cpuid)
258fcf5ef2aSThomas Huth {
259fcf5ef2aSThomas Huth     CPUState *target_cpu_state;
260fcf5ef2aSThomas Huth     ARMCPU *target_cpu;
261fcf5ef2aSThomas Huth 
262195801d7SStefan Hajnoczi     assert(bql_locked());
263062ba099SAlex Bennée 
264fcf5ef2aSThomas Huth     DPRINTF("cpu %" PRId64 "\n", cpuid);
265fcf5ef2aSThomas Huth 
266fcf5ef2aSThomas Huth     /* change to the cpu we are powering up */
267fcf5ef2aSThomas Huth     target_cpu_state = arm_get_cpu_by_id(cpuid);
268fcf5ef2aSThomas Huth     if (!target_cpu_state) {
269fcf5ef2aSThomas Huth         return QEMU_ARM_POWERCTL_INVALID_PARAM;
270fcf5ef2aSThomas Huth     }
271fcf5ef2aSThomas Huth     target_cpu = ARM_CPU(target_cpu_state);
272062ba099SAlex Bennée     if (target_cpu->power_state == PSCI_OFF) {
273fcf5ef2aSThomas Huth         qemu_log_mask(LOG_GUEST_ERROR,
274fcf5ef2aSThomas Huth                       "[ARM]%s: CPU %" PRId64 " is already off\n",
275fcf5ef2aSThomas Huth                       __func__, cpuid);
276fcf5ef2aSThomas Huth         return QEMU_ARM_POWERCTL_IS_OFF;
277fcf5ef2aSThomas Huth     }
278fcf5ef2aSThomas Huth 
279062ba099SAlex Bennée     /* Queue work to run under the target vCPUs context */
280062ba099SAlex Bennée     async_run_on_cpu(target_cpu_state, arm_set_cpu_off_async_work,
281062ba099SAlex Bennée                      RUN_ON_CPU_NULL);
282fcf5ef2aSThomas Huth 
283fcf5ef2aSThomas Huth     return QEMU_ARM_POWERCTL_RET_SUCCESS;
284fcf5ef2aSThomas Huth }
285fcf5ef2aSThomas Huth 
arm_reset_cpu_async_work(CPUState * target_cpu_state,run_on_cpu_data data)286062ba099SAlex Bennée static void arm_reset_cpu_async_work(CPUState *target_cpu_state,
287062ba099SAlex Bennée                                      run_on_cpu_data data)
288062ba099SAlex Bennée {
289062ba099SAlex Bennée     /* Reset the cpu */
290062ba099SAlex Bennée     cpu_reset(target_cpu_state);
291062ba099SAlex Bennée }
292062ba099SAlex Bennée 
arm_reset_cpu(uint64_t cpuid)293fcf5ef2aSThomas Huth int arm_reset_cpu(uint64_t cpuid)
294fcf5ef2aSThomas Huth {
295fcf5ef2aSThomas Huth     CPUState *target_cpu_state;
296fcf5ef2aSThomas Huth     ARMCPU *target_cpu;
297fcf5ef2aSThomas Huth 
298195801d7SStefan Hajnoczi     assert(bql_locked());
299062ba099SAlex Bennée 
300fcf5ef2aSThomas Huth     DPRINTF("cpu %" PRId64 "\n", cpuid);
301fcf5ef2aSThomas Huth 
302fcf5ef2aSThomas Huth     /* change to the cpu we are resetting */
303fcf5ef2aSThomas Huth     target_cpu_state = arm_get_cpu_by_id(cpuid);
304fcf5ef2aSThomas Huth     if (!target_cpu_state) {
305fcf5ef2aSThomas Huth         return QEMU_ARM_POWERCTL_INVALID_PARAM;
306fcf5ef2aSThomas Huth     }
307fcf5ef2aSThomas Huth     target_cpu = ARM_CPU(target_cpu_state);
308062ba099SAlex Bennée 
309062ba099SAlex Bennée     if (target_cpu->power_state == PSCI_OFF) {
310fcf5ef2aSThomas Huth         qemu_log_mask(LOG_GUEST_ERROR,
311fcf5ef2aSThomas Huth                       "[ARM]%s: CPU %" PRId64 " is off\n",
312fcf5ef2aSThomas Huth                       __func__, cpuid);
313fcf5ef2aSThomas Huth         return QEMU_ARM_POWERCTL_IS_OFF;
314fcf5ef2aSThomas Huth     }
315fcf5ef2aSThomas Huth 
316062ba099SAlex Bennée     /* Queue work to run under the target vCPUs context */
317062ba099SAlex Bennée     async_run_on_cpu(target_cpu_state, arm_reset_cpu_async_work,
318062ba099SAlex Bennée                      RUN_ON_CPU_NULL);
319fcf5ef2aSThomas Huth 
320fcf5ef2aSThomas Huth     return QEMU_ARM_POWERCTL_RET_SUCCESS;
321fcf5ef2aSThomas Huth }
322