Commit 8ffb1bcf authored by Gerd Hoffmann's avatar Gerd Hoffmann Committed by Anthony Liguori
Browse files

qdev: bus walker + qdev_device_add()



This patch implements a parser and qdev tree walker for bus paths and
adds qdev_device_add on top of this.

A bus path can be:
  (1) full path, i.e. /i440FX-pcihost/pci.0/lsi/scsi.0
  (2) bus name, i.e. "scsi.0".  Best used together with id= to make
      sure this is unique.
  (3) relative path starting with a bus name, i.e. "pci.0/lsi/scsi.0"

For the (common) case of a single child bus being attached to a device
it is enougth to specify the device only, i.e. "pci.0/lsi" will be
accepted too.

qdev_device_add() adds devices and accepts bus= parameters to find the
bus the device should be attached to.  Without bus= being specified it
takes the first bus it finds where the device can be attached to (i.e.
first pci bus for pci devices, ...).

Signed-off-by: default avatarGerd Hoffmann <kraxel@redhat.com>
Signed-off-by: default avatarAnthony Liguori <aliguori@us.ibm.com>
parent d271de9f
Loading
Loading
Loading
Loading
+251 −0
Original line number Diff line number Diff line
@@ -35,6 +35,10 @@ static BusState *main_system_bus;

static DeviceInfo *device_info_list;

static BusState *qbus_find_recursive(BusState *bus, const char *name,
                                     const BusInfo *info);
static BusState *qbus_find(const char *path);

/* Register a new device type.  */
void qdev_register(DeviceInfo *info)
{
@@ -101,6 +105,80 @@ DeviceState *qdev_create(BusState *bus, const char *name)
    return dev;
}

DeviceState *qdev_device_add(const char *cmdline)
{
    DeviceInfo *info;
    DeviceState *qdev;
    BusState *bus;
    char driver[32], path[128] = "";
    char tag[32], value[256];
    const char *params = NULL;
    int n = 0;

    if (1 != sscanf(cmdline, "%32[^,],%n", driver, &n)) {
        fprintf(stderr, "device parse error: \"%s\"\n", cmdline);
        return NULL;
    }
    if (strcmp(driver, "?") == 0) {
        for (info = device_info_list; info != NULL; info = info->next) {
            fprintf(stderr, "name \"%s\", bus %s\n", info->name, info->bus_info->name);
        }
        return NULL;
    }
    if (n) {
        params = cmdline + n;
        get_param_value(path, sizeof(path), "bus",  params);
    }
    info = qdev_find_info(NULL, driver);
    if (!info) {
        fprintf(stderr, "Device \"%s\" not found.  Try -device '?' for a list.\n",
                driver);
        return NULL;
    }
    if (info->no_user) {
        fprintf(stderr, "device \"%s\" can't be added via command line\n",
                info->name);
        return NULL;
    }

    if (strlen(path)) {
        bus = qbus_find(path);
        if (!bus)
            return NULL;
        qdev = qdev_create(bus, driver);
    } else {
        bus = qbus_find_recursive(main_system_bus, NULL, info->bus_info);
        if (!bus)
            return NULL;
        qdev = qdev_create(bus, driver);
    }

    if (params) {
        while (params[0]) {
            if (2 != sscanf(params, "%31[^=]=%255[^,]%n", tag, value, &n)) {
                fprintf(stderr, "parse error at \"%s\"\n", params);
                break;
            }
            params += n;
            if (params[0] == ',')
                params++;
            if (strcmp(tag, "bus") == 0)
                continue;
            if (strcmp(tag, "id") == 0) {
                qdev->id = qemu_strdup(value);
                continue;
            }
            if (-1 == qdev_prop_parse(qdev, tag, value)) {
                fprintf(stderr, "can't set property \"%s\" to \"%s\" for \"%s\"\n",
                        tag, value, driver);
            }
        }
    }

    qdev_init(qdev);
    return qdev;
}

/* Initialize a device.  Device properties should be set before calling
   this function.  IRQs and MMIO regions should be connected/mapped after
   calling this function.  */
@@ -229,6 +307,179 @@ void scsi_bus_new(DeviceState *host, SCSIAttachFn attach)
   }
}

static BusState *qbus_find_recursive(BusState *bus, const char *name,
                                     const BusInfo *info)
{
    DeviceState *dev;
    BusState *child, *ret;
    int match = 1;

    if (name && (strcmp(bus->name, name) != 0)) {
        match = 0;
    }
    if (info && (bus->info != info)) {
        match = 0;
    }
    if (match) {
        return bus;
    }

    LIST_FOREACH(dev, &bus->children, sibling) {
        LIST_FOREACH(child, &dev->child_bus, sibling) {
            ret = qbus_find_recursive(child, name, info);
            if (ret) {
                return ret;
            }
        }
    }
    return NULL;
}

