From: Paolo Bonzini Date: Tue, 6 Oct 2020 07:01:22 +0000 (+0200) Subject: softmmu: move more files to softmmu/ X-Git-Url: http://git.maquefel.me/?a=commitdiff_plain;h=800d4deda0;p=qemu.git softmmu: move more files to softmmu/ Keep most softmmu_ss files into the system-emulation-specific directory. Signed-off-by: Paolo Bonzini --- diff --git a/MAINTAINERS b/MAINTAINERS index 8d50b96c33..dda54f000d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2235,7 +2235,7 @@ Device Tree M: Alistair Francis R: David Gibson S: Maintained -F: device_tree.c +F: softmmu/device_tree.c F: include/sysemu/device_tree.h Dump @@ -2281,6 +2281,7 @@ F: include/exec/memop.h F: include/exec/memory.h F: include/exec/ram_addr.h F: include/exec/ramblock.h +F: softmmu/dma-helpers.c F: softmmu/ioport.c F: softmmu/memory.c F: include/exec/memory-internal.h @@ -2461,7 +2462,7 @@ F: include/monitor/qdev.h F: include/qom/ F: qapi/qom.json F: qapi/qdev.json -F: qdev-monitor.c +F: softmmu/qdev-monitor.c F: qom/ F: tests/check-qom-interface.c F: tests/check-qom-proplist.c @@ -2591,7 +2592,7 @@ F: docs/interop/dbus-vmstate.rst Seccomp M: Eduardo Otubo S: Supported -F: qemu-seccomp.c +F: softmmu/qemu-seccomp.c F: include/sysemu/seccomp.h Cryptography @@ -2957,7 +2958,7 @@ T: git https://github.com/stefanha/qemu.git block Bootdevice M: Gonglei S: Maintained -F: bootdevice.c +F: softmmu/bootdevice.c Quorum M: Alberto Garcia diff --git a/bootdevice.c b/bootdevice.c deleted file mode 100644 index add4e3d2d1..0000000000 --- a/bootdevice.c +++ /dev/null @@ -1,429 +0,0 @@ -/* - * QEMU Boot Device Implement - * - * Copyright (c) 2014 HUAWEI TECHNOLOGIES CO., LTD. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#include "qemu/osdep.h" -#include "qapi/error.h" -#include "sysemu/sysemu.h" -#include "qapi/visitor.h" -#include "qemu/error-report.h" -#include "sysemu/reset.h" -#include "hw/qdev-core.h" -#include "hw/boards.h" - -typedef struct FWBootEntry FWBootEntry; - -struct FWBootEntry { - QTAILQ_ENTRY(FWBootEntry) link; - int32_t bootindex; - DeviceState *dev; - char *suffix; -}; - -static QTAILQ_HEAD(, FWBootEntry) fw_boot_order = - QTAILQ_HEAD_INITIALIZER(fw_boot_order); -static QEMUBootSetHandler *boot_set_handler; -static void *boot_set_opaque; - -void qemu_register_boot_set(QEMUBootSetHandler *func, void *opaque) -{ - boot_set_handler = func; - boot_set_opaque = opaque; -} - -void qemu_boot_set(const char *boot_order, Error **errp) -{ - Error *local_err = NULL; - - if (!boot_set_handler) { - error_setg(errp, "no function defined to set boot device list for" - " this architecture"); - return; - } - - validate_bootdevices(boot_order, &local_err); - if (local_err) { - error_propagate(errp, local_err); - return; - } - - boot_set_handler(boot_set_opaque, boot_order, errp); -} - -void validate_bootdevices(const char *devices, Error **errp) -{ - /* We just do some generic consistency checks */ - const char *p; - int bitmap = 0; - - for (p = devices; *p != '\0'; p++) { - /* Allowed boot devices are: - * a-b: floppy disk drives - * c-f: IDE disk drives - * g-m: machine implementation dependent drives - * n-p: network devices - * It's up to each machine implementation to check if the given boot - * devices match the actual hardware implementation and firmware - * features. - */ - if (*p < 'a' || *p > 'p') { - error_setg(errp, "Invalid boot device '%c'", *p); - return; - } - if (bitmap & (1 << (*p - 'a'))) { - error_setg(errp, "Boot device '%c' was given twice", *p); - return; - } - bitmap |= 1 << (*p - 'a'); - } -} - -void restore_boot_order(void *opaque) -{ - char *normal_boot_order = opaque; - static int first = 1; - - /* Restore boot order and remove ourselves after the first boot */ - if (first) { - first = 0; - return; - } - - if (boot_set_handler) { - qemu_boot_set(normal_boot_order, &error_abort); - } - - qemu_unregister_reset(restore_boot_order, normal_boot_order); - g_free(normal_boot_order); -} - -void check_boot_index(int32_t bootindex, Error **errp) -{ - FWBootEntry *i; - - if (bootindex >= 0) { - QTAILQ_FOREACH(i, &fw_boot_order, link) { - if (i->bootindex == bootindex) { - error_setg(errp, "The bootindex %d has already been used", - bootindex); - return; - } - } - } -} - -void del_boot_device_path(DeviceState *dev, const char *suffix) -{ - FWBootEntry *i; - - if (dev == NULL) { - return; - } - - QTAILQ_FOREACH(i, &fw_boot_order, link) { - if ((!suffix || !g_strcmp0(i->suffix, suffix)) && - i->dev == dev) { - QTAILQ_REMOVE(&fw_boot_order, i, link); - g_free(i->suffix); - g_free(i); - - break; - } - } -} - -void add_boot_device_path(int32_t bootindex, DeviceState *dev, - const char *suffix) -{ - FWBootEntry *node, *i; - - if (bootindex < 0) { - del_boot_device_path(dev, suffix); - return; - } - - assert(dev != NULL || suffix != NULL); - - del_boot_device_path(dev, suffix); - - node = g_malloc0(sizeof(FWBootEntry)); - node->bootindex = bootindex; - node->suffix = g_strdup(suffix); - node->dev = dev; - - QTAILQ_FOREACH(i, &fw_boot_order, link) { - if (i->bootindex == bootindex) { - error_report("Two devices with same boot index %d", bootindex); - exit(1); - } else if (i->bootindex < bootindex) { - continue; - } - QTAILQ_INSERT_BEFORE(i, node, link); - return; - } - QTAILQ_INSERT_TAIL(&fw_boot_order, node, link); -} - -DeviceState *get_boot_device(uint32_t position) -{ - uint32_t counter = 0; - FWBootEntry *i = NULL; - DeviceState *res = NULL; - - if (!QTAILQ_EMPTY(&fw_boot_order)) { - QTAILQ_FOREACH(i, &fw_boot_order, link) { - if (counter == position) { - res = i->dev; - break; - } - counter++; - } - } - return res; -} - -static char *get_boot_device_path(DeviceState *dev, bool ignore_suffixes, - const char *suffix) -{ - char *devpath = NULL, *s = NULL, *d, *bootpath; - - if (dev) { - devpath = qdev_get_fw_dev_path(dev); - assert(devpath); - } - - if (!ignore_suffixes) { - if (dev) { - d = qdev_get_own_fw_dev_path_from_handler(dev->parent_bus, dev); - if (d) { - assert(!suffix); - s = d; - } else { - s = g_strdup(suffix); - } - } else { - s = g_strdup(suffix); - } - } - - bootpath = g_strdup_printf("%s%s", - devpath ? devpath : "", - s ? s : ""); - g_free(devpath); - g_free(s); - - return bootpath; -} - -/* - * This function returns null terminated string that consist of new line - * separated device paths. - * - * memory pointed by "size" is assigned total length of the array in bytes - * - */ -char *get_boot_devices_list(size_t *size) -{ - FWBootEntry *i; - size_t total = 0; - char *list = NULL; - MachineClass *mc = MACHINE_GET_CLASS(qdev_get_machine()); - bool ignore_suffixes = mc->ignore_boot_device_suffixes; - - QTAILQ_FOREACH(i, &fw_boot_order, link) { - char *bootpath; - size_t len; - - bootpath = get_boot_device_path(i->dev, ignore_suffixes, i->suffix); - - if (total) { - list[total-1] = '\n'; - } - len = strlen(bootpath) + 1; - list = g_realloc(list, total + len); - memcpy(&list[total], bootpath, len); - total += len; - g_free(bootpath); - } - - *size = total; - - if (boot_strict && *size > 0) { - list[total-1] = '\n'; - list = g_realloc(list, total + 5); - memcpy(&list[total], "HALT", 5); - *size = total + 5; - } - return list; -} - -typedef struct { - int32_t *bootindex; - const char *suffix; - DeviceState *dev; -} BootIndexProperty; - -static void device_get_bootindex(Object *obj, Visitor *v, const char *name, - void *opaque, Error **errp) -{ - BootIndexProperty *prop = opaque; - visit_type_int32(v, name, prop->bootindex, errp); -} - -static void device_set_bootindex(Object *obj, Visitor *v, const char *name, - void *opaque, Error **errp) -{ - BootIndexProperty *prop = opaque; - int32_t boot_index; - Error *local_err = NULL; - - if (!visit_type_int32(v, name, &boot_index, errp)) { - return; - } - /* check whether bootindex is present in fw_boot_order list */ - check_boot_index(boot_index, &local_err); - if (local_err) { - error_propagate(errp, local_err); - return; - } - /* change bootindex to a new one */ - *prop->bootindex = boot_index; - - add_boot_device_path(*prop->bootindex, prop->dev, prop->suffix); -} - -static void property_release_bootindex(Object *obj, const char *name, - void *opaque) - -{ - BootIndexProperty *prop = opaque; - - del_boot_device_path(prop->dev, prop->suffix); - g_free(prop); -} - -void device_add_bootindex_property(Object *obj, int32_t *bootindex, - const char *name, const char *suffix, - DeviceState *dev) -{ - BootIndexProperty *prop = g_malloc0(sizeof(*prop)); - - prop->bootindex = bootindex; - prop->suffix = suffix; - prop->dev = dev; - - object_property_add(obj, name, "int32", - device_get_bootindex, - device_set_bootindex, - property_release_bootindex, - prop); - - /* initialize devices' bootindex property to -1 */ - object_property_set_int(obj, name, -1, NULL); -} - -typedef struct FWLCHSEntry FWLCHSEntry; - -struct FWLCHSEntry { - QTAILQ_ENTRY(FWLCHSEntry) link; - DeviceState *dev; - char *suffix; - uint32_t lcyls; - uint32_t lheads; - uint32_t lsecs; -}; - -static QTAILQ_HEAD(, FWLCHSEntry) fw_lchs = - QTAILQ_HEAD_INITIALIZER(fw_lchs); - -void add_boot_device_lchs(DeviceState *dev, const char *suffix, - uint32_t lcyls, uint32_t lheads, uint32_t lsecs) -{ - FWLCHSEntry *node; - - if (!lcyls && !lheads && !lsecs) { - return; - } - - assert(dev != NULL || suffix != NULL); - - node = g_malloc0(sizeof(FWLCHSEntry)); - node->suffix = g_strdup(suffix); - node->dev = dev; - node->lcyls = lcyls; - node->lheads = lheads; - node->lsecs = lsecs; - - QTAILQ_INSERT_TAIL(&fw_lchs, node, link); -} - -void del_boot_device_lchs(DeviceState *dev, const char *suffix) -{ - FWLCHSEntry *i; - - if (dev == NULL) { - return; - } - - QTAILQ_FOREACH(i, &fw_lchs, link) { - if ((!suffix || !g_strcmp0(i->suffix, suffix)) && - i->dev == dev) { - QTAILQ_REMOVE(&fw_lchs, i, link); - g_free(i->suffix); - g_free(i); - - break; - } - } -} - -char *get_boot_devices_lchs_list(size_t *size) -{ - FWLCHSEntry *i; - size_t total = 0; - char *list = NULL; - - QTAILQ_FOREACH(i, &fw_lchs, link) { - char *bootpath; - char *chs_string; - size_t len; - - bootpath = get_boot_device_path(i->dev, false, i->suffix); - chs_string = g_strdup_printf("%s %" PRIu32 " %" PRIu32 " %" PRIu32, - bootpath, i->lcyls, i->lheads, i->lsecs); - - if (total) { - list[total - 1] = '\n'; - } - len = strlen(chs_string) + 1; - list = g_realloc(list, total + len); - memcpy(&list[total], chs_string, len); - total += len; - g_free(chs_string); - g_free(bootpath); - } - - *size = total; - - return list; -} diff --git a/device_tree.c b/device_tree.c deleted file mode 100644 index b335dae707..0000000000 --- a/device_tree.c +++ /dev/null @@ -1,579 +0,0 @@ -/* - * Functions to help device tree manipulation using libfdt. - * It also provides functions to read entries from device tree proc - * interface. - * - * Copyright 2008 IBM Corporation. - * Authors: Jerone Young - * Hollis Blanchard - * - * This work is licensed under the GNU GPL license version 2 or later. - * - */ - -#include "qemu/osdep.h" - -#ifdef CONFIG_LINUX -#include -#endif - -#include "qapi/error.h" -#include "qemu/error-report.h" -#include "qemu/option.h" -#include "qemu/bswap.h" -#include "sysemu/device_tree.h" -#include "sysemu/sysemu.h" -#include "hw/loader.h" -#include "hw/boards.h" -#include "qemu/config-file.h" - -#include - -#define FDT_MAX_SIZE 0x100000 - -void *create_device_tree(int *sizep) -{ - void *fdt; - int ret; - - *sizep = FDT_MAX_SIZE; - fdt = g_malloc0(FDT_MAX_SIZE); - ret = fdt_create(fdt, FDT_MAX_SIZE); - if (ret < 0) { - goto fail; - } - ret = fdt_finish_reservemap(fdt); - if (ret < 0) { - goto fail; - } - ret = fdt_begin_node(fdt, ""); - if (ret < 0) { - goto fail; - } - ret = fdt_end_node(fdt); - if (ret < 0) { - goto fail; - } - ret = fdt_finish(fdt); - if (ret < 0) { - goto fail; - } - ret = fdt_open_into(fdt, fdt, *sizep); - if (ret) { - error_report("Unable to copy device tree in memory"); - exit(1); - } - - return fdt; -fail: - error_report("%s Couldn't create dt: %s", __func__, fdt_strerror(ret)); - exit(1); -} - -void *load_device_tree(const char *filename_path, int *sizep) -{ - int dt_size; - int dt_file_load_size; - int ret; - void *fdt = NULL; - - *sizep = 0; - dt_size = get_image_size(filename_path); - if (dt_size < 0) { - error_report("Unable to get size of device tree file '%s'", - filename_path); - goto fail; - } - if (dt_size > INT_MAX / 2 - 10000) { - error_report("Device tree file '%s' is too large", filename_path); - goto fail; - } - - /* Expand to 2x size to give enough room for manipulation. */ - dt_size += 10000; - dt_size *= 2; - /* First allocate space in qemu for device tree */ - fdt = g_malloc0(dt_size); - - dt_file_load_size = load_image_size(filename_path, fdt, dt_size); - if (dt_file_load_size < 0) { - error_report("Unable to open device tree file '%s'", - filename_path); - goto fail; - } - - ret = fdt_open_into(fdt, fdt, dt_size); - if (ret) { - error_report("Unable to copy device tree in memory"); - goto fail; - } - - /* Check sanity of device tree */ - if (fdt_check_header(fdt)) { - error_report("Device tree file loaded into memory is invalid: %s", - filename_path); - goto fail; - } - *sizep = dt_size; - return fdt; - -fail: - g_free(fdt); - return NULL; -} - -#ifdef CONFIG_LINUX - -#define SYSFS_DT_BASEDIR "/proc/device-tree" - -/** - * read_fstree: this function is inspired from dtc read_fstree - * @fdt: preallocated fdt blob buffer, to be populated - * @dirname: directory to scan under SYSFS_DT_BASEDIR - * the search is recursive and the tree is searched down to the - * leaves (property files). - * - * the function asserts in case of error - */ -static void read_fstree(void *fdt, const char *dirname) -{ - DIR *d; - struct dirent *de; - struct stat st; - const char *root_dir = SYSFS_DT_BASEDIR; - const char *parent_node; - - if (strstr(dirname, root_dir) != dirname) { - error_report("%s: %s must be searched within %s", - __func__, dirname, root_dir); - exit(1); - } - parent_node = &dirname[strlen(SYSFS_DT_BASEDIR)]; - - d = opendir(dirname); - if (!d) { - error_report("%s cannot open %s", __func__, dirname); - exit(1); - } - - while ((de = readdir(d)) != NULL) { - char *tmpnam; - - if (!g_strcmp0(de->d_name, ".") - || !g_strcmp0(de->d_name, "..")) { - continue; - } - - tmpnam = g_strdup_printf("%s/%s", dirname, de->d_name); - - if (lstat(tmpnam, &st) < 0) { - error_report("%s cannot lstat %s", __func__, tmpnam); - exit(1); - } - - if (S_ISREG(st.st_mode)) { - gchar *val; - gsize len; - - if (!g_file_get_contents(tmpnam, &val, &len, NULL)) { - error_report("%s not able to extract info from %s", - __func__, tmpnam); - exit(1); - } - - if (strlen(parent_node) > 0) { - qemu_fdt_setprop(fdt, parent_node, - de->d_name, val, len); - } else { - qemu_fdt_setprop(fdt, "/", de->d_name, val, len); - } - g_free(val); - } else if (S_ISDIR(st.st_mode)) { - char *node_name; - - node_name = g_strdup_printf("%s/%s", - parent_node, de->d_name); - qemu_fdt_add_subnode(fdt, node_name); - g_free(node_name); - read_fstree(fdt, tmpnam); - } - - g_free(tmpnam); - } - - closedir(d); -} - -/* load_device_tree_from_sysfs: extract the dt blob from host sysfs */ -void *load_device_tree_from_sysfs(void) -{ - void *host_fdt; - int host_fdt_size; - - host_fdt = create_device_tree(&host_fdt_size); - read_fstree(host_fdt, SYSFS_DT_BASEDIR); - if (fdt_check_header(host_fdt)) { - error_report("%s host device tree extracted into memory is invalid", - __func__); - exit(1); - } - return host_fdt; -} - -#endif /* CONFIG_LINUX */ - -static int findnode_nofail(void *fdt, const char *node_path) -{ - int offset; - - offset = fdt_path_offset(fdt, node_path); - if (offset < 0) { - error_report("%s Couldn't find node %s: %s", __func__, node_path, - fdt_strerror(offset)); - exit(1); - } - - return offset; -} - -char **qemu_fdt_node_unit_path(void *fdt, const char *name, Error **errp) -{ - char *prefix = g_strdup_printf("%s@", name); - unsigned int path_len = 16, n = 0; - GSList *path_list = NULL, *iter; - const char *iter_name; - int offset, len, ret; - char **path_array; - - offset = fdt_next_node(fdt, -1, NULL); - - while (offset >= 0) { - iter_name = fdt_get_name(fdt, offset, &len); - if (!iter_name) { - offset = len; - break; - } - if (!strcmp(iter_name, name) || g_str_has_prefix(iter_name, prefix)) { - char *path; - - path = g_malloc(path_len); - while ((ret = fdt_get_path(fdt, offset, path, path_len)) - == -FDT_ERR_NOSPACE) { - path_len += 16; - path = g_realloc(path, path_len); - } - path_list = g_slist_prepend(path_list, path); - n++; - } - offset = fdt_next_node(fdt, offset, NULL); - } - g_free(prefix); - - if (offset < 0 && offset != -FDT_ERR_NOTFOUND) { - error_setg(errp, "%s: abort parsing dt for %s node units: %s", - __func__, name, fdt_strerror(offset)); - for (iter = path_list; iter; iter = iter->next) { - g_free(iter->data); - } - g_slist_free(path_list); - return NULL; - } - - path_array = g_new(char *, n + 1); - path_array[n--] = NULL; - - for (iter = path_list; iter; iter = iter->next) { - path_array[n--] = iter->data; - } - - g_slist_free(path_list); - - return path_array; -} - -char **qemu_fdt_node_path(void *fdt, const char *name, const char *compat, - Error **errp) -{ - int offset, len, ret; - const char *iter_name; - unsigned int path_len = 16, n = 0; - GSList *path_list = NULL, *iter; - char **path_array; - - offset = fdt_node_offset_by_compatible(fdt, -1, compat); - - while (offset >= 0) { - iter_name = fdt_get_name(fdt, offset, &len); - if (!iter_name) { - offset = len; - break; - } - if (!name || !strcmp(iter_name, name)) { - char *path; - - path = g_malloc(path_len); - while ((ret = fdt_get_path(fdt, offset, path, path_len)) - == -FDT_ERR_NOSPACE) { - path_len += 16; - path = g_realloc(path, path_len); - } - path_list = g_slist_prepend(path_list, path); - n++; - } - offset = fdt_node_offset_by_compatible(fdt, offset, compat); - } - - if (offset < 0 && offset != -FDT_ERR_NOTFOUND) { - error_setg(errp, "%s: abort parsing dt for %s/%s: %s", - __func__, name, compat, fdt_strerror(offset)); - for (iter = path_list; iter; iter = iter->next) { - g_free(iter->data); - } - g_slist_free(path_list); - return NULL; - } - - path_array = g_new(char *, n + 1); - path_array[n--] = NULL; - - for (iter = path_list; iter; iter = iter->next) { - path_array[n--] = iter->data; - } - - g_slist_free(path_list); - - return path_array; -} - -int qemu_fdt_setprop(void *fdt, const char *node_path, - const char *property, const void *val, int size) -{ - int r; - - r = fdt_setprop(fdt, findnode_nofail(fdt, node_path), property, val, size); - if (r < 0) { - error_report("%s: Couldn't set %s/%s: %s", __func__, node_path, - property, fdt_strerror(r)); - exit(1); - } - - return r; -} - -int qemu_fdt_setprop_cell(void *fdt, const char *node_path, - const char *property, uint32_t val) -{ - int r; - - r = fdt_setprop_cell(fdt, findnode_nofail(fdt, node_path), property, val); - if (r < 0) { - error_report("%s: Couldn't set %s/%s = %#08x: %s", __func__, - node_path, property, val, fdt_strerror(r)); - exit(1); - } - - return r; -} - -int qemu_fdt_setprop_u64(void *fdt, const char *node_path, - const char *property, uint64_t val) -{ - val = cpu_to_be64(val); - return qemu_fdt_setprop(fdt, node_path, property, &val, sizeof(val)); -} - -int qemu_fdt_setprop_string(void *fdt, const char *node_path, - const char *property, const char *string) -{ - int r; - - r = fdt_setprop_string(fdt, findnode_nofail(fdt, node_path), property, string); - if (r < 0) { - error_report("%s: Couldn't set %s/%s = %s: %s", __func__, - node_path, property, string, fdt_strerror(r)); - exit(1); - } - - return r; -} - -const void *qemu_fdt_getprop(void *fdt, const char *node_path, - const char *property, int *lenp, Error **errp) -{ - int len; - const void *r; - - if (!lenp) { - lenp = &len; - } - r = fdt_getprop(fdt, findnode_nofail(fdt, node_path), property, lenp); - if (!r) { - error_setg(errp, "%s: Couldn't get %s/%s: %s", __func__, - node_path, property, fdt_strerror(*lenp)); - } - return r; -} - -uint32_t qemu_fdt_getprop_cell(void *fdt, const char *node_path, - const char *property, int *lenp, Error **errp) -{ - int len; - const uint32_t *p; - - if (!lenp) { - lenp = &len; - } - p = qemu_fdt_getprop(fdt, node_path, property, lenp, errp); - if (!p) { - return 0; - } else if (*lenp != 4) { - error_setg(errp, "%s: %s/%s not 4 bytes long (not a cell?)", - __func__, node_path, property); - *lenp = -EINVAL; - return 0; - } - return be32_to_cpu(*p); -} - -uint32_t qemu_fdt_get_phandle(void *fdt, const char *path) -{ - uint32_t r; - - r = fdt_get_phandle(fdt, findnode_nofail(fdt, path)); - if (r == 0) { - error_report("%s: Couldn't get phandle for %s: %s", __func__, - path, fdt_strerror(r)); - exit(1); - } - - return r; -} - -int qemu_fdt_setprop_phandle(void *fdt, const char *node_path, - const char *property, - const char *target_node_path) -{ - uint32_t phandle = qemu_fdt_get_phandle(fdt, target_node_path); - return qemu_fdt_setprop_cell(fdt, node_path, property, phandle); -} - -uint32_t qemu_fdt_alloc_phandle(void *fdt) -{ - static int phandle = 0x0; - - /* - * We need to find out if the user gave us special instruction at - * which phandle id to start allocating phandles. - */ - if (!phandle) { - phandle = machine_phandle_start(current_machine); - } - - if (!phandle) { - /* - * None or invalid phandle given on the command line, so fall back to - * default starting point. - */ - phandle = 0x8000; - } - - return phandle++; -} - -int qemu_fdt_nop_node(void *fdt, const char *node_path) -{ - int r; - - r = fdt_nop_node(fdt, findnode_nofail(fdt, node_path)); - if (r < 0) { - error_report("%s: Couldn't nop node %s: %s", __func__, node_path, - fdt_strerror(r)); - exit(1); - } - - return r; -} - -int qemu_fdt_add_subnode(void *fdt, const char *name) -{ - char *dupname = g_strdup(name); - char *basename = strrchr(dupname, '/'); - int retval; - int parent = 0; - - if (!basename) { - g_free(dupname); - return -1; - } - - basename[0] = '\0'; - basename++; - - if (dupname[0]) { - parent = findnode_nofail(fdt, dupname); - } - - retval = fdt_add_subnode(fdt, parent, basename); - if (retval < 0) { - error_report("FDT: Failed to create subnode %s: %s", name, - fdt_strerror(retval)); - exit(1); - } - - g_free(dupname); - return retval; -} - -void qemu_fdt_dumpdtb(void *fdt, int size) -{ - const char *dumpdtb = qemu_opt_get(qemu_get_machine_opts(), "dumpdtb"); - - if (dumpdtb) { - /* Dump the dtb to a file and quit */ - if (g_file_set_contents(dumpdtb, fdt, size, NULL)) { - info_report("dtb dumped to %s. Exiting.", dumpdtb); - exit(0); - } - error_report("%s: Failed dumping dtb to %s", __func__, dumpdtb); - exit(1); - } -} - -int qemu_fdt_setprop_sized_cells_from_array(void *fdt, - const char *node_path, - const char *property, - int numvalues, - uint64_t *values) -{ - uint32_t *propcells; - uint64_t value; - int cellnum, vnum, ncells; - uint32_t hival; - int ret; - - propcells = g_new0(uint32_t, numvalues * 2); - - cellnum = 0; - for (vnum = 0; vnum < numvalues; vnum++) { - ncells = values[vnum * 2]; - if (ncells != 1 && ncells != 2) { - ret = -1; - goto out; - } - value = values[vnum * 2 + 1]; - hival = cpu_to_be32(value >> 32); - if (ncells > 1) { - propcells[cellnum++] = hival; - } else if (hival != 0) { - ret = -1; - goto out; - } - propcells[cellnum++] = cpu_to_be32(value); - } - - ret = qemu_fdt_setprop(fdt, node_path, property, propcells, - cellnum * sizeof(uint32_t)); -out: - g_free(propcells); - return ret; -} diff --git a/dma-helpers.c b/dma-helpers.c deleted file mode 100644 index 03c92e0cc6..0000000000 --- a/dma-helpers.c +++ /dev/null @@ -1,331 +0,0 @@ -/* - * DMA helper functions - * - * Copyright (c) 2009 Red Hat - * - * This work is licensed under the terms of the GNU General Public License - * (GNU GPL), version 2 or later. - */ - -#include "qemu/osdep.h" -#include "sysemu/block-backend.h" -#include "sysemu/dma.h" -#include "trace/trace-root.h" -#include "qemu/thread.h" -#include "qemu/main-loop.h" -#include "sysemu/cpu-timers.h" -#include "qemu/range.h" - -/* #define DEBUG_IOMMU */ - -int dma_memory_set(AddressSpace *as, dma_addr_t addr, uint8_t c, dma_addr_t len) -{ - dma_barrier(as, DMA_DIRECTION_FROM_DEVICE); - -#define FILLBUF_SIZE 512 - uint8_t fillbuf[FILLBUF_SIZE]; - int l; - bool error = false; - - memset(fillbuf, c, FILLBUF_SIZE); - while (len > 0) { - l = len < FILLBUF_SIZE ? len : FILLBUF_SIZE; - error |= address_space_write(as, addr, MEMTXATTRS_UNSPECIFIED, - fillbuf, l); - len -= l; - addr += l; - } - - return error; -} - -void qemu_sglist_init(QEMUSGList *qsg, DeviceState *dev, int alloc_hint, - AddressSpace *as) -{ - qsg->sg = g_malloc(alloc_hint * sizeof(ScatterGatherEntry)); - qsg->nsg = 0; - qsg->nalloc = alloc_hint; - qsg->size = 0; - qsg->as = as; - qsg->dev = dev; - object_ref(OBJECT(dev)); -} - -void qemu_sglist_add(QEMUSGList *qsg, dma_addr_t base, dma_addr_t len) -{ - if (qsg->nsg == qsg->nalloc) { - qsg->nalloc = 2 * qsg->nalloc + 1; - qsg->sg = g_realloc(qsg->sg, qsg->nalloc * sizeof(ScatterGatherEntry)); - } - qsg->sg[qsg->nsg].base = base; - qsg->sg[qsg->nsg].len = len; - qsg->size += len; - ++qsg->nsg; -} - -void qemu_sglist_destroy(QEMUSGList *qsg) -{ - object_unref(OBJECT(qsg->dev)); - g_free(qsg->sg); - memset(qsg, 0, sizeof(*qsg)); -} - -typedef struct { - BlockAIOCB common; - AioContext *ctx; - BlockAIOCB *acb; - QEMUSGList *sg; - uint32_t align; - uint64_t offset; - DMADirection dir; - int sg_cur_index; - dma_addr_t sg_cur_byte; - QEMUIOVector iov; - QEMUBH *bh; - DMAIOFunc *io_func; - void *io_func_opaque; -} DMAAIOCB; - -static void dma_blk_cb(void *opaque, int ret); - -static void reschedule_dma(void *opaque) -{ - DMAAIOCB *dbs = (DMAAIOCB *)opaque; - - assert(!dbs->acb && dbs->bh); - qemu_bh_delete(dbs->bh); - dbs->bh = NULL; - dma_blk_cb(dbs, 0); -} - -static void dma_blk_unmap(DMAAIOCB *dbs) -{ - int i; - - for (i = 0; i < dbs->iov.niov; ++i) { - dma_memory_unmap(dbs->sg->as, dbs->iov.iov[i].iov_base, - dbs->iov.iov[i].iov_len, dbs->dir, - dbs->iov.iov[i].iov_len); - } - qemu_iovec_reset(&dbs->iov); -} - -static void dma_complete(DMAAIOCB *dbs, int ret) -{ - trace_dma_complete(dbs, ret, dbs->common.cb); - - assert(!dbs->acb && !dbs->bh); - dma_blk_unmap(dbs); - if (dbs->common.cb) { - dbs->common.cb(dbs->common.opaque, ret); - } - qemu_iovec_destroy(&dbs->iov); - qemu_aio_unref(dbs); -} - -static void dma_blk_cb(void *opaque, int ret) -{ - DMAAIOCB *dbs = (DMAAIOCB *)opaque; - dma_addr_t cur_addr, cur_len; - void *mem; - - trace_dma_blk_cb(dbs, ret); - - dbs->acb = NULL; - dbs->offset += dbs->iov.size; - - if (dbs->sg_cur_index == dbs->sg->nsg || ret < 0) { - dma_complete(dbs, ret); - return; - } - dma_blk_unmap(dbs); - - while (dbs->sg_cur_index < dbs->sg->nsg) { - cur_addr = dbs->sg->sg[dbs->sg_cur_index].base + dbs->sg_cur_byte; - cur_len = dbs->sg->sg[dbs->sg_cur_index].len - dbs->sg_cur_byte; - mem = dma_memory_map(dbs->sg->as, cur_addr, &cur_len, dbs->dir); - /* - * Make reads deterministic in icount mode. Windows sometimes issues - * disk read requests with overlapping SGs. It leads - * to non-determinism, because resulting buffer contents may be mixed - * from several sectors. This code splits all SGs into several - * groups. SGs in every group do not overlap. - */ - if (mem && icount_enabled() && dbs->dir == DMA_DIRECTION_FROM_DEVICE) { - int i; - for (i = 0 ; i < dbs->iov.niov ; ++i) { - if (ranges_overlap((intptr_t)dbs->iov.iov[i].iov_base, - dbs->iov.iov[i].iov_len, (intptr_t)mem, - cur_len)) { - dma_memory_unmap(dbs->sg->as, mem, cur_len, - dbs->dir, cur_len); - mem = NULL; - break; - } - } - } - if (!mem) - break; - qemu_iovec_add(&dbs->iov, mem, cur_len); - dbs->sg_cur_byte += cur_len; - if (dbs->sg_cur_byte == dbs->sg->sg[dbs->sg_cur_index].len) { - dbs->sg_cur_byte = 0; - ++dbs->sg_cur_index; - } - } - - if (dbs->iov.size == 0) { - trace_dma_map_wait(dbs); - dbs->bh = aio_bh_new(dbs->ctx, reschedule_dma, dbs); - cpu_register_map_client(dbs->bh); - return; - } - - if (!QEMU_IS_ALIGNED(dbs->iov.size, dbs->align)) { - qemu_iovec_discard_back(&dbs->iov, - QEMU_ALIGN_DOWN(dbs->iov.size, dbs->align)); - } - - aio_context_acquire(dbs->ctx); - dbs->acb = dbs->io_func(dbs->offset, &dbs->iov, - dma_blk_cb, dbs, dbs->io_func_opaque); - aio_context_release(dbs->ctx); - assert(dbs->acb); -} - -static void dma_aio_cancel(BlockAIOCB *acb) -{ - DMAAIOCB *dbs = container_of(acb, DMAAIOCB, common); - - trace_dma_aio_cancel(dbs); - - assert(!(dbs->acb && dbs->bh)); - if (dbs->acb) { - /* This will invoke dma_blk_cb. */ - blk_aio_cancel_async(dbs->acb); - return; - } - - if (dbs->bh) { - cpu_unregister_map_client(dbs->bh); - qemu_bh_delete(dbs->bh); - dbs->bh = NULL; - } - if (dbs->common.cb) { - dbs->common.cb(dbs->common.opaque, -ECANCELED); - } -} - -static AioContext *dma_get_aio_context(BlockAIOCB *acb) -{ - DMAAIOCB *dbs = container_of(acb, DMAAIOCB, common); - - return dbs->ctx; -} - -static const AIOCBInfo dma_aiocb_info = { - .aiocb_size = sizeof(DMAAIOCB), - .cancel_async = dma_aio_cancel, - .get_aio_context = dma_get_aio_context, -}; - -BlockAIOCB *dma_blk_io(AioContext *ctx, - QEMUSGList *sg, uint64_t offset, uint32_t align, - DMAIOFunc *io_func, void *io_func_opaque, - BlockCompletionFunc *cb, - void *opaque, DMADirection dir) -{ - DMAAIOCB *dbs = qemu_aio_get(&dma_aiocb_info, NULL, cb, opaque); - - trace_dma_blk_io(dbs, io_func_opaque, offset, (dir == DMA_DIRECTION_TO_DEVICE)); - - dbs->acb = NULL; - dbs->sg = sg; - dbs->ctx = ctx; - dbs->offset = offset; - dbs->align = align; - dbs->sg_cur_index = 0; - dbs->sg_cur_byte = 0; - dbs->dir = dir; - dbs->io_func = io_func; - dbs->io_func_opaque = io_func_opaque; - dbs->bh = NULL; - qemu_iovec_init(&dbs->iov, sg->nsg); - dma_blk_cb(dbs, 0); - return &dbs->common; -} - - -static -BlockAIOCB *dma_blk_read_io_func(int64_t offset, QEMUIOVector *iov, - BlockCompletionFunc *cb, void *cb_opaque, - void *opaque) -{ - BlockBackend *blk = opaque; - return blk_aio_preadv(blk, offset, iov, 0, cb, cb_opaque); -} - -BlockAIOCB *dma_blk_read(BlockBackend *blk, - QEMUSGList *sg, uint64_t offset, uint32_t align, - void (*cb)(void *opaque, int ret), void *opaque) -{ - return dma_blk_io(blk_get_aio_context(blk), sg, offset, align, - dma_blk_read_io_func, blk, cb, opaque, - DMA_DIRECTION_FROM_DEVICE); -} - -static -BlockAIOCB *dma_blk_write_io_func(int64_t offset, QEMUIOVector *iov, - BlockCompletionFunc *cb, void *cb_opaque, - void *opaque) -{ - BlockBackend *blk = opaque; - return blk_aio_pwritev(blk, offset, iov, 0, cb, cb_opaque); -} - -BlockAIOCB *dma_blk_write(BlockBackend *blk, - QEMUSGList *sg, uint64_t offset, uint32_t align, - void (*cb)(void *opaque, int ret), void *opaque) -{ - return dma_blk_io(blk_get_aio_context(blk), sg, offset, align, - dma_blk_write_io_func, blk, cb, opaque, - DMA_DIRECTION_TO_DEVICE); -} - - -static uint64_t dma_buf_rw(uint8_t *ptr, int32_t len, QEMUSGList *sg, - DMADirection dir) -{ - uint64_t resid; - int sg_cur_index; - - resid = sg->size; - sg_cur_index = 0; - len = MIN(len, resid); - while (len > 0) { - ScatterGatherEntry entry = sg->sg[sg_cur_index++]; - int32_t xfer = MIN(len, entry.len); - dma_memory_rw(sg->as, entry.base, ptr, xfer, dir); - ptr += xfer; - len -= xfer; - resid -= xfer; - } - - return resid; -} - -uint64_t dma_buf_read(uint8_t *ptr, int32_t len, QEMUSGList *sg) -{ - return dma_buf_rw(ptr, len, sg, DMA_DIRECTION_FROM_DEVICE); -} - -uint64_t dma_buf_write(uint8_t *ptr, int32_t len, QEMUSGList *sg) -{ - return dma_buf_rw(ptr, len, sg, DMA_DIRECTION_TO_DEVICE); -} - -void dma_acct_start(BlockBackend *blk, BlockAcctCookie *cookie, - QEMUSGList *sg, enum BlockAcctType type) -{ - block_acct_start(blk_get_stats(blk), cookie, sg->size, type); -} diff --git a/meson.build b/meson.build index f4ef3b83f3..a280e4cf21 100644 --- a/meson.build +++ b/meson.build @@ -1365,17 +1365,7 @@ blockdev_ss.add(files( # os-win32.c does not blockdev_ss.add(when: 'CONFIG_POSIX', if_true: files('os-posix.c')) softmmu_ss.add(when: 'CONFIG_WIN32', if_true: [files('os-win32.c')]) - softmmu_ss.add_all(blockdev_ss) -softmmu_ss.add(files( - 'bootdevice.c', - 'dma-helpers.c', - 'qdev-monitor.c', -), sdl) - -softmmu_ss.add(when: 'CONFIG_TPM', if_true: files('tpm.c')) -softmmu_ss.add(when: 'CONFIG_SECCOMP', if_true: [files('qemu-seccomp.c'), seccomp]) -softmmu_ss.add(when: fdt, if_true: files('device_tree.c')) common_ss.add(files('cpus-common.c')) diff --git a/qdev-monitor.c b/qdev-monitor.c deleted file mode 100644 index e9b7228480..0000000000 --- a/qdev-monitor.c +++ /dev/null @@ -1,993 +0,0 @@ -/* - * Dynamic device configuration and creation. - * - * Copyright (c) 2009 CodeSourcery - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see . - */ - -#include "qemu/osdep.h" -#include "hw/sysbus.h" -#include "monitor/hmp.h" -#include "monitor/monitor.h" -#include "monitor/qdev.h" -#include "sysemu/arch_init.h" -#include "qapi/error.h" -#include "qapi/qapi-commands-qdev.h" -#include "qapi/qmp/qdict.h" -#include "qapi/qmp/qerror.h" -#include "qemu/config-file.h" -#include "qemu/error-report.h" -#include "qemu/help_option.h" -#include "qemu/option.h" -#include "qemu/qemu-print.h" -#include "qemu/option_int.h" -#include "sysemu/block-backend.h" -#include "sysemu/sysemu.h" -#include "migration/misc.h" -#include "migration/migration.h" -#include "qemu/cutils.h" -#include "hw/clock.h" - -/* - * Aliases were a bad idea from the start. Let's keep them - * from spreading further. - */ -typedef struct QDevAlias -{ - const char *typename; - const char *alias; - uint32_t arch_mask; -} QDevAlias; - -/* Please keep this table sorted by typename. */ -static const QDevAlias qdev_alias_table[] = { - { "AC97", "ac97" }, /* -soundhw name */ - { "e1000", "e1000-82540em" }, - { "ES1370", "es1370" }, /* -soundhw name */ - { "ich9-ahci", "ahci" }, - { "lsi53c895a", "lsi" }, - { "virtio-9p-ccw", "virtio-9p", QEMU_ARCH_S390X }, - { "virtio-9p-pci", "virtio-9p", QEMU_ARCH_ALL & ~QEMU_ARCH_S390X }, - { "virtio-balloon-ccw", "virtio-balloon", QEMU_ARCH_S390X }, - { "virtio-balloon-pci", "virtio-balloon", - QEMU_ARCH_ALL & ~QEMU_ARCH_S390X }, - { "virtio-blk-ccw", "virtio-blk", QEMU_ARCH_S390X }, - { "virtio-blk-pci", "virtio-blk", QEMU_ARCH_ALL & ~QEMU_ARCH_S390X }, - { "virtio-gpu-ccw", "virtio-gpu", QEMU_ARCH_S390X }, - { "virtio-gpu-pci", "virtio-gpu", QEMU_ARCH_ALL & ~QEMU_ARCH_S390X }, - { "virtio-input-host-ccw", "virtio-input-host", QEMU_ARCH_S390X }, - { "virtio-input-host-pci", "virtio-input-host", - QEMU_ARCH_ALL & ~QEMU_ARCH_S390X }, - { "virtio-iommu-pci", "virtio-iommu", QEMU_ARCH_ALL & ~QEMU_ARCH_S390X }, - { "virtio-keyboard-ccw", "virtio-keyboard", QEMU_ARCH_S390X }, - { "virtio-keyboard-pci", "virtio-keyboard", - QEMU_ARCH_ALL & ~QEMU_ARCH_S390X }, - { "virtio-mouse-ccw", "virtio-mouse", QEMU_ARCH_S390X }, - { "virtio-mouse-pci", "virtio-mouse", QEMU_ARCH_ALL & ~QEMU_ARCH_S390X }, - { "virtio-net-ccw", "virtio-net", QEMU_ARCH_S390X }, - { "virtio-net-pci", "virtio-net", QEMU_ARCH_ALL & ~QEMU_ARCH_S390X }, - { "virtio-rng-ccw", "virtio-rng", QEMU_ARCH_S390X }, - { "virtio-rng-pci", "virtio-rng", QEMU_ARCH_ALL & ~QEMU_ARCH_S390X }, - { "virtio-scsi-ccw", "virtio-scsi", QEMU_ARCH_S390X }, - { "virtio-scsi-pci", "virtio-scsi", QEMU_ARCH_ALL & ~QEMU_ARCH_S390X }, - { "virtio-serial-ccw", "virtio-serial", QEMU_ARCH_S390X }, - { "virtio-serial-pci", "virtio-serial", QEMU_ARCH_ALL & ~QEMU_ARCH_S390X }, - { "virtio-tablet-ccw", "virtio-tablet", QEMU_ARCH_S390X }, - { "virtio-tablet-pci", "virtio-tablet", QEMU_ARCH_ALL & ~QEMU_ARCH_S390X }, - { } -}; - -static const char *qdev_class_get_alias(DeviceClass *dc) -{ - const char *typename = object_class_get_name(OBJECT_CLASS(dc)); - int i; - - for (i = 0; qdev_alias_table[i].typename; i++) { - if (qdev_alias_table[i].arch_mask && - !(qdev_alias_table[i].arch_mask & arch_type)) { - continue; - } - - if (strcmp(qdev_alias_table[i].typename, typename) == 0) { - return qdev_alias_table[i].alias; - } - } - - return NULL; -} - -static bool qdev_class_has_alias(DeviceClass *dc) -{ - return (qdev_class_get_alias(dc) != NULL); -} - -static void qdev_print_devinfo(DeviceClass *dc) -{ - qemu_printf("name \"%s\"", object_class_get_name(OBJECT_CLASS(dc))); - if (dc->bus_type) { - qemu_printf(", bus %s", dc->bus_type); - } - if (qdev_class_has_alias(dc)) { - qemu_printf(", alias \"%s\"", qdev_class_get_alias(dc)); - } - if (dc->desc) { - qemu_printf(", desc \"%s\"", dc->desc); - } - if (!dc->user_creatable) { - qemu_printf(", no-user"); - } - qemu_printf("\n"); -} - -static void qdev_print_devinfos(bool show_no_user) -{ - static const char *cat_name[DEVICE_CATEGORY_MAX + 1] = { - [DEVICE_CATEGORY_BRIDGE] = "Controller/Bridge/Hub", - [DEVICE_CATEGORY_USB] = "USB", - [DEVICE_CATEGORY_STORAGE] = "Storage", - [DEVICE_CATEGORY_NETWORK] = "Network", - [DEVICE_CATEGORY_INPUT] = "Input", - [DEVICE_CATEGORY_DISPLAY] = "Display", - [DEVICE_CATEGORY_SOUND] = "Sound", - [DEVICE_CATEGORY_MISC] = "Misc", - [DEVICE_CATEGORY_CPU] = "CPU", - [DEVICE_CATEGORY_MAX] = "Uncategorized", - }; - GSList *list, *elt; - int i; - bool cat_printed; - - module_load_qom_all(); - list = object_class_get_list_sorted(TYPE_DEVICE, false); - - for (i = 0; i <= DEVICE_CATEGORY_MAX; i++) { - cat_printed = false; - for (elt = list; elt; elt = elt->next) { - DeviceClass *dc = OBJECT_CLASS_CHECK(DeviceClass, elt->data, - TYPE_DEVICE); - if ((i < DEVICE_CATEGORY_MAX - ? !test_bit(i, dc->categories) - : !bitmap_empty(dc->categories, DEVICE_CATEGORY_MAX)) - || (!show_no_user - && !dc->user_creatable)) { - continue; - } - if (!cat_printed) { - qemu_printf("%s%s devices:\n", i ? "\n" : "", cat_name[i]); - cat_printed = true; - } - qdev_print_devinfo(dc); - } - } - - g_slist_free(list); -} - -static int set_property(void *opaque, const char *name, const char *value, - Error **errp) -{ - Object *obj = opaque; - - if (strcmp(name, "driver") == 0) - return 0; - if (strcmp(name, "bus") == 0) - return 0; - - if (!object_property_parse(obj, name, value, errp)) { - return -1; - } - return 0; -} - -static const char *find_typename_by_alias(const char *alias) -{ - int i; - - for (i = 0; qdev_alias_table[i].alias; i++) { - if (qdev_alias_table[i].arch_mask && - !(qdev_alias_table[i].arch_mask & arch_type)) { - continue; - } - - if (strcmp(qdev_alias_table[i].alias, alias) == 0) { - return qdev_alias_table[i].typename; - } - } - - return NULL; -} - -static DeviceClass *qdev_get_device_class(const char **driver, Error **errp) -{ - ObjectClass *oc; - DeviceClass *dc; - const char *original_name = *driver; - - oc = module_object_class_by_name(*driver); - if (!oc) { - const char *typename = find_typename_by_alias(*driver); - - if (typename) { - *driver = typename; - oc = module_object_class_by_name(*driver); - } - } - - if (!object_class_dynamic_cast(oc, TYPE_DEVICE)) { - if (*driver != original_name) { - error_setg(errp, "'%s' (alias '%s') is not a valid device model" - " name", original_name, *driver); - } else { - error_setg(errp, "'%s' is not a valid device model name", *driver); - } - return NULL; - } - - if (object_class_is_abstract(oc)) { - error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "driver", - "non-abstract device type"); - return NULL; - } - - dc = DEVICE_CLASS(oc); - if (!dc->user_creatable || - (qdev_hotplug && !dc->hotpluggable)) { - error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "driver", - "pluggable device type"); - return NULL; - } - - return dc; -} - - -int qdev_device_help(QemuOpts *opts) -{ - Error *local_err = NULL; - const char *driver; - ObjectPropertyInfoList *prop_list; - ObjectPropertyInfoList *prop; - GPtrArray *array; - int i; - - driver = qemu_opt_get(opts, "driver"); - if (driver && is_help_option(driver)) { - qdev_print_devinfos(false); - return 1; - } - - if (!driver || !qemu_opt_has_help_opt(opts)) { - return 0; - } - - if (!object_class_by_name(driver)) { - const char *typename = find_typename_by_alias(driver); - - if (typename) { - driver = typename; - } - } - - prop_list = qmp_device_list_properties(driver, &local_err); - if (local_err) { - goto error; - } - - if (prop_list) { - qemu_printf("%s options:\n", driver); - } else { - qemu_printf("There are no options for %s.\n", driver); - } - array = g_ptr_array_new(); - for (prop = prop_list; prop; prop = prop->next) { - g_ptr_array_add(array, - object_property_help(prop->value->name, - prop->value->type, - prop->value->default_value, - prop->value->description)); - } - g_ptr_array_sort(array, (GCompareFunc)qemu_pstrcmp0); - for (i = 0; i < array->len; i++) { - qemu_printf("%s\n", (char *)array->pdata[i]); - } - g_ptr_array_set_free_func(array, g_free); - g_ptr_array_free(array, true); - qapi_free_ObjectPropertyInfoList(prop_list); - return 1; - -error: - error_report_err(local_err); - return 1; -} - -static Object *qdev_get_peripheral(void) -{ - static Object *dev; - - if (dev == NULL) { - dev = container_get(qdev_get_machine(), "/peripheral"); - } - - return dev; -} - -static Object *qdev_get_peripheral_anon(void) -{ - static Object *dev; - - if (dev == NULL) { - dev = container_get(qdev_get_machine(), "/peripheral-anon"); - } - - return dev; -} - -static void qbus_error_append_bus_list_hint(DeviceState *dev, - Error *const *errp) -{ - BusState *child; - const char *sep = " "; - - error_append_hint(errp, "child buses at \"%s\":", - dev->id ? dev->id : object_get_typename(OBJECT(dev))); - QLIST_FOREACH(child, &dev->child_bus, sibling) { - error_append_hint(errp, "%s\"%s\"", sep, child->name); - sep = ", "; - } - error_append_hint(errp, "\n"); -} - -static void qbus_error_append_dev_list_hint(BusState *bus, - Error *const *errp) -{ - BusChild *kid; - const char *sep = " "; - - error_append_hint(errp, "devices at \"%s\":", bus->name); - QTAILQ_FOREACH(kid, &bus->children, sibling) { - DeviceState *dev = kid->child; - error_append_hint(errp, "%s\"%s\"", sep, - object_get_typename(OBJECT(dev))); - if (dev->id) { - error_append_hint(errp, "/\"%s\"", dev->id); - } - sep = ", "; - } - error_append_hint(errp, "\n"); -} - -static BusState *qbus_find_bus(DeviceState *dev, char *elem) -{ - BusState *child; - - QLIST_FOREACH(child, &dev->child_bus, sibling) { - if (strcmp(child->name, elem) == 0) { - return child; - } - } - return NULL; -} - -static DeviceState *qbus_find_dev(BusState *bus, char *elem) -{ - BusChild *kid; - - /* - * try to match in order: - * (1) instance id, if present - * (2) driver name - * (3) driver alias, if present - */ - QTAILQ_FOREACH(kid, &bus->children, sibling) { - DeviceState *dev = kid->child; - if (dev->id && strcmp(dev->id, elem) == 0) { - return dev; - } - } - QTAILQ_FOREACH(kid, &bus->children, sibling) { - DeviceState *dev = kid->child; - if (strcmp(object_get_typename(OBJECT(dev)), elem) == 0) { - return dev; - } - } - QTAILQ_FOREACH(kid, &bus->children, sibling) { - DeviceState *dev = kid->child; - DeviceClass *dc = DEVICE_GET_CLASS(dev); - - if (qdev_class_has_alias(dc) && - strcmp(qdev_class_get_alias(dc), elem) == 0) { - return dev; - } - } - return NULL; -} - -static inline bool qbus_is_full(BusState *bus) -{ - BusClass *bus_class = BUS_GET_CLASS(bus); - return bus_class->max_dev && bus->num_children >= bus_class->max_dev; -} - -/* - * Search the tree rooted at @bus for a bus. - * If @name, search for a bus with that name. Note that bus names - * need not be unique. Yes, that's screwed up. - * Else search for a bus that is a subtype of @bus_typename. - * If more than one exists, prefer one that can take another device. - * Return the bus if found, else %NULL. - */ -static BusState *qbus_find_recursive(BusState *bus, const char *name, - const char *bus_typename) -{ - BusChild *kid; - BusState *pick, *child, *ret; - bool match; - - assert(name || bus_typename); - if (name) { - match = !strcmp(bus->name, name); - } else { - match = !!object_dynamic_cast(OBJECT(bus), bus_typename); - } - - if (match && !qbus_is_full(bus)) { - return bus; /* root matches and isn't full */ - } - - pick = match ? bus : NULL; - - QTAILQ_FOREACH(kid, &bus->children, sibling) { - DeviceState *dev = kid->child; - QLIST_FOREACH(child, &dev->child_bus, sibling) { - ret = qbus_find_recursive(child, name, bus_typename); - if (ret && !qbus_is_full(ret)) { - return ret; /* a descendant matches and isn't full */ - } - if (ret && !pick) { - pick = ret; - } - } - } - - /* root or a descendant matches, but is full */ - return pick; -} - -static BusState *qbus_find(const char *path, Error **errp) -{ - DeviceState *dev; - BusState *bus; - char elem[128]; - int pos, len; - - /* find start element */ - if (path[0] == '/') { - bus = sysbus_get_default(); - pos = 0; - } else { - if (sscanf(path, "%127[^/]%n", elem, &len) != 1) { - assert(!path[0]); - elem[0] = len = 0; - } - bus = qbus_find_recursive(sysbus_get_default(), elem, NULL); - if (!bus) { - error_setg(errp, "Bus '%s' not found", elem); - return NULL; - } - pos = len; - } - - for (;;) { - assert(path[pos] == '/' || !path[pos]); - while (path[pos] == '/') { - pos++; - } - if (path[pos] == '\0') { - break; - } - - /* find device */ - if (sscanf(path+pos, "%127[^/]%n", elem, &len) != 1) { - g_assert_not_reached(); - elem[0] = len = 0; - } - pos += len; - dev = qbus_find_dev(bus, elem); - if (!dev) { - error_set(errp, ERROR_CLASS_DEVICE_NOT_FOUND, - "Device '%s' not found", elem); - qbus_error_append_dev_list_hint(bus, errp); - return NULL; - } - - assert(path[pos] == '/' || !path[pos]); - while (path[pos] == '/') { - pos++; - } - if (path[pos] == '\0') { - /* last specified element is a device. If it has exactly - * one child bus accept it nevertheless */ - if (dev->num_child_bus == 1) { - bus = QLIST_FIRST(&dev->child_bus); - break; - } - if (dev->num_child_bus) { - error_setg(errp, "Device '%s' has multiple child buses", - elem); - qbus_error_append_bus_list_hint(dev, errp); - } else { - error_setg(errp, "Device '%s' has no child bus", elem); - } - return NULL; - } - - /* find bus */ - if (sscanf(path+pos, "%127[^/]%n", elem, &len) != 1) { - g_assert_not_reached(); - elem[0] = len = 0; - } - pos += len; - bus = qbus_find_bus(dev, elem); - if (!bus) { - error_setg(errp, "Bus '%s' not found", elem); - qbus_error_append_bus_list_hint(dev, errp); - return NULL; - } - } - - if (qbus_is_full(bus)) { - error_setg(errp, "Bus '%s' is full", path); - return NULL; - } - return bus; -} - -void qdev_set_id(DeviceState *dev, const char *id) -{ - if (id) { - dev->id = id; - } - - if (dev->id) { - object_property_add_child(qdev_get_peripheral(), dev->id, - OBJECT(dev)); - } else { - static int anon_count; - gchar *name = g_strdup_printf("device[%d]", anon_count++); - object_property_add_child(qdev_get_peripheral_anon(), name, - OBJECT(dev)); - g_free(name); - } -} - -static int is_failover_device(void *opaque, const char *name, const char *value, - Error **errp) -{ - if (strcmp(name, "failover_pair_id") == 0) { - QemuOpts *opts = (QemuOpts *)opaque; - - if (qdev_should_hide_device(opts)) { - return 1; - } - } - - return 0; -} - -static bool should_hide_device(QemuOpts *opts) -{ - if (qemu_opt_foreach(opts, is_failover_device, opts, NULL) == 0) { - return false; - } - return true; -} - -DeviceState *qdev_device_add(QemuOpts *opts, Error **errp) -{ - DeviceClass *dc; - const char *driver, *path; - DeviceState *dev = NULL; - BusState *bus = NULL; - bool hide; - - driver = qemu_opt_get(opts, "driver"); - if (!driver) { - error_setg(errp, QERR_MISSING_PARAMETER, "driver"); - return NULL; - } - - /* find driver */ - dc = qdev_get_device_class(&driver, errp); - if (!dc) { - return NULL; - } - - /* find bus */ - path = qemu_opt_get(opts, "bus"); - if (path != NULL) { - bus = qbus_find(path, errp); - if (!bus) { - return NULL; - } - if (!object_dynamic_cast(OBJECT(bus), dc->bus_type)) { - error_setg(errp, "Device '%s' can't go on %s bus", - driver, object_get_typename(OBJECT(bus))); - return NULL; - } - } else if (dc->bus_type != NULL) { - bus = qbus_find_recursive(sysbus_get_default(), NULL, dc->bus_type); - if (!bus || qbus_is_full(bus)) { - error_setg(errp, "No '%s' bus found for device '%s'", - dc->bus_type, driver); - return NULL; - } - } - hide = should_hide_device(opts); - - if ((hide || qdev_hotplug) && bus && !qbus_is_hotpluggable(bus)) { - error_setg(errp, QERR_BUS_NO_HOTPLUG, bus->name); - return NULL; - } - - if (hide) { - return NULL; - } - - if (!migration_is_idle()) { - error_setg(errp, "device_add not allowed while migrating"); - return NULL; - } - - /* create device */ - dev = qdev_new(driver); - - /* Check whether the hotplug is allowed by the machine */ - if (qdev_hotplug && !qdev_hotplug_allowed(dev, errp)) { - goto err_del_dev; - } - - if (!bus && qdev_hotplug && !qdev_get_machine_hotplug_handler(dev)) { - /* No bus, no machine hotplug handler --> device is not hotpluggable */ - error_setg(errp, "Device '%s' can not be hotplugged on this machine", - driver); - goto err_del_dev; - } - - qdev_set_id(dev, qemu_opts_id(opts)); - - /* set properties */ - if (qemu_opt_foreach(opts, set_property, dev, errp)) { - goto err_del_dev; - } - - dev->opts = opts; - if (!qdev_realize(DEVICE(dev), bus, errp)) { - dev->opts = NULL; - goto err_del_dev; - } - return dev; - -err_del_dev: - if (dev) { - object_unparent(OBJECT(dev)); - object_unref(OBJECT(dev)); - } - return NULL; -} - - -#define qdev_printf(fmt, ...) monitor_printf(mon, "%*s" fmt, indent, "", ## __VA_ARGS__) -static void qbus_print(Monitor *mon, BusState *bus, int indent); - -static void qdev_print_props(Monitor *mon, DeviceState *dev, Property *props, - int indent) -{ - if (!props) - return; - for (; props->name; props++) { - char *value; - char *legacy_name = g_strdup_printf("legacy-%s", props->name); - - if (object_property_get_type(OBJECT(dev), legacy_name, NULL)) { - value = object_property_get_str(OBJECT(dev), legacy_name, NULL); - } else { - value = object_property_print(OBJECT(dev), props->name, true, - NULL); - } - g_free(legacy_name); - - if (!value) { - continue; - } - qdev_printf("%s = %s\n", props->name, - *value ? value : ""); - g_free(value); - } -} - -static void bus_print_dev(BusState *bus, Monitor *mon, DeviceState *dev, int indent) -{ - BusClass *bc = BUS_GET_CLASS(bus); - - if (bc->print_dev) { - bc->print_dev(mon, dev, indent); - } -} - -static void qdev_print(Monitor *mon, DeviceState *dev, int indent) -{ - ObjectClass *class; - BusState *child; - NamedGPIOList *ngl; - NamedClockList *ncl; - - qdev_printf("dev: %s, id \"%s\"\n", object_get_typename(OBJECT(dev)), - dev->id ? dev->id : ""); - indent += 2; - QLIST_FOREACH(ngl, &dev->gpios, node) { - if (ngl->num_in) { - qdev_printf("gpio-in \"%s\" %d\n", ngl->name ? ngl->name : "", - ngl->num_in); - } - if (ngl->num_out) { - qdev_printf("gpio-out \"%s\" %d\n", ngl->name ? ngl->name : "", - ngl->num_out); - } - } - QLIST_FOREACH(ncl, &dev->clocks, node) { - qdev_printf("clock-%s%s \"%s\" freq_hz=%e\n", - ncl->output ? "out" : "in", - ncl->alias ? " (alias)" : "", - ncl->name, - CLOCK_PERIOD_TO_HZ(1.0 * clock_get(ncl->clock))); - } - class = object_get_class(OBJECT(dev)); - do { - qdev_print_props(mon, dev, DEVICE_CLASS(class)->props_, indent); - class = object_class_get_parent(class); - } while (class != object_class_by_name(TYPE_DEVICE)); - bus_print_dev(dev->parent_bus, mon, dev, indent); - QLIST_FOREACH(child, &dev->child_bus, sibling) { - qbus_print(mon, child, indent); - } -} - -static void qbus_print(Monitor *mon, BusState *bus, int indent) -{ - BusChild *kid; - - qdev_printf("bus: %s\n", bus->name); - indent += 2; - qdev_printf("type %s\n", object_get_typename(OBJECT(bus))); - QTAILQ_FOREACH(kid, &bus->children, sibling) { - DeviceState *dev = kid->child; - qdev_print(mon, dev, indent); - } -} -#undef qdev_printf - -void hmp_info_qtree(Monitor *mon, const QDict *qdict) -{ - if (sysbus_get_default()) - qbus_print(mon, sysbus_get_default(), 0); -} - -void hmp_info_qdm(Monitor *mon, const QDict *qdict) -{ - qdev_print_devinfos(true); -} - -void qmp_device_add(QDict *qdict, QObject **ret_data, Error **errp) -{ - QemuOpts *opts; - DeviceState *dev; - - opts = qemu_opts_from_qdict(qemu_find_opts("device"), qdict, errp); - if (!opts) { - return; - } - if (!monitor_cur_is_qmp() && qdev_device_help(opts)) { - qemu_opts_del(opts); - return; - } - dev = qdev_device_add(opts, errp); - if (!dev) { - qemu_opts_del(opts); - return; - } - object_unref(OBJECT(dev)); -} - -static DeviceState *find_device_state(const char *id, Error **errp) -{ - Object *obj; - - if (id[0] == '/') { - obj = object_resolve_path(id, NULL); - } else { - char *root_path = object_get_canonical_path(qdev_get_peripheral()); - char *path = g_strdup_printf("%s/%s", root_path, id); - - g_free(root_path); - obj = object_resolve_path_type(path, TYPE_DEVICE, NULL); - g_free(path); - } - - if (!obj) { - error_set(errp, ERROR_CLASS_DEVICE_NOT_FOUND, - "Device '%s' not found", id); - return NULL; - } - - if (!object_dynamic_cast(obj, TYPE_DEVICE)) { - error_setg(errp, "%s is not a hotpluggable device", id); - return NULL; - } - - return DEVICE(obj); -} - -void qdev_unplug(DeviceState *dev, Error **errp) -{ - DeviceClass *dc = DEVICE_GET_CLASS(dev); - HotplugHandler *hotplug_ctrl; - HotplugHandlerClass *hdc; - Error *local_err = NULL; - - if (dev->parent_bus && !qbus_is_hotpluggable(dev->parent_bus)) { - error_setg(errp, QERR_BUS_NO_HOTPLUG, dev->parent_bus->name); - return; - } - - if (!dc->hotpluggable) { - error_setg(errp, QERR_DEVICE_NO_HOTPLUG, - object_get_typename(OBJECT(dev))); - return; - } - - if (!migration_is_idle() && !dev->allow_unplug_during_migration) { - error_setg(errp, "device_del not allowed while migrating"); - return; - } - - qdev_hot_removed = true; - - hotplug_ctrl = qdev_get_hotplug_handler(dev); - /* hotpluggable device MUST have HotplugHandler, if it doesn't - * then something is very wrong with it */ - g_assert(hotplug_ctrl); - - /* If device supports async unplug just request it to be done, - * otherwise just remove it synchronously */ - hdc = HOTPLUG_HANDLER_GET_CLASS(hotplug_ctrl); - if (hdc->unplug_request) { - hotplug_handler_unplug_request(hotplug_ctrl, dev, &local_err); - } else { - hotplug_handler_unplug(hotplug_ctrl, dev, &local_err); - if (!local_err) { - object_unparent(OBJECT(dev)); - } - } - error_propagate(errp, local_err); -} - -void qmp_device_del(const char *id, Error **errp) -{ - DeviceState *dev = find_device_state(id, errp); - if (dev != NULL) { - if (dev->pending_deleted_event) { - error_setg(errp, "Device %s is already in the " - "process of unplug", id); - return; - } - - qdev_unplug(dev, errp); - } -} - -void hmp_device_add(Monitor *mon, const QDict *qdict) -{ - Error *err = NULL; - - qmp_device_add((QDict *)qdict, NULL, &err); - hmp_handle_error(mon, err); -} - -void hmp_device_del(Monitor *mon, const QDict *qdict) -{ - const char *id = qdict_get_str(qdict, "id"); - Error *err = NULL; - - qmp_device_del(id, &err); - hmp_handle_error(mon, err); -} - -BlockBackend *blk_by_qdev_id(const char *id, Error **errp) -{ - DeviceState *dev; - BlockBackend *blk; - - dev = find_device_state(id, errp); - if (dev == NULL) { - return NULL; - } - - blk = blk_by_dev(dev); - if (!blk) { - error_setg(errp, "Device does not have a block device backend"); - } - return blk; -} - -void qdev_machine_init(void) -{ - qdev_get_peripheral_anon(); - qdev_get_peripheral(); -} - -QemuOptsList qemu_device_opts = { - .name = "device", - .implied_opt_name = "driver", - .head = QTAILQ_HEAD_INITIALIZER(qemu_device_opts.head), - .desc = { - /* - * no elements => accept any - * sanity checking will happen later - * when setting device properties - */ - { /* end of list */ } - }, -}; - -QemuOptsList qemu_global_opts = { - .name = "global", - .head = QTAILQ_HEAD_INITIALIZER(qemu_global_opts.head), - .desc = { - { - .name = "driver", - .type = QEMU_OPT_STRING, - },{ - .name = "property", - .type = QEMU_OPT_STRING, - },{ - .name = "value", - .type = QEMU_OPT_STRING, - }, - { /* end of list */ } - }, -}; - -int qemu_global_option(const char *str) -{ - char driver[64], property[64]; - QemuOpts *opts; - int rc, offset; - - rc = sscanf(str, "%63[^.=].%63[^=]%n", driver, property, &offset); - if (rc == 2 && str[offset] == '=') { - opts = qemu_opts_create(&qemu_global_opts, NULL, 0, &error_abort); - qemu_opt_set(opts, "driver", driver, &error_abort); - qemu_opt_set(opts, "property", property, &error_abort); - qemu_opt_set(opts, "value", str + offset + 1, &error_abort); - return 0; - } - - opts = qemu_opts_parse_noisily(&qemu_global_opts, str, false); - if (!opts) { - return -1; - } - - return 0; -} diff --git a/qemu-seccomp.c b/qemu-seccomp.c deleted file mode 100644 index 8325ecb766..0000000000 --- a/qemu-seccomp.c +++ /dev/null @@ -1,331 +0,0 @@ -/* - * QEMU seccomp mode 2 support with libseccomp - * - * Copyright IBM, Corp. 2012 - * - * Authors: - * Eduardo Otubo - * - * This work is licensed under the terms of the GNU GPL, version 2. See - * the COPYING file in the top-level directory. - * - * Contributions after 2012-01-13 are licensed under the terms of the - * GNU GPL, version 2 or (at your option) any later version. - */ - -#include "qemu/osdep.h" -#include "qapi/error.h" -#include "qemu/config-file.h" -#include "qemu/option.h" -#include "qemu/module.h" -#include -#include -#include "sysemu/seccomp.h" -#include - -/* For some architectures (notably ARM) cacheflush is not supported until - * libseccomp 2.2.3, but configure enforces that we are using a more recent - * version on those hosts, so it is OK for this check to be less strict. - */ -#if SCMP_VER_MAJOR >= 3 - #define HAVE_CACHEFLUSH -#elif SCMP_VER_MAJOR == 2 && SCMP_VER_MINOR >= 2 - #define HAVE_CACHEFLUSH -#endif - -struct QemuSeccompSyscall { - int32_t num; - uint8_t set; - uint8_t narg; - const struct scmp_arg_cmp *arg_cmp; -}; - -const struct scmp_arg_cmp sched_setscheduler_arg[] = { - /* was SCMP_A1(SCMP_CMP_NE, SCHED_IDLE), but expanded due to GCC 4.x bug */ - { .arg = 1, .op = SCMP_CMP_NE, .datum_a = SCHED_IDLE } -}; - -static const struct QemuSeccompSyscall blacklist[] = { - /* default set of syscalls to blacklist */ - { SCMP_SYS(reboot), QEMU_SECCOMP_SET_DEFAULT }, - { SCMP_SYS(swapon), QEMU_SECCOMP_SET_DEFAULT }, - { SCMP_SYS(swapoff), QEMU_SECCOMP_SET_DEFAULT }, - { SCMP_SYS(syslog), QEMU_SECCOMP_SET_DEFAULT }, - { SCMP_SYS(mount), QEMU_SECCOMP_SET_DEFAULT }, - { SCMP_SYS(umount), QEMU_SECCOMP_SET_DEFAULT }, - { SCMP_SYS(kexec_load), QEMU_SECCOMP_SET_DEFAULT }, - { SCMP_SYS(afs_syscall), QEMU_SECCOMP_SET_DEFAULT }, - { SCMP_SYS(break), QEMU_SECCOMP_SET_DEFAULT }, - { SCMP_SYS(ftime), QEMU_SECCOMP_SET_DEFAULT }, - { SCMP_SYS(getpmsg), QEMU_SECCOMP_SET_DEFAULT }, - { SCMP_SYS(gtty), QEMU_SECCOMP_SET_DEFAULT }, - { SCMP_SYS(lock), QEMU_SECCOMP_SET_DEFAULT }, - { SCMP_SYS(mpx), QEMU_SECCOMP_SET_DEFAULT }, - { SCMP_SYS(prof), QEMU_SECCOMP_SET_DEFAULT }, - { SCMP_SYS(profil), QEMU_SECCOMP_SET_DEFAULT }, - { SCMP_SYS(putpmsg), QEMU_SECCOMP_SET_DEFAULT }, - { SCMP_SYS(security), QEMU_SECCOMP_SET_DEFAULT }, - { SCMP_SYS(stty), QEMU_SECCOMP_SET_DEFAULT }, - { SCMP_SYS(tuxcall), QEMU_SECCOMP_SET_DEFAULT }, - { SCMP_SYS(ulimit), QEMU_SECCOMP_SET_DEFAULT }, - { SCMP_SYS(vserver), QEMU_SECCOMP_SET_DEFAULT }, - /* obsolete */ - { SCMP_SYS(readdir), QEMU_SECCOMP_SET_OBSOLETE }, - { SCMP_SYS(_sysctl), QEMU_SECCOMP_SET_OBSOLETE }, - { SCMP_SYS(bdflush), QEMU_SECCOMP_SET_OBSOLETE }, - { SCMP_SYS(create_module), QEMU_SECCOMP_SET_OBSOLETE }, - { SCMP_SYS(get_kernel_syms), QEMU_SECCOMP_SET_OBSOLETE }, - { SCMP_SYS(query_module), QEMU_SECCOMP_SET_OBSOLETE }, - { SCMP_SYS(sgetmask), QEMU_SECCOMP_SET_OBSOLETE }, - { SCMP_SYS(ssetmask), QEMU_SECCOMP_SET_OBSOLETE }, - { SCMP_SYS(sysfs), QEMU_SECCOMP_SET_OBSOLETE }, - { SCMP_SYS(uselib), QEMU_SECCOMP_SET_OBSOLETE }, - { SCMP_SYS(ustat), QEMU_SECCOMP_SET_OBSOLETE }, - /* privileged */ - { SCMP_SYS(setuid), QEMU_SECCOMP_SET_PRIVILEGED }, - { SCMP_SYS(setgid), QEMU_SECCOMP_SET_PRIVILEGED }, - { SCMP_SYS(setpgid), QEMU_SECCOMP_SET_PRIVILEGED }, - { SCMP_SYS(setsid), QEMU_SECCOMP_SET_PRIVILEGED }, - { SCMP_SYS(setreuid), QEMU_SECCOMP_SET_PRIVILEGED }, - { SCMP_SYS(setregid), QEMU_SECCOMP_SET_PRIVILEGED }, - { SCMP_SYS(setresuid), QEMU_SECCOMP_SET_PRIVILEGED }, - { SCMP_SYS(setresgid), QEMU_SECCOMP_SET_PRIVILEGED }, - { SCMP_SYS(setfsuid), QEMU_SECCOMP_SET_PRIVILEGED }, - { SCMP_SYS(setfsgid), QEMU_SECCOMP_SET_PRIVILEGED }, - /* spawn */ - { SCMP_SYS(fork), QEMU_SECCOMP_SET_SPAWN }, - { SCMP_SYS(vfork), QEMU_SECCOMP_SET_SPAWN }, - { SCMP_SYS(execve), QEMU_SECCOMP_SET_SPAWN }, - /* resource control */ - { SCMP_SYS(getpriority), QEMU_SECCOMP_SET_RESOURCECTL }, - { SCMP_SYS(setpriority), QEMU_SECCOMP_SET_RESOURCECTL }, - { SCMP_SYS(sched_setparam), QEMU_SECCOMP_SET_RESOURCECTL }, - { SCMP_SYS(sched_getparam), QEMU_SECCOMP_SET_RESOURCECTL }, - { SCMP_SYS(sched_setscheduler), QEMU_SECCOMP_SET_RESOURCECTL, - ARRAY_SIZE(sched_setscheduler_arg), sched_setscheduler_arg }, - { SCMP_SYS(sched_getscheduler), QEMU_SECCOMP_SET_RESOURCECTL }, - { SCMP_SYS(sched_setaffinity), QEMU_SECCOMP_SET_RESOURCECTL }, - { SCMP_SYS(sched_getaffinity), QEMU_SECCOMP_SET_RESOURCECTL }, - { SCMP_SYS(sched_get_priority_max), QEMU_SECCOMP_SET_RESOURCECTL }, - { SCMP_SYS(sched_get_priority_min), QEMU_SECCOMP_SET_RESOURCECTL }, -}; - -static inline __attribute__((unused)) int -qemu_seccomp(unsigned int operation, unsigned int flags, void *args) -{ -#ifdef __NR_seccomp - return syscall(__NR_seccomp, operation, flags, args); -#else - errno = ENOSYS; - return -1; -#endif -} - -static uint32_t qemu_seccomp_get_action(int set) -{ - switch (set) { - case QEMU_SECCOMP_SET_DEFAULT: - case QEMU_SECCOMP_SET_OBSOLETE: - case QEMU_SECCOMP_SET_PRIVILEGED: - case QEMU_SECCOMP_SET_SPAWN: { -#if defined(SECCOMP_GET_ACTION_AVAIL) && defined(SCMP_ACT_KILL_PROCESS) && \ - defined(SECCOMP_RET_KILL_PROCESS) - static int kill_process = -1; - if (kill_process == -1) { - uint32_t action = SECCOMP_RET_KILL_PROCESS; - - if (qemu_seccomp(SECCOMP_GET_ACTION_AVAIL, 0, &action) == 0) { - kill_process = 1; - } else { - kill_process = 0; - } - } - if (kill_process == 1) { - return SCMP_ACT_KILL_PROCESS; - } -#endif - return SCMP_ACT_TRAP; - } - - case QEMU_SECCOMP_SET_RESOURCECTL: - return SCMP_ACT_ERRNO(EPERM); - - default: - g_assert_not_reached(); - } -} - - -static int seccomp_start(uint32_t seccomp_opts, Error **errp) -{ - int rc = -1; - unsigned int i = 0; - scmp_filter_ctx ctx; - - ctx = seccomp_init(SCMP_ACT_ALLOW); - if (ctx == NULL) { - error_setg(errp, "failed to initialize seccomp context"); - goto seccomp_return; - } - - rc = seccomp_attr_set(ctx, SCMP_FLTATR_CTL_TSYNC, 1); - if (rc != 0) { - error_setg_errno(errp, -rc, - "failed to set seccomp thread synchronization"); - goto seccomp_return; - } - - for (i = 0; i < ARRAY_SIZE(blacklist); i++) { - uint32_t action; - if (!(seccomp_opts & blacklist[i].set)) { - continue; - } - - action = qemu_seccomp_get_action(blacklist[i].set); - rc = seccomp_rule_add_array(ctx, action, blacklist[i].num, - blacklist[i].narg, blacklist[i].arg_cmp); - if (rc < 0) { - error_setg_errno(errp, -rc, - "failed to add seccomp blacklist rules"); - goto seccomp_return; - } - } - - rc = seccomp_load(ctx); - if (rc < 0) { - error_setg_errno(errp, -rc, - "failed to load seccomp syscall filter in kernel"); - } - - seccomp_return: - seccomp_release(ctx); - return rc < 0 ? -1 : 0; -} - -#ifdef CONFIG_SECCOMP -int parse_sandbox(void *opaque, QemuOpts *opts, Error **errp) -{ - if (qemu_opt_get_bool(opts, "enable", false)) { - uint32_t seccomp_opts = QEMU_SECCOMP_SET_DEFAULT - | QEMU_SECCOMP_SET_OBSOLETE; - const char *value = NULL; - - value = qemu_opt_get(opts, "obsolete"); - if (value) { - if (g_str_equal(value, "allow")) { - seccomp_opts &= ~QEMU_SECCOMP_SET_OBSOLETE; - } else if (g_str_equal(value, "deny")) { - /* this is the default option, this if is here - * to provide a little bit of consistency for - * the command line */ - } else { - error_setg(errp, "invalid argument for obsolete"); - return -1; - } - } - - value = qemu_opt_get(opts, "elevateprivileges"); - if (value) { - if (g_str_equal(value, "deny")) { - seccomp_opts |= QEMU_SECCOMP_SET_PRIVILEGED; - } else if (g_str_equal(value, "children")) { - seccomp_opts |= QEMU_SECCOMP_SET_PRIVILEGED; - - /* calling prctl directly because we're - * not sure if host has CAP_SYS_ADMIN set*/ - if (prctl(PR_SET_NO_NEW_PRIVS, 1)) { - error_setg(errp, "failed to set no_new_privs aborting"); - return -1; - } - } else if (g_str_equal(value, "allow")) { - /* default value */ - } else { - error_setg(errp, "invalid argument for elevateprivileges"); - return -1; - } - } - - value = qemu_opt_get(opts, "spawn"); - if (value) { - if (g_str_equal(value, "deny")) { - seccomp_opts |= QEMU_SECCOMP_SET_SPAWN; - } else if (g_str_equal(value, "allow")) { - /* default value */ - } else { - error_setg(errp, "invalid argument for spawn"); - return -1; - } - } - - value = qemu_opt_get(opts, "resourcecontrol"); - if (value) { - if (g_str_equal(value, "deny")) { - seccomp_opts |= QEMU_SECCOMP_SET_RESOURCECTL; - } else if (g_str_equal(value, "allow")) { - /* default value */ - } else { - error_setg(errp, "invalid argument for resourcecontrol"); - return -1; - } - } - - if (seccomp_start(seccomp_opts, errp) < 0) { - return -1; - } - } - - return 0; -} - -static QemuOptsList qemu_sandbox_opts = { - .name = "sandbox", - .implied_opt_name = "enable", - .head = QTAILQ_HEAD_INITIALIZER(qemu_sandbox_opts.head), - .desc = { - { - .name = "enable", - .type = QEMU_OPT_BOOL, - }, - { - .name = "obsolete", - .type = QEMU_OPT_STRING, - }, - { - .name = "elevateprivileges", - .type = QEMU_OPT_STRING, - }, - { - .name = "spawn", - .type = QEMU_OPT_STRING, - }, - { - .name = "resourcecontrol", - .type = QEMU_OPT_STRING, - }, - { /* end of list */ } - }, -}; - -static void seccomp_register(void) -{ - bool add = false; - - /* FIXME: use seccomp_api_get() >= 2 check when released */ - -#if defined(SECCOMP_FILTER_FLAG_TSYNC) - int check; - - /* check host TSYNC capability, it returns errno == ENOSYS if unavailable */ - check = qemu_seccomp(SECCOMP_SET_MODE_FILTER, - SECCOMP_FILTER_FLAG_TSYNC, NULL); - if (check < 0 && errno == EFAULT) { - add = true; - } -#endif - - if (add) { - qemu_add_opts(&qemu_sandbox_opts); - } -} -opts_init(seccomp_register); -#endif diff --git a/softmmu/bootdevice.c b/softmmu/bootdevice.c new file mode 100644 index 0000000000..add4e3d2d1 --- /dev/null +++ b/softmmu/bootdevice.c @@ -0,0 +1,429 @@ +/* + * QEMU Boot Device Implement + * + * Copyright (c) 2014 HUAWEI TECHNOLOGIES CO., LTD. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "sysemu/sysemu.h" +#include "qapi/visitor.h" +#include "qemu/error-report.h" +#include "sysemu/reset.h" +#include "hw/qdev-core.h" +#include "hw/boards.h" + +typedef struct FWBootEntry FWBootEntry; + +struct FWBootEntry { + QTAILQ_ENTRY(FWBootEntry) link; + int32_t bootindex; + DeviceState *dev; + char *suffix; +}; + +static QTAILQ_HEAD(, FWBootEntry) fw_boot_order = + QTAILQ_HEAD_INITIALIZER(fw_boot_order); +static QEMUBootSetHandler *boot_set_handler; +static void *boot_set_opaque; + +void qemu_register_boot_set(QEMUBootSetHandler *func, void *opaque) +{ + boot_set_handler = func; + boot_set_opaque = opaque; +} + +void qemu_boot_set(const char *boot_order, Error **errp) +{ + Error *local_err = NULL; + + if (!boot_set_handler) { + error_setg(errp, "no function defined to set boot device list for" + " this architecture"); + return; + } + + validate_bootdevices(boot_order, &local_err); + if (local_err) { + error_propagate(errp, local_err); + return; + } + + boot_set_handler(boot_set_opaque, boot_order, errp); +} + +void validate_bootdevices(const char *devices, Error **errp) +{ + /* We just do some generic consistency checks */ + const char *p; + int bitmap = 0; + + for (p = devices; *p != '\0'; p++) { + /* Allowed boot devices are: + * a-b: floppy disk drives + * c-f: IDE disk drives + * g-m: machine implementation dependent drives + * n-p: network devices + * It's up to each machine implementation to check if the given boot + * devices match the actual hardware implementation and firmware + * features. + */ + if (*p < 'a' || *p > 'p') { + error_setg(errp, "Invalid boot device '%c'", *p); + return; + } + if (bitmap & (1 << (*p - 'a'))) { + error_setg(errp, "Boot device '%c' was given twice", *p); + return; + } + bitmap |= 1 << (*p - 'a'); + } +} + +void restore_boot_order(void *opaque) +{ + char *normal_boot_order = opaque; + static int first = 1; + + /* Restore boot order and remove ourselves after the first boot */ + if (first) { + first = 0; + return; + } + + if (boot_set_handler) { + qemu_boot_set(normal_boot_order, &error_abort); + } + + qemu_unregister_reset(restore_boot_order, normal_boot_order); + g_free(normal_boot_order); +} + +void check_boot_index(int32_t bootindex, Error **errp) +{ + FWBootEntry *i; + + if (bootindex >= 0) { + QTAILQ_FOREACH(i, &fw_boot_order, link) { + if (i->bootindex == bootindex) { + error_setg(errp, "The bootindex %d has already been used", + bootindex); + return; + } + } + } +} + +void del_boot_device_path(DeviceState *dev, const char *suffix) +{ + FWBootEntry *i; + + if (dev == NULL) { + return; + } + + QTAILQ_FOREACH(i, &fw_boot_order, link) { + if ((!suffix || !g_strcmp0(i->suffix, suffix)) && + i->dev == dev) { + QTAILQ_REMOVE(&fw_boot_order, i, link); + g_free(i->suffix); + g_free(i); + + break; + } + } +} + +void add_boot_device_path(int32_t bootindex, DeviceState *dev, + const char *suffix) +{ + FWBootEntry *node, *i; + + if (bootindex < 0) { + del_boot_device_path(dev, suffix); + return; + } + + assert(dev != NULL || suffix != NULL); + + del_boot_device_path(dev, suffix); + + node = g_malloc0(sizeof(FWBootEntry)); + node->bootindex = bootindex; + node->suffix = g_strdup(suffix); + node->dev = dev; + + QTAILQ_FOREACH(i, &fw_boot_order, link) { + if (i->bootindex == bootindex) { + error_report("Two devices with same boot index %d", bootindex); + exit(1); + } else if (i->bootindex < bootindex) { + continue; + } + QTAILQ_INSERT_BEFORE(i, node, link); + return; + } + QTAILQ_INSERT_TAIL(&fw_boot_order, node, link); +} + +DeviceState *get_boot_device(uint32_t position) +{ + uint32_t counter = 0; + FWBootEntry *i = NULL; + DeviceState *res = NULL; + + if (!QTAILQ_EMPTY(&fw_boot_order)) { + QTAILQ_FOREACH(i, &fw_boot_order, link) { + if (counter == position) { + res = i->dev; + break; + } + counter++; + } + } + return res; +} + +static char *get_boot_device_path(DeviceState *dev, bool ignore_suffixes, + const char *suffix) +{ + char *devpath = NULL, *s = NULL, *d, *bootpath; + + if (dev) { + devpath = qdev_get_fw_dev_path(dev); + assert(devpath); + } + + if (!ignore_suffixes) { + if (dev) { + d = qdev_get_own_fw_dev_path_from_handler(dev->parent_bus, dev); + if (d) { + assert(!suffix); + s = d; + } else { + s = g_strdup(suffix); + } + } else { + s = g_strdup(suffix); + } + } + + bootpath = g_strdup_printf("%s%s", + devpath ? devpath : "", + s ? s : ""); + g_free(devpath); + g_free(s); + + return bootpath; +} + +/* + * This function returns null terminated string that consist of new line + * separated device paths. + * + * memory pointed by "size" is assigned total length of the array in bytes + * + */ +char *get_boot_devices_list(size_t *size) +{ + FWBootEntry *i; + size_t total = 0; + char *list = NULL; + MachineClass *mc = MACHINE_GET_CLASS(qdev_get_machine()); + bool ignore_suffixes = mc->ignore_boot_device_suffixes; + + QTAILQ_FOREACH(i, &fw_boot_order, link) { + char *bootpath; + size_t len; + + bootpath = get_boot_device_path(i->dev, ignore_suffixes, i->suffix); + + if (total) { + list[total-1] = '\n'; + } + len = strlen(bootpath) + 1; + list = g_realloc(list, total + len); + memcpy(&list[total], bootpath, len); + total += len; + g_free(bootpath); + } + + *size = total; + + if (boot_strict && *size > 0) { + list[total-1] = '\n'; + list = g_realloc(list, total + 5); + memcpy(&list[total], "HALT", 5); + *size = total + 5; + } + return list; +} + +typedef struct { + int32_t *bootindex; + const char *suffix; + DeviceState *dev; +} BootIndexProperty; + +static void device_get_bootindex(Object *obj, Visitor *v, const char *name, + void *opaque, Error **errp) +{ + BootIndexProperty *prop = opaque; + visit_type_int32(v, name, prop->bootindex, errp); +} + +static void device_set_bootindex(Object *obj, Visitor *v, const char *name, + void *opaque, Error **errp) +{ + BootIndexProperty *prop = opaque; + int32_t boot_index; + Error *local_err = NULL; + + if (!visit_type_int32(v, name, &boot_index, errp)) { + return; + } + /* check whether bootindex is present in fw_boot_order list */ + check_boot_index(boot_index, &local_err); + if (local_err) { + error_propagate(errp, local_err); + return; + } + /* change bootindex to a new one */ + *prop->bootindex = boot_index; + + add_boot_device_path(*prop->bootindex, prop->dev, prop->suffix); +} + +static void property_release_bootindex(Object *obj, const char *name, + void *opaque) + +{ + BootIndexProperty *prop = opaque; + + del_boot_device_path(prop->dev, prop->suffix); + g_free(prop); +} + +void device_add_bootindex_property(Object *obj, int32_t *bootindex, + const char *name, const char *suffix, + DeviceState *dev) +{ + BootIndexProperty *prop = g_malloc0(sizeof(*prop)); + + prop->bootindex = bootindex; + prop->suffix = suffix; + prop->dev = dev; + + object_property_add(obj, name, "int32", + device_get_bootindex, + device_set_bootindex, + property_release_bootindex, + prop); + + /* initialize devices' bootindex property to -1 */ + object_property_set_int(obj, name, -1, NULL); +} + +typedef struct FWLCHSEntry FWLCHSEntry; + +struct FWLCHSEntry { + QTAILQ_ENTRY(FWLCHSEntry) link; + DeviceState *dev; + char *suffix; + uint32_t lcyls; + uint32_t lheads; + uint32_t lsecs; +}; + +static QTAILQ_HEAD(, FWLCHSEntry) fw_lchs = + QTAILQ_HEAD_INITIALIZER(fw_lchs); + +void add_boot_device_lchs(DeviceState *dev, const char *suffix, + uint32_t lcyls, uint32_t lheads, uint32_t lsecs) +{ + FWLCHSEntry *node; + + if (!lcyls && !lheads && !lsecs) { + return; + } + + assert(dev != NULL || suffix != NULL); + + node = g_malloc0(sizeof(FWLCHSEntry)); + node->suffix = g_strdup(suffix); + node->dev = dev; + node->lcyls = lcyls; + node->lheads = lheads; + node->lsecs = lsecs; + + QTAILQ_INSERT_TAIL(&fw_lchs, node, link); +} + +void del_boot_device_lchs(DeviceState *dev, const char *suffix) +{ + FWLCHSEntry *i; + + if (dev == NULL) { + return; + } + + QTAILQ_FOREACH(i, &fw_lchs, link) { + if ((!suffix || !g_strcmp0(i->suffix, suffix)) && + i->dev == dev) { + QTAILQ_REMOVE(&fw_lchs, i, link); + g_free(i->suffix); + g_free(i); + + break; + } + } +} + +char *get_boot_devices_lchs_list(size_t *size) +{ + FWLCHSEntry *i; + size_t total = 0; + char *list = NULL; + + QTAILQ_FOREACH(i, &fw_lchs, link) { + char *bootpath; + char *chs_string; + size_t len; + + bootpath = get_boot_device_path(i->dev, false, i->suffix); + chs_string = g_strdup_printf("%s %" PRIu32 " %" PRIu32 " %" PRIu32, + bootpath, i->lcyls, i->lheads, i->lsecs); + + if (total) { + list[total - 1] = '\n'; + } + len = strlen(chs_string) + 1; + list = g_realloc(list, total + len); + memcpy(&list[total], chs_string, len); + total += len; + g_free(chs_string); + g_free(bootpath); + } + + *size = total; + + return list; +} diff --git a/softmmu/device_tree.c b/softmmu/device_tree.c new file mode 100644 index 0000000000..b335dae707 --- /dev/null +++ b/softmmu/device_tree.c @@ -0,0 +1,579 @@ +/* + * Functions to help device tree manipulation using libfdt. + * It also provides functions to read entries from device tree proc + * interface. + * + * Copyright 2008 IBM Corporation. + * Authors: Jerone Young + * Hollis Blanchard + * + * This work is licensed under the GNU GPL license version 2 or later. + * + */ + +#include "qemu/osdep.h" + +#ifdef CONFIG_LINUX +#include +#endif + +#include "qapi/error.h" +#include "qemu/error-report.h" +#include "qemu/option.h" +#include "qemu/bswap.h" +#include "sysemu/device_tree.h" +#include "sysemu/sysemu.h" +#include "hw/loader.h" +#include "hw/boards.h" +#include "qemu/config-file.h" + +#include + +#define FDT_MAX_SIZE 0x100000 + +void *create_device_tree(int *sizep) +{ + void *fdt; + int ret; + + *sizep = FDT_MAX_SIZE; + fdt = g_malloc0(FDT_MAX_SIZE); + ret = fdt_create(fdt, FDT_MAX_SIZE); + if (ret < 0) { + goto fail; + } + ret = fdt_finish_reservemap(fdt); + if (ret < 0) { + goto fail; + } + ret = fdt_begin_node(fdt, ""); + if (ret < 0) { + goto fail; + } + ret = fdt_end_node(fdt); + if (ret < 0) { + goto fail; + } + ret = fdt_finish(fdt); + if (ret < 0) { + goto fail; + } + ret = fdt_open_into(fdt, fdt, *sizep); + if (ret) { + error_report("Unable to copy device tree in memory"); + exit(1); + } + + return fdt; +fail: + error_report("%s Couldn't create dt: %s", __func__, fdt_strerror(ret)); + exit(1); +} + +void *load_device_tree(const char *filename_path, int *sizep) +{ + int dt_size; + int dt_file_load_size; + int ret; + void *fdt = NULL; + + *sizep = 0; + dt_size = get_image_size(filename_path); + if (dt_size < 0) { + error_report("Unable to get size of device tree file '%s'", + filename_path); + goto fail; + } + if (dt_size > INT_MAX / 2 - 10000) { + error_report("Device tree file '%s' is too large", filename_path); + goto fail; + } + + /* Expand to 2x size to give enough room for manipulation. */ + dt_size += 10000; + dt_size *= 2; + /* First allocate space in qemu for device tree */ + fdt = g_malloc0(dt_size); + + dt_file_load_size = load_image_size(filename_path, fdt, dt_size); + if (dt_file_load_size < 0) { + error_report("Unable to open device tree file '%s'", + filename_path); + goto fail; + } + + ret = fdt_open_into(fdt, fdt, dt_size); + if (ret) { + error_report("Unable to copy device tree in memory"); + goto fail; + } + + /* Check sanity of device tree */ + if (fdt_check_header(fdt)) { + error_report("Device tree file loaded into memory is invalid: %s", + filename_path); + goto fail; + } + *sizep = dt_size; + return fdt; + +fail: + g_free(fdt); + return NULL; +} + +#ifdef CONFIG_LINUX + +#define SYSFS_DT_BASEDIR "/proc/device-tree" + +/** + * read_fstree: this function is inspired from dtc read_fstree + * @fdt: preallocated fdt blob buffer, to be populated + * @dirname: directory to scan under SYSFS_DT_BASEDIR + * the search is recursive and the tree is searched down to the + * leaves (property files). + * + * the function asserts in case of error + */ +static void read_fstree(void *fdt, const char *dirname) +{ + DIR *d; + struct dirent *de; + struct stat st; + const char *root_dir = SYSFS_DT_BASEDIR; + const char *parent_node; + + if (strstr(dirname, root_dir) != dirname) { + error_report("%s: %s must be searched within %s", + __func__, dirname, root_dir); + exit(1); + } + parent_node = &dirname[strlen(SYSFS_DT_BASEDIR)]; + + d = opendir(dirname); + if (!d) { + error_report("%s cannot open %s", __func__, dirname); + exit(1); + } + + while ((de = readdir(d)) != NULL) { + char *tmpnam; + + if (!g_strcmp0(de->d_name, ".") + || !g_strcmp0(de->d_name, "..")) { + continue; + } + + tmpnam = g_strdup_printf("%s/%s", dirname, de->d_name); + + if (lstat(tmpnam, &st) < 0) { + error_report("%s cannot lstat %s", __func__, tmpnam); + exit(1); + } + + if (S_ISREG(st.st_mode)) { + gchar *val; + gsize len; + + if (!g_file_get_contents(tmpnam, &val, &len, NULL)) { + error_report("%s not able to extract info from %s", + __func__, tmpnam); + exit(1); + } + + if (strlen(parent_node) > 0) { + qemu_fdt_setprop(fdt, parent_node, + de->d_name, val, len); + } else { + qemu_fdt_setprop(fdt, "/", de->d_name, val, len); + } + g_free(val); + } else if (S_ISDIR(st.st_mode)) { + char *node_name; + + node_name = g_strdup_printf("%s/%s", + parent_node, de->d_name); + qemu_fdt_add_subnode(fdt, node_name); + g_free(node_name); + read_fstree(fdt, tmpnam); + } + + g_free(tmpnam); + } + + closedir(d); +} + +/* load_device_tree_from_sysfs: extract the dt blob from host sysfs */ +void *load_device_tree_from_sysfs(void) +{ + void *host_fdt; + int host_fdt_size; + + host_fdt = create_device_tree(&host_fdt_size); + read_fstree(host_fdt, SYSFS_DT_BASEDIR); + if (fdt_check_header(host_fdt)) { + error_report("%s host device tree extracted into memory is invalid", + __func__); + exit(1); + } + return host_fdt; +} + +#endif /* CONFIG_LINUX */ + +static int findnode_nofail(void *fdt, const char *node_path) +{ + int offset; + + offset = fdt_path_offset(fdt, node_path); + if (offset < 0) { + error_report("%s Couldn't find node %s: %s", __func__, node_path, + fdt_strerror(offset)); + exit(1); + } + + return offset; +} + +char **qemu_fdt_node_unit_path(void *fdt, const char *name, Error **errp) +{ + char *prefix = g_strdup_printf("%s@", name); + unsigned int path_len = 16, n = 0; + GSList *path_list = NULL, *iter; + const char *iter_name; + int offset, len, ret; + char **path_array; + + offset = fdt_next_node(fdt, -1, NULL); + + while (offset >= 0) { + iter_name = fdt_get_name(fdt, offset, &len); + if (!iter_name) { + offset = len; + break; + } + if (!strcmp(iter_name, name) || g_str_has_prefix(iter_name, prefix)) { + char *path; + + path = g_malloc(path_len); + while ((ret = fdt_get_path(fdt, offset, path, path_len)) + == -FDT_ERR_NOSPACE) { + path_len += 16; + path = g_realloc(path, path_len); + } + path_list = g_slist_prepend(path_list, path); + n++; + } + offset = fdt_next_node(fdt, offset, NULL); + } + g_free(prefix); + + if (offset < 0 && offset != -FDT_ERR_NOTFOUND) { + error_setg(errp, "%s: abort parsing dt for %s node units: %s", + __func__, name, fdt_strerror(offset)); + for (iter = path_list; iter; iter = iter->next) { + g_free(iter->data); + } + g_slist_free(path_list); + return NULL; + } + + path_array = g_new(char *, n + 1); + path_array[n--] = NULL; + + for (iter = path_list; iter; iter = iter->next) { + path_array[n--] = iter->data; + } + + g_slist_free(path_list); + + return path_array; +} + +char **qemu_fdt_node_path(void *fdt, const char *name, const char *compat, + Error **errp) +{ + int offset, len, ret; + const char *iter_name; + unsigned int path_len = 16, n = 0; + GSList *path_list = NULL, *iter; + char **path_array; + + offset = fdt_node_offset_by_compatible(fdt, -1, compat); + + while (offset >= 0) { + iter_name = fdt_get_name(fdt, offset, &len); + if (!iter_name) { + offset = len; + break; + } + if (!name || !strcmp(iter_name, name)) { + char *path; + + path = g_malloc(path_len); + while ((ret = fdt_get_path(fdt, offset, path, path_len)) + == -FDT_ERR_NOSPACE) { + path_len += 16; + path = g_realloc(path, path_len); + } + path_list = g_slist_prepend(path_list, path); + n++; + } + offset = fdt_node_offset_by_compatible(fdt, offset, compat); + } + + if (offset < 0 && offset != -FDT_ERR_NOTFOUND) { + error_setg(errp, "%s: abort parsing dt for %s/%s: %s", + __func__, name, compat, fdt_strerror(offset)); + for (iter = path_list; iter; iter = iter->next) { + g_free(iter->data); + } + g_slist_free(path_list); + return NULL; + } + + path_array = g_new(char *, n + 1); + path_array[n--] = NULL; + + for (iter = path_list; iter; iter = iter->next) { + path_array[n--] = iter->data; + } + + g_slist_free(path_list); + + return path_array; +} + +int qemu_fdt_setprop(void *fdt, const char *node_path, + const char *property, const void *val, int size) +{ + int r; + + r = fdt_setprop(fdt, findnode_nofail(fdt, node_path), property, val, size); + if (r < 0) { + error_report("%s: Couldn't set %s/%s: %s", __func__, node_path, + property, fdt_strerror(r)); + exit(1); + } + + return r; +} + +int qemu_fdt_setprop_cell(void *fdt, const char *node_path, + const char *property, uint32_t val) +{ + int r; + + r = fdt_setprop_cell(fdt, findnode_nofail(fdt, node_path), property, val); + if (r < 0) { + error_report("%s: Couldn't set %s/%s = %#08x: %s", __func__, + node_path, property, val, fdt_strerror(r)); + exit(1); + } + + return r; +} + +int qemu_fdt_setprop_u64(void *fdt, const char *node_path, + const char *property, uint64_t val) +{ + val = cpu_to_be64(val); + return qemu_fdt_setprop(fdt, node_path, property, &val, sizeof(val)); +} + +int qemu_fdt_setprop_string(void *fdt, const char *node_path, + const char *property, const char *string) +{ + int r; + + r = fdt_setprop_string(fdt, findnode_nofail(fdt, node_path), property, string); + if (r < 0) { + error_report("%s: Couldn't set %s/%s = %s: %s", __func__, + node_path, property, string, fdt_strerror(r)); + exit(1); + } + + return r; +} + +const void *qemu_fdt_getprop(void *fdt, const char *node_path, + const char *property, int *lenp, Error **errp) +{ + int len; + const void *r; + + if (!lenp) { + lenp = &len; + } + r = fdt_getprop(fdt, findnode_nofail(fdt, node_path), property, lenp); + if (!r) { + error_setg(errp, "%s: Couldn't get %s/%s: %s", __func__, + node_path, property, fdt_strerror(*lenp)); + } + return r; +} + +uint32_t qemu_fdt_getprop_cell(void *fdt, const char *node_path, + const char *property, int *lenp, Error **errp) +{ + int len; + const uint32_t *p; + + if (!lenp) { + lenp = &len; + } + p = qemu_fdt_getprop(fdt, node_path, property, lenp, errp); + if (!p) { + return 0; + } else if (*lenp != 4) { + error_setg(errp, "%s: %s/%s not 4 bytes long (not a cell?)", + __func__, node_path, property); + *lenp = -EINVAL; + return 0; + } + return be32_to_cpu(*p); +} + +uint32_t qemu_fdt_get_phandle(void *fdt, const char *path) +{ + uint32_t r; + + r = fdt_get_phandle(fdt, findnode_nofail(fdt, path)); + if (r == 0) { + error_report("%s: Couldn't get phandle for %s: %s", __func__, + path, fdt_strerror(r)); + exit(1); + } + + return r; +} + +int qemu_fdt_setprop_phandle(void *fdt, const char *node_path, + const char *property, + const char *target_node_path) +{ + uint32_t phandle = qemu_fdt_get_phandle(fdt, target_node_path); + return qemu_fdt_setprop_cell(fdt, node_path, property, phandle); +} + +uint32_t qemu_fdt_alloc_phandle(void *fdt) +{ + static int phandle = 0x0; + + /* + * We need to find out if the user gave us special instruction at + * which phandle id to start allocating phandles. + */ + if (!phandle) { + phandle = machine_phandle_start(current_machine); + } + + if (!phandle) { + /* + * None or invalid phandle given on the command line, so fall back to + * default starting point. + */ + phandle = 0x8000; + } + + return phandle++; +} + +int qemu_fdt_nop_node(void *fdt, const char *node_path) +{ + int r; + + r = fdt_nop_node(fdt, findnode_nofail(fdt, node_path)); + if (r < 0) { + error_report("%s: Couldn't nop node %s: %s", __func__, node_path, + fdt_strerror(r)); + exit(1); + } + + return r; +} + +int qemu_fdt_add_subnode(void *fdt, const char *name) +{ + char *dupname = g_strdup(name); + char *basename = strrchr(dupname, '/'); + int retval; + int parent = 0; + + if (!basename) { + g_free(dupname); + return -1; + } + + basename[0] = '\0'; + basename++; + + if (dupname[0]) { + parent = findnode_nofail(fdt, dupname); + } + + retval = fdt_add_subnode(fdt, parent, basename); + if (retval < 0) { + error_report("FDT: Failed to create subnode %s: %s", name, + fdt_strerror(retval)); + exit(1); + } + + g_free(dupname); + return retval; +} + +void qemu_fdt_dumpdtb(void *fdt, int size) +{ + const char *dumpdtb = qemu_opt_get(qemu_get_machine_opts(), "dumpdtb"); + + if (dumpdtb) { + /* Dump the dtb to a file and quit */ + if (g_file_set_contents(dumpdtb, fdt, size, NULL)) { + info_report("dtb dumped to %s. Exiting.", dumpdtb); + exit(0); + } + error_report("%s: Failed dumping dtb to %s", __func__, dumpdtb); + exit(1); + } +} + +int qemu_fdt_setprop_sized_cells_from_array(void *fdt, + const char *node_path, + const char *property, + int numvalues, + uint64_t *values) +{ + uint32_t *propcells; + uint64_t value; + int cellnum, vnum, ncells; + uint32_t hival; + int ret; + + propcells = g_new0(uint32_t, numvalues * 2); + + cellnum = 0; + for (vnum = 0; vnum < numvalues; vnum++) { + ncells = values[vnum * 2]; + if (ncells != 1 && ncells != 2) { + ret = -1; + goto out; + } + value = values[vnum * 2 + 1]; + hival = cpu_to_be32(value >> 32); + if (ncells > 1) { + propcells[cellnum++] = hival; + } else if (hival != 0) { + ret = -1; + goto out; + } + propcells[cellnum++] = cpu_to_be32(value); + } + + ret = qemu_fdt_setprop(fdt, node_path, property, propcells, + cellnum * sizeof(uint32_t)); +out: + g_free(propcells); + return ret; +} diff --git a/softmmu/dma-helpers.c b/softmmu/dma-helpers.c new file mode 100644 index 0000000000..03c92e0cc6 --- /dev/null +++ b/softmmu/dma-helpers.c @@ -0,0 +1,331 @@ +/* + * DMA helper functions + * + * Copyright (c) 2009 Red Hat + * + * This work is licensed under the terms of the GNU General Public License + * (GNU GPL), version 2 or later. + */ + +#include "qemu/osdep.h" +#include "sysemu/block-backend.h" +#include "sysemu/dma.h" +#include "trace/trace-root.h" +#include "qemu/thread.h" +#include "qemu/main-loop.h" +#include "sysemu/cpu-timers.h" +#include "qemu/range.h" + +/* #define DEBUG_IOMMU */ + +int dma_memory_set(AddressSpace *as, dma_addr_t addr, uint8_t c, dma_addr_t len) +{ + dma_barrier(as, DMA_DIRECTION_FROM_DEVICE); + +#define FILLBUF_SIZE 512 + uint8_t fillbuf[FILLBUF_SIZE]; + int l; + bool error = false; + + memset(fillbuf, c, FILLBUF_SIZE); + while (len > 0) { + l = len < FILLBUF_SIZE ? len : FILLBUF_SIZE; + error |= address_space_write(as, addr, MEMTXATTRS_UNSPECIFIED, + fillbuf, l); + len -= l; + addr += l; + } + + return error; +} + +void qemu_sglist_init(QEMUSGList *qsg, DeviceState *dev, int alloc_hint, + AddressSpace *as) +{ + qsg->sg = g_malloc(alloc_hint * sizeof(ScatterGatherEntry)); + qsg->nsg = 0; + qsg->nalloc = alloc_hint; + qsg->size = 0; + qsg->as = as; + qsg->dev = dev; + object_ref(OBJECT(dev)); +} + +void qemu_sglist_add(QEMUSGList *qsg, dma_addr_t base, dma_addr_t len) +{ + if (qsg->nsg == qsg->nalloc) { + qsg->nalloc = 2 * qsg->nalloc + 1; + qsg->sg = g_realloc(qsg->sg, qsg->nalloc * sizeof(ScatterGatherEntry)); + } + qsg->sg[qsg->nsg].base = base; + qsg->sg[qsg->nsg].len = len; + qsg->size += len; + ++qsg->nsg; +} + +void qemu_sglist_destroy(QEMUSGList *qsg) +{ + object_unref(OBJECT(qsg->dev)); + g_free(qsg->sg); + memset(qsg, 0, sizeof(*qsg)); +} + +typedef struct { + BlockAIOCB common; + AioContext *ctx; + BlockAIOCB *acb; + QEMUSGList *sg; + uint32_t align; + uint64_t offset; + DMADirection dir; + int sg_cur_index; + dma_addr_t sg_cur_byte; + QEMUIOVector iov; + QEMUBH *bh; + DMAIOFunc *io_func; + void *io_func_opaque; +} DMAAIOCB; + +static void dma_blk_cb(void *opaque, int ret); + +static void reschedule_dma(void *opaque) +{ + DMAAIOCB *dbs = (DMAAIOCB *)opaque; + + assert(!dbs->acb && dbs->bh); + qemu_bh_delete(dbs->bh); + dbs->bh = NULL; + dma_blk_cb(dbs, 0); +} + +static void dma_blk_unmap(DMAAIOCB *dbs) +{ + int i; + + for (i = 0; i < dbs->iov.niov; ++i) { + dma_memory_unmap(dbs->sg->as, dbs->iov.iov[i].iov_base, + dbs->iov.iov[i].iov_len, dbs->dir, + dbs->iov.iov[i].iov_len); + } + qemu_iovec_reset(&dbs->iov); +} + +static void dma_complete(DMAAIOCB *dbs, int ret) +{ + trace_dma_complete(dbs, ret, dbs->common.cb); + + assert(!dbs->acb && !dbs->bh); + dma_blk_unmap(dbs); + if (dbs->common.cb) { + dbs->common.cb(dbs->common.opaque, ret); + } + qemu_iovec_destroy(&dbs->iov); + qemu_aio_unref(dbs); +} + +static void dma_blk_cb(void *opaque, int ret) +{ + DMAAIOCB *dbs = (DMAAIOCB *)opaque; + dma_addr_t cur_addr, cur_len; + void *mem; + + trace_dma_blk_cb(dbs, ret); + + dbs->acb = NULL; + dbs->offset += dbs->iov.size; + + if (dbs->sg_cur_index == dbs->sg->nsg || ret < 0) { + dma_complete(dbs, ret); + return; + } + dma_blk_unmap(dbs); + + while (dbs->sg_cur_index < dbs->sg->nsg) { + cur_addr = dbs->sg->sg[dbs->sg_cur_index].base + dbs->sg_cur_byte; + cur_len = dbs->sg->sg[dbs->sg_cur_index].len - dbs->sg_cur_byte; + mem = dma_memory_map(dbs->sg->as, cur_addr, &cur_len, dbs->dir); + /* + * Make reads deterministic in icount mode. Windows sometimes issues + * disk read requests with overlapping SGs. It leads + * to non-determinism, because resulting buffer contents may be mixed + * from several sectors. This code splits all SGs into several + * groups. SGs in every group do not overlap. + */ + if (mem && icount_enabled() && dbs->dir == DMA_DIRECTION_FROM_DEVICE) { + int i; + for (i = 0 ; i < dbs->iov.niov ; ++i) { + if (ranges_overlap((intptr_t)dbs->iov.iov[i].iov_base, + dbs->iov.iov[i].iov_len, (intptr_t)mem, + cur_len)) { + dma_memory_unmap(dbs->sg->as, mem, cur_len, + dbs->dir, cur_len); + mem = NULL; + break; + } + } + } + if (!mem) + break; + qemu_iovec_add(&dbs->iov, mem, cur_len); + dbs->sg_cur_byte += cur_len; + if (dbs->sg_cur_byte == dbs->sg->sg[dbs->sg_cur_index].len) { + dbs->sg_cur_byte = 0; + ++dbs->sg_cur_index; + } + } + + if (dbs->iov.size == 0) { + trace_dma_map_wait(dbs); + dbs->bh = aio_bh_new(dbs->ctx, reschedule_dma, dbs); + cpu_register_map_client(dbs->bh); + return; + } + + if (!QEMU_IS_ALIGNED(dbs->iov.size, dbs->align)) { + qemu_iovec_discard_back(&dbs->iov, + QEMU_ALIGN_DOWN(dbs->iov.size, dbs->align)); + } + + aio_context_acquire(dbs->ctx); + dbs->acb = dbs->io_func(dbs->offset, &dbs->iov, + dma_blk_cb, dbs, dbs->io_func_opaque); + aio_context_release(dbs->ctx); + assert(dbs->acb); +} + +static void dma_aio_cancel(BlockAIOCB *acb) +{ + DMAAIOCB *dbs = container_of(acb, DMAAIOCB, common); + + trace_dma_aio_cancel(dbs); + + assert(!(dbs->acb && dbs->bh)); + if (dbs->acb) { + /* This will invoke dma_blk_cb. */ + blk_aio_cancel_async(dbs->acb); + return; + } + + if (dbs->bh) { + cpu_unregister_map_client(dbs->bh); + qemu_bh_delete(dbs->bh); + dbs->bh = NULL; + } + if (dbs->common.cb) { + dbs->common.cb(dbs->common.opaque, -ECANCELED); + } +} + +static AioContext *dma_get_aio_context(BlockAIOCB *acb) +{ + DMAAIOCB *dbs = container_of(acb, DMAAIOCB, common); + + return dbs->ctx; +} + +static const AIOCBInfo dma_aiocb_info = { + .aiocb_size = sizeof(DMAAIOCB), + .cancel_async = dma_aio_cancel, + .get_aio_context = dma_get_aio_context, +}; + +BlockAIOCB *dma_blk_io(AioContext *ctx, + QEMUSGList *sg, uint64_t offset, uint32_t align, + DMAIOFunc *io_func, void *io_func_opaque, + BlockCompletionFunc *cb, + void *opaque, DMADirection dir) +{ + DMAAIOCB *dbs = qemu_aio_get(&dma_aiocb_info, NULL, cb, opaque); + + trace_dma_blk_io(dbs, io_func_opaque, offset, (dir == DMA_DIRECTION_TO_DEVICE)); + + dbs->acb = NULL; + dbs->sg = sg; + dbs->ctx = ctx; + dbs->offset = offset; + dbs->align = align; + dbs->sg_cur_index = 0; + dbs->sg_cur_byte = 0; + dbs->dir = dir; + dbs->io_func = io_func; + dbs->io_func_opaque = io_func_opaque; + dbs->bh = NULL; + qemu_iovec_init(&dbs->iov, sg->nsg); + dma_blk_cb(dbs, 0); + return &dbs->common; +} + + +static +BlockAIOCB *dma_blk_read_io_func(int64_t offset, QEMUIOVector *iov, + BlockCompletionFunc *cb, void *cb_opaque, + void *opaque) +{ + BlockBackend *blk = opaque; + return blk_aio_preadv(blk, offset, iov, 0, cb, cb_opaque); +} + +BlockAIOCB *dma_blk_read(BlockBackend *blk, + QEMUSGList *sg, uint64_t offset, uint32_t align, + void (*cb)(void *opaque, int ret), void *opaque) +{ + return dma_blk_io(blk_get_aio_context(blk), sg, offset, align, + dma_blk_read_io_func, blk, cb, opaque, + DMA_DIRECTION_FROM_DEVICE); +} + +static +BlockAIOCB *dma_blk_write_io_func(int64_t offset, QEMUIOVector *iov, + BlockCompletionFunc *cb, void *cb_opaque, + void *opaque) +{ + BlockBackend *blk = opaque; + return blk_aio_pwritev(blk, offset, iov, 0, cb, cb_opaque); +} + +BlockAIOCB *dma_blk_write(BlockBackend *blk, + QEMUSGList *sg, uint64_t offset, uint32_t align, + void (*cb)(void *opaque, int ret), void *opaque) +{ + return dma_blk_io(blk_get_aio_context(blk), sg, offset, align, + dma_blk_write_io_func, blk, cb, opaque, + DMA_DIRECTION_TO_DEVICE); +} + + +static uint64_t dma_buf_rw(uint8_t *ptr, int32_t len, QEMUSGList *sg, + DMADirection dir) +{ + uint64_t resid; + int sg_cur_index; + + resid = sg->size; + sg_cur_index = 0; + len = MIN(len, resid); + while (len > 0) { + ScatterGatherEntry entry = sg->sg[sg_cur_index++]; + int32_t xfer = MIN(len, entry.len); + dma_memory_rw(sg->as, entry.base, ptr, xfer, dir); + ptr += xfer; + len -= xfer; + resid -= xfer; + } + + return resid; +} + +uint64_t dma_buf_read(uint8_t *ptr, int32_t len, QEMUSGList *sg) +{ + return dma_buf_rw(ptr, len, sg, DMA_DIRECTION_FROM_DEVICE); +} + +uint64_t dma_buf_write(uint8_t *ptr, int32_t len, QEMUSGList *sg) +{ + return dma_buf_rw(ptr, len, sg, DMA_DIRECTION_TO_DEVICE); +} + +void dma_acct_start(BlockBackend *blk, BlockAcctCookie *cookie, + QEMUSGList *sg, enum BlockAcctType type) +{ + block_acct_start(blk_get_stats(blk), cookie, sg->size, type); +} diff --git a/softmmu/meson.build b/softmmu/meson.build index 36c96e7b15..862ab24878 100644 --- a/softmmu/meson.build +++ b/softmmu/meson.build @@ -14,3 +14,13 @@ specific_ss.add(when: 'CONFIG_SOFTMMU', if_true: [files( specific_ss.add(when: ['CONFIG_SOFTMMU', 'CONFIG_TCG'], if_true: [files( 'icount.c' )]) + +softmmu_ss.add(files( + 'bootdevice.c', + 'dma-helpers.c', + 'qdev-monitor.c', +), sdl) + +softmmu_ss.add(when: 'CONFIG_TPM', if_true: files('tpm.c')) +softmmu_ss.add(when: 'CONFIG_SECCOMP', if_true: [files('qemu-seccomp.c'), seccomp]) +softmmu_ss.add(when: fdt, if_true: files('device_tree.c')) diff --git a/softmmu/qdev-monitor.c b/softmmu/qdev-monitor.c new file mode 100644 index 0000000000..e9b7228480 --- /dev/null +++ b/softmmu/qdev-monitor.c @@ -0,0 +1,993 @@ +/* + * Dynamic device configuration and creation. + * + * Copyright (c) 2009 CodeSourcery + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#include "qemu/osdep.h" +#include "hw/sysbus.h" +#include "monitor/hmp.h" +#include "monitor/monitor.h" +#include "monitor/qdev.h" +#include "sysemu/arch_init.h" +#include "qapi/error.h" +#include "qapi/qapi-commands-qdev.h" +#include "qapi/qmp/qdict.h" +#include "qapi/qmp/qerror.h" +#include "qemu/config-file.h" +#include "qemu/error-report.h" +#include "qemu/help_option.h" +#include "qemu/option.h" +#include "qemu/qemu-print.h" +#include "qemu/option_int.h" +#include "sysemu/block-backend.h" +#include "sysemu/sysemu.h" +#include "migration/misc.h" +#include "migration/migration.h" +#include "qemu/cutils.h" +#include "hw/clock.h" + +/* + * Aliases were a bad idea from the start. Let's keep them + * from spreading further. + */ +typedef struct QDevAlias +{ + const char *typename; + const char *alias; + uint32_t arch_mask; +} QDevAlias; + +/* Please keep this table sorted by typename. */ +static const QDevAlias qdev_alias_table[] = { + { "AC97", "ac97" }, /* -soundhw name */ + { "e1000", "e1000-82540em" }, + { "ES1370", "es1370" }, /* -soundhw name */ + { "ich9-ahci", "ahci" }, + { "lsi53c895a", "lsi" }, + { "virtio-9p-ccw", "virtio-9p", QEMU_ARCH_S390X }, + { "virtio-9p-pci", "virtio-9p", QEMU_ARCH_ALL & ~QEMU_ARCH_S390X }, + { "virtio-balloon-ccw", "virtio-balloon", QEMU_ARCH_S390X }, + { "virtio-balloon-pci", "virtio-balloon", + QEMU_ARCH_ALL & ~QEMU_ARCH_S390X }, + { "virtio-blk-ccw", "virtio-blk", QEMU_ARCH_S390X }, + { "virtio-blk-pci", "virtio-blk", QEMU_ARCH_ALL & ~QEMU_ARCH_S390X }, + { "virtio-gpu-ccw", "virtio-gpu", QEMU_ARCH_S390X }, + { "virtio-gpu-pci", "virtio-gpu", QEMU_ARCH_ALL & ~QEMU_ARCH_S390X }, + { "virtio-input-host-ccw", "virtio-input-host", QEMU_ARCH_S390X }, + { "virtio-input-host-pci", "virtio-input-host", + QEMU_ARCH_ALL & ~QEMU_ARCH_S390X }, + { "virtio-iommu-pci", "virtio-iommu", QEMU_ARCH_ALL & ~QEMU_ARCH_S390X }, + { "virtio-keyboard-ccw", "virtio-keyboard", QEMU_ARCH_S390X }, + { "virtio-keyboard-pci", "virtio-keyboard", + QEMU_ARCH_ALL & ~QEMU_ARCH_S390X }, + { "virtio-mouse-ccw", "virtio-mouse", QEMU_ARCH_S390X }, + { "virtio-mouse-pci", "virtio-mouse", QEMU_ARCH_ALL & ~QEMU_ARCH_S390X }, + { "virtio-net-ccw", "virtio-net", QEMU_ARCH_S390X }, + { "virtio-net-pci", "virtio-net", QEMU_ARCH_ALL & ~QEMU_ARCH_S390X }, + { "virtio-rng-ccw", "virtio-rng", QEMU_ARCH_S390X }, + { "virtio-rng-pci", "virtio-rng", QEMU_ARCH_ALL & ~QEMU_ARCH_S390X }, + { "virtio-scsi-ccw", "virtio-scsi", QEMU_ARCH_S390X }, + { "virtio-scsi-pci", "virtio-scsi", QEMU_ARCH_ALL & ~QEMU_ARCH_S390X }, + { "virtio-serial-ccw", "virtio-serial", QEMU_ARCH_S390X }, + { "virtio-serial-pci", "virtio-serial", QEMU_ARCH_ALL & ~QEMU_ARCH_S390X }, + { "virtio-tablet-ccw", "virtio-tablet", QEMU_ARCH_S390X }, + { "virtio-tablet-pci", "virtio-tablet", QEMU_ARCH_ALL & ~QEMU_ARCH_S390X }, + { } +}; + +static const char *qdev_class_get_alias(DeviceClass *dc) +{ + const char *typename = object_class_get_name(OBJECT_CLASS(dc)); + int i; + + for (i = 0; qdev_alias_table[i].typename; i++) { + if (qdev_alias_table[i].arch_mask && + !(qdev_alias_table[i].arch_mask & arch_type)) { + continue; + } + + if (strcmp(qdev_alias_table[i].typename, typename) == 0) { + return qdev_alias_table[i].alias; + } + } + + return NULL; +} + +static bool qdev_class_has_alias(DeviceClass *dc) +{ + return (qdev_class_get_alias(dc) != NULL); +} + +static void qdev_print_devinfo(DeviceClass *dc) +{ + qemu_printf("name \"%s\"", object_class_get_name(OBJECT_CLASS(dc))); + if (dc->bus_type) { + qemu_printf(", bus %s", dc->bus_type); + } + if (qdev_class_has_alias(dc)) { + qemu_printf(", alias \"%s\"", qdev_class_get_alias(dc)); + } + if (dc->desc) { + qemu_printf(", desc \"%s\"", dc->desc); + } + if (!dc->user_creatable) { + qemu_printf(", no-user"); + } + qemu_printf("\n"); +} + +static void qdev_print_devinfos(bool show_no_user) +{ + static const char *cat_name[DEVICE_CATEGORY_MAX + 1] = { + [DEVICE_CATEGORY_BRIDGE] = "Controller/Bridge/Hub", + [DEVICE_CATEGORY_USB] = "USB", + [DEVICE_CATEGORY_STORAGE] = "Storage", + [DEVICE_CATEGORY_NETWORK] = "Network", + [DEVICE_CATEGORY_INPUT] = "Input", + [DEVICE_CATEGORY_DISPLAY] = "Display", + [DEVICE_CATEGORY_SOUND] = "Sound", + [DEVICE_CATEGORY_MISC] = "Misc", + [DEVICE_CATEGORY_CPU] = "CPU", + [DEVICE_CATEGORY_MAX] = "Uncategorized", + }; + GSList *list, *elt; + int i; + bool cat_printed; + + module_load_qom_all(); + list = object_class_get_list_sorted(TYPE_DEVICE, false); + + for (i = 0; i <= DEVICE_CATEGORY_MAX; i++) { + cat_printed = false; + for (elt = list; elt; elt = elt->next) { + DeviceClass *dc = OBJECT_CLASS_CHECK(DeviceClass, elt->data, + TYPE_DEVICE); + if ((i < DEVICE_CATEGORY_MAX + ? !test_bit(i, dc->categories) + : !bitmap_empty(dc->categories, DEVICE_CATEGORY_MAX)) + || (!show_no_user + && !dc->user_creatable)) { + continue; + } + if (!cat_printed) { + qemu_printf("%s%s devices:\n", i ? "\n" : "", cat_name[i]); + cat_printed = true; + } + qdev_print_devinfo(dc); + } + } + + g_slist_free(list); +} + +static int set_property(void *opaque, const char *name, const char *value, + Error **errp) +{ + Object *obj = opaque; + + if (strcmp(name, "driver") == 0) + return 0; + if (strcmp(name, "bus") == 0) + return 0; + + if (!object_property_parse(obj, name, value, errp)) { + return -1; + } + return 0; +} + +static const char *find_typename_by_alias(const char *alias) +{ + int i; + + for (i = 0; qdev_alias_table[i].alias; i++) { + if (qdev_alias_table[i].arch_mask && + !(qdev_alias_table[i].arch_mask & arch_type)) { + continue; + } + + if (strcmp(qdev_alias_table[i].alias, alias) == 0) { + return qdev_alias_table[i].typename; + } + } + + return NULL; +} + +static DeviceClass *qdev_get_device_class(const char **driver, Error **errp) +{ + ObjectClass *oc; + DeviceClass *dc; + const char *original_name = *driver; + + oc = module_object_class_by_name(*driver); + if (!oc) { + const char *typename = find_typename_by_alias(*driver); + + if (typename) { + *driver = typename; + oc = module_object_class_by_name(*driver); + } + } + + if (!object_class_dynamic_cast(oc, TYPE_DEVICE)) { + if (*driver != original_name) { + error_setg(errp, "'%s' (alias '%s') is not a valid device model" + " name", original_name, *driver); + } else { + error_setg(errp, "'%s' is not a valid device model name", *driver); + } + return NULL; + } + + if (object_class_is_abstract(oc)) { + error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "driver", + "non-abstract device type"); + return NULL; + } + + dc = DEVICE_CLASS(oc); + if (!dc->user_creatable || + (qdev_hotplug && !dc->hotpluggable)) { + error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "driver", + "pluggable device type"); + return NULL; + } + + return dc; +} + + +int qdev_device_help(QemuOpts *opts) +{ + Error *local_err = NULL; + const char *driver; + ObjectPropertyInfoList *prop_list; + ObjectPropertyInfoList *prop; + GPtrArray *array; + int i; + + driver = qemu_opt_get(opts, "driver"); + if (driver && is_help_option(driver)) { + qdev_print_devinfos(false); + return 1; + } + + if (!driver || !qemu_opt_has_help_opt(opts)) { + return 0; + } + + if (!object_class_by_name(driver)) { + const char *typename = find_typename_by_alias(driver); + + if (typename) { + driver = typename; + } + } + + prop_list = qmp_device_list_properties(driver, &local_err); + if (local_err) { + goto error; + } + + if (prop_list) { + qemu_printf("%s options:\n", driver); + } else { + qemu_printf("There are no options for %s.\n", driver); + } + array = g_ptr_array_new(); + for (prop = prop_list; prop; prop = prop->next) { + g_ptr_array_add(array, + object_property_help(prop->value->name, + prop->value->type, + prop->value->default_value, + prop->value->description)); + } + g_ptr_array_sort(array, (GCompareFunc)qemu_pstrcmp0); + for (i = 0; i < array->len; i++) { + qemu_printf("%s\n", (char *)array->pdata[i]); + } + g_ptr_array_set_free_func(array, g_free); + g_ptr_array_free(array, true); + qapi_free_ObjectPropertyInfoList(prop_list); + return 1; + +error: + error_report_err(local_err); + return 1; +} + +static Object *qdev_get_peripheral(void) +{ + static Object *dev; + + if (dev == NULL) { + dev = container_get(qdev_get_machine(), "/peripheral"); + } + + return dev; +} + +static Object *qdev_get_peripheral_anon(void) +{ + static Object *dev; + + if (dev == NULL) { + dev = container_get(qdev_get_machine(), "/peripheral-anon"); + } + + return dev; +} + +static void qbus_error_append_bus_list_hint(DeviceState *dev, + Error *const *errp) +{ + BusState *child; + const char *sep = " "; + + error_append_hint(errp, "child buses at \"%s\":", + dev->id ? dev->id : object_get_typename(OBJECT(dev))); + QLIST_FOREACH(child, &dev->child_bus, sibling) { + error_append_hint(errp, "%s\"%s\"", sep, child->name); + sep = ", "; + } + error_append_hint(errp, "\n"); +} + +static void qbus_error_append_dev_list_hint(BusState *bus, + Error *const *errp) +{ + BusChild *kid; + const char *sep = " "; + + error_append_hint(errp, "devices at \"%s\":", bus->name); + QTAILQ_FOREACH(kid, &bus->children, sibling) { + DeviceState *dev = kid->child; + error_append_hint(errp, "%s\"%s\"", sep, + object_get_typename(OBJECT(dev))); + if (dev->id) { + error_append_hint(errp, "/\"%s\"", dev->id); + } + sep = ", "; + } + error_append_hint(errp, "\n"); +} + +static BusState *qbus_find_bus(DeviceState *dev, char *elem) +{ + BusState *child; + + QLIST_FOREACH(child, &dev->child_bus, sibling) { + if (strcmp(child->name, elem) == 0) { + return child; + } + } + return NULL; +} + +static DeviceState *qbus_find_dev(BusState *bus, char *elem) +{ + BusChild *kid; + + /* + * try to match in order: + * (1) instance id, if present + * (2) driver name + * (3) driver alias, if present + */ + QTAILQ_FOREACH(kid, &bus->children, sibling) { + DeviceState *dev = kid->child; + if (dev->id && strcmp(dev->id, elem) == 0) { + return dev; + } + } + QTAILQ_FOREACH(kid, &bus->children, sibling) { + DeviceState *dev = kid->child; + if (strcmp(object_get_typename(OBJECT(dev)), elem) == 0) { + return dev; + } + } + QTAILQ_FOREACH(kid, &bus->children, sibling) { + DeviceState *dev = kid->child; + DeviceClass *dc = DEVICE_GET_CLASS(dev); + + if (qdev_class_has_alias(dc) && + strcmp(qdev_class_get_alias(dc), elem) == 0) { + return dev; + } + } + return NULL; +} + +static inline bool qbus_is_full(BusState *bus) +{ + BusClass *bus_class = BUS_GET_CLASS(bus); + return bus_class->max_dev && bus->num_children >= bus_class->max_dev; +} + +/* + * Search the tree rooted at @bus for a bus. + * If @name, search for a bus with that name. Note that bus names + * need not be unique. Yes, that's screwed up. + * Else search for a bus that is a subtype of @bus_typename. + * If more than one exists, prefer one that can take another device. + * Return the bus if found, else %NULL. + */ +static BusState *qbus_find_recursive(BusState *bus, const char *name, + const char *bus_typename) +{ + BusChild *kid; + BusState *pick, *child, *ret; + bool match; + + assert(name || bus_typename); + if (name) { + match = !strcmp(bus->name, name); + } else { + match = !!object_dynamic_cast(OBJECT(bus), bus_typename); + } + + if (match && !qbus_is_full(bus)) { + return bus; /* root matches and isn't full */ + } + + pick = match ? bus : NULL; + + QTAILQ_FOREACH(kid, &bus->children, sibling) { + DeviceState *dev = kid->child; + QLIST_FOREACH(child, &dev->child_bus, sibling) { + ret = qbus_find_recursive(child, name, bus_typename); + if (ret && !qbus_is_full(ret)) { + return ret; /* a descendant matches and isn't full */ + } + if (ret && !pick) { + pick = ret; + } + } + } + + /* root or a descendant matches, but is full */ + return pick; +} + +static BusState *qbus_find(const char *path, Error **errp) +{ + DeviceState *dev; + BusState *bus; + char elem[128]; + int pos, len; + + /* find start element */ + if (path[0] == '/') { + bus = sysbus_get_default(); + pos = 0; + } else { + if (sscanf(path, "%127[^/]%n", elem, &len) != 1) { + assert(!path[0]); + elem[0] = len = 0; + } + bus = qbus_find_recursive(sysbus_get_default(), elem, NULL); + if (!bus) { + error_setg(errp, "Bus '%s' not found", elem); + return NULL; + } + pos = len; + } + + for (;;) { + assert(path[pos] == '/' || !path[pos]); + while (path[pos] == '/') { + pos++; + } + if (path[pos] == '\0') { + break; + } + + /* find device */ + if (sscanf(path+pos, "%127[^/]%n", elem, &len) != 1) { + g_assert_not_reached(); + elem[0] = len = 0; + } + pos += len; + dev = qbus_find_dev(bus, elem); + if (!dev) { + error_set(errp, ERROR_CLASS_DEVICE_NOT_FOUND, + "Device '%s' not found", elem); + qbus_error_append_dev_list_hint(bus, errp); + return NULL; + } + + assert(path[pos] == '/' || !path[pos]); + while (path[pos] == '/') { + pos++; + } + if (path[pos] == '\0') { + /* last specified element is a device. If it has exactly + * one child bus accept it nevertheless */ + if (dev->num_child_bus == 1) { + bus = QLIST_FIRST(&dev->child_bus); + break; + } + if (dev->num_child_bus) { + error_setg(errp, "Device '%s' has multiple child buses", + elem); + qbus_error_append_bus_list_hint(dev, errp); + } else { + error_setg(errp, "Device '%s' has no child bus", elem); + } + return NULL; + } + + /* find bus */ + if (sscanf(path+pos, "%127[^/]%n", elem, &len) != 1) { + g_assert_not_reached(); + elem[0] = len = 0; + } + pos += len; + bus = qbus_find_bus(dev, elem); + if (!bus) { + error_setg(errp, "Bus '%s' not found", elem); + qbus_error_append_bus_list_hint(dev, errp); + return NULL; + } + } + + if (qbus_is_full(bus)) { + error_setg(errp, "Bus '%s' is full", path); + return NULL; + } + return bus; +} + +void qdev_set_id(DeviceState *dev, const char *id) +{ + if (id) { + dev->id = id; + } + + if (dev->id) { + object_property_add_child(qdev_get_peripheral(), dev->id, + OBJECT(dev)); + } else { + static int anon_count; + gchar *name = g_strdup_printf("device[%d]", anon_count++); + object_property_add_child(qdev_get_peripheral_anon(), name, + OBJECT(dev)); + g_free(name); + } +} + +static int is_failover_device(void *opaque, const char *name, const char *value, + Error **errp) +{ + if (strcmp(name, "failover_pair_id") == 0) { + QemuOpts *opts = (QemuOpts *)opaque; + + if (qdev_should_hide_device(opts)) { + return 1; + } + } + + return 0; +} + +static bool should_hide_device(QemuOpts *opts) +{ + if (qemu_opt_foreach(opts, is_failover_device, opts, NULL) == 0) { + return false; + } + return true; +} + +DeviceState *qdev_device_add(QemuOpts *opts, Error **errp) +{ + DeviceClass *dc; + const char *driver, *path; + DeviceState *dev = NULL; + BusState *bus = NULL; + bool hide; + + driver = qemu_opt_get(opts, "driver"); + if (!driver) { + error_setg(errp, QERR_MISSING_PARAMETER, "driver"); + return NULL; + } + + /* find driver */ + dc = qdev_get_device_class(&driver, errp); + if (!dc) { + return NULL; + } + + /* find bus */ + path = qemu_opt_get(opts, "bus"); + if (path != NULL) { + bus = qbus_find(path, errp); + if (!bus) { + return NULL; + } + if (!object_dynamic_cast(OBJECT(bus), dc->bus_type)) { + error_setg(errp, "Device '%s' can't go on %s bus", + driver, object_get_typename(OBJECT(bus))); + return NULL; + } + } else if (dc->bus_type != NULL) { + bus = qbus_find_recursive(sysbus_get_default(), NULL, dc->bus_type); + if (!bus || qbus_is_full(bus)) { + error_setg(errp, "No '%s' bus found for device '%s'", + dc->bus_type, driver); + return NULL; + } + } + hide = should_hide_device(opts); + + if ((hide || qdev_hotplug) && bus && !qbus_is_hotpluggable(bus)) { + error_setg(errp, QERR_BUS_NO_HOTPLUG, bus->name); + return NULL; + } + + if (hide) { + return NULL; + } + + if (!migration_is_idle()) { + error_setg(errp, "device_add not allowed while migrating"); + return NULL; + } + + /* create device */ + dev = qdev_new(driver); + + /* Check whether the hotplug is allowed by the machine */ + if (qdev_hotplug && !qdev_hotplug_allowed(dev, errp)) { + goto err_del_dev; + } + + if (!bus && qdev_hotplug && !qdev_get_machine_hotplug_handler(dev)) { + /* No bus, no machine hotplug handler --> device is not hotpluggable */ + error_setg(errp, "Device '%s' can not be hotplugged on this machine", + driver); + goto err_del_dev; + } + + qdev_set_id(dev, qemu_opts_id(opts)); + + /* set properties */ + if (qemu_opt_foreach(opts, set_property, dev, errp)) { + goto err_del_dev; + } + + dev->opts = opts; + if (!qdev_realize(DEVICE(dev), bus, errp)) { + dev->opts = NULL; + goto err_del_dev; + } + return dev; + +err_del_dev: + if (dev) { + object_unparent(OBJECT(dev)); + object_unref(OBJECT(dev)); + } + return NULL; +} + + +#define qdev_printf(fmt, ...) monitor_printf(mon, "%*s" fmt, indent, "", ## __VA_ARGS__) +static void qbus_print(Monitor *mon, BusState *bus, int indent); + +static void qdev_print_props(Monitor *mon, DeviceState *dev, Property *props, + int indent) +{ + if (!props) + return; + for (; props->name; props++) { + char *value; + char *legacy_name = g_strdup_printf("legacy-%s", props->name); + + if (object_property_get_type(OBJECT(dev), legacy_name, NULL)) { + value = object_property_get_str(OBJECT(dev), legacy_name, NULL); + } else { + value = object_property_print(OBJECT(dev), props->name, true, + NULL); + } + g_free(legacy_name); + + if (!value) { + continue; + } + qdev_printf("%s = %s\n", props->name, + *value ? value : ""); + g_free(value); + } +} + +static void bus_print_dev(BusState *bus, Monitor *mon, DeviceState *dev, int indent) +{ + BusClass *bc = BUS_GET_CLASS(bus); + + if (bc->print_dev) { + bc->print_dev(mon, dev, indent); + } +} + +static void qdev_print(Monitor *mon, DeviceState *dev, int indent) +{ + ObjectClass *class; + BusState *child; + NamedGPIOList *ngl; + NamedClockList *ncl; + + qdev_printf("dev: %s, id \"%s\"\n", object_get_typename(OBJECT(dev)), + dev->id ? dev->id : ""); + indent += 2; + QLIST_FOREACH(ngl, &dev->gpios, node) { + if (ngl->num_in) { + qdev_printf("gpio-in \"%s\" %d\n", ngl->name ? ngl->name : "", + ngl->num_in); + } + if (ngl->num_out) { + qdev_printf("gpio-out \"%s\" %d\n", ngl->name ? ngl->name : "", + ngl->num_out); + } + } + QLIST_FOREACH(ncl, &dev->clocks, node) { + qdev_printf("clock-%s%s \"%s\" freq_hz=%e\n", + ncl->output ? "out" : "in", + ncl->alias ? " (alias)" : "", + ncl->name, + CLOCK_PERIOD_TO_HZ(1.0 * clock_get(ncl->clock))); + } + class = object_get_class(OBJECT(dev)); + do { + qdev_print_props(mon, dev, DEVICE_CLASS(class)->props_, indent); + class = object_class_get_parent(class); + } while (class != object_class_by_name(TYPE_DEVICE)); + bus_print_dev(dev->parent_bus, mon, dev, indent); + QLIST_FOREACH(child, &dev->child_bus, sibling) { + qbus_print(mon, child, indent); + } +} + +static void qbus_print(Monitor *mon, BusState *bus, int indent) +{ + BusChild *kid; + + qdev_printf("bus: %s\n", bus->name); + indent += 2; + qdev_printf("type %s\n", object_get_typename(OBJECT(bus))); + QTAILQ_FOREACH(kid, &bus->children, sibling) { + DeviceState *dev = kid->child; + qdev_print(mon, dev, indent); + } +} +#undef qdev_printf + +void hmp_info_qtree(Monitor *mon, const QDict *qdict) +{ + if (sysbus_get_default()) + qbus_print(mon, sysbus_get_default(), 0); +} + +void hmp_info_qdm(Monitor *mon, const QDict *qdict) +{ + qdev_print_devinfos(true); +} + +void qmp_device_add(QDict *qdict, QObject **ret_data, Error **errp) +{ + QemuOpts *opts; + DeviceState *dev; + + opts = qemu_opts_from_qdict(qemu_find_opts("device"), qdict, errp); + if (!opts) { + return; + } + if (!monitor_cur_is_qmp() && qdev_device_help(opts)) { + qemu_opts_del(opts); + return; + } + dev = qdev_device_add(opts, errp); + if (!dev) { + qemu_opts_del(opts); + return; + } + object_unref(OBJECT(dev)); +} + +static DeviceState *find_device_state(const char *id, Error **errp) +{ + Object *obj; + + if (id[0] == '/') { + obj = object_resolve_path(id, NULL); + } else { + char *root_path = object_get_canonical_path(qdev_get_peripheral()); + char *path = g_strdup_printf("%s/%s", root_path, id); + + g_free(root_path); + obj = object_resolve_path_type(path, TYPE_DEVICE, NULL); + g_free(path); + } + + if (!obj) { + error_set(errp, ERROR_CLASS_DEVICE_NOT_FOUND, + "Device '%s' not found", id); + return NULL; + } + + if (!object_dynamic_cast(obj, TYPE_DEVICE)) { + error_setg(errp, "%s is not a hotpluggable device", id); + return NULL; + } + + return DEVICE(obj); +} + +void qdev_unplug(DeviceState *dev, Error **errp) +{ + DeviceClass *dc = DEVICE_GET_CLASS(dev); + HotplugHandler *hotplug_ctrl; + HotplugHandlerClass *hdc; + Error *local_err = NULL; + + if (dev->parent_bus && !qbus_is_hotpluggable(dev->parent_bus)) { + error_setg(errp, QERR_BUS_NO_HOTPLUG, dev->parent_bus->name); + return; + } + + if (!dc->hotpluggable) { + error_setg(errp, QERR_DEVICE_NO_HOTPLUG, + object_get_typename(OBJECT(dev))); + return; + } + + if (!migration_is_idle() && !dev->allow_unplug_during_migration) { + error_setg(errp, "device_del not allowed while migrating"); + return; + } + + qdev_hot_removed = true; + + hotplug_ctrl = qdev_get_hotplug_handler(dev); + /* hotpluggable device MUST have HotplugHandler, if it doesn't + * then something is very wrong with it */ + g_assert(hotplug_ctrl); + + /* If device supports async unplug just request it to be done, + * otherwise just remove it synchronously */ + hdc = HOTPLUG_HANDLER_GET_CLASS(hotplug_ctrl); + if (hdc->unplug_request) { + hotplug_handler_unplug_request(hotplug_ctrl, dev, &local_err); + } else { + hotplug_handler_unplug(hotplug_ctrl, dev, &local_err); + if (!local_err) { + object_unparent(OBJECT(dev)); + } + } + error_propagate(errp, local_err); +} + +void qmp_device_del(const char *id, Error **errp) +{ + DeviceState *dev = find_device_state(id, errp); + if (dev != NULL) { + if (dev->pending_deleted_event) { + error_setg(errp, "Device %s is already in the " + "process of unplug", id); + return; + } + + qdev_unplug(dev, errp); + } +} + +void hmp_device_add(Monitor *mon, const QDict *qdict) +{ + Error *err = NULL; + + qmp_device_add((QDict *)qdict, NULL, &err); + hmp_handle_error(mon, err); +} + +void hmp_device_del(Monitor *mon, const QDict *qdict) +{ + const char *id = qdict_get_str(qdict, "id"); + Error *err = NULL; + + qmp_device_del(id, &err); + hmp_handle_error(mon, err); +} + +BlockBackend *blk_by_qdev_id(const char *id, Error **errp) +{ + DeviceState *dev; + BlockBackend *blk; + + dev = find_device_state(id, errp); + if (dev == NULL) { + return NULL; + } + + blk = blk_by_dev(dev); + if (!blk) { + error_setg(errp, "Device does not have a block device backend"); + } + return blk; +} + +void qdev_machine_init(void) +{ + qdev_get_peripheral_anon(); + qdev_get_peripheral(); +} + +QemuOptsList qemu_device_opts = { + .name = "device", + .implied_opt_name = "driver", + .head = QTAILQ_HEAD_INITIALIZER(qemu_device_opts.head), + .desc = { + /* + * no elements => accept any + * sanity checking will happen later + * when setting device properties + */ + { /* end of list */ } + }, +}; + +QemuOptsList qemu_global_opts = { + .name = "global", + .head = QTAILQ_HEAD_INITIALIZER(qemu_global_opts.head), + .desc = { + { + .name = "driver", + .type = QEMU_OPT_STRING, + },{ + .name = "property", + .type = QEMU_OPT_STRING, + },{ + .name = "value", + .type = QEMU_OPT_STRING, + }, + { /* end of list */ } + }, +}; + +int qemu_global_option(const char *str) +{ + char driver[64], property[64]; + QemuOpts *opts; + int rc, offset; + + rc = sscanf(str, "%63[^.=].%63[^=]%n", driver, property, &offset); + if (rc == 2 && str[offset] == '=') { + opts = qemu_opts_create(&qemu_global_opts, NULL, 0, &error_abort); + qemu_opt_set(opts, "driver", driver, &error_abort); + qemu_opt_set(opts, "property", property, &error_abort); + qemu_opt_set(opts, "value", str + offset + 1, &error_abort); + return 0; + } + + opts = qemu_opts_parse_noisily(&qemu_global_opts, str, false); + if (!opts) { + return -1; + } + + return 0; +} diff --git a/softmmu/qemu-seccomp.c b/softmmu/qemu-seccomp.c new file mode 100644 index 0000000000..8325ecb766 --- /dev/null +++ b/softmmu/qemu-seccomp.c @@ -0,0 +1,331 @@ +/* + * QEMU seccomp mode 2 support with libseccomp + * + * Copyright IBM, Corp. 2012 + * + * Authors: + * Eduardo Otubo + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + * + * Contributions after 2012-01-13 are licensed under the terms of the + * GNU GPL, version 2 or (at your option) any later version. + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "qemu/config-file.h" +#include "qemu/option.h" +#include "qemu/module.h" +#include +#include +#include "sysemu/seccomp.h" +#include + +/* For some architectures (notably ARM) cacheflush is not supported until + * libseccomp 2.2.3, but configure enforces that we are using a more recent + * version on those hosts, so it is OK for this check to be less strict. + */ +#if SCMP_VER_MAJOR >= 3 + #define HAVE_CACHEFLUSH +#elif SCMP_VER_MAJOR == 2 && SCMP_VER_MINOR >= 2 + #define HAVE_CACHEFLUSH +#endif + +struct QemuSeccompSyscall { + int32_t num; + uint8_t set; + uint8_t narg; + const struct scmp_arg_cmp *arg_cmp; +}; + +const struct scmp_arg_cmp sched_setscheduler_arg[] = { + /* was SCMP_A1(SCMP_CMP_NE, SCHED_IDLE), but expanded due to GCC 4.x bug */ + { .arg = 1, .op = SCMP_CMP_NE, .datum_a = SCHED_IDLE } +}; + +static const struct QemuSeccompSyscall blacklist[] = { + /* default set of syscalls to blacklist */ + { SCMP_SYS(reboot), QEMU_SECCOMP_SET_DEFAULT }, + { SCMP_SYS(swapon), QEMU_SECCOMP_SET_DEFAULT }, + { SCMP_SYS(swapoff), QEMU_SECCOMP_SET_DEFAULT }, + { SCMP_SYS(syslog), QEMU_SECCOMP_SET_DEFAULT }, + { SCMP_SYS(mount), QEMU_SECCOMP_SET_DEFAULT }, + { SCMP_SYS(umount), QEMU_SECCOMP_SET_DEFAULT }, + { SCMP_SYS(kexec_load), QEMU_SECCOMP_SET_DEFAULT }, + { SCMP_SYS(afs_syscall), QEMU_SECCOMP_SET_DEFAULT }, + { SCMP_SYS(break), QEMU_SECCOMP_SET_DEFAULT }, + { SCMP_SYS(ftime), QEMU_SECCOMP_SET_DEFAULT }, + { SCMP_SYS(getpmsg), QEMU_SECCOMP_SET_DEFAULT }, + { SCMP_SYS(gtty), QEMU_SECCOMP_SET_DEFAULT }, + { SCMP_SYS(lock), QEMU_SECCOMP_SET_DEFAULT }, + { SCMP_SYS(mpx), QEMU_SECCOMP_SET_DEFAULT }, + { SCMP_SYS(prof), QEMU_SECCOMP_SET_DEFAULT }, + { SCMP_SYS(profil), QEMU_SECCOMP_SET_DEFAULT }, + { SCMP_SYS(putpmsg), QEMU_SECCOMP_SET_DEFAULT }, + { SCMP_SYS(security), QEMU_SECCOMP_SET_DEFAULT }, + { SCMP_SYS(stty), QEMU_SECCOMP_SET_DEFAULT }, + { SCMP_SYS(tuxcall), QEMU_SECCOMP_SET_DEFAULT }, + { SCMP_SYS(ulimit), QEMU_SECCOMP_SET_DEFAULT }, + { SCMP_SYS(vserver), QEMU_SECCOMP_SET_DEFAULT }, + /* obsolete */ + { SCMP_SYS(readdir), QEMU_SECCOMP_SET_OBSOLETE }, + { SCMP_SYS(_sysctl), QEMU_SECCOMP_SET_OBSOLETE }, + { SCMP_SYS(bdflush), QEMU_SECCOMP_SET_OBSOLETE }, + { SCMP_SYS(create_module), QEMU_SECCOMP_SET_OBSOLETE }, + { SCMP_SYS(get_kernel_syms), QEMU_SECCOMP_SET_OBSOLETE }, + { SCMP_SYS(query_module), QEMU_SECCOMP_SET_OBSOLETE }, + { SCMP_SYS(sgetmask), QEMU_SECCOMP_SET_OBSOLETE }, + { SCMP_SYS(ssetmask), QEMU_SECCOMP_SET_OBSOLETE }, + { SCMP_SYS(sysfs), QEMU_SECCOMP_SET_OBSOLETE }, + { SCMP_SYS(uselib), QEMU_SECCOMP_SET_OBSOLETE }, + { SCMP_SYS(ustat), QEMU_SECCOMP_SET_OBSOLETE }, + /* privileged */ + { SCMP_SYS(setuid), QEMU_SECCOMP_SET_PRIVILEGED }, + { SCMP_SYS(setgid), QEMU_SECCOMP_SET_PRIVILEGED }, + { SCMP_SYS(setpgid), QEMU_SECCOMP_SET_PRIVILEGED }, + { SCMP_SYS(setsid), QEMU_SECCOMP_SET_PRIVILEGED }, + { SCMP_SYS(setreuid), QEMU_SECCOMP_SET_PRIVILEGED }, + { SCMP_SYS(setregid), QEMU_SECCOMP_SET_PRIVILEGED }, + { SCMP_SYS(setresuid), QEMU_SECCOMP_SET_PRIVILEGED }, + { SCMP_SYS(setresgid), QEMU_SECCOMP_SET_PRIVILEGED }, + { SCMP_SYS(setfsuid), QEMU_SECCOMP_SET_PRIVILEGED }, + { SCMP_SYS(setfsgid), QEMU_SECCOMP_SET_PRIVILEGED }, + /* spawn */ + { SCMP_SYS(fork), QEMU_SECCOMP_SET_SPAWN }, + { SCMP_SYS(vfork), QEMU_SECCOMP_SET_SPAWN }, + { SCMP_SYS(execve), QEMU_SECCOMP_SET_SPAWN }, + /* resource control */ + { SCMP_SYS(getpriority), QEMU_SECCOMP_SET_RESOURCECTL }, + { SCMP_SYS(setpriority), QEMU_SECCOMP_SET_RESOURCECTL }, + { SCMP_SYS(sched_setparam), QEMU_SECCOMP_SET_RESOURCECTL }, + { SCMP_SYS(sched_getparam), QEMU_SECCOMP_SET_RESOURCECTL }, + { SCMP_SYS(sched_setscheduler), QEMU_SECCOMP_SET_RESOURCECTL, + ARRAY_SIZE(sched_setscheduler_arg), sched_setscheduler_arg }, + { SCMP_SYS(sched_getscheduler), QEMU_SECCOMP_SET_RESOURCECTL }, + { SCMP_SYS(sched_setaffinity), QEMU_SECCOMP_SET_RESOURCECTL }, + { SCMP_SYS(sched_getaffinity), QEMU_SECCOMP_SET_RESOURCECTL }, + { SCMP_SYS(sched_get_priority_max), QEMU_SECCOMP_SET_RESOURCECTL }, + { SCMP_SYS(sched_get_priority_min), QEMU_SECCOMP_SET_RESOURCECTL }, +}; + +static inline __attribute__((unused)) int +qemu_seccomp(unsigned int operation, unsigned int flags, void *args) +{ +#ifdef __NR_seccomp + return syscall(__NR_seccomp, operation, flags, args); +#else + errno = ENOSYS; + return -1; +#endif +} + +static uint32_t qemu_seccomp_get_action(int set) +{ + switch (set) { + case QEMU_SECCOMP_SET_DEFAULT: + case QEMU_SECCOMP_SET_OBSOLETE: + case QEMU_SECCOMP_SET_PRIVILEGED: + case QEMU_SECCOMP_SET_SPAWN: { +#if defined(SECCOMP_GET_ACTION_AVAIL) && defined(SCMP_ACT_KILL_PROCESS) && \ + defined(SECCOMP_RET_KILL_PROCESS) + static int kill_process = -1; + if (kill_process == -1) { + uint32_t action = SECCOMP_RET_KILL_PROCESS; + + if (qemu_seccomp(SECCOMP_GET_ACTION_AVAIL, 0, &action) == 0) { + kill_process = 1; + } else { + kill_process = 0; + } + } + if (kill_process == 1) { + return SCMP_ACT_KILL_PROCESS; + } +#endif + return SCMP_ACT_TRAP; + } + + case QEMU_SECCOMP_SET_RESOURCECTL: + return SCMP_ACT_ERRNO(EPERM); + + default: + g_assert_not_reached(); + } +} + + +static int seccomp_start(uint32_t seccomp_opts, Error **errp) +{ + int rc = -1; + unsigned int i = 0; + scmp_filter_ctx ctx; + + ctx = seccomp_init(SCMP_ACT_ALLOW); + if (ctx == NULL) { + error_setg(errp, "failed to initialize seccomp context"); + goto seccomp_return; + } + + rc = seccomp_attr_set(ctx, SCMP_FLTATR_CTL_TSYNC, 1); + if (rc != 0) { + error_setg_errno(errp, -rc, + "failed to set seccomp thread synchronization"); + goto seccomp_return; + } + + for (i = 0; i < ARRAY_SIZE(blacklist); i++) { + uint32_t action; + if (!(seccomp_opts & blacklist[i].set)) { + continue; + } + + action = qemu_seccomp_get_action(blacklist[i].set); + rc = seccomp_rule_add_array(ctx, action, blacklist[i].num, + blacklist[i].narg, blacklist[i].arg_cmp); + if (rc < 0) { + error_setg_errno(errp, -rc, + "failed to add seccomp blacklist rules"); + goto seccomp_return; + } + } + + rc = seccomp_load(ctx); + if (rc < 0) { + error_setg_errno(errp, -rc, + "failed to load seccomp syscall filter in kernel"); + } + + seccomp_return: + seccomp_release(ctx); + return rc < 0 ? -1 : 0; +} + +#ifdef CONFIG_SECCOMP +int parse_sandbox(void *opaque, QemuOpts *opts, Error **errp) +{ + if (qemu_opt_get_bool(opts, "enable", false)) { + uint32_t seccomp_opts = QEMU_SECCOMP_SET_DEFAULT + | QEMU_SECCOMP_SET_OBSOLETE; + const char *value = NULL; + + value = qemu_opt_get(opts, "obsolete"); + if (value) { + if (g_str_equal(value, "allow")) { + seccomp_opts &= ~QEMU_SECCOMP_SET_OBSOLETE; + } else if (g_str_equal(value, "deny")) { + /* this is the default option, this if is here + * to provide a little bit of consistency for + * the command line */ + } else { + error_setg(errp, "invalid argument for obsolete"); + return -1; + } + } + + value = qemu_opt_get(opts, "elevateprivileges"); + if (value) { + if (g_str_equal(value, "deny")) { + seccomp_opts |= QEMU_SECCOMP_SET_PRIVILEGED; + } else if (g_str_equal(value, "children")) { + seccomp_opts |= QEMU_SECCOMP_SET_PRIVILEGED; + + /* calling prctl directly because we're + * not sure if host has CAP_SYS_ADMIN set*/ + if (prctl(PR_SET_NO_NEW_PRIVS, 1)) { + error_setg(errp, "failed to set no_new_privs aborting"); + return -1; + } + } else if (g_str_equal(value, "allow")) { + /* default value */ + } else { + error_setg(errp, "invalid argument for elevateprivileges"); + return -1; + } + } + + value = qemu_opt_get(opts, "spawn"); + if (value) { + if (g_str_equal(value, "deny")) { + seccomp_opts |= QEMU_SECCOMP_SET_SPAWN; + } else if (g_str_equal(value, "allow")) { + /* default value */ + } else { + error_setg(errp, "invalid argument for spawn"); + return -1; + } + } + + value = qemu_opt_get(opts, "resourcecontrol"); + if (value) { + if (g_str_equal(value, "deny")) { + seccomp_opts |= QEMU_SECCOMP_SET_RESOURCECTL; + } else if (g_str_equal(value, "allow")) { + /* default value */ + } else { + error_setg(errp, "invalid argument for resourcecontrol"); + return -1; + } + } + + if (seccomp_start(seccomp_opts, errp) < 0) { + return -1; + } + } + + return 0; +} + +static QemuOptsList qemu_sandbox_opts = { + .name = "sandbox", + .implied_opt_name = "enable", + .head = QTAILQ_HEAD_INITIALIZER(qemu_sandbox_opts.head), + .desc = { + { + .name = "enable", + .type = QEMU_OPT_BOOL, + }, + { + .name = "obsolete", + .type = QEMU_OPT_STRING, + }, + { + .name = "elevateprivileges", + .type = QEMU_OPT_STRING, + }, + { + .name = "spawn", + .type = QEMU_OPT_STRING, + }, + { + .name = "resourcecontrol", + .type = QEMU_OPT_STRING, + }, + { /* end of list */ } + }, +}; + +static void seccomp_register(void) +{ + bool add = false; + + /* FIXME: use seccomp_api_get() >= 2 check when released */ + +#if defined(SECCOMP_FILTER_FLAG_TSYNC) + int check; + + /* check host TSYNC capability, it returns errno == ENOSYS if unavailable */ + check = qemu_seccomp(SECCOMP_SET_MODE_FILTER, + SECCOMP_FILTER_FLAG_TSYNC, NULL); + if (check < 0 && errno == EFAULT) { + add = true; + } +#endif + + if (add) { + qemu_add_opts(&qemu_sandbox_opts); + } +} +opts_init(seccomp_register); +#endif diff --git a/softmmu/tpm.c b/softmmu/tpm.c new file mode 100644 index 0000000000..cab206355a --- /dev/null +++ b/softmmu/tpm.c @@ -0,0 +1,265 @@ +/* + * TPM configuration + * + * Copyright (C) 2011-2013 IBM Corporation + * + * Authors: + * Stefan Berger + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + * Based on net.c + */ + +#include "qemu/osdep.h" + +#include "qapi/error.h" +#include "qapi/qapi-commands-tpm.h" +#include "qapi/qmp/qerror.h" +#include "sysemu/tpm_backend.h" +#include "sysemu/tpm.h" +#include "qemu/config-file.h" +#include "qemu/error-report.h" + +static QLIST_HEAD(, TPMBackend) tpm_backends = + QLIST_HEAD_INITIALIZER(tpm_backends); + +static const TPMBackendClass * +tpm_be_find_by_type(enum TpmType type) +{ + ObjectClass *oc; + char *typename = g_strdup_printf("tpm-%s", TpmType_str(type)); + + oc = object_class_by_name(typename); + g_free(typename); + + if (!object_class_dynamic_cast(oc, TYPE_TPM_BACKEND)) { + return NULL; + } + + return TPM_BACKEND_CLASS(oc); +} + +/* + * Walk the list of available TPM backend drivers and display them on the + * screen. + */ +static void tpm_display_backend_drivers(void) +{ + bool got_one = false; + int i; + + for (i = 0; i < TPM_TYPE__MAX; i++) { + const TPMBackendClass *bc = tpm_be_find_by_type(i); + if (!bc) { + continue; + } + if (!got_one) { + error_printf("Supported TPM types (choose only one):\n"); + got_one = true; + } + error_printf("%12s %s\n", TpmType_str(i), bc->desc); + } + if (!got_one) { + error_printf("No TPM backend types are available\n"); + } +} + +/* + * Find the TPM with the given Id + */ +TPMBackend *qemu_find_tpm_be(const char *id) +{ + TPMBackend *drv; + + if (id) { + QLIST_FOREACH(drv, &tpm_backends, list) { + if (!strcmp(drv->id, id)) { + return drv; + } + } + } + + return NULL; +} + +static int tpm_init_tpmdev(void *dummy, QemuOpts *opts, Error **errp) +{ + /* + * Use of error_report() in a function with an Error ** parameter + * is suspicious. It is okay here. The parameter only exists to + * make the function usable with qemu_opts_foreach(). It is not + * actually used. + */ + const char *value; + const char *id; + const TPMBackendClass *be; + TPMBackend *drv; + Error *local_err = NULL; + int i; + + if (!QLIST_EMPTY(&tpm_backends)) { + error_report("Only one TPM is allowed."); + return 1; + } + + id = qemu_opts_id(opts); + if (id == NULL) { + error_report(QERR_MISSING_PARAMETER, "id"); + return 1; + } + + value = qemu_opt_get(opts, "type"); + if (!value) { + error_report(QERR_MISSING_PARAMETER, "type"); + tpm_display_backend_drivers(); + return 1; + } + + i = qapi_enum_parse(&TpmType_lookup, value, -1, NULL); + be = i >= 0 ? tpm_be_find_by_type(i) : NULL; + if (be == NULL) { + error_report(QERR_INVALID_PARAMETER_VALUE, + "type", "a TPM backend type"); + tpm_display_backend_drivers(); + return 1; + } + + /* validate backend specific opts */ + if (!qemu_opts_validate(opts, be->opts, &local_err)) { + error_report_err(local_err); + return 1; + } + + drv = be->create(opts); + if (!drv) { + return 1; + } + + drv->id = g_strdup(id); + QLIST_INSERT_HEAD(&tpm_backends, drv, list); + + return 0; +} + +/* + * Walk the list of TPM backend drivers that are in use and call their + * destroy function to have them cleaned up. + */ +void tpm_cleanup(void) +{ + TPMBackend *drv, *next; + + QLIST_FOREACH_SAFE(drv, &tpm_backends, list, next) { + QLIST_REMOVE(drv, list); + object_unref(OBJECT(drv)); + } +} + +/* + * Initialize the TPM. Process the tpmdev command line options describing the + * TPM backend. + */ +int tpm_init(void) +{ + if (qemu_opts_foreach(qemu_find_opts("tpmdev"), + tpm_init_tpmdev, NULL, NULL)) { + return -1; + } + + return 0; +} + +/* + * Parse the TPM configuration options. + * To display all available TPM backends the user may use '-tpmdev help' + */ +int tpm_config_parse(QemuOptsList *opts_list, const char *optarg) +{ + QemuOpts *opts; + + if (!strcmp(optarg, "help")) { + tpm_display_backend_drivers(); + return -1; + } + opts = qemu_opts_parse_noisily(opts_list, optarg, true); + if (!opts) { + return -1; + } + return 0; +} + +/* + * Walk the list of active TPM backends and collect information about them. + */ +TPMInfoList *qmp_query_tpm(Error **errp) +{ + TPMBackend *drv; + TPMInfoList *info, *head = NULL, *cur_item = NULL; + + QLIST_FOREACH(drv, &tpm_backends, list) { + if (!drv->tpmif) { + continue; + } + + info = g_new0(TPMInfoList, 1); + info->value = tpm_backend_query_tpm(drv); + + if (!cur_item) { + head = cur_item = info; + } else { + cur_item->next = info; + cur_item = info; + } + } + + return head; +} + +TpmTypeList *qmp_query_tpm_types(Error **errp) +{ + unsigned int i = 0; + TpmTypeList *head = NULL, *prev = NULL, *cur_item; + + for (i = 0; i < TPM_TYPE__MAX; i++) { + if (!tpm_be_find_by_type(i)) { + continue; + } + cur_item = g_new0(TpmTypeList, 1); + cur_item->value = i; + + if (prev) { + prev->next = cur_item; + } + if (!head) { + head = cur_item; + } + prev = cur_item; + } + + return head; +} +TpmModelList *qmp_query_tpm_models(Error **errp) +{ + TpmModelList *head = NULL, *prev = NULL, *cur_item; + GSList *e, *l = object_class_get_list(TYPE_TPM_IF, false); + + for (e = l; e; e = e->next) { + TPMIfClass *c = TPM_IF_CLASS(e->data); + + cur_item = g_new0(TpmModelList, 1); + cur_item->value = c->model; + + if (prev) { + prev->next = cur_item; + } + if (!head) { + head = cur_item; + } + prev = cur_item; + } + g_slist_free(l); + + return head; +} diff --git a/tpm.c b/tpm.c deleted file mode 100644 index cab206355a..0000000000 --- a/tpm.c +++ /dev/null @@ -1,265 +0,0 @@ -/* - * TPM configuration - * - * Copyright (C) 2011-2013 IBM Corporation - * - * Authors: - * Stefan Berger - * - * This work is licensed under the terms of the GNU GPL, version 2 or later. - * See the COPYING file in the top-level directory. - * - * Based on net.c - */ - -#include "qemu/osdep.h" - -#include "qapi/error.h" -#include "qapi/qapi-commands-tpm.h" -#include "qapi/qmp/qerror.h" -#include "sysemu/tpm_backend.h" -#include "sysemu/tpm.h" -#include "qemu/config-file.h" -#include "qemu/error-report.h" - -static QLIST_HEAD(, TPMBackend) tpm_backends = - QLIST_HEAD_INITIALIZER(tpm_backends); - -static const TPMBackendClass * -tpm_be_find_by_type(enum TpmType type) -{ - ObjectClass *oc; - char *typename = g_strdup_printf("tpm-%s", TpmType_str(type)); - - oc = object_class_by_name(typename); - g_free(typename); - - if (!object_class_dynamic_cast(oc, TYPE_TPM_BACKEND)) { - return NULL; - } - - return TPM_BACKEND_CLASS(oc); -} - -/* - * Walk the list of available TPM backend drivers and display them on the - * screen. - */ -static void tpm_display_backend_drivers(void) -{ - bool got_one = false; - int i; - - for (i = 0; i < TPM_TYPE__MAX; i++) { - const TPMBackendClass *bc = tpm_be_find_by_type(i); - if (!bc) { - continue; - } - if (!got_one) { - error_printf("Supported TPM types (choose only one):\n"); - got_one = true; - } - error_printf("%12s %s\n", TpmType_str(i), bc->desc); - } - if (!got_one) { - error_printf("No TPM backend types are available\n"); - } -} - -/* - * Find the TPM with the given Id - */ -TPMBackend *qemu_find_tpm_be(const char *id) -{ - TPMBackend *drv; - - if (id) { - QLIST_FOREACH(drv, &tpm_backends, list) { - if (!strcmp(drv->id, id)) { - return drv; - } - } - } - - return NULL; -} - -static int tpm_init_tpmdev(void *dummy, QemuOpts *opts, Error **errp) -{ - /* - * Use of error_report() in a function with an Error ** parameter - * is suspicious. It is okay here. The parameter only exists to - * make the function usable with qemu_opts_foreach(). It is not - * actually used. - */ - const char *value; - const char *id; - const TPMBackendClass *be; - TPMBackend *drv; - Error *local_err = NULL; - int i; - - if (!QLIST_EMPTY(&tpm_backends)) { - error_report("Only one TPM is allowed."); - return 1; - } - - id = qemu_opts_id(opts); - if (id == NULL) { - error_report(QERR_MISSING_PARAMETER, "id"); - return 1; - } - - value = qemu_opt_get(opts, "type"); - if (!value) { - error_report(QERR_MISSING_PARAMETER, "type"); - tpm_display_backend_drivers(); - return 1; - } - - i = qapi_enum_parse(&TpmType_lookup, value, -1, NULL); - be = i >= 0 ? tpm_be_find_by_type(i) : NULL; - if (be == NULL) { - error_report(QERR_INVALID_PARAMETER_VALUE, - "type", "a TPM backend type"); - tpm_display_backend_drivers(); - return 1; - } - - /* validate backend specific opts */ - if (!qemu_opts_validate(opts, be->opts, &local_err)) { - error_report_err(local_err); - return 1; - } - - drv = be->create(opts); - if (!drv) { - return 1; - } - - drv->id = g_strdup(id); - QLIST_INSERT_HEAD(&tpm_backends, drv, list); - - return 0; -} - -/* - * Walk the list of TPM backend drivers that are in use and call their - * destroy function to have them cleaned up. - */ -void tpm_cleanup(void) -{ - TPMBackend *drv, *next; - - QLIST_FOREACH_SAFE(drv, &tpm_backends, list, next) { - QLIST_REMOVE(drv, list); - object_unref(OBJECT(drv)); - } -} - -/* - * Initialize the TPM. Process the tpmdev command line options describing the - * TPM backend. - */ -int tpm_init(void) -{ - if (qemu_opts_foreach(qemu_find_opts("tpmdev"), - tpm_init_tpmdev, NULL, NULL)) { - return -1; - } - - return 0; -} - -/* - * Parse the TPM configuration options. - * To display all available TPM backends the user may use '-tpmdev help' - */ -int tpm_config_parse(QemuOptsList *opts_list, const char *optarg) -{ - QemuOpts *opts; - - if (!strcmp(optarg, "help")) { - tpm_display_backend_drivers(); - return -1; - } - opts = qemu_opts_parse_noisily(opts_list, optarg, true); - if (!opts) { - return -1; - } - return 0; -} - -/* - * Walk the list of active TPM backends and collect information about them. - */ -TPMInfoList *qmp_query_tpm(Error **errp) -{ - TPMBackend *drv; - TPMInfoList *info, *head = NULL, *cur_item = NULL; - - QLIST_FOREACH(drv, &tpm_backends, list) { - if (!drv->tpmif) { - continue; - } - - info = g_new0(TPMInfoList, 1); - info->value = tpm_backend_query_tpm(drv); - - if (!cur_item) { - head = cur_item = info; - } else { - cur_item->next = info; - cur_item = info; - } - } - - return head; -} - -TpmTypeList *qmp_query_tpm_types(Error **errp) -{ - unsigned int i = 0; - TpmTypeList *head = NULL, *prev = NULL, *cur_item; - - for (i = 0; i < TPM_TYPE__MAX; i++) { - if (!tpm_be_find_by_type(i)) { - continue; - } - cur_item = g_new0(TpmTypeList, 1); - cur_item->value = i; - - if (prev) { - prev->next = cur_item; - } - if (!head) { - head = cur_item; - } - prev = cur_item; - } - - return head; -} -TpmModelList *qmp_query_tpm_models(Error **errp) -{ - TpmModelList *head = NULL, *prev = NULL, *cur_item; - GSList *e, *l = object_class_get_list(TYPE_TPM_IF, false); - - for (e = l; e; e = e->next) { - TPMIfClass *c = TPM_IF_CLASS(e->data); - - cur_item = g_new0(TpmModelList, 1); - cur_item->value = c->model; - - if (prev) { - prev->next = cur_item; - } - if (!head) { - head = cur_item; - } - prev = cur_item; - } - g_slist_free(l); - - return head; -}