diff options
author | Paolo Bonzini <pbonzini@redhat.com> | 2020-10-06 09:01:22 +0200 |
---|---|---|
committer | Paolo Bonzini <pbonzini@redhat.com> | 2020-10-12 11:50:21 -0400 |
commit | 800d4deda04be016a95fbbf397c830a2d14ff9f6 (patch) | |
tree | 3079145f9804bb8c96deb3640fa51c5680ccae2a /softmmu | |
parent | 8d0bceba24cc0917d9c6c5252d78db3d9fd5c58d (diff) |
softmmu: move more files to softmmu/
Keep most softmmu_ss files into the system-emulation-specific
directory.
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
Diffstat (limited to 'softmmu')
-rw-r--r-- | softmmu/bootdevice.c | 429 | ||||
-rw-r--r-- | softmmu/device_tree.c | 579 | ||||
-rw-r--r-- | softmmu/dma-helpers.c | 331 | ||||
-rw-r--r-- | softmmu/meson.build | 10 | ||||
-rw-r--r-- | softmmu/qdev-monitor.c | 993 | ||||
-rw-r--r-- | softmmu/qemu-seccomp.c | 331 | ||||
-rw-r--r-- | softmmu/tpm.c | 265 |
7 files changed, 2938 insertions, 0 deletions
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 <jyoung5@us.ibm.com> + * Hollis Blanchard <hollisb@us.ibm.com> + * + * This work is licensed under the GNU GPL license version 2 or later. + * + */ + +#include "qemu/osdep.h" + +#ifdef CONFIG_LINUX +#include <dirent.h> +#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 <libfdt.h> + +#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 <http://www.gnu.org/licenses/>. + */ + +#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 : "<null>"); + 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 <eotubo@br.ibm.com> + * + * 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 <sys/prctl.h> +#include <seccomp.h> +#include "sysemu/seccomp.h" +#include <linux/seccomp.h> + +/* 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 <stefanb@us.ibm.com> + * + * 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; +} |