]>
Commit | Line | Data |
---|---|---|
fc8c745d AK |
1 | /* |
2 | * QEMU PowerPC Virtual Open Firmware. | |
3 | * | |
4 | * This implements client interface from OpenFirmware IEEE1275 on the QEMU | |
5 | * side to leave only a very basic firmware in the VM. | |
6 | * | |
7 | * Copyright (c) 2021 IBM Corporation. | |
8 | * | |
9 | * SPDX-License-Identifier: GPL-2.0-or-later | |
10 | */ | |
11 | ||
12 | #include "qemu/osdep.h" | |
fc8c745d AK |
13 | #include "qemu/timer.h" |
14 | #include "qemu/range.h" | |
15 | #include "qemu/units.h" | |
16 | #include "qemu/log.h" | |
17 | #include "qapi/error.h" | |
fc8c745d AK |
18 | #include "exec/address-spaces.h" |
19 | #include "hw/ppc/vof.h" | |
20 | #include "hw/ppc/fdt.h" | |
21 | #include "sysemu/runstate.h" | |
22 | #include "qom/qom-qobject.h" | |
23 | #include "trace.h" | |
24 | ||
25 | #include <libfdt.h> | |
26 | ||
27 | /* | |
28 | * OF 1275 "nextprop" description suggests is it 32 bytes max but | |
29 | * LoPAPR defines "ibm,query-interrupt-source-number" which is 33 chars long. | |
30 | */ | |
31 | #define OF_PROPNAME_LEN_MAX 64 | |
32 | ||
33 | #define VOF_MAX_PATH 256 | |
34 | #define VOF_MAX_SETPROPLEN 2048 | |
35 | #define VOF_MAX_METHODLEN 256 | |
36 | #define VOF_MAX_FORTHCODE 256 | |
37 | #define VOF_VTY_BUF_SIZE 256 | |
38 | ||
39 | typedef struct { | |
40 | uint64_t start; | |
41 | uint64_t size; | |
42 | } OfClaimed; | |
43 | ||
44 | typedef struct { | |
45 | char *path; /* the path used to open the instance */ | |
46 | uint32_t phandle; | |
47 | } OfInstance; | |
48 | ||
49 | static int readstr(hwaddr pa, char *buf, int size) | |
50 | { | |
51 | if (VOF_MEM_READ(pa, buf, size) != MEMTX_OK) { | |
52 | return -1; | |
53 | } | |
54 | if (strnlen(buf, size) == size) { | |
55 | buf[size - 1] = '\0'; | |
56 | trace_vof_error_str_truncated(buf, size); | |
57 | return -1; | |
58 | } | |
59 | return 0; | |
60 | } | |
61 | ||
62 | static bool cmpservice(const char *s, unsigned nargs, unsigned nret, | |
63 | const char *s1, unsigned nargscheck, unsigned nretcheck) | |
64 | { | |
65 | if (strcmp(s, s1)) { | |
66 | return false; | |
67 | } | |
68 | if ((nargscheck && (nargs != nargscheck)) || | |
69 | (nretcheck && (nret != nretcheck))) { | |
70 | trace_vof_error_param(s, nargscheck, nretcheck, nargs, nret); | |
71 | return false; | |
72 | } | |
73 | ||
74 | return true; | |
75 | } | |
76 | ||
77 | static void prop_format(char *tval, int tlen, const void *prop, int len) | |
78 | { | |
79 | int i; | |
80 | const unsigned char *c; | |
81 | char *t; | |
82 | const char bin[] = "..."; | |
83 | ||
84 | for (i = 0, c = prop; i < len; ++i, ++c) { | |
85 | if (*c == '\0' && i == len - 1) { | |
86 | strncpy(tval, prop, tlen - 1); | |
87 | return; | |
88 | } | |
89 | if (*c < 0x20 || *c >= 0x80) { | |
90 | break; | |
91 | } | |
92 | } | |
93 | ||
94 | for (i = 0, c = prop, t = tval; i < len; ++i, ++c) { | |
95 | if (t >= tval + tlen - sizeof(bin) - 1 - 2 - 1) { | |
96 | strcpy(t, bin); | |
97 | return; | |
98 | } | |
99 | if (i && i % 4 == 0 && i != len - 1) { | |
100 | strcat(t, " "); | |
101 | ++t; | |
102 | } | |
103 | t += sprintf(t, "%02X", *c & 0xFF); | |
104 | } | |
105 | } | |
106 | ||
107 | static int get_path(const void *fdt, int offset, char *buf, int len) | |
108 | { | |
109 | int ret; | |
110 | ||
111 | ret = fdt_get_path(fdt, offset, buf, len - 1); | |
112 | if (ret < 0) { | |
113 | return ret; | |
114 | } | |
115 | ||
116 | buf[len - 1] = '\0'; | |
117 | ||
118 | return strlen(buf) + 1; | |
119 | } | |
120 | ||
121 | static int phandle_to_path(const void *fdt, uint32_t ph, char *buf, int len) | |
122 | { | |
123 | int ret; | |
124 | ||
125 | ret = fdt_node_offset_by_phandle(fdt, ph); | |
126 | if (ret < 0) { | |
127 | return ret; | |
128 | } | |
129 | ||
130 | return get_path(fdt, ret, buf, len); | |
131 | } | |
132 | ||
133 | static int path_offset(const void *fdt, const char *path) | |
134 | { | |
135 | g_autofree char *p = NULL; | |
136 | char *at; | |
137 | ||
138 | /* | |
139 | * https://www.devicetree.org/open-firmware/bindings/ppc/release/ppc-2_1.html#HDR16 | |
140 | * | |
141 | * "Conversion from numeric representation to text representation shall use | |
142 | * the lower case forms of the hexadecimal digits in the range a..f, | |
143 | * suppressing leading zeros". | |
144 | */ | |
fc8c745d | 145 | p = g_strdup(path); |
21bde1ec AK |
146 | for (at = strchr(p, '@'); at && *at; ) { |
147 | if (*at == '/') { | |
148 | at = strchr(at, '@'); | |
149 | } else { | |
150 | *at = tolower(*at); | |
151 | ++at; | |
152 | } | |
fc8c745d | 153 | } |
21bde1ec | 154 | |
fc8c745d AK |
155 | return fdt_path_offset(fdt, p); |
156 | } | |
157 | ||
158 | static uint32_t vof_finddevice(const void *fdt, uint32_t nodeaddr) | |
159 | { | |
160 | char fullnode[VOF_MAX_PATH]; | |
14c7e06e | 161 | uint32_t ret = PROM_ERROR; |
fc8c745d AK |
162 | int offset; |
163 | ||
164 | if (readstr(nodeaddr, fullnode, sizeof(fullnode))) { | |
165 | return (uint32_t) ret; | |
166 | } | |
167 | ||
168 | offset = path_offset(fdt, fullnode); | |
169 | if (offset >= 0) { | |
170 | ret = fdt_get_phandle(fdt, offset); | |
171 | } | |
172 | trace_vof_finddevice(fullnode, ret); | |
14c7e06e | 173 | return ret; |
fc8c745d AK |
174 | } |
175 | ||
176 | static const void *getprop(const void *fdt, int nodeoff, const char *propname, | |
177 | int *proplen, bool *write0) | |
178 | { | |
179 | const char *unit, *prop; | |
180 | const void *ret = fdt_getprop(fdt, nodeoff, propname, proplen); | |
181 | ||
182 | if (ret) { | |
183 | if (write0) { | |
184 | *write0 = false; | |
185 | } | |
186 | return ret; | |
187 | } | |
188 | ||
189 | if (strcmp(propname, "name")) { | |
190 | return NULL; | |
191 | } | |
192 | /* | |
193 | * We return a value for "name" from path if queried but property does not | |
194 | * exist. @proplen does not include the unit part in this case. | |
195 | */ | |
196 | prop = fdt_get_name(fdt, nodeoff, proplen); | |
197 | if (!prop) { | |
198 | *proplen = 0; | |
199 | return NULL; | |
200 | } | |
201 | ||
202 | unit = memchr(prop, '@', *proplen); | |
203 | if (unit) { | |
204 | *proplen = unit - prop; | |
205 | } | |
206 | *proplen += 1; | |
207 | ||
208 | /* | |
209 | * Since it might be cut at "@" and there will be no trailing zero | |
210 | * in the prop buffer, tell the caller to write zero at the end. | |
211 | */ | |
212 | if (write0) { | |
213 | *write0 = true; | |
214 | } | |
215 | return prop; | |
216 | } | |
217 | ||
218 | static uint32_t vof_getprop(const void *fdt, uint32_t nodeph, uint32_t pname, | |
219 | uint32_t valaddr, uint32_t vallen) | |
220 | { | |
221 | char propname[OF_PROPNAME_LEN_MAX + 1]; | |
222 | uint32_t ret = 0; | |
223 | int proplen = 0; | |
224 | const void *prop; | |
225 | char trval[64] = ""; | |
226 | int nodeoff = fdt_node_offset_by_phandle(fdt, nodeph); | |
227 | bool write0; | |
228 | ||
229 | if (nodeoff < 0) { | |
14c7e06e | 230 | return PROM_ERROR; |
fc8c745d AK |
231 | } |
232 | if (readstr(pname, propname, sizeof(propname))) { | |
14c7e06e | 233 | return PROM_ERROR; |
fc8c745d AK |
234 | } |
235 | prop = getprop(fdt, nodeoff, propname, &proplen, &write0); | |
236 | if (prop) { | |
237 | const char zero = 0; | |
238 | int cb = MIN(proplen, vallen); | |
239 | ||
240 | if (VOF_MEM_WRITE(valaddr, prop, cb) != MEMTX_OK || | |
241 | /* if that was "name" with a unit address, overwrite '@' with '0' */ | |
242 | (write0 && | |
243 | cb == proplen && | |
244 | VOF_MEM_WRITE(valaddr + cb - 1, &zero, 1) != MEMTX_OK)) { | |
14c7e06e | 245 | ret = PROM_ERROR; |
fc8c745d AK |
246 | } else { |
247 | /* | |
248 | * OF1275 says: | |
249 | * "Size is either the actual size of the property, or -1 if name | |
250 | * does not exist", hence returning proplen instead of cb. | |
251 | */ | |
252 | ret = proplen; | |
253 | /* Do not format a value if tracepoint is silent, for performance */ | |
254 | if (trace_event_get_state(TRACE_VOF_GETPROP) && | |
255 | qemu_loglevel_mask(LOG_TRACE)) { | |
256 | prop_format(trval, sizeof(trval), prop, ret); | |
257 | } | |
258 | } | |
259 | } else { | |
14c7e06e | 260 | ret = PROM_ERROR; |
fc8c745d AK |
261 | } |
262 | trace_vof_getprop(nodeph, propname, ret, trval); | |
263 | ||
264 | return ret; | |
265 | } | |
266 | ||
267 | static uint32_t vof_getproplen(const void *fdt, uint32_t nodeph, uint32_t pname) | |
268 | { | |
269 | char propname[OF_PROPNAME_LEN_MAX + 1]; | |
270 | uint32_t ret = 0; | |
271 | int proplen = 0; | |
272 | const void *prop; | |
273 | int nodeoff = fdt_node_offset_by_phandle(fdt, nodeph); | |
274 | ||
275 | if (nodeoff < 0) { | |
14c7e06e | 276 | return PROM_ERROR; |
fc8c745d AK |
277 | } |
278 | if (readstr(pname, propname, sizeof(propname))) { | |
14c7e06e | 279 | return PROM_ERROR; |
fc8c745d AK |
280 | } |
281 | prop = getprop(fdt, nodeoff, propname, &proplen, NULL); | |
282 | if (prop) { | |
283 | ret = proplen; | |
284 | } else { | |
14c7e06e | 285 | ret = PROM_ERROR; |
fc8c745d AK |
286 | } |
287 | trace_vof_getproplen(nodeph, propname, ret); | |
288 | ||
289 | return ret; | |
290 | } | |
291 | ||
292 | static uint32_t vof_setprop(MachineState *ms, void *fdt, Vof *vof, | |
293 | uint32_t nodeph, uint32_t pname, | |
294 | uint32_t valaddr, uint32_t vallen) | |
295 | { | |
23bd5fc3 | 296 | char propname[OF_PROPNAME_LEN_MAX + 1] = ""; |
14c7e06e AK |
297 | uint32_t ret = PROM_ERROR; |
298 | int offset, rc; | |
fc8c745d AK |
299 | char trval[64] = ""; |
300 | char nodepath[VOF_MAX_PATH] = ""; | |
301 | Object *vmo = object_dynamic_cast(OBJECT(ms), TYPE_VOF_MACHINE_IF); | |
21bde1ec | 302 | VofMachineIfClass *vmc; |
fc8c745d AK |
303 | g_autofree char *val = NULL; |
304 | ||
305 | if (vallen > VOF_MAX_SETPROPLEN) { | |
306 | goto trace_exit; | |
307 | } | |
308 | if (readstr(pname, propname, sizeof(propname))) { | |
309 | goto trace_exit; | |
310 | } | |
311 | offset = fdt_node_offset_by_phandle(fdt, nodeph); | |
312 | if (offset < 0) { | |
313 | goto trace_exit; | |
314 | } | |
14c7e06e AK |
315 | rc = get_path(fdt, offset, nodepath, sizeof(nodepath)); |
316 | if (rc <= 0) { | |
fc8c745d AK |
317 | goto trace_exit; |
318 | } | |
319 | ||
320 | val = g_malloc0(vallen); | |
321 | if (VOF_MEM_READ(valaddr, val, vallen) != MEMTX_OK) { | |
322 | goto trace_exit; | |
323 | } | |
324 | ||
21bde1ec AK |
325 | if (!vmo) { |
326 | goto trace_exit; | |
327 | } | |
fc8c745d | 328 | |
21bde1ec AK |
329 | vmc = VOF_MACHINE_GET_CLASS(vmo); |
330 | if (!vmc->setprop || !vmc->setprop(ms, nodepath, propname, val, vallen)) { | |
331 | goto trace_exit; | |
fc8c745d AK |
332 | } |
333 | ||
14c7e06e AK |
334 | rc = fdt_setprop(fdt, offset, propname, val, vallen); |
335 | if (rc) { | |
fc8c745d AK |
336 | goto trace_exit; |
337 | } | |
338 | ||
339 | if (trace_event_get_state(TRACE_VOF_SETPROP) && | |
340 | qemu_loglevel_mask(LOG_TRACE)) { | |
341 | prop_format(trval, sizeof(trval), val, vallen); | |
342 | } | |
343 | ret = vallen; | |
344 | ||
345 | trace_exit: | |
346 | trace_vof_setprop(nodeph, propname, trval, vallen, ret); | |
347 | ||
348 | return ret; | |
349 | } | |
350 | ||
351 | static uint32_t vof_nextprop(const void *fdt, uint32_t phandle, | |
352 | uint32_t prevaddr, uint32_t nameaddr) | |
353 | { | |
354 | int offset, nodeoff = fdt_node_offset_by_phandle(fdt, phandle); | |
355 | char prev[OF_PROPNAME_LEN_MAX + 1]; | |
356 | const char *tmp; | |
357 | ||
358 | if (readstr(prevaddr, prev, sizeof(prev))) { | |
14c7e06e | 359 | return PROM_ERROR; |
fc8c745d AK |
360 | } |
361 | ||
362 | fdt_for_each_property_offset(offset, fdt, nodeoff) { | |
363 | if (!fdt_getprop_by_offset(fdt, offset, &tmp, NULL)) { | |
364 | return 0; | |
365 | } | |
366 | if (prev[0] == '\0' || strcmp(prev, tmp) == 0) { | |
367 | if (prev[0] != '\0') { | |
368 | offset = fdt_next_property_offset(fdt, offset); | |
369 | if (offset < 0) { | |
370 | return 0; | |
371 | } | |
372 | } | |
373 | if (!fdt_getprop_by_offset(fdt, offset, &tmp, NULL)) { | |
374 | return 0; | |
375 | } | |
376 | ||
377 | if (VOF_MEM_WRITE(nameaddr, tmp, strlen(tmp) + 1) != MEMTX_OK) { | |
14c7e06e | 378 | return PROM_ERROR; |
fc8c745d AK |
379 | } |
380 | return 1; | |
381 | } | |
382 | } | |
383 | ||
384 | return 0; | |
385 | } | |
386 | ||
387 | static uint32_t vof_peer(const void *fdt, uint32_t phandle) | |
388 | { | |
14c7e06e AK |
389 | uint32_t ret = 0; |
390 | int rc; | |
fc8c745d AK |
391 | |
392 | if (phandle == 0) { | |
14c7e06e | 393 | rc = fdt_path_offset(fdt, "/"); |
fc8c745d | 394 | } else { |
14c7e06e | 395 | rc = fdt_next_subnode(fdt, fdt_node_offset_by_phandle(fdt, phandle)); |
fc8c745d AK |
396 | } |
397 | ||
14c7e06e AK |
398 | if (rc >= 0) { |
399 | ret = fdt_get_phandle(fdt, rc); | |
fc8c745d AK |
400 | } |
401 | ||
402 | return ret; | |
403 | } | |
404 | ||
405 | static uint32_t vof_child(const void *fdt, uint32_t phandle) | |
406 | { | |
14c7e06e AK |
407 | uint32_t ret = 0; |
408 | int rc = fdt_first_subnode(fdt, fdt_node_offset_by_phandle(fdt, phandle)); | |
fc8c745d | 409 | |
14c7e06e AK |
410 | if (rc >= 0) { |
411 | ret = fdt_get_phandle(fdt, rc); | |
fc8c745d AK |
412 | } |
413 | ||
414 | return ret; | |
415 | } | |
416 | ||
417 | static uint32_t vof_parent(const void *fdt, uint32_t phandle) | |
418 | { | |
14c7e06e AK |
419 | uint32_t ret = 0; |
420 | int rc = fdt_parent_offset(fdt, fdt_node_offset_by_phandle(fdt, phandle)); | |
fc8c745d | 421 | |
14c7e06e AK |
422 | if (rc >= 0) { |
423 | ret = fdt_get_phandle(fdt, rc); | |
fc8c745d AK |
424 | } |
425 | ||
426 | return ret; | |
427 | } | |
428 | ||
429 | static uint32_t vof_do_open(void *fdt, Vof *vof, int offset, const char *path) | |
430 | { | |
14c7e06e | 431 | uint32_t ret = PROM_ERROR; |
fc8c745d AK |
432 | OfInstance *inst = NULL; |
433 | ||
434 | if (vof->of_instance_last == 0xFFFFFFFF) { | |
435 | /* We do not recycle ihandles yet */ | |
436 | goto trace_exit; | |
437 | } | |
438 | ||
439 | inst = g_new0(OfInstance, 1); | |
440 | inst->phandle = fdt_get_phandle(fdt, offset); | |
441 | g_assert(inst->phandle); | |
442 | ++vof->of_instance_last; | |
443 | ||
444 | inst->path = g_strdup(path); | |
445 | g_hash_table_insert(vof->of_instances, | |
446 | GINT_TO_POINTER(vof->of_instance_last), | |
447 | inst); | |
448 | ret = vof->of_instance_last; | |
449 | ||
450 | trace_exit: | |
451 | trace_vof_open(path, inst ? inst->phandle : 0, ret); | |
452 | ||
453 | return ret; | |
454 | } | |
455 | ||
456 | uint32_t vof_client_open_store(void *fdt, Vof *vof, const char *nodename, | |
457 | const char *prop, const char *path) | |
458 | { | |
14c7e06e AK |
459 | int offset, node = fdt_path_offset(fdt, nodename); |
460 | uint32_t inst; | |
fc8c745d AK |
461 | |
462 | offset = fdt_path_offset(fdt, path); | |
463 | if (offset < 0) { | |
464 | trace_vof_error_unknown_path(path); | |
14c7e06e | 465 | return PROM_ERROR; |
fc8c745d AK |
466 | } |
467 | ||
468 | inst = vof_do_open(fdt, vof, offset, path); | |
469 | ||
14c7e06e | 470 | return fdt_setprop_cell(fdt, node, prop, inst) >= 0 ? 0 : PROM_ERROR; |
fc8c745d AK |
471 | } |
472 | ||
473 | static uint32_t vof_open(void *fdt, Vof *vof, uint32_t pathaddr) | |
474 | { | |
475 | char path[VOF_MAX_PATH]; | |
476 | int offset; | |
477 | ||
478 | if (readstr(pathaddr, path, sizeof(path))) { | |
14c7e06e | 479 | return PROM_ERROR; |
fc8c745d AK |
480 | } |
481 | ||
482 | offset = path_offset(fdt, path); | |
483 | if (offset < 0) { | |
484 | trace_vof_error_unknown_path(path); | |
14c7e06e | 485 | return PROM_ERROR; |
fc8c745d AK |
486 | } |
487 | ||
488 | return vof_do_open(fdt, vof, offset, path); | |
489 | } | |
490 | ||
491 | static void vof_close(Vof *vof, uint32_t ihandle) | |
492 | { | |
493 | if (!g_hash_table_remove(vof->of_instances, GINT_TO_POINTER(ihandle))) { | |
494 | trace_vof_error_unknown_ihandle_close(ihandle); | |
495 | } | |
496 | } | |
497 | ||
498 | static uint32_t vof_instance_to_package(Vof *vof, uint32_t ihandle) | |
499 | { | |
500 | gpointer instp = g_hash_table_lookup(vof->of_instances, | |
501 | GINT_TO_POINTER(ihandle)); | |
14c7e06e | 502 | uint32_t ret = PROM_ERROR; |
fc8c745d AK |
503 | |
504 | if (instp) { | |
505 | ret = ((OfInstance *)instp)->phandle; | |
506 | } | |
507 | trace_vof_instance_to_package(ihandle, ret); | |
508 | ||
509 | return ret; | |
510 | } | |
511 | ||
512 | static uint32_t vof_package_to_path(const void *fdt, uint32_t phandle, | |
513 | uint32_t buf, uint32_t len) | |
514 | { | |
14c7e06e | 515 | int rc; |
fc8c745d AK |
516 | char tmp[VOF_MAX_PATH] = ""; |
517 | ||
14c7e06e AK |
518 | rc = phandle_to_path(fdt, phandle, tmp, sizeof(tmp)); |
519 | if (rc > 0) { | |
520 | if (VOF_MEM_WRITE(buf, tmp, rc) != MEMTX_OK) { | |
521 | rc = -1; | |
fc8c745d AK |
522 | } |
523 | } | |
524 | ||
14c7e06e | 525 | trace_vof_package_to_path(phandle, tmp, rc); |
fc8c745d | 526 | |
14c7e06e | 527 | return rc > 0 ? (uint32_t)rc : PROM_ERROR; |
fc8c745d AK |
528 | } |
529 | ||
530 | static uint32_t vof_instance_to_path(void *fdt, Vof *vof, uint32_t ihandle, | |
531 | uint32_t buf, uint32_t len) | |
532 | { | |
14c7e06e | 533 | int rc = -1; |
fc8c745d AK |
534 | uint32_t phandle = vof_instance_to_package(vof, ihandle); |
535 | char tmp[VOF_MAX_PATH] = ""; | |
536 | ||
537 | if (phandle != -1) { | |
14c7e06e AK |
538 | rc = phandle_to_path(fdt, phandle, tmp, sizeof(tmp)); |
539 | if (rc > 0) { | |
540 | if (VOF_MEM_WRITE(buf, tmp, rc) != MEMTX_OK) { | |
541 | rc = -1; | |
fc8c745d AK |
542 | } |
543 | } | |
544 | } | |
14c7e06e | 545 | trace_vof_instance_to_path(ihandle, phandle, tmp, rc); |
fc8c745d | 546 | |
14c7e06e | 547 | return rc > 0 ? (uint32_t)rc : PROM_ERROR; |
fc8c745d AK |
548 | } |
549 | ||
550 | static uint32_t vof_write(Vof *vof, uint32_t ihandle, uint32_t buf, | |
551 | uint32_t len) | |
552 | { | |
553 | char tmp[VOF_VTY_BUF_SIZE]; | |
554 | unsigned cb; | |
555 | OfInstance *inst = (OfInstance *) | |
556 | g_hash_table_lookup(vof->of_instances, GINT_TO_POINTER(ihandle)); | |
557 | ||
558 | if (!inst) { | |
559 | trace_vof_error_write(ihandle); | |
14c7e06e | 560 | return PROM_ERROR; |
fc8c745d AK |
561 | } |
562 | ||
563 | for ( ; len > 0; len -= cb) { | |
564 | cb = MIN(len, sizeof(tmp) - 1); | |
565 | if (VOF_MEM_READ(buf, tmp, cb) != MEMTX_OK) { | |
14c7e06e | 566 | return PROM_ERROR; |
fc8c745d AK |
567 | } |
568 | ||
569 | /* FIXME: there is no backend(s) yet so just call a trace */ | |
570 | if (trace_event_get_state(TRACE_VOF_WRITE) && | |
571 | qemu_loglevel_mask(LOG_TRACE)) { | |
572 | tmp[cb] = '\0'; | |
573 | trace_vof_write(ihandle, cb, tmp); | |
574 | } | |
575 | } | |
576 | ||
577 | return len; | |
578 | } | |
579 | ||
580 | static void vof_claimed_dump(GArray *claimed) | |
581 | { | |
582 | int i; | |
583 | OfClaimed c; | |
584 | ||
585 | if (trace_event_get_state(TRACE_VOF_CLAIMED) && | |
586 | qemu_loglevel_mask(LOG_TRACE)) { | |
587 | ||
588 | for (i = 0; i < claimed->len; ++i) { | |
589 | c = g_array_index(claimed, OfClaimed, i); | |
590 | trace_vof_claimed(c.start, c.start + c.size, c.size); | |
591 | } | |
592 | } | |
593 | } | |
594 | ||
595 | static bool vof_claim_avail(GArray *claimed, uint64_t virt, uint64_t size) | |
596 | { | |
597 | int i; | |
598 | OfClaimed c; | |
599 | ||
600 | for (i = 0; i < claimed->len; ++i) { | |
601 | c = g_array_index(claimed, OfClaimed, i); | |
602 | if (ranges_overlap(c.start, c.size, virt, size)) { | |
603 | return false; | |
604 | } | |
605 | } | |
606 | ||
607 | return true; | |
608 | } | |
609 | ||
610 | static void vof_claim_add(GArray *claimed, uint64_t virt, uint64_t size) | |
611 | { | |
612 | OfClaimed newclaim; | |
613 | ||
614 | newclaim.start = virt; | |
615 | newclaim.size = size; | |
616 | g_array_append_val(claimed, newclaim); | |
617 | } | |
618 | ||
619 | static gint of_claimed_compare_func(gconstpointer a, gconstpointer b) | |
620 | { | |
621 | return ((OfClaimed *)a)->start - ((OfClaimed *)b)->start; | |
622 | } | |
623 | ||
624 | static void vof_dt_memory_available(void *fdt, GArray *claimed, uint64_t base) | |
625 | { | |
626 | int i, n, offset, proplen = 0, sc, ac; | |
627 | target_ulong mem0_end; | |
628 | const uint8_t *mem0_reg; | |
629 | g_autofree uint8_t *avail = NULL; | |
630 | uint8_t *availcur; | |
631 | ||
632 | if (!fdt || !claimed) { | |
633 | return; | |
634 | } | |
635 | ||
636 | offset = fdt_path_offset(fdt, "/"); | |
637 | _FDT(offset); | |
638 | ac = fdt_address_cells(fdt, offset); | |
639 | g_assert(ac == 1 || ac == 2); | |
640 | sc = fdt_size_cells(fdt, offset); | |
641 | g_assert(sc == 1 || sc == 2); | |
642 | ||
643 | offset = fdt_path_offset(fdt, "/memory@0"); | |
644 | _FDT(offset); | |
645 | ||
646 | mem0_reg = fdt_getprop(fdt, offset, "reg", &proplen); | |
647 | g_assert(mem0_reg && proplen == sizeof(uint32_t) * (ac + sc)); | |
648 | if (sc == 2) { | |
649 | mem0_end = be64_to_cpu(*(uint64_t *)(mem0_reg + sizeof(uint32_t) * ac)); | |
650 | } else { | |
651 | mem0_end = be32_to_cpu(*(uint32_t *)(mem0_reg + sizeof(uint32_t) * ac)); | |
652 | } | |
653 | ||
654 | g_array_sort(claimed, of_claimed_compare_func); | |
655 | vof_claimed_dump(claimed); | |
656 | ||
657 | /* | |
658 | * VOF resides in the first page so we do not need to check if there is | |
659 | * available memory before the first claimed block | |
660 | */ | |
661 | g_assert(claimed->len && (g_array_index(claimed, OfClaimed, 0).start == 0)); | |
662 | ||
663 | avail = g_malloc0(sizeof(uint32_t) * (ac + sc) * claimed->len); | |
664 | for (i = 0, n = 0, availcur = avail; i < claimed->len; ++i) { | |
665 | OfClaimed c = g_array_index(claimed, OfClaimed, i); | |
666 | uint64_t start, size; | |
667 | ||
668 | start = c.start + c.size; | |
669 | if (i < claimed->len - 1) { | |
670 | OfClaimed cn = g_array_index(claimed, OfClaimed, i + 1); | |
671 | ||
672 | size = cn.start - start; | |
673 | } else { | |
674 | size = mem0_end - start; | |
675 | } | |
676 | ||
677 | if (ac == 2) { | |
678 | *(uint64_t *) availcur = cpu_to_be64(start); | |
679 | } else { | |
680 | *(uint32_t *) availcur = cpu_to_be32(start); | |
681 | } | |
682 | availcur += sizeof(uint32_t) * ac; | |
683 | if (sc == 2) { | |
684 | *(uint64_t *) availcur = cpu_to_be64(size); | |
685 | } else { | |
686 | *(uint32_t *) availcur = cpu_to_be32(size); | |
687 | } | |
688 | availcur += sizeof(uint32_t) * sc; | |
689 | ||
690 | if (size) { | |
691 | trace_vof_avail(c.start + c.size, c.start + c.size + size, size); | |
692 | ++n; | |
693 | } | |
694 | } | |
695 | _FDT((fdt_setprop(fdt, offset, "available", avail, availcur - avail))); | |
696 | } | |
697 | ||
698 | /* | |
699 | * OF1275: | |
700 | * "Allocates size bytes of memory. If align is zero, the allocated range | |
701 | * begins at the virtual address virt. Otherwise, an aligned address is | |
702 | * automatically chosen and the input argument virt is ignored". | |
703 | * | |
704 | * In other words, exactly one of @virt and @align is non-zero. | |
705 | */ | |
706 | uint64_t vof_claim(Vof *vof, uint64_t virt, uint64_t size, | |
707 | uint64_t align) | |
708 | { | |
709 | uint64_t ret; | |
710 | ||
711 | if (size == 0) { | |
712 | ret = -1; | |
713 | } else if (align == 0) { | |
714 | if (!vof_claim_avail(vof->claimed, virt, size)) { | |
715 | ret = -1; | |
716 | } else { | |
717 | ret = virt; | |
718 | } | |
719 | } else { | |
720 | vof->claimed_base = QEMU_ALIGN_UP(vof->claimed_base, align); | |
721 | while (1) { | |
722 | if (vof->claimed_base >= vof->top_addr) { | |
723 | error_report("Out of RMA memory for the OF client"); | |
724 | return -1; | |
725 | } | |
726 | if (vof_claim_avail(vof->claimed, vof->claimed_base, size)) { | |
727 | break; | |
728 | } | |
729 | vof->claimed_base += size; | |
730 | } | |
731 | ret = vof->claimed_base; | |
732 | } | |
733 | ||
734 | if (ret != -1) { | |
735 | vof->claimed_base = MAX(vof->claimed_base, ret + size); | |
736 | vof_claim_add(vof->claimed, ret, size); | |
737 | } | |
738 | trace_vof_claim(virt, size, align, ret); | |
739 | ||
740 | return ret; | |
741 | } | |
742 | ||
743 | static uint32_t vof_release(Vof *vof, uint64_t virt, uint64_t size) | |
744 | { | |
14c7e06e | 745 | uint32_t ret = PROM_ERROR; |
fc8c745d AK |
746 | int i; |
747 | GArray *claimed = vof->claimed; | |
748 | OfClaimed c; | |
749 | ||
750 | for (i = 0; i < claimed->len; ++i) { | |
751 | c = g_array_index(claimed, OfClaimed, i); | |
752 | if (c.start == virt && c.size == size) { | |
753 | g_array_remove_index(claimed, i); | |
754 | ret = 0; | |
755 | break; | |
756 | } | |
757 | } | |
758 | ||
759 | trace_vof_release(virt, size, ret); | |
760 | ||
761 | return ret; | |
762 | } | |
763 | ||
764 | static void vof_instantiate_rtas(Error **errp) | |
765 | { | |
766 | error_setg(errp, "The firmware should have instantiated RTAS"); | |
767 | } | |
768 | ||
769 | static uint32_t vof_call_method(MachineState *ms, Vof *vof, uint32_t methodaddr, | |
770 | uint32_t ihandle, uint32_t param1, | |
771 | uint32_t param2, uint32_t param3, | |
772 | uint32_t param4, uint32_t *ret2) | |
773 | { | |
14c7e06e | 774 | uint32_t ret = PROM_ERROR; |
fc8c745d AK |
775 | char method[VOF_MAX_METHODLEN] = ""; |
776 | OfInstance *inst; | |
777 | ||
778 | if (!ihandle) { | |
779 | goto trace_exit; | |
780 | } | |
781 | ||
782 | inst = (OfInstance *)g_hash_table_lookup(vof->of_instances, | |
783 | GINT_TO_POINTER(ihandle)); | |
784 | if (!inst) { | |
785 | goto trace_exit; | |
786 | } | |
787 | ||
788 | if (readstr(methodaddr, method, sizeof(method))) { | |
789 | goto trace_exit; | |
790 | } | |
791 | ||
792 | if (strcmp(inst->path, "/") == 0) { | |
793 | if (strcmp(method, "ibm,client-architecture-support") == 0) { | |
794 | Object *vmo = object_dynamic_cast(OBJECT(ms), TYPE_VOF_MACHINE_IF); | |
795 | ||
796 | if (vmo) { | |
797 | VofMachineIfClass *vmc = VOF_MACHINE_GET_CLASS(vmo); | |
798 | ||
799 | g_assert(vmc->client_architecture_support); | |
14c7e06e AK |
800 | ret = (uint32_t)vmc->client_architecture_support(ms, first_cpu, |
801 | param1); | |
fc8c745d AK |
802 | } |
803 | ||
804 | *ret2 = 0; | |
805 | } | |
806 | } else if (strcmp(inst->path, "/rtas") == 0) { | |
807 | if (strcmp(method, "instantiate-rtas") == 0) { | |
808 | vof_instantiate_rtas(&error_fatal); | |
809 | ret = 0; | |
810 | *ret2 = param1; /* rtas-base */ | |
811 | } | |
812 | } else { | |
813 | trace_vof_error_unknown_method(method); | |
814 | } | |
815 | ||
816 | trace_exit: | |
817 | trace_vof_method(ihandle, method, param1, ret, *ret2); | |
818 | ||
819 | return ret; | |
820 | } | |
821 | ||
822 | static uint32_t vof_call_interpret(uint32_t cmdaddr, uint32_t param1, | |
823 | uint32_t param2, uint32_t *ret2) | |
824 | { | |
14c7e06e | 825 | uint32_t ret = PROM_ERROR; |
fc8c745d AK |
826 | char cmd[VOF_MAX_FORTHCODE] = ""; |
827 | ||
828 | /* No interpret implemented so just call a trace */ | |
829 | readstr(cmdaddr, cmd, sizeof(cmd)); | |
830 | trace_vof_interpret(cmd, param1, param2, ret, *ret2); | |
831 | ||
832 | return ret; | |
833 | } | |
834 | ||
835 | static void vof_quiesce(MachineState *ms, void *fdt, Vof *vof) | |
836 | { | |
837 | Object *vmo = object_dynamic_cast(OBJECT(ms), TYPE_VOF_MACHINE_IF); | |
838 | /* After "quiesce", no change is expected to the FDT, pack FDT to ensure */ | |
839 | int rc = fdt_pack(fdt); | |
840 | ||
841 | assert(rc == 0); | |
842 | ||
843 | if (vmo) { | |
844 | VofMachineIfClass *vmc = VOF_MACHINE_GET_CLASS(vmo); | |
845 | ||
846 | if (vmc->quiesce) { | |
847 | vmc->quiesce(ms); | |
848 | } | |
849 | } | |
850 | ||
851 | vof_claimed_dump(vof->claimed); | |
852 | } | |
853 | ||
854 | static uint32_t vof_client_handle(MachineState *ms, void *fdt, Vof *vof, | |
855 | const char *service, | |
856 | uint32_t *args, unsigned nargs, | |
857 | uint32_t *rets, unsigned nrets) | |
858 | { | |
859 | uint32_t ret = 0; | |
860 | ||
861 | /* @nrets includes the value which this function returns */ | |
862 | #define cmpserv(s, a, r) \ | |
863 | cmpservice(service, nargs, nrets, (s), (a), (r)) | |
864 | ||
865 | if (cmpserv("finddevice", 1, 1)) { | |
866 | ret = vof_finddevice(fdt, args[0]); | |
867 | } else if (cmpserv("getprop", 4, 1)) { | |
868 | ret = vof_getprop(fdt, args[0], args[1], args[2], args[3]); | |
869 | } else if (cmpserv("getproplen", 2, 1)) { | |
870 | ret = vof_getproplen(fdt, args[0], args[1]); | |
871 | } else if (cmpserv("setprop", 4, 1)) { | |
872 | ret = vof_setprop(ms, fdt, vof, args[0], args[1], args[2], args[3]); | |
873 | } else if (cmpserv("nextprop", 3, 1)) { | |
874 | ret = vof_nextprop(fdt, args[0], args[1], args[2]); | |
875 | } else if (cmpserv("peer", 1, 1)) { | |
876 | ret = vof_peer(fdt, args[0]); | |
877 | } else if (cmpserv("child", 1, 1)) { | |
878 | ret = vof_child(fdt, args[0]); | |
879 | } else if (cmpserv("parent", 1, 1)) { | |
880 | ret = vof_parent(fdt, args[0]); | |
881 | } else if (cmpserv("open", 1, 1)) { | |
882 | ret = vof_open(fdt, vof, args[0]); | |
883 | } else if (cmpserv("close", 1, 0)) { | |
884 | vof_close(vof, args[0]); | |
885 | } else if (cmpserv("instance-to-package", 1, 1)) { | |
886 | ret = vof_instance_to_package(vof, args[0]); | |
887 | } else if (cmpserv("package-to-path", 3, 1)) { | |
888 | ret = vof_package_to_path(fdt, args[0], args[1], args[2]); | |
889 | } else if (cmpserv("instance-to-path", 3, 1)) { | |
890 | ret = vof_instance_to_path(fdt, vof, args[0], args[1], args[2]); | |
891 | } else if (cmpserv("write", 3, 1)) { | |
892 | ret = vof_write(vof, args[0], args[1], args[2]); | |
893 | } else if (cmpserv("claim", 3, 1)) { | |
14c7e06e AK |
894 | uint64_t ret64 = vof_claim(vof, args[0], args[1], args[2]); |
895 | ||
896 | if (ret64 < 0x100000000UL) { | |
fc8c745d | 897 | vof_dt_memory_available(fdt, vof->claimed, vof->claimed_base); |
14c7e06e AK |
898 | ret = (uint32_t)ret64; |
899 | } else { | |
900 | if (ret64 != -1) { | |
901 | vof_release(vof, ret, args[1]); | |
902 | } | |
903 | ret = PROM_ERROR; | |
fc8c745d AK |
904 | } |
905 | } else if (cmpserv("release", 2, 0)) { | |
906 | ret = vof_release(vof, args[0], args[1]); | |
14c7e06e | 907 | if (ret != PROM_ERROR) { |
fc8c745d AK |
908 | vof_dt_memory_available(fdt, vof->claimed, vof->claimed_base); |
909 | } | |
910 | } else if (cmpserv("call-method", 0, 0)) { | |
911 | ret = vof_call_method(ms, vof, args[0], args[1], args[2], args[3], | |
912 | args[4], args[5], rets); | |
913 | } else if (cmpserv("interpret", 0, 0)) { | |
914 | ret = vof_call_interpret(args[0], args[1], args[2], rets); | |
915 | } else if (cmpserv("milliseconds", 0, 1)) { | |
916 | ret = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL); | |
917 | } else if (cmpserv("quiesce", 0, 0)) { | |
918 | vof_quiesce(ms, fdt, vof); | |
919 | } else if (cmpserv("exit", 0, 0)) { | |
920 | error_report("Stopped as the VM requested \"exit\""); | |
921 | vm_stop(RUN_STATE_PAUSED); | |
922 | } else { | |
923 | trace_vof_error_unknown_service(service, nargs, nrets); | |
924 | ret = -1; | |
925 | } | |
926 | ||
21bde1ec AK |
927 | #undef cmpserv |
928 | ||
fc8c745d AK |
929 | return ret; |
930 | } | |
931 | ||
932 | /* Defined as Big Endian */ | |
933 | struct prom_args { | |
934 | uint32_t service; | |
935 | uint32_t nargs; | |
936 | uint32_t nret; | |
937 | uint32_t args[10]; | |
938 | } QEMU_PACKED; | |
939 | ||
940 | int vof_client_call(MachineState *ms, Vof *vof, void *fdt, | |
941 | target_ulong args_real) | |
942 | { | |
943 | struct prom_args args_be; | |
944 | uint32_t args[ARRAY_SIZE(args_be.args)]; | |
945 | uint32_t rets[ARRAY_SIZE(args_be.args)] = { 0 }, ret; | |
946 | char service[64]; | |
947 | unsigned nargs, nret, i; | |
948 | ||
949 | if (VOF_MEM_READ(args_real, &args_be, sizeof(args_be)) != MEMTX_OK) { | |
950 | return -EINVAL; | |
951 | } | |
952 | nargs = be32_to_cpu(args_be.nargs); | |
953 | if (nargs >= ARRAY_SIZE(args_be.args)) { | |
954 | return -EINVAL; | |
955 | } | |
956 | ||
957 | if (VOF_MEM_READ(be32_to_cpu(args_be.service), service, sizeof(service)) != | |
958 | MEMTX_OK) { | |
959 | return -EINVAL; | |
960 | } | |
961 | if (strnlen(service, sizeof(service)) == sizeof(service)) { | |
962 | /* Too long service name */ | |
963 | return -EINVAL; | |
964 | } | |
965 | ||
966 | for (i = 0; i < nargs; ++i) { | |
967 | args[i] = be32_to_cpu(args_be.args[i]); | |
968 | } | |
969 | ||
970 | nret = be32_to_cpu(args_be.nret); | |
14c7e06e AK |
971 | if (nret > ARRAY_SIZE(args_be.args) - nargs) { |
972 | return -EINVAL; | |
973 | } | |
fc8c745d AK |
974 | ret = vof_client_handle(ms, fdt, vof, service, args, nargs, rets, nret); |
975 | if (!nret) { | |
976 | return 0; | |
977 | } | |
978 | ||
14c7e06e | 979 | /* @nrets includes the value which this function returns */ |
fc8c745d AK |
980 | args_be.args[nargs] = cpu_to_be32(ret); |
981 | for (i = 1; i < nret; ++i) { | |
982 | args_be.args[nargs + i] = cpu_to_be32(rets[i - 1]); | |
983 | } | |
984 | ||
985 | if (VOF_MEM_WRITE(args_real + offsetof(struct prom_args, args[nargs]), | |
986 | args_be.args + nargs, sizeof(args_be.args[0]) * nret) != | |
987 | MEMTX_OK) { | |
988 | return -EINVAL; | |
989 | } | |
990 | ||
991 | return 0; | |
992 | } | |
993 | ||
994 | static void vof_instance_free(gpointer data) | |
995 | { | |
996 | OfInstance *inst = (OfInstance *)data; | |
997 | ||
998 | g_free(inst->path); | |
999 | g_free(inst); | |
1000 | } | |
1001 | ||
1002 | void vof_init(Vof *vof, uint64_t top_addr, Error **errp) | |
1003 | { | |
1004 | vof_cleanup(vof); | |
1005 | ||
1006 | vof->of_instances = g_hash_table_new_full(g_direct_hash, g_direct_equal, | |
1007 | NULL, vof_instance_free); | |
1008 | vof->claimed = g_array_new(false, false, sizeof(OfClaimed)); | |
1009 | ||
1010 | /* Keep allocations in 32bit as CLI ABI can only return cells==32bit */ | |
1011 | vof->top_addr = MIN(top_addr, 4 * GiB); | |
1012 | if (vof_claim(vof, 0, vof->fw_size, 0) == -1) { | |
1013 | error_setg(errp, "Memory for firmware is in use"); | |
1014 | } | |
1015 | } | |
1016 | ||
1017 | void vof_cleanup(Vof *vof) | |
1018 | { | |
1019 | if (vof->claimed) { | |
1020 | g_array_unref(vof->claimed); | |
1021 | } | |
1022 | if (vof->of_instances) { | |
1023 | g_hash_table_unref(vof->of_instances); | |
1024 | } | |
1025 | vof->claimed = NULL; | |
1026 | vof->of_instances = NULL; | |
1027 | } | |
1028 | ||
1029 | void vof_build_dt(void *fdt, Vof *vof) | |
1030 | { | |
1031 | uint32_t phandle = fdt_get_max_phandle(fdt); | |
1032 | int offset, proplen = 0; | |
1033 | const void *prop; | |
1034 | ||
1035 | /* Assign phandles to nodes without predefined phandles (like XICS/XIVE) */ | |
1036 | for (offset = fdt_next_node(fdt, -1, NULL); | |
1037 | offset >= 0; | |
1038 | offset = fdt_next_node(fdt, offset, NULL)) { | |
1039 | prop = fdt_getprop(fdt, offset, "phandle", &proplen); | |
1040 | if (prop) { | |
1041 | continue; | |
1042 | } | |
1043 | ++phandle; | |
1044 | _FDT(fdt_setprop_cell(fdt, offset, "phandle", phandle)); | |
1045 | } | |
1046 | ||
1047 | vof_dt_memory_available(fdt, vof->claimed, vof->claimed_base); | |
1048 | } | |
1049 | ||
1050 | static const TypeInfo vof_machine_if_info = { | |
1051 | .name = TYPE_VOF_MACHINE_IF, | |
1052 | .parent = TYPE_INTERFACE, | |
1053 | .class_size = sizeof(VofMachineIfClass), | |
1054 | }; | |
1055 | ||
1056 | static void vof_machine_if_register_types(void) | |
1057 | { | |
1058 | type_register_static(&vof_machine_if_info); | |
1059 | } | |
1060 | type_init(vof_machine_if_register_types) |