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