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