|  | /* | 
|  | * QEMU PowerPC Virtual Open Firmware. | 
|  | * | 
|  | * This implements client interface from OpenFirmware IEEE1275 on the QEMU | 
|  | * side to leave only a very basic firmware in the VM. | 
|  | * | 
|  | * Copyright (c) 2021 IBM Corporation. | 
|  | * | 
|  | * SPDX-License-Identifier: GPL-2.0-or-later | 
|  | */ | 
|  |  | 
|  | #include "qemu/osdep.h" | 
|  | #include "qemu/timer.h" | 
|  | #include "qemu/range.h" | 
|  | #include "qemu/units.h" | 
|  | #include "qemu/log.h" | 
|  | #include "qapi/error.h" | 
|  | #include "exec/address-spaces.h" | 
|  | #include "hw/ppc/vof.h" | 
|  | #include "hw/ppc/fdt.h" | 
|  | #include "sysemu/runstate.h" | 
|  | #include "qom/qom-qobject.h" | 
|  | #include "trace.h" | 
|  |  | 
|  | #include <libfdt.h> | 
|  |  | 
|  | /* | 
|  | * OF 1275 "nextprop" description suggests is it 32 bytes max but | 
|  | * LoPAPR defines "ibm,query-interrupt-source-number" which is 33 chars long. | 
|  | */ | 
|  | #define OF_PROPNAME_LEN_MAX 64 | 
|  |  | 
|  | #define VOF_MAX_PATH        256 | 
|  | #define VOF_MAX_SETPROPLEN  2048 | 
|  | #define VOF_MAX_METHODLEN   256 | 
|  | #define VOF_MAX_FORTHCODE   256 | 
|  | #define VOF_VTY_BUF_SIZE    256 | 
|  |  | 
|  | typedef struct { | 
|  | uint64_t start; | 
|  | uint64_t size; | 
|  | } OfClaimed; | 
|  |  | 
|  | typedef struct { | 
|  | char *path; /* the path used to open the instance */ | 
|  | uint32_t phandle; | 
|  | } OfInstance; | 
|  |  | 
|  | static int readstr(hwaddr pa, char *buf, int size) | 
|  | { | 
|  | if (VOF_MEM_READ(pa, buf, size) != MEMTX_OK) { | 
|  | return -1; | 
|  | } | 
|  | if (strnlen(buf, size) == size) { | 
|  | buf[size - 1] = '\0'; | 
|  | trace_vof_error_str_truncated(buf, size); | 
|  | return -1; | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static bool cmpservice(const char *s, unsigned nargs, unsigned nret, | 
|  | const char *s1, unsigned nargscheck, unsigned nretcheck) | 
|  | { | 
|  | if (strcmp(s, s1)) { | 
|  | return false; | 
|  | } | 
|  | if ((nargscheck && (nargs != nargscheck)) || | 
|  | (nretcheck && (nret != nretcheck))) { | 
|  | trace_vof_error_param(s, nargscheck, nretcheck, nargs, nret); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static void prop_format(char *tval, int tlen, const void *prop, int len) | 
|  | { | 
|  | int i; | 
|  | const unsigned char *c; | 
|  | char *t; | 
|  | const char bin[] = "..."; | 
|  |  | 
|  | for (i = 0, c = prop; i < len; ++i, ++c) { | 
|  | if (*c == '\0' && i == len - 1) { | 
|  | strncpy(tval, prop, tlen - 1); | 
|  | return; | 
|  | } | 
|  | if (*c < 0x20 || *c >= 0x80) { | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | for (i = 0, c = prop, t = tval; i < len; ++i, ++c) { | 
|  | if (t >= tval + tlen - sizeof(bin) - 1 - 2 - 1) { | 
|  | strcpy(t, bin); | 
|  | return; | 
|  | } | 
|  | if (i && i % 4 == 0 && i != len - 1) { | 
|  | strcat(t, " "); | 
|  | ++t; | 
|  | } | 
|  | t += sprintf(t, "%02X", *c & 0xFF); | 
|  | } | 
|  | } | 
|  |  | 
|  | static int get_path(const void *fdt, int offset, char *buf, int len) | 
|  | { | 
|  | int ret; | 
|  |  | 
|  | ret = fdt_get_path(fdt, offset, buf, len - 1); | 
|  | if (ret < 0) { | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | buf[len - 1] = '\0'; | 
|  |  | 
|  | return strlen(buf) + 1; | 
|  | } | 
|  |  | 
|  | static int phandle_to_path(const void *fdt, uint32_t ph, char *buf, int len) | 
|  | { | 
|  | int ret; | 
|  |  | 
|  | ret = fdt_node_offset_by_phandle(fdt, ph); | 
|  | if (ret < 0) { | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | return get_path(fdt, ret, buf, len); | 
|  | } | 
|  |  | 
|  | static int path_offset(const void *fdt, const char *path) | 
|  | { | 
|  | g_autofree char *p = NULL; | 
|  | char *at; | 
|  |  | 
|  | /* | 
|  | * https://www.devicetree.org/open-firmware/bindings/ppc/release/ppc-2_1.html#HDR16 | 
|  | * | 
|  | * "Conversion from numeric representation to text representation shall use | 
|  | * the lower case forms of the hexadecimal digits in the range a..f, | 
|  | * suppressing leading zeros". | 
|  | */ | 
|  | p = g_strdup(path); | 
|  | for (at = strchr(p, '@'); at && *at; ) { | 
|  | if (*at == '/') { | 
|  | at = strchr(at, '@'); | 
|  | } else { | 
|  | *at = tolower(*at); | 
|  | ++at; | 
|  | } | 
|  | } | 
|  |  | 
|  | return fdt_path_offset(fdt, p); | 
|  | } | 
|  |  | 
|  | static uint32_t vof_finddevice(const void *fdt, uint32_t nodeaddr) | 
|  | { | 
|  | char fullnode[VOF_MAX_PATH]; | 
|  | uint32_t ret = PROM_ERROR; | 
|  | int offset; | 
|  |  | 
|  | if (readstr(nodeaddr, fullnode, sizeof(fullnode))) { | 
|  | return (uint32_t) ret; | 
|  | } | 
|  |  | 
|  | offset = path_offset(fdt, fullnode); | 
|  | if (offset >= 0) { | 
|  | ret = fdt_get_phandle(fdt, offset); | 
|  | } | 
|  | trace_vof_finddevice(fullnode, ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static const void *getprop(const void *fdt, int nodeoff, const char *propname, | 
|  | int *proplen, bool *write0) | 
|  | { | 
|  | const char *unit, *prop; | 
|  | const void *ret = fdt_getprop(fdt, nodeoff, propname, proplen); | 
|  |  | 
|  | if (ret) { | 
|  | if (write0) { | 
|  | *write0 = false; | 
|  | } | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | if (strcmp(propname, "name")) { | 
|  | return NULL; | 
|  | } | 
|  | /* | 
|  | * We return a value for "name" from path if queried but property does not | 
|  | * exist. @proplen does not include the unit part in this case. | 
|  | */ | 
|  | prop = fdt_get_name(fdt, nodeoff, proplen); | 
|  | if (!prop) { | 
|  | *proplen = 0; | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | unit = memchr(prop, '@', *proplen); | 
|  | if (unit) { | 
|  | *proplen = unit - prop; | 
|  | } | 
|  | *proplen += 1; | 
|  |  | 
|  | /* | 
|  | * Since it might be cut at "@" and there will be no trailing zero | 
|  | * in the prop buffer, tell the caller to write zero at the end. | 
|  | */ | 
|  | if (write0) { | 
|  | *write0 = true; | 
|  | } | 
|  | return prop; | 
|  | } | 
|  |  | 
|  | static uint32_t vof_getprop(const void *fdt, uint32_t nodeph, uint32_t pname, | 
|  | uint32_t valaddr, uint32_t vallen) | 
|  | { | 
|  | char propname[OF_PROPNAME_LEN_MAX + 1]; | 
|  | uint32_t ret = 0; | 
|  | int proplen = 0; | 
|  | const void *prop; | 
|  | char trval[64] = ""; | 
|  | int nodeoff = fdt_node_offset_by_phandle(fdt, nodeph); | 
|  | bool write0; | 
|  |  | 
|  | if (nodeoff < 0) { | 
|  | return PROM_ERROR; | 
|  | } | 
|  | if (readstr(pname, propname, sizeof(propname))) { | 
|  | return PROM_ERROR; | 
|  | } | 
|  | prop = getprop(fdt, nodeoff, propname, &proplen, &write0); | 
|  | if (prop) { | 
|  | const char zero = 0; | 
|  | int cb = MIN(proplen, vallen); | 
|  |  | 
|  | if (VOF_MEM_WRITE(valaddr, prop, cb) != MEMTX_OK || | 
|  | /* if that was "name" with a unit address, overwrite '@' with '0' */ | 
|  | (write0 && | 
|  | cb == proplen && | 
|  | VOF_MEM_WRITE(valaddr + cb - 1, &zero, 1) != MEMTX_OK)) { | 
|  | ret = PROM_ERROR; | 
|  | } else { | 
|  | /* | 
|  | * OF1275 says: | 
|  | * "Size is either the actual size of the property, or -1 if name | 
|  | * does not exist", hence returning proplen instead of cb. | 
|  | */ | 
|  | ret = proplen; | 
|  | /* Do not format a value if tracepoint is silent, for performance */ | 
|  | if (trace_event_get_state(TRACE_VOF_GETPROP) && | 
|  | qemu_loglevel_mask(LOG_TRACE)) { | 
|  | prop_format(trval, sizeof(trval), prop, ret); | 
|  | } | 
|  | } | 
|  | } else { | 
|  | ret = PROM_ERROR; | 
|  | } | 
|  | trace_vof_getprop(nodeph, propname, ret, trval); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static uint32_t vof_getproplen(const void *fdt, uint32_t nodeph, uint32_t pname) | 
|  | { | 
|  | char propname[OF_PROPNAME_LEN_MAX + 1]; | 
|  | uint32_t ret = 0; | 
|  | int proplen = 0; | 
|  | const void *prop; | 
|  | int nodeoff = fdt_node_offset_by_phandle(fdt, nodeph); | 
|  |  | 
|  | if (nodeoff < 0) { | 
|  | return PROM_ERROR; | 
|  | } | 
|  | if (readstr(pname, propname, sizeof(propname))) { | 
|  | return PROM_ERROR; | 
|  | } | 
|  | prop = getprop(fdt, nodeoff, propname, &proplen, NULL); | 
|  | if (prop) { | 
|  | ret = proplen; | 
|  | } else { | 
|  | ret = PROM_ERROR; | 
|  | } | 
|  | trace_vof_getproplen(nodeph, propname, ret); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static uint32_t vof_setprop(MachineState *ms, void *fdt, Vof *vof, | 
|  | uint32_t nodeph, uint32_t pname, | 
|  | uint32_t valaddr, uint32_t vallen) | 
|  | { | 
|  | char propname[OF_PROPNAME_LEN_MAX + 1] = ""; | 
|  | uint32_t ret = PROM_ERROR; | 
|  | int offset, rc; | 
|  | char trval[64] = ""; | 
|  | char nodepath[VOF_MAX_PATH] = ""; | 
|  | Object *vmo = object_dynamic_cast(OBJECT(ms), TYPE_VOF_MACHINE_IF); | 
|  | VofMachineIfClass *vmc; | 
|  | g_autofree char *val = NULL; | 
|  |  | 
|  | if (vallen > VOF_MAX_SETPROPLEN) { | 
|  | goto trace_exit; | 
|  | } | 
|  | if (readstr(pname, propname, sizeof(propname))) { | 
|  | goto trace_exit; | 
|  | } | 
|  | offset = fdt_node_offset_by_phandle(fdt, nodeph); | 
|  | if (offset < 0) { | 
|  | goto trace_exit; | 
|  | } | 
|  | rc = get_path(fdt, offset, nodepath, sizeof(nodepath)); | 
|  | if (rc <= 0) { | 
|  | goto trace_exit; | 
|  | } | 
|  |  | 
|  | val = g_malloc0(vallen); | 
|  | if (VOF_MEM_READ(valaddr, val, vallen) != MEMTX_OK) { | 
|  | goto trace_exit; | 
|  | } | 
|  |  | 
|  | if (!vmo) { | 
|  | goto trace_exit; | 
|  | } | 
|  |  | 
|  | vmc = VOF_MACHINE_GET_CLASS(vmo); | 
|  | if (!vmc->setprop || !vmc->setprop(ms, nodepath, propname, val, vallen)) { | 
|  | goto trace_exit; | 
|  | } | 
|  |  | 
|  | rc = fdt_setprop(fdt, offset, propname, val, vallen); | 
|  | if (rc) { | 
|  | goto trace_exit; | 
|  | } | 
|  |  | 
|  | if (trace_event_get_state(TRACE_VOF_SETPROP) && | 
|  | qemu_loglevel_mask(LOG_TRACE)) { | 
|  | prop_format(trval, sizeof(trval), val, vallen); | 
|  | } | 
|  | ret = vallen; | 
|  |  | 
|  | trace_exit: | 
|  | trace_vof_setprop(nodeph, propname, trval, vallen, ret); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static uint32_t vof_nextprop(const void *fdt, uint32_t phandle, | 
|  | uint32_t prevaddr, uint32_t nameaddr) | 
|  | { | 
|  | int offset, nodeoff = fdt_node_offset_by_phandle(fdt, phandle); | 
|  | char prev[OF_PROPNAME_LEN_MAX + 1]; | 
|  | const char *tmp; | 
|  |  | 
|  | if (readstr(prevaddr, prev, sizeof(prev))) { | 
|  | return PROM_ERROR; | 
|  | } | 
|  |  | 
|  | fdt_for_each_property_offset(offset, fdt, nodeoff) { | 
|  | if (!fdt_getprop_by_offset(fdt, offset, &tmp, NULL)) { | 
|  | return 0; | 
|  | } | 
|  | if (prev[0] == '\0' || strcmp(prev, tmp) == 0) { | 
|  | if (prev[0] != '\0') { | 
|  | offset = fdt_next_property_offset(fdt, offset); | 
|  | if (offset < 0) { | 
|  | return 0; | 
|  | } | 
|  | } | 
|  | if (!fdt_getprop_by_offset(fdt, offset, &tmp, NULL)) { | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | if (VOF_MEM_WRITE(nameaddr, tmp, strlen(tmp) + 1) != MEMTX_OK) { | 
|  | return PROM_ERROR; | 
|  | } | 
|  | return 1; | 
|  | } | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static uint32_t vof_peer(const void *fdt, uint32_t phandle) | 
|  | { | 
|  | uint32_t ret = 0; | 
|  | int rc; | 
|  |  | 
|  | if (phandle == 0) { | 
|  | rc = fdt_path_offset(fdt, "/"); | 
|  | } else { | 
|  | rc = fdt_next_subnode(fdt, fdt_node_offset_by_phandle(fdt, phandle)); | 
|  | } | 
|  |  | 
|  | if (rc >= 0) { | 
|  | ret = fdt_get_phandle(fdt, rc); | 
|  | } | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static uint32_t vof_child(const void *fdt, uint32_t phandle) | 
|  | { | 
|  | uint32_t ret = 0; | 
|  | int rc = fdt_first_subnode(fdt, fdt_node_offset_by_phandle(fdt, phandle)); | 
|  |  | 
|  | if (rc >= 0) { | 
|  | ret = fdt_get_phandle(fdt, rc); | 
|  | } | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static uint32_t vof_parent(const void *fdt, uint32_t phandle) | 
|  | { | 
|  | uint32_t ret = 0; | 
|  | int rc = fdt_parent_offset(fdt, fdt_node_offset_by_phandle(fdt, phandle)); | 
|  |  | 
|  | if (rc >= 0) { | 
|  | ret = fdt_get_phandle(fdt, rc); | 
|  | } | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static uint32_t vof_do_open(void *fdt, Vof *vof, int offset, const char *path) | 
|  | { | 
|  | uint32_t ret = PROM_ERROR; | 
|  | OfInstance *inst = NULL; | 
|  |  | 
|  | if (vof->of_instance_last == 0xFFFFFFFF) { | 
|  | /* We do not recycle ihandles yet */ | 
|  | goto trace_exit; | 
|  | } | 
|  |  | 
|  | inst = g_new0(OfInstance, 1); | 
|  | inst->phandle = fdt_get_phandle(fdt, offset); | 
|  | g_assert(inst->phandle); | 
|  | ++vof->of_instance_last; | 
|  |  | 
|  | inst->path = g_strdup(path); | 
|  | g_hash_table_insert(vof->of_instances, | 
|  | GINT_TO_POINTER(vof->of_instance_last), | 
|  | inst); | 
|  | ret = vof->of_instance_last; | 
|  |  | 
|  | trace_exit: | 
|  | trace_vof_open(path, inst ? inst->phandle : 0, ret); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | uint32_t vof_client_open_store(void *fdt, Vof *vof, const char *nodename, | 
|  | const char *prop, const char *path) | 
|  | { | 
|  | int offset, node = fdt_path_offset(fdt, nodename); | 
|  | uint32_t inst; | 
|  |  | 
|  | offset = fdt_path_offset(fdt, path); | 
|  | if (offset < 0) { | 
|  | trace_vof_error_unknown_path(path); | 
|  | return PROM_ERROR; | 
|  | } | 
|  |  | 
|  | inst = vof_do_open(fdt, vof, offset, path); | 
|  |  | 
|  | return fdt_setprop_cell(fdt, node, prop, inst) >= 0 ? 0 : PROM_ERROR; | 
|  | } | 
|  |  | 
|  | static uint32_t vof_open(void *fdt, Vof *vof, uint32_t pathaddr) | 
|  | { | 
|  | char path[VOF_MAX_PATH]; | 
|  | int offset; | 
|  |  | 
|  | if (readstr(pathaddr, path, sizeof(path))) { | 
|  | return PROM_ERROR; | 
|  | } | 
|  |  | 
|  | offset = path_offset(fdt, path); | 
|  | if (offset < 0) { | 
|  | trace_vof_error_unknown_path(path); | 
|  | return PROM_ERROR; | 
|  | } | 
|  |  | 
|  | return vof_do_open(fdt, vof, offset, path); | 
|  | } | 
|  |  | 
|  | static void vof_close(Vof *vof, uint32_t ihandle) | 
|  | { | 
|  | if (!g_hash_table_remove(vof->of_instances, GINT_TO_POINTER(ihandle))) { | 
|  | trace_vof_error_unknown_ihandle_close(ihandle); | 
|  | } | 
|  | } | 
|  |  | 
|  | static uint32_t vof_instance_to_package(Vof *vof, uint32_t ihandle) | 
|  | { | 
|  | gpointer instp = g_hash_table_lookup(vof->of_instances, | 
|  | GINT_TO_POINTER(ihandle)); | 
|  | uint32_t ret = PROM_ERROR; | 
|  |  | 
|  | if (instp) { | 
|  | ret = ((OfInstance *)instp)->phandle; | 
|  | } | 
|  | trace_vof_instance_to_package(ihandle, ret); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static uint32_t vof_package_to_path(const void *fdt, uint32_t phandle, | 
|  | uint32_t buf, uint32_t len) | 
|  | { | 
|  | int rc; | 
|  | char tmp[VOF_MAX_PATH] = ""; | 
|  |  | 
|  | rc = phandle_to_path(fdt, phandle, tmp, sizeof(tmp)); | 
|  | if (rc > 0) { | 
|  | if (VOF_MEM_WRITE(buf, tmp, rc) != MEMTX_OK) { | 
|  | rc = -1; | 
|  | } | 
|  | } | 
|  |  | 
|  | trace_vof_package_to_path(phandle, tmp, rc); | 
|  |  | 
|  | return rc > 0 ? (uint32_t)rc : PROM_ERROR; | 
|  | } | 
|  |  | 
|  | static uint32_t vof_instance_to_path(void *fdt, Vof *vof, uint32_t ihandle, | 
|  | uint32_t buf, uint32_t len) | 
|  | { | 
|  | int rc = -1; | 
|  | uint32_t phandle = vof_instance_to_package(vof, ihandle); | 
|  | char tmp[VOF_MAX_PATH] = ""; | 
|  |  | 
|  | if (phandle != -1) { | 
|  | rc = phandle_to_path(fdt, phandle, tmp, sizeof(tmp)); | 
|  | if (rc > 0) { | 
|  | if (VOF_MEM_WRITE(buf, tmp, rc) != MEMTX_OK) { | 
|  | rc = -1; | 
|  | } | 
|  | } | 
|  | } | 
|  | trace_vof_instance_to_path(ihandle, phandle, tmp, rc); | 
|  |  | 
|  | return rc > 0 ? (uint32_t)rc : PROM_ERROR; | 
|  | } | 
|  |  | 
|  | static uint32_t vof_write(Vof *vof, uint32_t ihandle, uint32_t buf, | 
|  | uint32_t len) | 
|  | { | 
|  | char tmp[VOF_VTY_BUF_SIZE]; | 
|  | unsigned cb; | 
|  | OfInstance *inst = (OfInstance *) | 
|  | g_hash_table_lookup(vof->of_instances, GINT_TO_POINTER(ihandle)); | 
|  |  | 
|  | if (!inst) { | 
|  | trace_vof_error_write(ihandle); | 
|  | return PROM_ERROR; | 
|  | } | 
|  |  | 
|  | for ( ; len > 0; len -= cb) { | 
|  | cb = MIN(len, sizeof(tmp) - 1); | 
|  | if (VOF_MEM_READ(buf, tmp, cb) != MEMTX_OK) { | 
|  | return PROM_ERROR; | 
|  | } | 
|  |  | 
|  | /* FIXME: there is no backend(s) yet so just call a trace */ | 
|  | if (trace_event_get_state(TRACE_VOF_WRITE) && | 
|  | qemu_loglevel_mask(LOG_TRACE)) { | 
|  | tmp[cb] = '\0'; | 
|  | trace_vof_write(ihandle, cb, tmp); | 
|  | } | 
|  | } | 
|  |  | 
|  | return len; | 
|  | } | 
|  |  | 
|  | static void vof_claimed_dump(GArray *claimed) | 
|  | { | 
|  | int i; | 
|  | OfClaimed c; | 
|  |  | 
|  | if (trace_event_get_state(TRACE_VOF_CLAIMED) && | 
|  | qemu_loglevel_mask(LOG_TRACE)) { | 
|  |  | 
|  | for (i = 0; i < claimed->len; ++i) { | 
|  | c = g_array_index(claimed, OfClaimed, i); | 
|  | trace_vof_claimed(c.start, c.start + c.size, c.size); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | static bool vof_claim_avail(GArray *claimed, uint64_t virt, uint64_t size) | 
|  | { | 
|  | int i; | 
|  | OfClaimed c; | 
|  |  | 
|  | for (i = 0; i < claimed->len; ++i) { | 
|  | c = g_array_index(claimed, OfClaimed, i); | 
|  | if (ranges_overlap(c.start, c.size, virt, size)) { | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static void vof_claim_add(GArray *claimed, uint64_t virt, uint64_t size) | 
|  | { | 
|  | OfClaimed newclaim; | 
|  |  | 
|  | newclaim.start = virt; | 
|  | newclaim.size = size; | 
|  | g_array_append_val(claimed, newclaim); | 
|  | } | 
|  |  | 
|  | static gint of_claimed_compare_func(gconstpointer a, gconstpointer b) | 
|  | { | 
|  | return ((OfClaimed *)a)->start - ((OfClaimed *)b)->start; | 
|  | } | 
|  |  | 
|  | static void vof_dt_memory_available(void *fdt, GArray *claimed, uint64_t base) | 
|  | { | 
|  | int i, n, offset, proplen = 0, sc, ac; | 
|  | target_ulong mem0_end; | 
|  | const uint8_t *mem0_reg; | 
|  | g_autofree uint8_t *avail = NULL; | 
|  | uint8_t *availcur; | 
|  |  | 
|  | if (!fdt || !claimed) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | offset = fdt_path_offset(fdt, "/"); | 
|  | _FDT(offset); | 
|  | ac = fdt_address_cells(fdt, offset); | 
|  | g_assert(ac == 1 || ac == 2); | 
|  | sc = fdt_size_cells(fdt, offset); | 
|  | g_assert(sc == 1 || sc == 2); | 
|  |  | 
|  | offset = fdt_path_offset(fdt, "/memory@0"); | 
|  | _FDT(offset); | 
|  |  | 
|  | mem0_reg = fdt_getprop(fdt, offset, "reg", &proplen); | 
|  | g_assert(mem0_reg && proplen == sizeof(uint32_t) * (ac + sc)); | 
|  | if (sc == 2) { | 
|  | mem0_end = be64_to_cpu(*(uint64_t *)(mem0_reg + sizeof(uint32_t) * ac)); | 
|  | } else { | 
|  | mem0_end = be32_to_cpu(*(uint32_t *)(mem0_reg + sizeof(uint32_t) * ac)); | 
|  | } | 
|  |  | 
|  | g_array_sort(claimed, of_claimed_compare_func); | 
|  | vof_claimed_dump(claimed); | 
|  |  | 
|  | /* | 
|  | * VOF resides in the first page so we do not need to check if there is | 
|  | * available memory before the first claimed block | 
|  | */ | 
|  | g_assert(claimed->len && (g_array_index(claimed, OfClaimed, 0).start == 0)); | 
|  |  | 
|  | avail = g_malloc0(sizeof(uint32_t) * (ac + sc) * claimed->len); | 
|  | for (i = 0, n = 0, availcur = avail; i < claimed->len; ++i) { | 
|  | OfClaimed c = g_array_index(claimed, OfClaimed, i); | 
|  | uint64_t start, size; | 
|  |  | 
|  | start = c.start + c.size; | 
|  | if (i < claimed->len - 1) { | 
|  | OfClaimed cn = g_array_index(claimed, OfClaimed, i + 1); | 
|  |  | 
|  | size = cn.start - start; | 
|  | } else { | 
|  | size = mem0_end - start; | 
|  | } | 
|  |  | 
|  | if (ac == 2) { | 
|  | *(uint64_t *) availcur = cpu_to_be64(start); | 
|  | } else { | 
|  | *(uint32_t *) availcur = cpu_to_be32(start); | 
|  | } | 
|  | availcur += sizeof(uint32_t) * ac; | 
|  | if (sc == 2) { | 
|  | *(uint64_t *) availcur = cpu_to_be64(size); | 
|  | } else { | 
|  | *(uint32_t *) availcur = cpu_to_be32(size); | 
|  | } | 
|  | availcur += sizeof(uint32_t) * sc; | 
|  |  | 
|  | if (size) { | 
|  | trace_vof_avail(c.start + c.size, c.start + c.size + size, size); | 
|  | ++n; | 
|  | } | 
|  | } | 
|  | _FDT((fdt_setprop(fdt, offset, "available", avail, availcur - avail))); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * OF1275: | 
|  | * "Allocates size bytes of memory. If align is zero, the allocated range | 
|  | * begins at the virtual address virt. Otherwise, an aligned address is | 
|  | * automatically chosen and the input argument virt is ignored". | 
|  | * | 
|  | * In other words, exactly one of @virt and @align is non-zero. | 
|  | */ | 
|  | uint64_t vof_claim(Vof *vof, uint64_t virt, uint64_t size, | 
|  | uint64_t align) | 
|  | { | 
|  | uint64_t ret; | 
|  |  | 
|  | if (size == 0) { | 
|  | ret = -1; | 
|  | } else if (align == 0) { | 
|  | if (!vof_claim_avail(vof->claimed, virt, size)) { | 
|  | ret = -1; | 
|  | } else { | 
|  | ret = virt; | 
|  | } | 
|  | } else { | 
|  | vof->claimed_base = QEMU_ALIGN_UP(vof->claimed_base, align); | 
|  | while (1) { | 
|  | if (vof->claimed_base >= vof->top_addr) { | 
|  | error_report("Out of RMA memory for the OF client"); | 
|  | return -1; | 
|  | } | 
|  | if (vof_claim_avail(vof->claimed, vof->claimed_base, size)) { | 
|  | break; | 
|  | } | 
|  | vof->claimed_base += size; | 
|  | } | 
|  | ret = vof->claimed_base; | 
|  | } | 
|  |  | 
|  | if (ret != -1) { | 
|  | vof->claimed_base = MAX(vof->claimed_base, ret + size); | 
|  | vof_claim_add(vof->claimed, ret, size); | 
|  | } | 
|  | trace_vof_claim(virt, size, align, ret); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static uint32_t vof_release(Vof *vof, uint64_t virt, uint64_t size) | 
|  | { | 
|  | uint32_t ret = PROM_ERROR; | 
|  | int i; | 
|  | GArray *claimed = vof->claimed; | 
|  | OfClaimed c; | 
|  |  | 
|  | for (i = 0; i < claimed->len; ++i) { | 
|  | c = g_array_index(claimed, OfClaimed, i); | 
|  | if (c.start == virt && c.size == size) { | 
|  | g_array_remove_index(claimed, i); | 
|  | ret = 0; | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | trace_vof_release(virt, size, ret); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static void vof_instantiate_rtas(Error **errp) | 
|  | { | 
|  | error_setg(errp, "The firmware should have instantiated RTAS"); | 
|  | } | 
|  |  | 
|  | static uint32_t vof_call_method(MachineState *ms, Vof *vof, uint32_t methodaddr, | 
|  | uint32_t ihandle, uint32_t param1, | 
|  | uint32_t param2, uint32_t param3, | 
|  | uint32_t param4, uint32_t *ret2) | 
|  | { | 
|  | uint32_t ret = PROM_ERROR; | 
|  | char method[VOF_MAX_METHODLEN] = ""; | 
|  | OfInstance *inst; | 
|  |  | 
|  | if (!ihandle) { | 
|  | goto trace_exit; | 
|  | } | 
|  |  | 
|  | inst = (OfInstance *)g_hash_table_lookup(vof->of_instances, | 
|  | GINT_TO_POINTER(ihandle)); | 
|  | if (!inst) { | 
|  | goto trace_exit; | 
|  | } | 
|  |  | 
|  | if (readstr(methodaddr, method, sizeof(method))) { | 
|  | goto trace_exit; | 
|  | } | 
|  |  | 
|  | if (strcmp(inst->path, "/") == 0) { | 
|  | if (strcmp(method, "ibm,client-architecture-support") == 0) { | 
|  | Object *vmo = object_dynamic_cast(OBJECT(ms), TYPE_VOF_MACHINE_IF); | 
|  |  | 
|  | if (vmo) { | 
|  | VofMachineIfClass *vmc = VOF_MACHINE_GET_CLASS(vmo); | 
|  |  | 
|  | g_assert(vmc->client_architecture_support); | 
|  | ret = (uint32_t)vmc->client_architecture_support(ms, first_cpu, | 
|  | param1); | 
|  | } | 
|  |  | 
|  | *ret2 = 0; | 
|  | } | 
|  | } else if (strcmp(inst->path, "/rtas") == 0) { | 
|  | if (strcmp(method, "instantiate-rtas") == 0) { | 
|  | vof_instantiate_rtas(&error_fatal); | 
|  | ret = 0; | 
|  | *ret2 = param1; /* rtas-base */ | 
|  | } | 
|  | } else { | 
|  | trace_vof_error_unknown_method(method); | 
|  | } | 
|  |  | 
|  | trace_exit: | 
|  | trace_vof_method(ihandle, method, param1, ret, *ret2); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static uint32_t vof_call_interpret(uint32_t cmdaddr, uint32_t param1, | 
|  | uint32_t param2, uint32_t *ret2) | 
|  | { | 
|  | uint32_t ret = PROM_ERROR; | 
|  | char cmd[VOF_MAX_FORTHCODE] = ""; | 
|  |  | 
|  | /* No interpret implemented so just call a trace */ | 
|  | readstr(cmdaddr, cmd, sizeof(cmd)); | 
|  | trace_vof_interpret(cmd, param1, param2, ret, *ret2); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static void vof_quiesce(MachineState *ms, void *fdt, Vof *vof) | 
|  | { | 
|  | Object *vmo = object_dynamic_cast(OBJECT(ms), TYPE_VOF_MACHINE_IF); | 
|  | /* After "quiesce", no change is expected to the FDT, pack FDT to ensure */ | 
|  | int rc = fdt_pack(fdt); | 
|  |  | 
|  | assert(rc == 0); | 
|  |  | 
|  | if (vmo) { | 
|  | VofMachineIfClass *vmc = VOF_MACHINE_GET_CLASS(vmo); | 
|  |  | 
|  | if (vmc->quiesce) { | 
|  | vmc->quiesce(ms); | 
|  | } | 
|  | } | 
|  |  | 
|  | vof_claimed_dump(vof->claimed); | 
|  | } | 
|  |  | 
|  | static uint32_t vof_client_handle(MachineState *ms, void *fdt, Vof *vof, | 
|  | const char *service, | 
|  | uint32_t *args, unsigned nargs, | 
|  | uint32_t *rets, unsigned nrets) | 
|  | { | 
|  | uint32_t ret = 0; | 
|  |  | 
|  | /* @nrets includes the value which this function returns */ | 
|  | #define cmpserv(s, a, r) \ | 
|  | cmpservice(service, nargs, nrets, (s), (a), (r)) | 
|  |  | 
|  | if (cmpserv("finddevice", 1, 1)) { | 
|  | ret = vof_finddevice(fdt, args[0]); | 
|  | } else if (cmpserv("getprop", 4, 1)) { | 
|  | ret = vof_getprop(fdt, args[0], args[1], args[2], args[3]); | 
|  | } else if (cmpserv("getproplen", 2, 1)) { | 
|  | ret = vof_getproplen(fdt, args[0], args[1]); | 
|  | } else if (cmpserv("setprop", 4, 1)) { | 
|  | ret = vof_setprop(ms, fdt, vof, args[0], args[1], args[2], args[3]); | 
|  | } else if (cmpserv("nextprop", 3, 1)) { | 
|  | ret = vof_nextprop(fdt, args[0], args[1], args[2]); | 
|  | } else if (cmpserv("peer", 1, 1)) { | 
|  | ret = vof_peer(fdt, args[0]); | 
|  | } else if (cmpserv("child", 1, 1)) { | 
|  | ret = vof_child(fdt, args[0]); | 
|  | } else if (cmpserv("parent", 1, 1)) { | 
|  | ret = vof_parent(fdt, args[0]); | 
|  | } else if (cmpserv("open", 1, 1)) { | 
|  | ret = vof_open(fdt, vof, args[0]); | 
|  | } else if (cmpserv("close", 1, 0)) { | 
|  | vof_close(vof, args[0]); | 
|  | } else if (cmpserv("instance-to-package", 1, 1)) { | 
|  | ret = vof_instance_to_package(vof, args[0]); | 
|  | } else if (cmpserv("package-to-path", 3, 1)) { | 
|  | ret = vof_package_to_path(fdt, args[0], args[1], args[2]); | 
|  | } else if (cmpserv("instance-to-path", 3, 1)) { | 
|  | ret = vof_instance_to_path(fdt, vof, args[0], args[1], args[2]); | 
|  | } else if (cmpserv("write", 3, 1)) { | 
|  | ret = vof_write(vof, args[0], args[1], args[2]); | 
|  | } else if (cmpserv("claim", 3, 1)) { | 
|  | uint64_t ret64 = vof_claim(vof, args[0], args[1], args[2]); | 
|  |  | 
|  | if (ret64 < 0x100000000UL) { | 
|  | vof_dt_memory_available(fdt, vof->claimed, vof->claimed_base); | 
|  | ret = (uint32_t)ret64; | 
|  | } else { | 
|  | if (ret64 != -1) { | 
|  | vof_release(vof, ret, args[1]); | 
|  | } | 
|  | ret = PROM_ERROR; | 
|  | } | 
|  | } else if (cmpserv("release", 2, 0)) { | 
|  | ret = vof_release(vof, args[0], args[1]); | 
|  | if (ret != PROM_ERROR) { | 
|  | vof_dt_memory_available(fdt, vof->claimed, vof->claimed_base); | 
|  | } | 
|  | } else if (cmpserv("call-method", 0, 0)) { | 
|  | ret = vof_call_method(ms, vof, args[0], args[1], args[2], args[3], | 
|  | args[4], args[5], rets); | 
|  | } else if (cmpserv("interpret", 0, 0)) { | 
|  | ret = vof_call_interpret(args[0], args[1], args[2], rets); | 
|  | } else if (cmpserv("milliseconds", 0, 1)) { | 
|  | ret = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL); | 
|  | } else if (cmpserv("quiesce", 0, 0)) { | 
|  | vof_quiesce(ms, fdt, vof); | 
|  | } else if (cmpserv("exit", 0, 0)) { | 
|  | error_report("Stopped as the VM requested \"exit\""); | 
|  | vm_stop(RUN_STATE_PAUSED); | 
|  | } else { | 
|  | trace_vof_error_unknown_service(service, nargs, nrets); | 
|  | ret = -1; | 
|  | } | 
|  |  | 
|  | #undef cmpserv | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /* Defined as Big Endian */ | 
|  | struct prom_args { | 
|  | uint32_t service; | 
|  | uint32_t nargs; | 
|  | uint32_t nret; | 
|  | uint32_t args[10]; | 
|  | } QEMU_PACKED; | 
|  |  | 
|  | int vof_client_call(MachineState *ms, Vof *vof, void *fdt, | 
|  | target_ulong args_real) | 
|  | { | 
|  | struct prom_args args_be; | 
|  | uint32_t args[ARRAY_SIZE(args_be.args)]; | 
|  | uint32_t rets[ARRAY_SIZE(args_be.args)] = { 0 }, ret; | 
|  | char service[64]; | 
|  | unsigned nargs, nret, i; | 
|  |  | 
|  | if (VOF_MEM_READ(args_real, &args_be, sizeof(args_be)) != MEMTX_OK) { | 
|  | return -EINVAL; | 
|  | } | 
|  | nargs = be32_to_cpu(args_be.nargs); | 
|  | if (nargs >= ARRAY_SIZE(args_be.args)) { | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | if (VOF_MEM_READ(be32_to_cpu(args_be.service), service, sizeof(service)) != | 
|  | MEMTX_OK) { | 
|  | return -EINVAL; | 
|  | } | 
|  | if (strnlen(service, sizeof(service)) == sizeof(service)) { | 
|  | /* Too long service name */ | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | for (i = 0; i < nargs; ++i) { | 
|  | args[i] = be32_to_cpu(args_be.args[i]); | 
|  | } | 
|  |  | 
|  | nret = be32_to_cpu(args_be.nret); | 
|  | if (nret > ARRAY_SIZE(args_be.args) - nargs) { | 
|  | return -EINVAL; | 
|  | } | 
|  | ret = vof_client_handle(ms, fdt, vof, service, args, nargs, rets, nret); | 
|  | if (!nret) { | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* @nrets includes the value which this function returns */ | 
|  | args_be.args[nargs] = cpu_to_be32(ret); | 
|  | for (i = 1; i < nret; ++i) { | 
|  | args_be.args[nargs + i] = cpu_to_be32(rets[i - 1]); | 
|  | } | 
|  |  | 
|  | if (VOF_MEM_WRITE(args_real + offsetof(struct prom_args, args[nargs]), | 
|  | args_be.args + nargs, sizeof(args_be.args[0]) * nret) != | 
|  | MEMTX_OK) { | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void vof_instance_free(gpointer data) | 
|  | { | 
|  | OfInstance *inst = (OfInstance *)data; | 
|  |  | 
|  | g_free(inst->path); | 
|  | g_free(inst); | 
|  | } | 
|  |  | 
|  | void vof_init(Vof *vof, uint64_t top_addr, Error **errp) | 
|  | { | 
|  | vof_cleanup(vof); | 
|  |  | 
|  | vof->of_instances = g_hash_table_new_full(g_direct_hash, g_direct_equal, | 
|  | NULL, vof_instance_free); | 
|  | vof->claimed = g_array_new(false, false, sizeof(OfClaimed)); | 
|  |  | 
|  | /* Keep allocations in 32bit as CLI ABI can only return cells==32bit */ | 
|  | vof->top_addr = MIN(top_addr, 4 * GiB); | 
|  | if (vof_claim(vof, 0, vof->fw_size, 0) == -1) { | 
|  | error_setg(errp, "Memory for firmware is in use"); | 
|  | } | 
|  | } | 
|  |  | 
|  | void vof_cleanup(Vof *vof) | 
|  | { | 
|  | if (vof->claimed) { | 
|  | g_array_unref(vof->claimed); | 
|  | } | 
|  | if (vof->of_instances) { | 
|  | g_hash_table_unref(vof->of_instances); | 
|  | } | 
|  | vof->claimed = NULL; | 
|  | vof->of_instances = NULL; | 
|  | vof->of_instance_last = 0; | 
|  | vof->claimed_base = 0; | 
|  | } | 
|  |  | 
|  | void vof_build_dt(void *fdt, Vof *vof) | 
|  | { | 
|  | uint32_t phandle = fdt_get_max_phandle(fdt); | 
|  | int offset, proplen = 0; | 
|  | const void *prop; | 
|  |  | 
|  | /* Assign phandles to nodes without predefined phandles (like XICS/XIVE) */ | 
|  | for (offset = fdt_next_node(fdt, -1, NULL); | 
|  | offset >= 0; | 
|  | offset = fdt_next_node(fdt, offset, NULL)) { | 
|  | prop = fdt_getprop(fdt, offset, "phandle", &proplen); | 
|  | if (prop) { | 
|  | continue; | 
|  | } | 
|  | ++phandle; | 
|  | _FDT(fdt_setprop_cell(fdt, offset, "phandle", phandle)); | 
|  | } | 
|  |  | 
|  | vof_dt_memory_available(fdt, vof->claimed, vof->claimed_base); | 
|  | } | 
|  |  | 
|  | static const TypeInfo vof_machine_if_info = { | 
|  | .name = TYPE_VOF_MACHINE_IF, | 
|  | .parent = TYPE_INTERFACE, | 
|  | .class_size = sizeof(VofMachineIfClass), | 
|  | }; | 
|  |  | 
|  | static void vof_machine_if_register_types(void) | 
|  | { | 
|  | type_register_static(&vof_machine_if_info); | 
|  | } | 
|  | type_init(vof_machine_if_register_types) |