diff --git a/qapi-schema.json b/qapi-schema.json
index d48ff3f..3d597b5 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -3165,6 +3165,115 @@
 ##
 { 'command': 'closefd', 'data': {'fdname': 'str'} }
 
+
+##
+# @IntegerSet:
+#
+# A set of integers.
+##
+{ 'struct': 'IntegerSet',
+  'data': { 'ranges': [ 'IntegerRange' ] } }
+
+##
+# @IntegerRange:
+#
+# A contiguous range of integers.
+#
+# @min: The first value in the range
+# @max: The last value in the range
+##
+{ 'struct': 'IntegerRange',
+  'data': { 'min': 'int', 'max': 'int' } }
+
+##
+# @ValueSet:
+#
+# A set of values.
+#
+# @list: A set of elements in a list.
+#
+# @int-set: A set represented by an IntegerSet.
+##
+{ 'union': 'ValueSet',
+  'data': { 'list': [ 'any' ], 'int-set': 'IntegerSet' } }
+
+##
+# @SlotOption:
+#
+# A option to be used when plugging a device to a slot.
+#
+# @option: Option name.
+#
+# @values: Set of valid option values.
+##
+{ 'struct': 'SlotOption',
+  'data': { 'option': 'str', 'values': 'ValueSet' } }
+
+##
+# @DeviceSlotInfo:
+#
+# Information on a set of slots where devices can be plugged.
+#
+# @type: type of device slot.
+#
+# @accepted-device-types: List of device types accepted by the slots.
+#                         Any device plugged to the slot should implement
+#                         one of the accepted device types.
+#
+# @available: If false, the slot is not available for plugging any device.
+#             This value can change at runtime if condition changes
+#             (e.g. if the slot becomes full, or if the machine
+#             was already initialized and the slot doesn't support
+#             hotplug).
+#
+# @hotpluggable: If true, the slot accepts hotplugged devices.
+#
+# @count: #optional Number of slots represented by this slot set.
+#         Will always be present if @incomplete=false.
+#
+# @incomplete: Slot information is incomplete. Slot count is not
+#              available, and additional arguments are missing
+#              on @props.
+#
+# @props: Information on the arguments that should be given to
+#         @device_add if plugging a device to this slot.
+#
+# How to interpret @props
+# -----------------------
+#
+# Each key/value pair in @props represent an option that should
+# be set when using @device_add to plug a new device.
+#
+# Each value in @props represent a single value that should be
+# provided to @device_add, or a set of possible values for the option.
+#
+# Incomplete Slot Sets
+# --------------------
+#
+# Slot sets with @incomplete=true represent a bus that doesn't
+# support slot enumeration yet. Slots of this type should be replaced
+# by more detailed slot sets in future QEMU versions.
+#
+# Slots of this type may or may not support multiple devices.
+#
+# Slots in incomplete slot sets might require extra arguments to
+# be set to specify the device address.
+##
+{ 'struct': 'DeviceSlotInfo',
+  'data': { 'accepted-device-types': [ 'str' ],
+            'available': 'bool', 'hotpluggable': 'bool',
+            '*count': 'int', 'incomplete': 'bool',
+            'props': [ 'SlotOption' ] } }
+
+##
+# @query-device-slots:
+#
+# Return the list of possible slots for plugging devices using
+# @device_add.
+##
+{ 'command': 'query-device-slots',
+  'returns': [ 'DeviceSlotInfo' ] }
+
 ##
 # @MachineBusInfo
 #
