diff --git a/qapi-schema.json b/qapi-schema.json
index d48ff3f..484d91e 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -3166,6 +3166,120 @@
 { 'command': 'closefd', 'data': {'fdname': 'str'} }
 
 ##
+# @DeviceSlotType:
+#
+# Type of device slot
+#
+# @generic: Generic device slot, with no bus-specific information
+# @pci: PCI device slot
+# @cpu: CPU device slot
+##
+{ 'enum': 'DeviceSlotType',
+  'data': ['generic', 'pci', 'cpu'] }
+
+##
+# @DeviceSlotInfo:
+#
+# Information on a slot where devices can be plugged. Some buses
+# are represented as a single slot that can support multiple devices,
+# and some buses have multiple slots that are identified by arguments
+# to @device_add.
+#
+# @bus: ID of the bus object where the device can be plugged. Optional,
+#       as some devices don't need a bus to be plugged (e.g. CPUs).
+#       Can be copied to the "bus" argument to @device_add.
+#
+# @type: type of device slot.
+#
+# @accepted-device-types: List of device types accepted by the slot.
+#                         Any device plugged to the slot should implement
+#                         one of the accepted device types.
+#
+# @max-devices: #optional maximum number of devices that can be plugged
+#               to the slot.
+#
+# @devices: List of QOM paths for devices that are already plugged.
+#
+# @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 device becomes full, or if the machine
+#             was already initialized and the slot doesn't support
+#             hotplug).
+#
+# @hotpluggable: If true, the slot accepts hotplugged devices.
+#
+# DeviceSlotInfo structs always have a @props member, whose members
+# can be directly copied to the arguments to @device_add.
+##
+{ 'union': 'DeviceSlotInfo',
+  'base': { 'type': 'DeviceSlotType',
+            'accepted-device-types': [ 'str' ],
+            '*max-devices': 'int', 'devices': [ 'str' ],
+            'available': 'bool', 'hotpluggable': 'bool' },
+  'discriminator': 'type',
+  'data': { 'generic': 'GenericSlotInfo',
+            'pci': 'PCISlotInfo',
+            'cpu': 'CPUSlotInfo' } }
+
+##
+# @GenericSlotProperties:
+##
+{ 'struct': 'GenericSlotProperties',
+  'data': { 'bus': 'str' } }
+
+
+##
+# @GenericSlotInfo:
+#
+# Generic slot information, with no bus-specific information
+##
+{ 'struct': 'GenericSlotInfo',
+  'data': { 'props': 'GenericSlotProperties' } }
+
+##
+# @PCIDeviceSlotProperties:
+#
+# Properties that can be set when plugging a PCI device.
+#
+# @addr: "addr" argument to @device_add.
+#
+#FIXME: replace @addr with slot and function properties.
+##
+{ 'struct': 'PCIDeviceSlotProperties',
+  'data': { 'bus': 'str', 'addr': 'int' } }
+
+##
+# @PCISlotInfo:
+#
+# Information on a PCI device slot.
+#
+# @props: The @device_add arguments that can be used when plugging
+# the device.
+##
+{ 'struct': 'PCISlotInfo',
+  'data': { 'props': 'PCIDeviceSlotProperties' } }
+
+##
+# @CPUSlotInfo:
+#
+# Information on a CPU device slot.
+#
+# @props: The @device_add arguments that can be used when plugging
+# the device.
+##
+{ 'struct': 'CPUSlotInfo',
+  'data': { 'props': 'CpuInstanceProperties' } }
+
+##
+# @query-device-slots:
+#
+# Return the list of possible slots for plugging devices using
+# @device_add.
+##
+{ 'command': 'query-device-slots',
+  'returns': [ 'DeviceSlotInfo' ] }
+
+##
 # @MachineBusInfo
 #
 # Information about a bus present on a machine.
