| /* |
| * 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; |
| |
| visit_type_int32(v, name, &boot_index, &local_err); |
| if (local_err) { |
| goto out; |
| } |
| /* check whether bootindex is present in fw_boot_order list */ |
| check_boot_index(boot_index, &local_err); |
| if (local_err) { |
| goto out; |
| } |
| /* change bootindex to a new one */ |
| *prop->bootindex = boot_index; |
| |
| add_boot_device_path(*prop->bootindex, prop->dev, prop->suffix); |
| |
| out: |
| error_propagate(errp, local_err); |
| } |
| |
| 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, Error **errp) |
| { |
| Error *local_err = NULL; |
| 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, &local_err); |
| |
| if (local_err) { |
| error_propagate(errp, local_err); |
| g_free(prop); |
| return; |
| } |
| /* initialize devices' bootindex property to -1 */ |
| object_property_set_int(obj, -1, name, 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; |
| } |