diff --git a/scripts/qmp/query-slots.py b/scripts/qmp/query-slots.py
new file mode 100755
index 0000000..02c60b7
--- /dev/null
+++ b/scripts/qmp/query-slots.py
@@ -0,0 +1,50 @@
+#!/usr/bin/env python
+# test script to dump slot info in a more human-friendly way
+
+import qmp
+import sys
+
+q = qmp.QEMUMonitorProtocol(sys.argv[1])
+q.connect()
+slots = q.command('query-device-slots')
+for slot in slots:
+    types = slot.pop('accepted-device-types')
+    print 'Slot set for: %s' % (', '.join(types))
+    inc = slot.pop('incomplete')
+    if inc:
+        print '  Incomplete set'
+    else:
+        count = slot.pop('count')
+        print '  Slot count: %d' % (count)
+    props = slot.pop('props')
+    for k,v in slot.items():
+        if type(v) == bool:
+            if v:
+                v = 'yes'
+            else:
+                v = 'no'
+        elif type(v) == unicode:
+            v = str(v)
+        else:
+            v = repr(v)
+        print '  %s: %s' % (k, v)
+
+    print '  valid device_add arguments:'
+    for p in props:
+        option = p.pop('option')
+        values = p.pop('values')
+        assert not p # no other field
+        valuestr = repr(values)
+        if values['type'] == 'list':
+            valuestr = ', '.join(str(v) for v in values['data'])
+        elif values['type'] == 'int-set':
+            ranges = []
+            for r in values['data']['ranges']:
+                if r['max'] == r['min']:
+                    ranges.append('%d' % (r['max']))
+                else:
+                    ranges.append('%d-%d' % (r['min'], r['max']))
+            valuestr = ','.join(ranges)
+        else:
+            assert False
+        print '    %s: %s' % (option, valuestr)
diff --git a/include/hw/qdev-core.h b/include/hw/qdev-core.h
index a7f9ac4..2cb043a 100644
--- a/include/hw/qdev-core.h
+++ b/include/hw/qdev-core.h
@@ -214,6 +214,10 @@ struct BusClass {
      * but on some cases bus instances may override it.
      */
     const char *device_type;
+
+    /*TODO: write doc */
+    DeviceSlotInfoList *(*enumerate_slots)(BusState *bus, Error **errp);
+
 };
 
 typedef struct BusChild {
@@ -412,4 +416,6 @@ static inline bool qbus_is_hotpluggable(BusState *bus)
 void device_listener_register(DeviceListener *listener);
 void device_listener_unregister(DeviceListener *listener);
 
+bool qbus_is_full(BusState *bus);
+
 #endif
diff --git a/include/qapi/qobject-output-visitor.h b/include/qapi/qobject-output-visitor.h
index 8241877..319985e 100644
--- a/include/qapi/qobject-output-visitor.h
+++ b/include/qapi/qobject-output-visitor.h
@@ -16,6 +16,7 @@
 
 #include "qapi/visitor.h"
 #include "qapi/qmp/qobject.h"
+#include "qapi/error.h"
 
 typedef struct QObjectOutputVisitor QObjectOutputVisitor;
 
@@ -27,4 +28,12 @@ typedef struct QObjectOutputVisitor QObjectOutputVisitor;
  */
 Visitor *qobject_output_visitor_new(QObject **result);
 
+QObject *qapi_to_qobject(const void *src,
+                         void (*visit_fn)(Visitor *, const char *,
+                                          void**, Error **));
+
+#define QAPI_TO_QOBJ(type, src)                                        \
+    (qapi_to_qobject((src), (void (*)(Visitor *, const char *, void**, \
+                                      Error **))visit_type_ ## type))
+
 #endif
diff --git a/hw/core/bus.c b/hw/core/bus.c
index d2bf717..2f47fe1 100644
--- a/hw/core/bus.c
+++ b/hw/core/bus.c
@@ -21,7 +21,9 @@
 #include "qemu-common.h"
 #include "hw/qdev.h"
 #include "qapi/error.h"
+#include "qapi/clone-visitor.h"
 #include "qapi-visit.h"
+#include "qapi/qmp/qstring.h"
 
 static void qbus_set_hotplug_handler_internal(BusState *bus, Object *handler,
                                               Error **errp)
@@ -225,12 +227,55 @@ static void bus_get_device_type(Object *obj, Visitor *v,
     visit_type_strList(v, NULL, &bus->accepted_device_types, errp);
 }
 
+bool qbus_is_full(BusState *bus)
+{
+    BusClass *bus_class = BUS_GET_CLASS(bus);
+    return bus_class->max_dev && bus->max_index >= bus_class->max_dev;
+}
+
+/* Generic slot enumeration function that will return a generic-slot slot type.
+ */
+static DeviceSlotInfoList *bus_generic_enumerate_slots(BusState *bus, Error **errp)
+{
+    Error *local_err = NULL;
+    DeviceSlotInfoList *r = g_new0(DeviceSlotInfoList, 1);
+
+    r->value = g_new0(DeviceSlotInfo, 1);
+    r->value->accepted_device_types = QAPI_CLONE(strList, bus->accepted_device_types);
+
+    r->value->hotpluggable = qbus_is_hotpluggable(bus);
+
+    r->value->incomplete = true;
+
+    /* Conditions that make a bus unavailable:
+     * - Bus already full
+     * - Hotplug when the bus is not hotpluggable
+     */
+    r->value->available =
+        !(qbus_is_full(bus) ||
+          (qdev_hotplug && !qbus_is_hotpluggable(bus)));
+
+
+    /* r->value->props = { 'bus': bus->name } */
+    r->value->props = g_new0(SlotOptionList, 1);
+    r->value->props->value = g_new0(SlotOption, 1);
+    r->value->props->value->option = g_strdup("bus");
+    r->value->props->value->values = g_new0(ValueSet, 1);
+    r->value->props->value->values->type = VALUE_SET_KIND_LIST;;
+    r->value->props->value->values->u.list.data = g_new0(anyList, 1);
+    r->value->props->value->values->u.list.data->value = QOBJECT(qstring_from_str(bus->name));
+
+    error_propagate(errp, local_err);
+    return r;
+}
+
 static void bus_class_init(ObjectClass *class, void *data)
 {
     BusClass *bc = BUS_CLASS(class);
 
     class->unparent = bus_unparent;
     bc->get_fw_dev_path = default_bus_get_fw_dev_path;
+    bc->enumerate_slots = bus_generic_enumerate_slots;
 
     object_class_property_add(class, "accepted-device-types", "strList",
                               bus_get_device_type, NULL, NULL, NULL,
diff --git a/hw/pci/pci.c b/hw/pci/pci.c
index 2eac71a..26721a6 100644
--- a/hw/pci/pci.c
+++ b/hw/pci/pci.c
@@ -41,6 +41,8 @@
 #include "hw/hotplug.h"
 #include "hw/boards.h"
 #include "qemu/cutils.h"
+#include "qapi/clone-visitor.h"
+#include "qapi/qmp/qstring.h"
 
 //#define DEBUG_PCI
 #ifdef DEBUG_PCI
@@ -141,6 +143,8 @@ static uint16_t pcibus_numa_node(PCIBus *bus)
     return NUMA_NODE_UNASSIGNED;
 }
 
+static DeviceSlotInfoList *pci_bus_enumerate_slots(BusState *bus, Error **errp);
+
 static void pci_bus_class_init(ObjectClass *klass, void *data)
 {
     BusClass *k = BUS_CLASS(klass);
@@ -156,6 +160,7 @@ static void pci_bus_class_init(ObjectClass *klass, void *data)
      * but overrides BusClass::device_type to INTERFACE_PCIE_DEVICE
      */
     k->device_type = INTERFACE_LEGACY_PCI_DEVICE;
+    k->enumerate_slots = pci_bus_enumerate_slots;
 
     pbc->is_root = pcibus_is_root;
     pbc->bus_num = pcibus_num;
@@ -967,6 +972,274 @@ uint16_t pci_requester_id(PCIDevice *dev)
     return pci_req_id_cache_extract(&dev->requester_id_cache);
 }
 
+static bool pci_bus_has_pcie_upstream_port(PCIBus *bus)
+{
+    PCIDevice *parent_dev = pci_bridge_get_device(bus);
+
+    /* Device associated with an upstream port.
+     * As there are several types of these, it's easier to check the
+     * parent device: upstream ports are always connected to
+     * root or downstream ports.
+     */
+    return parent_dev &&
+        pci_is_express(parent_dev) &&
+        parent_dev->exp.exp_cap &&
+        (pcie_cap_get_type(parent_dev) == PCI_EXP_TYPE_ROOT_PORT ||
+         pcie_cap_get_type(parent_dev) == PCI_EXP_TYPE_DOWNSTREAM);
+}
+
+static PCIDevice *pci_bus_get_function_0(PCIBus *bus, int devfn)
+{
+    if(pci_bus_has_pcie_upstream_port(bus)) {
+        /* With an upstream PCIe port, we only support 1 device at slot 0 */
+        return bus->devices[0];
+    } else {
+        /* Other bus types might support multiple devices at slots 0-31 */
+        return bus->devices[PCI_DEVFN(PCI_SLOT(devfn), 0)];
+    }
+}
+
+PCIDevice *pci_get_function_0(PCIDevice *pci_dev)
+{
+    return pci_bus_get_function_0(pci_dev->bus, pci_dev->devfn);
+}
+
+static bool int_set_empty(IntegerSet *set)
+{
+    return !set->ranges;
+}
+
+static int int_range_size(IntegerRange *range)
+{
+    return range->max - range->min + 1;
+}
+
+static int int_set_size(IntegerSet *set)
+{
+    int r = 0;
+    IntegerRangeList *rl;
+    for (rl = set->ranges; rl; rl = rl->next) {
+        r += int_range_size(rl->value);
+    }
+    return r;
+}
+
+static void int_set_add(IntegerSet *set, int min, int max)
+{
+    IntegerRangeList *rl;
+    IntegerRangeList **pnext = &set->ranges;
+    IntegerRangeList *prev = NULL;
+
+    for (rl = set->ranges; rl; rl = rl->next) {
+        IntegerRange *r = rl->value;
+        if (max < r->min - 1) {
+            goto insert;
+        } else if (max <= r->max) {
+            r->min = min;
+            goto try_merge_back;
+        } else if (min == r->max + 1) {
+            r->max = max;
+            goto try_merge_front;
+        } else {
+            pnext = &rl->next;
+            prev = rl;
+        }
+    }
+
+insert:
+    rl = g_new0(IntegerRangeList, 1);
+    rl->value = g_new0(IntegerRange, 1);
+    rl->value->min = min;
+    rl->value->max = max;
+    rl->next = *pnext;
+    *pnext = rl;
+
+try_merge_back:
+    /*TODO: can't merge more than 1 range */
+    if (prev && prev->value->max == rl->value->min - 1) {
+        prev->value->max = rl->value->max;
+        prev->next = rl->next;
+        g_free(rl);
+    }
+
+try_merge_front:
+    while (rl->next && rl->value->max + 1 == rl->next->value->min) {
+        IntegerRangeList *old = rl->next;
+        rl->next = old->next;
+        g_free(old);
+    }
+}
+
+static ValueSet *new_slot_prop(DeviceSlotInfo *slot, const char *option, ValueSetKind type)
+{
+    SlotOptionList *l =  g_new0(SlotOptionList, 1);
+    SlotOption *opt = g_new0(SlotOption, 1);
+    ValueSet *vs = g_new0(ValueSet, 1);
+
+    vs->type = type;
+    opt->option = g_strdup(option);
+    opt->values = vs;
+    l->value = opt;
+    l->next = slot->props;
+    slot->props = l;
+    return vs;
+}
+
+static void value_set_list_append(ValueSet *vs, QObject *value)
+{
+    anyList *new = g_new0(anyList, 1);
+
+    assert(vs->type == VALUE_SET_KIND_LIST);
+    new->value = value;
+    new->next = vs->u.list.data;
+    vs->u.list.data = new;
+}
+
+static DeviceSlotInfoList *pci_bus_enumerate_slots(BusState *bus, Error **errp)
+{
+    PCIBus *pb = PCI_BUS(bus);
+    int devnr, devnrs;
+    DeviceSlotInfoList *r = NULL;
+    DeviceSlotInfoList **next = &r;
+    IntegerSet *empty_slots = g_new0(IntegerSet, 1);
+
+    if (pci_bus_has_pcie_upstream_port(pb)) {
+        devnrs = 1;
+    } else {
+        devnrs = PCI_SLOT_MAX;
+    }
+
+    /* Each PCI devfn (device number + function) is a separate slot,
+     * because we implement multi-function PCI devices as separate
+     * device objects.
+     *
+     * As an experiment, return a slot set for each device number.
+     */
+    for(devnr = PCI_SLOT(pb->devfn_min); devnr < devnrs; devnr++) {
+        IntegerSet *empty_funcs = g_new0(IntegerSet, 1);
+
+        int func;
+        for (func = 0; func < PCI_FUNC_MAX; func++) {
+            if (pb->devices[PCI_DEVFN(devnr, func)]) {
+                break;
+            }
+        }
+        if (func == PCI_FUNC_MAX) {
+            /* All functions are empty, we can return the whole slot in the "empty" slot set */
+            int_set_add(empty_slots, devnr, devnr);
+            continue;
+        }
+
+        /* Not all functions are empty, so return one entry for each function */
+        for (func = 0; func < PCI_FUNC_MAX; func++) {
+            /* Empty functions will be reported as a single slot set later: */
+            if (!pb->devices[PCI_DEVFN(devnr, func)]) {
+                int_set_add(empty_funcs, func, func);
+                continue;
+            }
+
+            DeviceSlotInfoList *i = g_new0(DeviceSlotInfoList, 1);
+            i->value = g_new0(DeviceSlotInfo, 1);
+            /*TODO: add info about accepting only bridges on extra PCI root buses */
+            i->value->accepted_device_types = QAPI_CLONE(strList, bus->accepted_device_types);
+
+            i->value->hotpluggable = qbus_is_hotpluggable(bus);
+
+            /* Conditions that make a devnr unavailable:
+             * - function already occupied
+             * - function 0 already occupied by a device
+             * - Hotplug when the bus is not hotpluggable
+             */
+            i->value->available =
+                !((pb->devices[PCI_DEVFN(devnr, func)]) ||
+                  (pb->devices[PCI_DEVFN(devnr, 0)]) ||
+                  (0 && qdev_hotplug && !qbus_is_hotpluggable(bus)));
+
+            ValueSet *bus_values = new_slot_prop(i->value, "bus", VALUE_SET_KIND_LIST);
+            value_set_list_append(bus_values, QOBJECT(qstring_from_str(bus->name)));
+
+            ValueSet *dev_values = new_slot_prop(i->value, "device-number", VALUE_SET_KIND_INT_SET);
+            dev_values->u.int_set.data = g_new0(IntegerSet, 1);
+            int_set_add(dev_values->u.int_set.data, devnr, devnr);
+
+            ValueSet *func_values = new_slot_prop(i->value, "function", VALUE_SET_KIND_INT_SET);
+            func_values->u.int_set.data = g_new0(IntegerSet, 1);
+            int_set_add(func_values->u.int_set.data, func, func);
+
+            i->value->has_count = true;
+            i->value->count = 1;
+
+            *next = i;
+            next = &i->next;
+        }
+
+        if (!int_set_empty(empty_funcs)) {
+            DeviceSlotInfoList *i = g_new0(DeviceSlotInfoList, 1);
+            i->value = g_new0(DeviceSlotInfo, 1);
+            /*TODO: add info about accepting only bridges on extra PCI root buses */
+            i->value->accepted_device_types = QAPI_CLONE(strList, bus->accepted_device_types);
+
+            i->value->hotpluggable = qbus_is_hotpluggable(bus);
+
+            i->value->available =
+                !(0 && qdev_hotplug && !qbus_is_hotpluggable(bus));
+
+            ValueSet *bus_values = new_slot_prop(i->value, "bus", VALUE_SET_KIND_LIST);
+            value_set_list_append(bus_values, QOBJECT(qstring_from_str(bus->name)));
+
+            ValueSet *dev_values = new_slot_prop(i->value, "device-number", VALUE_SET_KIND_INT_SET);
+            dev_values->u.int_set.data = g_new0(IntegerSet, 1);
+            int_set_add(dev_values->u.int_set.data, devnr, devnr);
+
+            ValueSet *func_values = new_slot_prop(i->value, "function", VALUE_SET_KIND_INT_SET);
+            func_values->u.int_set.data = empty_funcs;
+
+            i->value->has_count = true;
+            i->value->count = int_set_size(empty_funcs);
+
+            *next = i;
+            next = &i->next;
+        } else {
+            qapi_free_IntegerSet(empty_slots);
+        }
+    }
+
+    /* Return a single slot set for the empty slots */
+    if (!int_set_empty(empty_slots)) {
+        DeviceSlotInfoList *i = g_new0(DeviceSlotInfoList, 1);
+        i->value = g_new0(DeviceSlotInfo, 1);
+        /*TODO: add info about accepting only bridges on extra PCI root buses */
+        i->value->accepted_device_types = QAPI_CLONE(strList, bus->accepted_device_types);
+
+        i->value->hotpluggable = qbus_is_hotpluggable(bus);
+
+        /* Conditions that make a devnr unavailable:
+         * - function 0 already occupied by a device
+         * - Hotplug when the bus is not hotpluggable
+         */
+        i->value->available = !(0 && qdev_hotplug && !qbus_is_hotpluggable(bus));
+
+        ValueSet *bus_values = new_slot_prop(i->value, "bus", VALUE_SET_KIND_LIST);
+        value_set_list_append(bus_values, QOBJECT(qstring_from_str(bus->name)));
+
+        ValueSet *dev_values = new_slot_prop(i->value, "device-number", VALUE_SET_KIND_INT_SET);
+        dev_values->u.int_set.data = empty_slots;
+
+        ValueSet *func_values = new_slot_prop(i->value, "function", VALUE_SET_KIND_INT_SET);
+        func_values->u.int_set.data = g_new0(IntegerSet, 1);
+        int_set_add(func_values->u.int_set.data, 0, PCI_FUNC_MAX - 1);
+
+        i->value->has_count = true;
+        i->value->count = PCI_FUNC_MAX * int_set_size(empty_slots);
+
+        *next = i;
+        next = &i->next;
+    } else {
+        qapi_free_IntegerSet(empty_slots);
+    }
+    return r;
+}
+
 /* -1 for devfn means auto assign */
 static PCIDevice *do_pci_register_device(PCIDevice *pci_dev, PCIBus *bus,
                                          const char *name, int devfn,
@@ -2509,6 +2782,56 @@ MemoryRegion *pci_address_space_io(PCIDevice *dev)
     return dev->bus->address_space_io;
 }
 
+static void pci_device_get_devnr(Object *obj, Visitor *v, const char *name,
+                                void *opaque, Error **errp)
+{
+    PCIDevice *dev = PCI_DEVICE(obj);
+    uint32_t devnr = PCI_SLOT(dev->devfn);
+
+    visit_type_uint32(v, "device-number", &devnr, errp);
+}
+
+static void pci_device_set_devnr(Object *obj, Visitor *v, const char *name,
+                                void *opaque, Error **errp)
+{
+    PCIDevice *dev = PCI_DEVICE(obj);
+    uint32_t devnr;
+    Error *local_err = NULL;
+
+    visit_type_uint32(v, "device-number", &devnr, &local_err);
+    if (local_err) {
+        goto out;
+    }
+    dev->devfn = PCI_DEVFN(devnr, PCI_FUNC(dev->devfn));
+out:
+    error_propagate(errp, local_err);
+}
+
+static void pci_device_get_function(Object *obj, Visitor *v, const char *name,
+                                void *opaque, Error **errp)
+{
+    PCIDevice *dev = PCI_DEVICE(obj);
+    uint32_t function = PCI_FUNC(dev->devfn);
+
+    visit_type_uint32(v, "function", &function, errp);
+}
+
+static void pci_device_set_function(Object *obj, Visitor *v, const char *name,
+                                void *opaque, Error **errp)
+{
+    PCIDevice *dev = PCI_DEVICE(obj);
+    uint32_t function;
+    Error *local_err = NULL;
+
+    visit_type_uint32(v, "function", &function, &local_err);
+    if (local_err) {
+        goto out;
+    }
+    dev->devfn = PCI_DEVFN(PCI_SLOT(dev->devfn), function);
+out:
+    error_propagate(errp, local_err);
+}
+
 static void pci_device_class_init(ObjectClass *klass, void *data)
 {
     DeviceClass *k = DEVICE_CLASS(klass);
@@ -2519,6 +2842,19 @@ static void pci_device_class_init(ObjectClass *klass, void *data)
     k->bus_type = TYPE_PCI_BUS;
     k->props = pci_props;
     pc->realize = pci_default_realize;
+
+    /* Internally, bits 3:8 of devfn are called "slots", but:
+     * - they can be confused with physical slot numbers;
+     * - TYPE_PCIE_SLOT objects already have a "slot" property.
+     * So we use the terminology used in the PCI specifiction:
+     * "device number".
+     */
+    object_class_property_add(klass, "device-number", "uint32",
+                              pci_device_get_devnr, pci_device_set_devnr,
+                              NULL, NULL, &error_abort);
+    object_class_property_add(klass, "function", "uint32",
+                              pci_device_get_function, pci_device_set_function,
+                              NULL, NULL, &error_abort);
 }
 
 static void pci_device_class_base_init(ObjectClass *klass, void *data)
@@ -2607,33 +2943,6 @@ void pci_bus_get_w64_range(PCIBus *bus, Range *range)
     pci_for_each_device_under_bus(bus, pci_dev_get_w64, range);
 }
 
-static bool pcie_has_upstream_port(PCIDevice *dev)
-{
-    PCIDevice *parent_dev = pci_bridge_get_device(dev->bus);
-
-    /* Device associated with an upstream port.
-     * As there are several types of these, it's easier to check the
-     * parent device: upstream ports are always connected to
-     * root or downstream ports.
-     */
-    return parent_dev &&
-        pci_is_express(parent_dev) &&
-        parent_dev->exp.exp_cap &&
-        (pcie_cap_get_type(parent_dev) == PCI_EXP_TYPE_ROOT_PORT ||
-         pcie_cap_get_type(parent_dev) == PCI_EXP_TYPE_DOWNSTREAM);
-}
-
-PCIDevice *pci_get_function_0(PCIDevice *pci_dev)
-{
-    if(pcie_has_upstream_port(pci_dev)) {
-        /* With an upstream PCIe port, we only support 1 device at slot 0 */
-        return pci_dev->bus->devices[0];
-    } else {
-        /* Other bus types might support multiple devices at slots 0-31 */
-        return pci_dev->bus->devices[PCI_DEVFN(PCI_SLOT(pci_dev->devfn), 0)];
-    }
-}
-
 MSIMessage pci_get_msi_message(PCIDevice *dev, int vector)
 {
     MSIMessage msg;
diff --git a/qapi/qobject-output-visitor.c b/qapi/qobject-output-visitor.c
index 8711270..86c3e1b 100644
--- a/qapi/qobject-output-visitor.c
+++ b/qapi/qobject-output-visitor.c
@@ -252,3 +252,17 @@ Visitor *qobject_output_visitor_new(QObject **result)
 
     return &v->visitor;
 }
+
+QObject *qapi_to_qobject(const void *src,
+                         void (*visit_fn)(Visitor *, const char *,
+                                          void**, Error **))
+{
+    QObject *qobj = NULL;
+    void *obj = (void *) src;
+    Visitor *v = qobject_output_visitor_new(&qobj);
+
+    visit_fn(v, "unused", &obj, &error_abort);
+    visit_complete(v, &qobj);
+    visit_free(v);
+    return qobj;
+}
diff --git a/qdev-monitor.c b/qdev-monitor.c
index c73410c..4df29c4 100644
--- a/qdev-monitor.c
+++ b/qdev-monitor.c
@@ -29,6 +29,9 @@
 #include "qemu/error-report.h"
 #include "qemu/help_option.h"
 #include "sysemu/block-backend.h"
+#include "qapi/clone-visitor.h"
+#include "qapi/qobject-output-visitor.h"
+#include "hw/boards.h"
 
 /*
  * Aliases were a bad idea from the start.  Let's keep them
@@ -399,12 +402,6 @@ static DeviceState *qbus_find_dev(BusState *bus, char *elem)
     return NULL;
 }
 
-static inline bool qbus_is_full(BusState *bus)
-{
-    BusClass *bus_class = BUS_GET_CLASS(bus);
-    return bus_class->max_dev && bus->max_index >= 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
@@ -631,6 +628,125 @@ DeviceState *qdev_device_add(QemuOpts *opts, Error **errp)
     return dev;
 }
 
+typedef struct SlotListState {
+    MachineState *machine;
+    DeviceSlotInfoList *result;
+    DeviceSlotInfoList **next;
+    Error *err;
+} SlotListState;
+
+static int walk_bus(Object *obj, void *opaque)
+{
+    SlotListState *s = opaque;
+
+    /* sysbus is special: never return it unless the machine
+     * supports dynamic sysbus devices.
+     */
+    if (object_dynamic_cast(obj, TYPE_BUS) &&
+        (!object_dynamic_cast(obj, TYPE_SYSTEM_BUS) ||
+         MACHINE_GET_CLASS(s->machine)->has_dynamic_sysbus)) {
+        BusState *bus = BUS(obj);
+        BusClass *bc = BUS_GET_CLASS(bus);
+        DeviceSlotInfoList *l = bc->enumerate_slots(bus, &s->err);
+        *s->next = l;
+        for (; l; l = l->next) {
+            s->next = &l->next;
+        }
+        if (s->err) {
+            return 1;
+        }
+    }
+    return 0;
+}
+
+static void add_option(const char *key, QObject *obj, void *opaque)
+{
+    SlotOptionList **l = opaque;
+    SlotOption *opt = g_new0(SlotOption, 1);
+    SlotOptionList *new = g_new0(SlotOptionList, 1);
+
+    opt->option = g_strdup(key);
+    opt->values = g_new0(ValueSet, 1);
+    opt->values->type = VALUE_SET_KIND_LIST;
+    opt->values->u.list.data = g_new0(anyList, 1);
+    opt->values->u.list.data->value = obj;
+    qobject_incref(obj);
+
+    new->next = *l;
+    new->value = opt;
+    *l = new;
+}
+
+/* Convert a simple QDict to a list of single-value slot properties */
+static void qdict_to_slot_props(QDict *d, SlotOptionList **l)
+{
+    qdict_iter(d, add_option, l);
+}
+
+static QObject *cpu_instance_props_to_qobj(CpuInstanceProperties *src, Error **errp)
+{
+    QObject *qobj = NULL;
+    Visitor *v = qobject_output_visitor_new(&qobj);
+
+    visit_type_CpuInstanceProperties(v, "unused", &src, &error_abort);
+    visit_complete(v, &qobj);
+    visit_free(v);
+    return qobj;
+}
+
+DeviceSlotInfoList *qmp_query_device_slots(Error **errp)
+{
+    SlotListState s = { };
+    MachineState *ms = MACHINE(qdev_get_machine());
+    MachineClass *mc = MACHINE_GET_CLASS(ms);
+
+    s.machine = ms;
+    s.next = &s.result;
+
+    /* We build the device slot list from two sources:
+     * 1) Calling the BusClass::enumerate_slots() method on all buses;
+     * 2) The return value of MachineClass::query_hotpluggable_cpus()
+     */
+
+
+    object_child_foreach_recursive(qdev_get_machine(), walk_bus, &s);
+    if (s.err) {
+        goto out;
+    }
+
+    if (mc->query_hotpluggable_cpus) {
+        HotpluggableCPUList *hcl = mc->query_hotpluggable_cpus(ms);
+        HotpluggableCPUList *i;
+
+        for (i = hcl; i; i = i->next) {
+            DeviceSlotInfoList *r = g_new0(DeviceSlotInfoList, 1);
+            HotpluggableCPU *hc = i->value;
+            QObject *props;
+
+            r->value = g_new0(DeviceSlotInfo, 1);
+            r->value->accepted_device_types = g_new0(strList, 1);
+            r->value->accepted_device_types->value = g_strdup(hc->type);
+            r->value->available = !hc->has_qom_path;
+            /*TODO: should it be always true? */
+            r->value->hotpluggable = true;
+            r->value->has_count = true;
+            r->value->count = 1;
+
+            props = cpu_instance_props_to_qobj(hc->props, &s.err);
+            qdict_to_slot_props(qobject_to_qdict(props), &r->value->props);
+
+            *s.next = r;
+            s.next = & r->next;
+        }
+
+        qapi_free_HotpluggableCPUList(hcl);
+    }
+
+out:
+    error_propagate(errp, s.err);
+    return s.result;
+}
+
 
 #define qdev_printf(fmt, ...) monitor_printf(mon, "%*s" fmt, indent, "", ## __VA_ARGS__)
 static void qbus_print(Monitor *mon, BusState *bus, int indent);
diff --git a/tests/qmp-machine-info.py b/tests/qmp-machine-info.py
index 7905afb..850e89a 100755
--- a/tests/qmp-machine-info.py
+++ b/tests/qmp-machine-info.py
@@ -6,6 +6,8 @@ import qtest
 import unittest
 import logging
 import argparse
+import itertools
+import operator
 
 logger = logging.getLogger('qemu.tests.machineinfo')
 
@@ -35,6 +37,34 @@ NODEFAULTS_BLACKLIST = set([
     'z2',              # "qemu: missing SecureDigital device"
 ])
 
+# iterators for QAPI ValueSets:
+# all of the iterators below should support iter() and len()
+
+def int_range(d):
+    """Iterator for IntegerRange"""
+    return xrange(d['min'], d['max'] + 1)
+
+class IntegerSet:
+    """Iterator for IntegerSet"""
+    def __init__(self, d):
+        self._ranges = d['ranges']
+
+    def __iter__(self):
+        iters = [int_range(r) for r in self._ranges]
+        return itertools.chain(*iters)
+
+    def __len__(self):
+        return sum([len(int_range(r)) for r in self._ranges])
+
+def value_set(d):
+    """Iterator for ValueSet"""
+    if d['type'] == 'list':
+        return d['data']
+    elif d['type'] == 'int-set':
+        return IntegerSet(d['data'])
+    else:
+        raise NotImplementedError
+
 class QueryMachinesTest(unittest.TestCase):
     def setUp(self):
         self.vm = None
@@ -154,9 +184,12 @@ class QueryMachinesTest(unittest.TestCase):
         types_to_check = {}
         buses_to_check = {}
         for slot in slots:
-            if slot['props'].has_key('bus'):
-                bus = slot['props']['bus']
-                buses_to_check.setdefault(bus, []).append(slot)
+            for prop in slot['props']:
+                if prop['option'] == 'bus':
+                    values = value_set(bus['values'])
+                    self.assertEquals(len(values), 1)
+                    bus = values[0]
+                    buses_to_check.setdefault(v, []).append(slot)
 
             for t in slot['accepted-device-types']:
                 types_to_check.setdefault(t, set()).update(slot['props'].keys())
@@ -185,27 +218,32 @@ class QueryMachinesTest(unittest.TestCase):
                 self.assertFalse(slot['available'])
 
     def checkSlotInfo(self, args):
-        #TODO:
-        # * check if -device works with at least one device type
-        # * check if query-hotpluggable-cpus matches what's in query-device-slots
-        # * check if accepted-device-types match the property on the bus
-        # * check if available=false if len(devices) >= max-devices
-        # * check if all plugged devices are really in the QOM tree
+        #TODO: check if:
+        # * -device works with at least one device type
+        # * query-hotpluggable-cpus matches what's in query-device-slots
+        # * accepted-device-types match the property on the bus
+        # * available=false if hotpluggable=false
+        # * 'count' is always set if not incomplete
+        # * slot count is <= set of possible values for @props
         self.vm = qtest.QEMUQtestMachine(args=args, logging=False)
         self.vm.launch()
 
         slots = self.vm.command('query-device-slots')
-        self.checkSlotProps(slots)
+        #self.checkSlotProps(slots)
         #self.checkSlotDevices(slots)
-        self.checkAvailableField(slots)
+        #self.checkAvailableField(slots)
+
+        for slot in slots:
+            logging.debug('slot: %r', slot)
+            if not slot['incomplete']:
+                self.assertTrue(slot.has_key('count'))
+
+                all_counts = [len(value_set(p['values'])) for p in slot['props']]
+                total_count = reduce(operator.mul, all_counts, 1)
+                logging.debug('%d possible values', total_count)
+                self.assertEquals(total_count, slot['count'])
 
     def machineTestSlotInfo(self, machine):
-        #TODO:
-        # * check if -device works with at least one device type
-        # * check if query-hotpluggable-cpus matches what's in query-device-slots
-        # * check if accepted-device-types match the property on the bus
-        # * check if available=false if len(devices) >= max-devices
-        # * check if all plugged devices are really in the QOM tree
         if machine['name'] in BLACKLIST:
             self.skipTest("machine %s on BLACKLIST" % (machine['name']))
 