static void qbus_list_bus(DeviceState *dev, char *dest, int len)
{
    BusState *child;
    const char *sep = " ";
    int pos = 0;

    pos += snprintf(dest+pos, len-pos,"child busses at \"%s\":",
                    dev->id ? dev->id : dev->info->name);
    LIST_FOREACH(child, &dev->child_bus, sibling) {
        pos += snprintf(dest+pos, len-pos, "%s\"%s\"", sep, child->name);
        sep = ", ";
    }
}

static void qbus_list_dev(BusState *bus, char *dest, int len)
{
    DeviceState *dev;
    const char *sep = " ";
    int pos = 0;

    pos += snprintf(dest+pos, len-pos, "devices at \"%s\":",
                    bus->name);
    LIST_FOREACH(dev, &bus->children, sibling) {
        pos += snprintf(dest+pos, len-pos, "%s\"%s\"",
                        sep, dev->info->name);
        if (dev->id)
            pos += snprintf(dest+pos, len-pos, "/\"%s\"", dev->id);
        sep = ", ";
    }
}

static BusState *qbus_find_bus(DeviceState *dev, char *elem)
{
    BusState *child;

    LIST_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)
{
    DeviceState *dev;

    /*
     * try to match in order:
     *   (1) instance id, if present
     *   (2) driver name
     *   (3) driver alias, if present
     */
    LIST_FOREACH(dev, &bus->children, sibling) {
        if (dev->id  &&  strcmp(dev->id, elem) == 0) {
            return dev;
        }
    }
    LIST_FOREACH(dev, &bus->children, sibling) {
        if (strcmp(dev->info->name, elem) == 0) {
            return dev;
        }
    }
    LIST_FOREACH(dev, &bus->children, sibling) {
        if (dev->info->alias && strcmp(dev->info->alias, elem) == 0) {
            return dev;
        }
    }
    return NULL;
}

static BusState *qbus_find(const char *path)
{
    DeviceState *dev;
    BusState *bus;
    char elem[128], msg[256];
    int pos, len;

    /* find start element */
    if (path[0] == '/') {
        bus = main_system_bus;
        pos = 0;
    } else {
        if (sscanf(path, "%127[^/]%n", elem, &len) != 1) {
            fprintf(stderr, "path parse error (\"%s\")\n", path);
            return NULL;
        }
        bus = qbus_find_recursive(main_system_bus, elem, NULL);
        if (!bus) {
            fprintf(stderr, "bus \"%s\" not found\n", elem);
            return NULL;
        }
        pos = len;
    }

    for (;;) {
        if (path[pos] == '\0') {
            /* we are done */
            return bus;
        }

        /* find device */
        if (sscanf(path+pos, "/%127[^/]%n", elem, &len) != 1) {
            fprintf(stderr, "path parse error (\"%s\" pos %d)\n", path, pos);
            return NULL;
        }
        pos += len;
        dev = qbus_find_dev(bus, elem);
        if (!dev) {
            qbus_list_dev(bus, msg, sizeof(msg));
            fprintf(stderr, "device \"%s\" not found\n%s\n", elem, msg);
            return NULL;
        }
        if (path[pos] == '\0') {
            /* last specified element is a device.  If it has exactly
             * one child bus accept it nevertheless */
            switch (dev->num_child_bus) {
            case 0:
                fprintf(stderr, "device has no child bus (%s)\n", path);
                return NULL;
            case 1:
                return LIST_FIRST(&dev->child_bus);
            default:
                qbus_list_bus(dev, msg, sizeof(msg));
                fprintf(stderr, "device has multiple child busses (%s)\n%s\n",
                        path, msg);
                return NULL;
            }
        }

        /* find bus */
        if (sscanf(path+pos, "/%127[^/]%n", elem, &len) != 1) {
            fprintf(stderr, "path parse error (\"%s\" pos %d)\n", path, pos);
            return NULL;
        }
        pos += len;
        bus = qbus_find_bus(dev, elem);
        if (!bus) {
            qbus_list_bus(dev, msg, sizeof(msg));
            fprintf(stderr, "child bus \"%s\" not found\n%s\n", elem, msg);
            return NULL;
        }
    }
}

BusState *qbus_create(BusInfo *info, DeviceState *parent, const char *name)
{
    BusState *bus;
+1 −0
Original line number Diff line number Diff line
@@ -82,6 +82,7 @@ struct CompatProperty {
/*** Board API.  This should go away once we have a machine config file.  ***/

DeviceState *qdev_create(BusState *bus, const char *name);
DeviceState *qdev_device_add(const char *cmdline);
void qdev_init(DeviceState *dev);
void qdev_free(DeviceState *dev);