]>
Commit | Line | Data |
---|---|---|
0bf4d77e NP |
1 | /* |
2 | * QEMU PowerPC PowerNV Emulation of some SBE behaviour | |
3 | * | |
4 | * Copyright (c) 2022, IBM Corporation. | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License, version 2, as | |
8 | * published by the Free Software Foundation. | |
9 | * | |
10 | * This program is distributed in the hope that it will be useful, | |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | * GNU General Public License for more details. | |
14 | * | |
15 | * You should have received a copy of the GNU General Public License | |
16 | * along with this program; if not, see <http://www.gnu.org/licenses/>. | |
17 | */ | |
18 | ||
19 | #include "qemu/osdep.h" | |
20 | #include "target/ppc/cpu.h" | |
21 | #include "qapi/error.h" | |
22 | #include "qemu/log.h" | |
23 | #include "qemu/module.h" | |
24 | #include "hw/irq.h" | |
25 | #include "hw/qdev-properties.h" | |
26 | #include "hw/ppc/pnv.h" | |
27 | #include "hw/ppc/pnv_xscom.h" | |
28 | #include "hw/ppc/pnv_sbe.h" | |
29 | #include "trace.h" | |
30 | ||
31 | /* | |
32 | * Most register and command definitions come from skiboot. | |
33 | * | |
34 | * xscom addresses are adjusted to be relative to xscom subregion bases | |
35 | */ | |
36 | ||
37 | /* | |
38 | * SBE MBOX register address | |
39 | * Reg 0 - 3 : Host to send command packets to SBE | |
40 | * Reg 4 - 7 : SBE to send response packets to Host | |
41 | */ | |
42 | #define PSU_HOST_SBE_MBOX_REG0 0x00000000 | |
43 | #define PSU_HOST_SBE_MBOX_REG1 0x00000001 | |
44 | #define PSU_HOST_SBE_MBOX_REG2 0x00000002 | |
45 | #define PSU_HOST_SBE_MBOX_REG3 0x00000003 | |
46 | #define PSU_HOST_SBE_MBOX_REG4 0x00000004 | |
47 | #define PSU_HOST_SBE_MBOX_REG5 0x00000005 | |
48 | #define PSU_HOST_SBE_MBOX_REG6 0x00000006 | |
49 | #define PSU_HOST_SBE_MBOX_REG7 0x00000007 | |
50 | #define PSU_SBE_DOORBELL_REG_RW 0x00000010 | |
51 | #define PSU_SBE_DOORBELL_REG_AND 0x00000011 | |
52 | #define PSU_SBE_DOORBELL_REG_OR 0x00000012 | |
53 | #define PSU_HOST_DOORBELL_REG_RW 0x00000013 | |
54 | #define PSU_HOST_DOORBELL_REG_AND 0x00000014 | |
55 | #define PSU_HOST_DOORBELL_REG_OR 0x00000015 | |
56 | ||
57 | /* | |
58 | * Doorbell register to trigger SBE interrupt. Set by OPAL to inform | |
59 | * the SBE about a waiting message in the Host/SBE mailbox registers | |
60 | */ | |
61 | #define HOST_SBE_MSG_WAITING PPC_BIT(0) | |
62 | ||
63 | /* | |
64 | * Doorbell register for host bridge interrupt. Set by the SBE to inform | |
65 | * host about a response message in the Host/SBE mailbox registers | |
66 | */ | |
67 | #define SBE_HOST_RESPONSE_WAITING PPC_BIT(0) | |
68 | #define SBE_HOST_MSG_READ PPC_BIT(1) | |
69 | #define SBE_HOST_STOP15_EXIT PPC_BIT(2) | |
70 | #define SBE_HOST_RESET PPC_BIT(3) | |
71 | #define SBE_HOST_PASSTHROUGH PPC_BIT(4) | |
72 | #define SBE_HOST_TIMER_EXPIRY PPC_BIT(14) | |
73 | #define SBE_HOST_RESPONSE_MASK (PPC_BITMASK(0, 4) | \ | |
74 | SBE_HOST_TIMER_EXPIRY) | |
75 | ||
76 | /* SBE Control Register */ | |
77 | #define SBE_CONTROL_REG_RW 0x00000000 | |
78 | ||
79 | /* SBE interrupt s0/s1 bits */ | |
80 | #define SBE_CONTROL_REG_S0 PPC_BIT(14) | |
81 | #define SBE_CONTROL_REG_S1 PPC_BIT(15) | |
82 | ||
83 | struct sbe_msg { | |
84 | uint64_t reg[4]; | |
85 | }; | |
86 | ||
87 | static uint64_t pnv_sbe_power9_xscom_ctrl_read(void *opaque, hwaddr addr, | |
88 | unsigned size) | |
89 | { | |
90 | uint32_t offset = addr >> 3; | |
91 | uint64_t val = 0; | |
92 | ||
93 | switch (offset) { | |
94 | default: | |
95 | qemu_log_mask(LOG_UNIMP, "SBE Unimplemented register: Ox%" | |
96 | HWADDR_PRIx "\n", addr >> 3); | |
97 | } | |
98 | ||
99 | trace_pnv_sbe_xscom_ctrl_read(addr, val); | |
100 | ||
101 | return val; | |
102 | } | |
103 | ||
104 | static void pnv_sbe_power9_xscom_ctrl_write(void *opaque, hwaddr addr, | |
105 | uint64_t val, unsigned size) | |
106 | { | |
107 | uint32_t offset = addr >> 3; | |
108 | ||
109 | trace_pnv_sbe_xscom_ctrl_write(addr, val); | |
110 | ||
111 | switch (offset) { | |
112 | default: | |
113 | qemu_log_mask(LOG_UNIMP, "SBE Unimplemented register: Ox%" | |
114 | HWADDR_PRIx "\n", addr >> 3); | |
115 | } | |
116 | } | |
117 | ||
118 | static const MemoryRegionOps pnv_sbe_power9_xscom_ctrl_ops = { | |
119 | .read = pnv_sbe_power9_xscom_ctrl_read, | |
120 | .write = pnv_sbe_power9_xscom_ctrl_write, | |
121 | .valid.min_access_size = 8, | |
122 | .valid.max_access_size = 8, | |
123 | .impl.min_access_size = 8, | |
124 | .impl.max_access_size = 8, | |
125 | .endianness = DEVICE_BIG_ENDIAN, | |
126 | }; | |
127 | ||
128 | static void pnv_sbe_set_host_doorbell(PnvSBE *sbe, uint64_t val) | |
129 | { | |
130 | val &= SBE_HOST_RESPONSE_MASK; /* Is this right? What does HW do? */ | |
131 | sbe->host_doorbell = val; | |
132 | ||
133 | trace_pnv_sbe_reg_set_host_doorbell(val); | |
134 | qemu_set_irq(sbe->psi_irq, !!val); | |
135 | } | |
136 | ||
137 | /* SBE Target Type */ | |
138 | #define SBE_TARGET_TYPE_PROC 0x00 | |
139 | #define SBE_TARGET_TYPE_EX 0x01 | |
140 | #define SBE_TARGET_TYPE_PERV 0x02 | |
141 | #define SBE_TARGET_TYPE_MCS 0x03 | |
142 | #define SBE_TARGET_TYPE_EQ 0x04 | |
143 | #define SBE_TARGET_TYPE_CORE 0x05 | |
144 | ||
145 | /* SBE MBOX command class */ | |
146 | #define SBE_MCLASS_FIRST 0xD1 | |
147 | #define SBE_MCLASS_CORE_STATE 0xD1 | |
148 | #define SBE_MCLASS_SCOM 0xD2 | |
149 | #define SBE_MCLASS_RING 0xD3 | |
150 | #define SBE_MCLASS_TIMER 0xD4 | |
151 | #define SBE_MCLASS_MPIPL 0xD5 | |
152 | #define SBE_MCLASS_SECURITY 0xD6 | |
153 | #define SBE_MCLASS_GENERIC 0xD7 | |
154 | #define SBE_MCLASS_LAST 0xD7 | |
155 | ||
156 | /* | |
157 | * Commands are provided in xxyy form where: | |
158 | * - xx : command class | |
159 | * - yy : command | |
160 | * | |
161 | * Both request and response message uses same seq ID, | |
162 | * command class and command. | |
163 | */ | |
164 | #define SBE_CMD_CTRL_DEADMAN_LOOP 0xD101 | |
165 | #define SBE_CMD_MULTI_SCOM 0xD201 | |
166 | #define SBE_CMD_PUT_RING_FORM_IMAGE 0xD301 | |
167 | #define SBE_CMD_CONTROL_TIMER 0xD401 | |
168 | #define SBE_CMD_GET_ARCHITECTED_REG 0xD501 | |
169 | #define SBE_CMD_CLR_ARCHITECTED_REG 0xD502 | |
170 | #define SBE_CMD_SET_UNSEC_MEM_WINDOW 0xD601 | |
171 | #define SBE_CMD_GET_SBE_FFDC 0xD701 | |
172 | #define SBE_CMD_GET_CAPABILITY 0xD702 | |
173 | #define SBE_CMD_READ_SBE_SEEPROM 0xD703 | |
174 | #define SBE_CMD_SET_FFDC_ADDR 0xD704 | |
175 | #define SBE_CMD_QUIESCE_SBE 0xD705 | |
176 | #define SBE_CMD_SET_FABRIC_ID_MAP 0xD706 | |
177 | #define SBE_CMD_STASH_MPIPL_CONFIG 0xD707 | |
178 | ||
179 | /* SBE MBOX control flags */ | |
180 | ||
181 | /* Generic flags */ | |
182 | #define SBE_CMD_CTRL_RESP_REQ 0x0100 | |
183 | #define SBE_CMD_CTRL_ACK_REQ 0x0200 | |
184 | ||
185 | /* Deadman loop */ | |
186 | #define CTRL_DEADMAN_LOOP_START 0x0001 | |
187 | #define CTRL_DEADMAN_LOOP_STOP 0x0002 | |
188 | ||
189 | /* Control timer */ | |
190 | #define CONTROL_TIMER_START 0x0001 | |
191 | #define CONTROL_TIMER_STOP 0x0002 | |
192 | ||
193 | /* Stash MPIPL config */ | |
194 | #define SBE_STASH_KEY_SKIBOOT_BASE 0x03 | |
195 | ||
196 | static void sbe_timer(void *opaque) | |
197 | { | |
198 | PnvSBE *sbe = opaque; | |
199 | ||
200 | trace_pnv_sbe_cmd_timer_expired(); | |
201 | ||
202 | pnv_sbe_set_host_doorbell(sbe, sbe->host_doorbell | SBE_HOST_TIMER_EXPIRY); | |
203 | } | |
204 | ||
205 | static void do_sbe_msg(PnvSBE *sbe) | |
206 | { | |
207 | struct sbe_msg msg; | |
208 | uint16_t cmd, ctrl_flags, seq_id; | |
209 | int i; | |
210 | ||
211 | memset(&msg, 0, sizeof(msg)); | |
212 | ||
213 | for (i = 0; i < 4; i++) { | |
214 | msg.reg[i] = sbe->mbox[i]; | |
215 | } | |
216 | ||
217 | cmd = msg.reg[0]; | |
218 | seq_id = msg.reg[0] >> 16; | |
219 | ctrl_flags = msg.reg[0] >> 32; | |
220 | ||
221 | trace_pnv_sbe_msg_recv(cmd, seq_id, ctrl_flags); | |
222 | ||
223 | if (ctrl_flags & SBE_CMD_CTRL_ACK_REQ) { | |
224 | pnv_sbe_set_host_doorbell(sbe, sbe->host_doorbell | SBE_HOST_MSG_READ); | |
225 | } | |
226 | ||
227 | switch (cmd) { | |
228 | case SBE_CMD_CONTROL_TIMER: | |
229 | if (ctrl_flags & CONTROL_TIMER_START) { | |
230 | uint64_t us = msg.reg[1]; | |
231 | trace_pnv_sbe_cmd_timer_start(us); | |
232 | timer_mod(sbe->timer, qemu_clock_get_us(QEMU_CLOCK_VIRTUAL) + us); | |
233 | } | |
234 | if (ctrl_flags & CONTROL_TIMER_STOP) { | |
235 | trace_pnv_sbe_cmd_timer_stop(); | |
236 | timer_del(sbe->timer); | |
237 | } | |
238 | break; | |
239 | default: | |
240 | qemu_log_mask(LOG_UNIMP, "SBE Unimplemented command: 0x%x\n", cmd); | |
241 | } | |
242 | } | |
243 | ||
244 | static void pnv_sbe_set_sbe_doorbell(PnvSBE *sbe, uint64_t val) | |
245 | { | |
246 | val &= HOST_SBE_MSG_WAITING; | |
247 | sbe->sbe_doorbell = val; | |
248 | ||
249 | if (val & HOST_SBE_MSG_WAITING) { | |
250 | sbe->sbe_doorbell &= ~HOST_SBE_MSG_WAITING; | |
251 | do_sbe_msg(sbe); | |
252 | } | |
253 | } | |
254 | ||
255 | static uint64_t pnv_sbe_power9_xscom_mbox_read(void *opaque, hwaddr addr, | |
256 | unsigned size) | |
257 | { | |
258 | PnvSBE *sbe = PNV_SBE(opaque); | |
259 | uint32_t offset = addr >> 3; | |
260 | uint64_t val = 0; | |
261 | ||
262 | if (offset <= PSU_HOST_SBE_MBOX_REG7) { | |
263 | uint32_t idx = offset - PSU_HOST_SBE_MBOX_REG0; | |
264 | val = sbe->mbox[idx]; | |
265 | } else { | |
266 | switch (offset) { | |
267 | case PSU_SBE_DOORBELL_REG_RW: | |
268 | val = sbe->sbe_doorbell; | |
269 | break; | |
270 | case PSU_HOST_DOORBELL_REG_RW: | |
271 | val = sbe->host_doorbell; | |
272 | break; | |
273 | default: | |
274 | qemu_log_mask(LOG_UNIMP, "SBE Unimplemented register: Ox%" | |
275 | HWADDR_PRIx "\n", addr >> 3); | |
276 | } | |
277 | } | |
278 | ||
279 | trace_pnv_sbe_xscom_mbox_read(addr, val); | |
280 | ||
281 | return val; | |
282 | } | |
283 | ||
284 | static void pnv_sbe_power9_xscom_mbox_write(void *opaque, hwaddr addr, | |
285 | uint64_t val, unsigned size) | |
286 | { | |
287 | PnvSBE *sbe = PNV_SBE(opaque); | |
288 | uint32_t offset = addr >> 3; | |
289 | ||
290 | trace_pnv_sbe_xscom_mbox_write(addr, val); | |
291 | ||
292 | if (offset <= PSU_HOST_SBE_MBOX_REG7) { | |
293 | uint32_t idx = offset - PSU_HOST_SBE_MBOX_REG0; | |
294 | sbe->mbox[idx] = val; | |
295 | } else { | |
296 | switch (offset) { | |
297 | case PSU_SBE_DOORBELL_REG_RW: | |
298 | pnv_sbe_set_sbe_doorbell(sbe, val); | |
299 | break; | |
300 | case PSU_SBE_DOORBELL_REG_AND: | |
301 | pnv_sbe_set_sbe_doorbell(sbe, sbe->sbe_doorbell & val); | |
302 | break; | |
303 | case PSU_SBE_DOORBELL_REG_OR: | |
304 | pnv_sbe_set_sbe_doorbell(sbe, sbe->sbe_doorbell | val); | |
305 | break; | |
306 | ||
307 | case PSU_HOST_DOORBELL_REG_RW: | |
308 | pnv_sbe_set_host_doorbell(sbe, val); | |
309 | break; | |
310 | case PSU_HOST_DOORBELL_REG_AND: | |
311 | pnv_sbe_set_host_doorbell(sbe, sbe->host_doorbell & val); | |
312 | break; | |
313 | case PSU_HOST_DOORBELL_REG_OR: | |
314 | pnv_sbe_set_host_doorbell(sbe, sbe->host_doorbell | val); | |
315 | break; | |
316 | ||
317 | default: | |
318 | qemu_log_mask(LOG_UNIMP, "SBE Unimplemented register: Ox%" | |
319 | HWADDR_PRIx "\n", addr >> 3); | |
320 | } | |
321 | } | |
322 | } | |
323 | ||
324 | static const MemoryRegionOps pnv_sbe_power9_xscom_mbox_ops = { | |
325 | .read = pnv_sbe_power9_xscom_mbox_read, | |
326 | .write = pnv_sbe_power9_xscom_mbox_write, | |
327 | .valid.min_access_size = 8, | |
328 | .valid.max_access_size = 8, | |
329 | .impl.min_access_size = 8, | |
330 | .impl.max_access_size = 8, | |
331 | .endianness = DEVICE_BIG_ENDIAN, | |
332 | }; | |
333 | ||
334 | static void pnv_sbe_power9_class_init(ObjectClass *klass, void *data) | |
335 | { | |
336 | PnvSBEClass *psc = PNV_SBE_CLASS(klass); | |
337 | DeviceClass *dc = DEVICE_CLASS(klass); | |
338 | ||
339 | dc->desc = "PowerNV SBE Controller (POWER9)"; | |
340 | psc->xscom_ctrl_size = PNV9_XSCOM_SBE_CTRL_SIZE; | |
341 | psc->xscom_ctrl_ops = &pnv_sbe_power9_xscom_ctrl_ops; | |
342 | psc->xscom_mbox_size = PNV9_XSCOM_SBE_MBOX_SIZE; | |
343 | psc->xscom_mbox_ops = &pnv_sbe_power9_xscom_mbox_ops; | |
344 | } | |
345 | ||
346 | static const TypeInfo pnv_sbe_power9_type_info = { | |
347 | .name = TYPE_PNV9_SBE, | |
348 | .parent = TYPE_PNV_SBE, | |
349 | .instance_size = sizeof(PnvSBE), | |
350 | .class_init = pnv_sbe_power9_class_init, | |
351 | }; | |
352 | ||
353 | static void pnv_sbe_power10_class_init(ObjectClass *klass, void *data) | |
354 | { | |
355 | PnvSBEClass *psc = PNV_SBE_CLASS(klass); | |
356 | DeviceClass *dc = DEVICE_CLASS(klass); | |
357 | ||
358 | dc->desc = "PowerNV SBE Controller (POWER10)"; | |
359 | psc->xscom_ctrl_size = PNV10_XSCOM_SBE_CTRL_SIZE; | |
360 | psc->xscom_ctrl_ops = &pnv_sbe_power9_xscom_ctrl_ops; | |
361 | psc->xscom_mbox_size = PNV10_XSCOM_SBE_MBOX_SIZE; | |
362 | psc->xscom_mbox_ops = &pnv_sbe_power9_xscom_mbox_ops; | |
363 | } | |
364 | ||
365 | static const TypeInfo pnv_sbe_power10_type_info = { | |
366 | .name = TYPE_PNV10_SBE, | |
367 | .parent = TYPE_PNV9_SBE, | |
368 | .class_init = pnv_sbe_power10_class_init, | |
369 | }; | |
370 | ||
371 | static void pnv_sbe_realize(DeviceState *dev, Error **errp) | |
372 | { | |
373 | PnvSBE *sbe = PNV_SBE(dev); | |
374 | PnvSBEClass *psc = PNV_SBE_GET_CLASS(sbe); | |
375 | ||
376 | /* XScom regions for SBE registers */ | |
377 | pnv_xscom_region_init(&sbe->xscom_ctrl_regs, OBJECT(dev), | |
378 | psc->xscom_ctrl_ops, sbe, "xscom-sbe-ctrl", | |
379 | psc->xscom_ctrl_size); | |
380 | pnv_xscom_region_init(&sbe->xscom_mbox_regs, OBJECT(dev), | |
381 | psc->xscom_mbox_ops, sbe, "xscom-sbe-mbox", | |
382 | psc->xscom_mbox_size); | |
383 | ||
7d5b0d68 | 384 | qdev_init_gpio_out(dev, &sbe->psi_irq, 1); |
0bf4d77e NP |
385 | |
386 | sbe->timer = timer_new_us(QEMU_CLOCK_VIRTUAL, sbe_timer, sbe); | |
387 | } | |
388 | ||
389 | static void pnv_sbe_class_init(ObjectClass *klass, void *data) | |
390 | { | |
391 | DeviceClass *dc = DEVICE_CLASS(klass); | |
392 | ||
393 | dc->realize = pnv_sbe_realize; | |
394 | dc->desc = "PowerNV SBE Controller"; | |
395 | dc->user_creatable = false; | |
396 | } | |
397 | ||
398 | static const TypeInfo pnv_sbe_type_info = { | |
399 | .name = TYPE_PNV_SBE, | |
400 | .parent = TYPE_DEVICE, | |
401 | .instance_size = sizeof(PnvSBE), | |
402 | .class_init = pnv_sbe_class_init, | |
403 | .class_size = sizeof(PnvSBEClass), | |
404 | .abstract = true, | |
405 | }; | |
406 | ||
407 | static void pnv_sbe_register_types(void) | |
408 | { | |
409 | type_register_static(&pnv_sbe_type_info); | |
410 | type_register_static(&pnv_sbe_power9_type_info); | |
411 | type_register_static(&pnv_sbe_power10_type_info); | |
412 | } | |
413 | ||
414 | type_init(pnv_sbe_register_types); |