|  | /* | 
|  | * 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 bootcount; | 
|  |  | 
|  | switch (bootcount++) { | 
|  | case 0: | 
|  | /* First boot: use the one-time config */ | 
|  | return; | 
|  | case 1: | 
|  | /* Second boot: restore normal boot order */ | 
|  | if (boot_set_handler) { | 
|  | qemu_boot_set(normal_boot_order, &error_abort); | 
|  | } | 
|  | g_free(normal_boot_order); | 
|  | return; | 
|  | default: | 
|  | /* Subsequent boots: keep using normal boot order */ | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | 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_new0(FWBootEntry, 1); | 
|  | 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 (current_machine->boot_config.has_strict && | 
|  | current_machine->boot_config.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_new0(FWLCHSEntry, 1); | 
|  | 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; | 
|  | } |