xref: /openbmc/qemu/target/arm/arm-powerctl.c (revision e2d8cf9b)
1 /*
2  * QEMU support -- ARM Power Control specific functions.
3  *
4  * Copyright (c) 2016 Jean-Christophe Dubois
5  *
6  * This work is licensed under the terms of the GNU GPL, version 2 or later.
7  * See the COPYING file in the top-level directory.
8  *
9  */
10 
11 #include "qemu/osdep.h"
12 #include "cpu.h"
13 #include "cpu-qom.h"
14 #include "internals.h"
15 #include "arm-powerctl.h"
16 #include "qemu/log.h"
17 #include "qemu/main-loop.h"
18 #include "sysemu/tcg.h"
19 #include "target/arm/multiprocessing.h"
20 
21 #ifndef DEBUG_ARM_POWERCTL
22 #define DEBUG_ARM_POWERCTL 0
23 #endif
24 
25 #define DPRINTF(fmt, args...) \
26     do { \
27         if (DEBUG_ARM_POWERCTL) { \
28             fprintf(stderr, "[ARM]%s: " fmt , __func__, ##args); \
29         } \
30     } while (0)
31 
arm_get_cpu_by_id(uint64_t id)32 CPUState *arm_get_cpu_by_id(uint64_t id)
33 {
34     CPUState *cpu;
35 
36     DPRINTF("cpu %" PRId64 "\n", id);
37 
38     CPU_FOREACH(cpu) {
39         ARMCPU *armcpu = ARM_CPU(cpu);
40 
41         if (arm_cpu_mp_affinity(armcpu) == id) {
42             return cpu;
43         }
44     }
45 
46     qemu_log_mask(LOG_GUEST_ERROR,
47                   "[ARM]%s: Requesting unknown CPU %" PRId64 "\n",
48                   __func__, id);
49 
50     return NULL;
51 }
52 
53 struct CpuOnInfo {
54     uint64_t entry;
55     uint64_t context_id;
56     uint32_t target_el;
57     bool target_aa64;
58 };
59 
60 
arm_set_cpu_on_async_work(CPUState * target_cpu_state,run_on_cpu_data data)61 static void arm_set_cpu_on_async_work(CPUState *target_cpu_state,
62                                       run_on_cpu_data data)
63 {
64     ARMCPU *target_cpu = ARM_CPU(target_cpu_state);
65     struct CpuOnInfo *info = (struct CpuOnInfo *) data.host_ptr;
66 
67     /* Initialize the cpu we are turning on */
68     cpu_reset(target_cpu_state);
69     arm_emulate_firmware_reset(target_cpu_state, info->target_el);
70     target_cpu_state->halted = 0;
71 
72     /* We check if the started CPU is now at the correct level */
73     assert(info->target_el == arm_current_el(&target_cpu->env));
74 
75     if (info->target_aa64) {
76         target_cpu->env.xregs[0] = info->context_id;
77     } else {
78         target_cpu->env.regs[0] = info->context_id;
79     }
80 
81     if (tcg_enabled()) {
82         /* CP15 update requires rebuilding hflags */
83         arm_rebuild_hflags(&target_cpu->env);
84     }
85 
86     /* Start the new CPU at the requested address */
87     cpu_set_pc(target_cpu_state, info->entry);
88 
89     g_free(info);
90 
91     /* Finally set the power status */
92     assert(bql_locked());
93     target_cpu->power_state = PSCI_ON;
94 }
95 
arm_set_cpu_on(uint64_t cpuid,uint64_t entry,uint64_t context_id,uint32_t target_el,bool target_aa64)96 int arm_set_cpu_on(uint64_t cpuid, uint64_t entry, uint64_t context_id,
97                    uint32_t target_el, bool target_aa64)
98 {
99     CPUState *target_cpu_state;
100     ARMCPU *target_cpu;
101     struct CpuOnInfo *info;
102 
103     assert(bql_locked());
104 
105     DPRINTF("cpu %" PRId64 " (EL %d, %s) @ 0x%" PRIx64 " with R0 = 0x%" PRIx64
106             "\n", cpuid, target_el, target_aa64 ? "aarch64" : "aarch32", entry,
107             context_id);
108 
109     /* requested EL level need to be in the 1 to 3 range */
110     assert((target_el > 0) && (target_el < 4));
111 
112     if (target_aa64 && (entry & 3)) {
113         /*
114          * if we are booting in AArch64 mode then "entry" needs to be 4 bytes
115          * aligned.
116          */
117         return QEMU_ARM_POWERCTL_INVALID_PARAM;
118     }
119 
120     /* Retrieve the cpu we are powering up */
121     target_cpu_state = arm_get_cpu_by_id(cpuid);
122     if (!target_cpu_state) {
123         /* The cpu was not found */
124         return QEMU_ARM_POWERCTL_INVALID_PARAM;
125     }
126 
127     target_cpu = ARM_CPU(target_cpu_state);
128     if (target_cpu->power_state == PSCI_ON) {
129         qemu_log_mask(LOG_GUEST_ERROR,
130                       "[ARM]%s: CPU %" PRId64 " is already on\n",
131                       __func__, cpuid);
132         return QEMU_ARM_POWERCTL_ALREADY_ON;
133     }
134 
135     /*
136      * The newly brought CPU is requested to enter the exception level
137      * "target_el" and be in the requested mode (AArch64 or AArch32).
138      */
139 
140     if (((target_el == 3) && !arm_feature(&target_cpu->env, ARM_FEATURE_EL3)) ||
141         ((target_el == 2) && !arm_feature(&target_cpu->env, ARM_FEATURE_EL2))) {
142         /*
143          * The CPU does not support requested level
144          */
145         return QEMU_ARM_POWERCTL_INVALID_PARAM;
146     }
147 
148     if (!target_aa64 && arm_feature(&target_cpu->env, ARM_FEATURE_AARCH64)) {
149         /*
150          * For now we don't support booting an AArch64 CPU in AArch32 mode
151          * TODO: We should add this support later
152          */
153         qemu_log_mask(LOG_UNIMP,
154                       "[ARM]%s: Starting AArch64 CPU %" PRId64
155                       " in AArch32 mode is not supported yet\n",
156                       __func__, cpuid);
157         return QEMU_ARM_POWERCTL_INVALID_PARAM;
158     }
159 
160     /*
161      * If another CPU has powered the target on we are in the state
162      * ON_PENDING and additional attempts to power on the CPU should
163      * fail (see 6.6 Implementation CPU_ON/CPU_OFF races in the PSCI
164      * spec)
165      */
166     if (target_cpu->power_state == PSCI_ON_PENDING) {
167         qemu_log_mask(LOG_GUEST_ERROR,
168                       "[ARM]%s: CPU %" PRId64 " is already powering on\n",
169                       __func__, cpuid);
170         return QEMU_ARM_POWERCTL_ON_PENDING;
171     }
172 
173     /* To avoid racing with a CPU we are just kicking off we do the
174      * final bit of preparation for the work in the target CPUs
175      * context.
176      */
177     info = g_new(struct CpuOnInfo, 1);
178     info->entry = entry;
179     info->context_id = context_id;
180     info->target_el = target_el;
181     info->target_aa64 = target_aa64;
182 
183     async_run_on_cpu(target_cpu_state, arm_set_cpu_on_async_work,
184                      RUN_ON_CPU_HOST_PTR(info));
185 
186     /* We are good to go */
187     return QEMU_ARM_POWERCTL_RET_SUCCESS;
188 }
189 
arm_set_cpu_on_and_reset_async_work(CPUState * target_cpu_state,run_on_cpu_data data)190 static void arm_set_cpu_on_and_reset_async_work(CPUState *target_cpu_state,
191                                                 run_on_cpu_data data)
192 {
193     ARMCPU *target_cpu = ARM_CPU(target_cpu_state);
194 
195     /* Initialize the cpu we are turning on */
196     cpu_reset(target_cpu_state);
197     target_cpu_state->halted = 0;
198 
199     /* Finally set the power status */
200     assert(bql_locked());
201     target_cpu->power_state = PSCI_ON;
202 }
203 
arm_set_cpu_on_and_reset(uint64_t cpuid)204 int arm_set_cpu_on_and_reset(uint64_t cpuid)
205 {
206     CPUState *target_cpu_state;
207     ARMCPU *target_cpu;
208 
209     assert(bql_locked());
210 
211     /* Retrieve the cpu we are powering up */
212     target_cpu_state = arm_get_cpu_by_id(cpuid);
213     if (!target_cpu_state) {
214         /* The cpu was not found */
215         return QEMU_ARM_POWERCTL_INVALID_PARAM;
216     }
217 
218     target_cpu = ARM_CPU(target_cpu_state);
219     if (target_cpu->power_state == PSCI_ON) {
220         qemu_log_mask(LOG_GUEST_ERROR,
221                       "[ARM]%s: CPU %" PRId64 " is already on\n",
222                       __func__, cpuid);
223         return QEMU_ARM_POWERCTL_ALREADY_ON;
224     }
225 
226     /*
227      * If another CPU has powered the target on we are in the state
228      * ON_PENDING and additional attempts to power on the CPU should
229      * fail (see 6.6 Implementation CPU_ON/CPU_OFF races in the PSCI
230      * spec)
231      */
232     if (target_cpu->power_state == PSCI_ON_PENDING) {
233         qemu_log_mask(LOG_GUEST_ERROR,
234                       "[ARM]%s: CPU %" PRId64 " is already powering on\n",
235                       __func__, cpuid);
236         return QEMU_ARM_POWERCTL_ON_PENDING;
237     }
238 
239     async_run_on_cpu(target_cpu_state, arm_set_cpu_on_and_reset_async_work,
240                      RUN_ON_CPU_NULL);
241 
242     /* We are good to go */
243     return QEMU_ARM_POWERCTL_RET_SUCCESS;
244 }
245 
arm_set_cpu_off_async_work(CPUState * target_cpu_state,run_on_cpu_data data)246 static void arm_set_cpu_off_async_work(CPUState *target_cpu_state,
247                                        run_on_cpu_data data)
248 {
249     ARMCPU *target_cpu = ARM_CPU(target_cpu_state);
250 
251     assert(bql_locked());
252     target_cpu->power_state = PSCI_OFF;
253     target_cpu_state->halted = 1;
254     target_cpu_state->exception_index = EXCP_HLT;
255 }
256 
arm_set_cpu_off(uint64_t cpuid)257 int arm_set_cpu_off(uint64_t cpuid)
258 {
259     CPUState *target_cpu_state;
260     ARMCPU *target_cpu;
261 
262     assert(bql_locked());
263 
264     DPRINTF("cpu %" PRId64 "\n", cpuid);
265 
266     /* change to the cpu we are powering up */
267     target_cpu_state = arm_get_cpu_by_id(cpuid);
268     if (!target_cpu_state) {
269         return QEMU_ARM_POWERCTL_INVALID_PARAM;
270     }
271     target_cpu = ARM_CPU(target_cpu_state);
272     if (target_cpu->power_state == PSCI_OFF) {
273         qemu_log_mask(LOG_GUEST_ERROR,
274                       "[ARM]%s: CPU %" PRId64 " is already off\n",
275                       __func__, cpuid);
276         return QEMU_ARM_POWERCTL_IS_OFF;
277     }
278 
279     /* Queue work to run under the target vCPUs context */
280     async_run_on_cpu(target_cpu_state, arm_set_cpu_off_async_work,
281                      RUN_ON_CPU_NULL);
282 
283     return QEMU_ARM_POWERCTL_RET_SUCCESS;
284 }
285 
arm_reset_cpu_async_work(CPUState * target_cpu_state,run_on_cpu_data data)286 static void arm_reset_cpu_async_work(CPUState *target_cpu_state,
287                                      run_on_cpu_data data)
288 {
289     /* Reset the cpu */
290     cpu_reset(target_cpu_state);
291 }
292 
arm_reset_cpu(uint64_t cpuid)293 int arm_reset_cpu(uint64_t cpuid)
294 {
295     CPUState *target_cpu_state;
296     ARMCPU *target_cpu;
297 
298     assert(bql_locked());
299 
300     DPRINTF("cpu %" PRId64 "\n", cpuid);
301 
302     /* change to the cpu we are resetting */
303     target_cpu_state = arm_get_cpu_by_id(cpuid);
304     if (!target_cpu_state) {
305         return QEMU_ARM_POWERCTL_INVALID_PARAM;
306     }
307     target_cpu = ARM_CPU(target_cpu_state);
308 
309     if (target_cpu->power_state == PSCI_OFF) {
310         qemu_log_mask(LOG_GUEST_ERROR,
311                       "[ARM]%s: CPU %" PRId64 " is off\n",
312                       __func__, cpuid);
313         return QEMU_ARM_POWERCTL_IS_OFF;
314     }
315 
316     /* Queue work to run under the target vCPUs context */
317     async_run_on_cpu(target_cpu_state, arm_reset_cpu_async_work,
318                      RUN_ON_CPU_NULL);
319 
320     return QEMU_ARM_POWERCTL_RET_SUCCESS;
321 }
322