diff --git a/include/hw/qdev-core.h b/include/hw/qdev-core.h
index a7f9ac4..7a87234 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;
+
+    /*TOD: 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/hw/core/bus.c b/hw/core/bus.c
index d2bf717..58b5c3d 100644
--- a/hw/core/bus.c
+++ b/hw/core/bus.c
@@ -21,6 +21,7 @@
 #include "qemu-common.h"
 #include "hw/qdev.h"
 #include "qapi/error.h"
+#include "qapi/clone-visitor.h"
 #include "qapi-visit.h"
 
 static void qbus_set_hotplug_handler_internal(BusState *bus, Object *handler,
@@ -225,12 +226,60 @@ 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: will return a single slot
+ * for the bus, with max_devices set to BusClass::max_dev, if set.
+ */
+static DeviceSlotInfoList *bus_generic_enumerate_slots(BusState *bus, Error **errp)
+{
+    Error *local_err = NULL;
+    BusClass *bc = BUS_GET_CLASS(bus);
+    strList **devlist;
+    BusChild *kid;
+    DeviceSlotInfoList *r = g_new0(DeviceSlotInfoList, 1);
+
+    r->value = g_new0(DeviceSlotInfo, 1);
+    r->value->type = DEVICE_SLOT_TYPE_GENERIC;
+    r->value->has_max_devices = bc->max_dev > 0;
+    r->value->max_devices = bc->max_dev;
+    r->value->accepted_device_types = QAPI_CLONE(strList, bus->accepted_device_types);
+    r->value->u.generic.props = g_new0(GenericSlotProperties, 1);
+    r->value->u.generic.props->bus = object_get_canonical_path(OBJECT(bus));
+
+    devlist = &r->value->devices;
+    QTAILQ_FOREACH(kid, &bus->children, sibling) {
+        DeviceState *dev = kid->child;
+        strList *item = g_new0(strList, 1);
+        item->value = object_get_canonical_path(OBJECT(dev));
+        *devlist = item;
+        devlist = &item->next;
+    }
+    r->value->hotpluggable = qbus_is_hotpluggable(bus);
+
+    /* 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)));
+
+    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..9bcc116 100644
--- a/hw/pci/pci.c
+++ b/hw/pci/pci.c
@@ -41,6 +41,7 @@
 #include "hw/hotplug.h"
 #include "hw/boards.h"
 #include "qemu/cutils.h"
+#include "qapi/clone-visitor.h"
 
 //#define DEBUG_PCI
 #ifdef DEBUG_PCI
@@ -141,6 +142,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 +159,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 +971,81 @@ 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 DeviceSlotInfoList *pci_bus_enumerate_slots(BusState *bus, Error **errp)
+{
+    PCIBus *pb = PCI_BUS(bus);
+    int devfn;
+    DeviceSlotInfoList *r = NULL;
+    DeviceSlotInfoList **next = &r;
+
+    for(devfn = pb->devfn_min; devfn < ARRAY_SIZE(pb->devices);
+        devfn += PCI_FUNC_MAX) {
+        DeviceSlotInfoList *i = g_new0(DeviceSlotInfoList, 1);
+        i->value = g_new0(DeviceSlotInfo, 1);
+        i->value->type = DEVICE_SLOT_TYPE_PCI;
+        i->value->has_max_devices = true;
+        i->value->max_devices = 1;
+        i->value->u.pci.props = g_new0(PCIDeviceSlotProperties, 1);
+        i->value->u.pci.props->bus = object_get_canonical_path(OBJECT(bus));
+        i->value->u.pci.props->addr = devfn;
+        /*TODO: add info about accepting only bridges on extra PCI root buses */
+        i->value->accepted_device_types = QAPI_CLONE(strList, bus->accepted_device_types);
+        if (pb->devices[devfn]) {
+            i->value->devices = g_new0(strList, 1);
+            i->value->devices->value =
+                object_get_canonical_path(OBJECT(pb->devices[devfn]));
+        }
+
+        i->value->hotpluggable = qbus_is_hotpluggable(bus);
+        /* Conditions that make a devfn unavailable:
+         * - Hotplug when function 0 is already occupied
+         * - devfn already occupied by a device
+         * - Hotplug when the bus is not hotpluggable
+         */
+        i->value->available =
+            !((qdev_hotplug && pci_bus_get_function_0(pb, devfn)) ||
+              (pb->devices[devfn]) ||
+              (qdev_hotplug && !qbus_is_hotpluggable(bus)));
+
+        *next = i;
+        next = &i->next;
+    }
+
+    return r;
+}
+
 /* -1 for devfn means auto assign */
 static PCIDevice *do_pci_register_device(PCIDevice *pci_dev, PCIBus *bus,
                                          const char *name, int devfn,
@@ -2607,33 +2686,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/qdev-monitor.c b/qdev-monitor.c
index c73410c..b4071c4 100644
--- a/qdev-monitor.c
+++ b/qdev-monitor.c
@@ -29,6 +29,8 @@
 #include "qemu/error-report.h"
 #include "qemu/help_option.h"
 #include "sysemu/block-backend.h"
+#include "qapi/clone-visitor.h"
+#include "hw/boards.h"
 
 /*
  * Aliases were a bad idea from the start.  Let's keep them
@@ -399,12 +401,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 +627,84 @@ DeviceState *qdev_device_add(QemuOpts *opts, Error **errp)
     return dev;
 }
 
+typedef struct SlotListState {
+    DeviceSlotInfoList *result;
+    DeviceSlotInfoList **next;
+    Error *err;
+} SlotListState;
+
+static int walk_bus(Object *obj, void *opaque)
+{
+    SlotListState *s = opaque;
+    if (object_dynamic_cast(obj, TYPE_BUS)) {
+        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;
+}
+
+DeviceSlotInfoList *qmp_query_device_slots(Error **errp)
+{
+    SlotListState s = { };
+    MachineState *ms = MACHINE(qdev_get_machine());
+    MachineClass *mc = MACHINE_GET_CLASS(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;
+            r->value = g_new0(DeviceSlotInfo, 1);
+            r->value->type = DEVICE_SLOT_TYPE_CPU;
+            r->value->accepted_device_types = g_new0(strList, 1);
+            r->value->accepted_device_types->value = g_strdup(hc->type);
+            r->value->has_max_devices = true;
+            r->value->max_devices = 1;
+            r->value->available = !hc->has_qom_path;
+            /*TODO: should it be always true? */
+            r->value->hotpluggable = true;
+
+            if (hc->has_qom_path) {
+                r->value->devices = g_new0(strList, 1);
+                r->value->devices->value = g_strdup(hc->qom_path);
+            }
+            r->value->u.cpu.props = QAPI_CLONE(CpuInstanceProperties,
+                                               hc->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);
