diff --git a/hw/core/machine.c b/hw/core/machine.c
index b0fd91f..5be1297 100644
--- a/hw/core/machine.c
+++ b/hw/core/machine.c
@@ -19,6 +19,7 @@
 #include "sysemu/sysemu.h"
 #include "qemu/error-report.h"
 #include "qemu/cutils.h"
+#include "qapi/clone-visitor.h"
 
 static char *machine_get_accel(Object *obj, Error **errp)
 {
@@ -357,6 +358,32 @@ static void machine_init_notify(Notifier *notifier, void *data)
     foreach_dynamic_sysbus_device(error_on_sysbus_device, NULL);
 }
 
+
+/* Add an item to always_available_bus list
+ *
+ * The accepted_device_types field is automatically filled using
+ * BusClass::device_type.
+ */
+MachineBusInfo *machine_class_add_always_available_bus(MachineClass *mc,
+                                                       const char *bus_id,
+                                                       const char *bus_type)
+{
+    BusClass *bc = BUS_CLASS(object_class_by_name(bus_type));
+    MachineBusInfo *bi = g_new0(MachineBusInfo, 1);
+    MachineBusInfoList *bl = g_new0(MachineBusInfoList, 1);
+
+    bi->bus_id = g_strdup(bus_id);
+    bi->bus_type = g_strdup(bus_type);
+    bi->accepted_device_types = g_new0(strList, 1);
+    bi->accepted_device_types->value = g_strdup(bc->device_type);
+
+    bl->value = bi;
+    bl->next = mc->always_available_buses;
+    mc->always_available_buses = bl;
+
+    return bi;
+}
+
 static void machine_class_init(ObjectClass *oc, void *data)
 {
     MachineClass *mc = MACHINE_CLASS(oc);
@@ -466,13 +493,17 @@ static void machine_class_init(ObjectClass *oc, void *data)
 
 static void machine_class_base_init(ObjectClass *oc, void *data)
 {
+    MachineClass *mc = MACHINE_CLASS(oc);
+
     if (!object_class_is_abstract(oc)) {
-        MachineClass *mc = MACHINE_CLASS(oc);
         const char *cname = object_class_get_name(oc);
         assert(g_str_has_suffix(cname, TYPE_MACHINE_SUFFIX));
         mc->name = g_strndup(cname,
                             strlen(cname) - strlen(TYPE_MACHINE_SUFFIX));
     }
+
+    mc->always_available_buses =
+        QAPI_CLONE(MachineBusInfoList, mc->always_available_buses);
 }
 
 static void machine_initfn(Object *obj)
diff --git a/include/hw/boards.h b/include/hw/boards.h
index a51da9c..915a46d 100644
--- a/include/hw/boards.h
+++ b/include/hw/boards.h
@@ -42,6 +42,10 @@ bool machine_dump_guest_core(MachineState *machine);
 bool machine_mem_merge(MachineState *machine);
 void machine_register_compat_props(MachineState *machine);
 
+MachineBusInfo *machine_class_add_always_available_bus(MachineClass *mc,
+                                                       const char *bus_id,
+                                                       const char *bus_type);
+
 /**
  * CPUArchId:
  * @arch_id - architecture-dependent CPU ID of present or possible CPU
@@ -92,6 +96,8 @@ typedef struct {
  *    size than the target architecture's minimum. (Attempting to create
  *    such a CPU will fail.) Note that changing this is a migration
  *    compatibility break for the machine.
+ * @default_buses:
+ *    List of typenames of buses that are created by default by the machine.
  */
 struct MachineClass {
     /*< private >*/
@@ -131,6 +137,7 @@ struct MachineClass {
     bool option_rom_has_mr;
     bool rom_file_has_mr;
     int minimum_page_bits;
+    MachineBusInfoList *always_available_buses;
 
     HotplugHandler *(*get_hotplug_handler)(MachineState *machine,
                                            DeviceState *dev);
diff --git a/qapi-schema.json b/qapi-schema.json
index f3e9bfc..807c5a8 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -3163,6 +3163,29 @@
 { 'command': 'closefd', 'data': {'fdname': 'str'} }
 
 ##
+# @MachineBusInfo
+#
+# Information about a bus present on a machine.
+#
+# @bus-id: Path of the bus. Can be a partial path, as long
+#          as it is not ambiguous and can be used as
+#          the @path argument of @qom-get and @bus argument
+#          of @device_add
+#
+# @bus-type: Type name of the bus
+#
+# @accepted-device-types: List of device types accepted by the bus.
+#                         The type names can be used as the @implements
+#                         parameter of @qom-list-types to find the full
+#                         list of device types accepted by the bus.
+#
+# Since: 2.9.0
+##
+{ 'struct': 'MachineBusInfo',
+  'data': { 'bus-id': 'str', 'bus-type': 'str',
+            'accepted-device-types': [ 'str' ] } }
+
+##
 # @MachineInfo:
 #
 # Information describing a machine.
@@ -3178,12 +3201,24 @@
 #
 # @hotpluggable-cpus: cpu hotplug via -device is supported (since 2.7.0)
 #
+# @always-available-buses: Information on buses that are always available
+#                          on the machine. The buses present on this list
+#                          will be available on the machine even if the
+#                          "-nodefaults" option is present on the command-line.
+#                          The field is not provided by all machines, but if
+#                          it is present software must make no assumptions
+#                          about the buses on the machine, and their IDs, and
+#                          should use this field to build -device arguments.
+#                          configuration options.
+#                          (since 2.9.0)
+#
 # Since: 1.2.0
 ##
 { 'struct': 'MachineInfo',
   'data': { 'name': 'str', '*alias': 'str',
             '*is-default': 'bool', 'cpu-max': 'int',
-            'hotpluggable-cpus': 'bool'} }
+            'hotpluggable-cpus': 'bool',
+            '*always-available-buses': [ 'MachineBusInfo' ] } }
 
 ##
 # @query-machines:
diff --git a/tests/Makefile.include b/tests/Makefile.include
index 63c4347..feb65ea 100644
--- a/tests/Makefile.include
+++ b/tests/Makefile.include
@@ -315,6 +315,8 @@ check-qtest-s390x-y = tests/boot-serial-test$(EXESUF)
 
 check-qtest-generic-y += tests/qom-test$(EXESUF)
 
+check-simpleqtest-generic-y += $(SRC_PATH)/tests/qmp-machine-info.py
+
 qapi-schema += alternate-any.json
 qapi-schema += alternate-array.json
 qapi-schema += alternate-base.json
diff --git a/tests/qmp-machine-info.py b/tests/qmp-machine-info.py
new file mode 100755
index 0000000..b113eeb
--- /dev/null
+++ b/tests/qmp-machine-info.py
@@ -0,0 +1,173 @@
+#!/usr/bin/env python
+import sys, os
+sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'scripts'))
+import qtest
+import unittest
+import logging
+import argparse
+
+logger = logging.getLogger('qemu.tests.machineinfo')
+
+# machines that we can't easily test because they can't run on all hosts:
+BLACKLIST = set(['xenpv', 'xenfv'])
+
+# machines known to be broken when using -nodefaults:
+NODEFAULTS_BLACKLIST = set([
+    'cubieboard',      # segfaults
+    'petalogix-ml605', # segfaults
+    'or32-sim',        # segfaults
+    'virtex-ml507',    # segfaults
+    'Niagara',         # segfaults
+    'akita',           # "qemu: missing SecureDigital device"
+    'borzoi',          # "qemu: missing SecureDigital device"
+    'cheetah',         # "qemu: missing SecureDigital device"
+    'connex',          # "qemu: missing SecureDigital device"
+    'mainstone',       # "qemu: missing SecureDigital device"
+    'n800',            # "qemu: missing SecureDigital device"
+    'n810',            # "qemu: missing SecureDigital device"
+    'spitz',           # "qemu: missing SecureDigital device"
+    'sx1',             # "qemu: missing SecureDigital device"
+    'sx1-v1',          # "qemu: missing SecureDigital device"
+    'terrier',         # "qemu: missing SecureDigital device"
+    'tosa',            # "qemu: missing SecureDigital device"
+    'verdex',          # "qemu: missing SecureDigital device"
+    'z2',              # "qemu: missing SecureDigital device"
+])
+
+class QueryMachinesTest(unittest.TestCase):
+    def walkQOMTree(self, vm, path):
+        """Walk QOM tree recusrively, starting at path"""
+        children = vm.qmp('qom-list', path=path)['return']
+        for c in children:
+            logging.debug('walking %s. child: %s', path, c)
+            if not c['type'].startswith('child<'):
+                continue
+
+            cp = '%s/%s' % (path, c['name'])
+            yield cp
+
+            for gc in self.walkQOMTree(vm, cp):
+                yield gc
+
+    def findAllBuses(self, vm):
+        """Find all bus objects in the QOM tree"""
+        r = vm.qmp('qom-list-types', implements='bus')
+        bus_types = set([b['name'] for b in r['return']])
+        for cp in self.walkQOMTree(vm, '/machine'):
+            t = vm.qmp('qom-get', path=cp, property='type')['return']
+            if t in bus_types:
+                dt = vm.qmp('qom-get', path=cp, property='accepted-device-types').get('return')
+                yield dict(path=cp, type=t, accepted_device_types=dt)
+
+    def checkBuses(self, machine, extra_args=[], strict_mode=False):
+        """Validate 'supported-device-types' on 'query-machines'"""
+        if machine['name'] in BLACKLIST:
+            self.skipTest("machine %s on BLACKLIST" % (machine['name']))
+
+        if not machine.has_key('always-available-buses'):
+            self.skipTest('machine %s has no always-available-buses field' %
+                          (machine['name']))
+
+        args = ['-S', '-machine', machine['name']]
+        args.extend(extra_args)
+        logger.debug('QEMU args: %s', ' '.join(args))
+        vm = qtest.QEMUQtestMachine(args=args, logging=False)
+        vm.launch()
+        try:
+            found_buses = set()
+            for b in machine['always-available-buses']:
+                bus_id = b['bus-id']
+                btype = vm.qmp('qom-get', path=bus_id, property='type').get('return')
+                self.assertEquals(btype, b['bus-type'], "bus-type mismatch for %s" % (bus_id))
+                devtypes = vm.qmp('qom-get', path=bus_id, property='accepted-device-types').get('return')
+                self.assertEquals(set(devtypes), set(b['accepted-device-types']), "device-type msimatch for %s" % (bus_id))
+
+                found_buses.add(bus_id)
+
+            all_buses = list(self.findAllBuses(vm))
+            missing_buses = []
+            for b in all_buses:
+                full_path = b['path']
+                short_name = full_path.split('/')[-1]
+                if full_path in found_buses or short_name in found_buses:
+                    found_buses.discard(full_path)
+                    found_buses.discard(short_name)
+                    logger.debug("bus %s was found", full_path)
+                    continue
+                missing_buses.append(full_path)
+
+            if found_buses:
+                self.fail("Unexpected inconsistency: some buses were found using qom-get, but not on the device tree: %r", found_buses)
+
+            if missing_buses:
+                logger.info("missing buses on machine %s: %s",
+                            machine['name'], ' '.join(missing_buses))
+                if strict_mode:
+                    self.fail("missing buses: %s" % (' '.join(missing_buses)))
+        finally:
+            vm.shutdown()
+
+    def machineTestDefaultBuses(self, machine):
+        self.checkBuses(machine, [], False)
+
+    def machineTestNodefaultsBuses(self, machine):
+        if machine['name'] in NODEFAULTS_BLACKLIST:
+            self.skipTest("machine %s on NODEFAULTS_BLACKLIST" % (machine['name']))
+
+        self.checkBuses(machine, ['-nodefaults'], True)
+
+    @classmethod
+    def addMachineTest(klass, method_name, machine):
+        """Dynamically add a testMachine_<arch>_<name>_<machine> method to the class"""
+        method = getattr(klass, method_name)
+        def testMachine(self):
+            return method(self, machine)
+        machine_name = machine['name'].replace('-', '_').replace('.', '_')
+        method_name = 'test_%s_%s_%s' % (method_name, machine['arch'], machine_name)
+        setattr(klass, method_name, testMachine)
+        return method_name
+
+
+    @classmethod
+    def discoverMachines(klass, binary):
+        """Run query-machines
+
+        This method is run before test cases are started, so we
+        can dynamically add test cases for each machine supported
+        by the binary.
+        """
+        vm = qtest.QEMUQtestMachine(binary=binary, args=['-S', '-machine', 'none'], logging=False)
+        vm.launch()
+        try:
+            arch = vm.qmp('query-target')['return']['arch']
+            machines = vm.qmp('query-machines')['return']
+            for m in machines:
+                m['arch'] = arch
+        finally:
+            vm.shutdown()
+        return machines
+
+    @classmethod
+    def addMachineTests(klass, binary):
+        """Dynamically add test methods for each machine found on QEMU binary
+
+        Look for all methods with "machineTest" prefix, and add
+        custom test methods that will test them, for each machine-type
+        found on QEMU binary 'binary'.
+        """
+        method_names = unittest.loader.getTestCaseNames(klass, prefix='machineTest')
+        machines = klass.discoverMachines(binary)
+        for machine in machines:
+            for mname in method_names:
+                klass.addMachineTest(mname, machine)
+
+
+if os.getenv('QTEST_QEMU_BINARY'):
+    QueryMachinesTest.addMachineTests(os.getenv('QTEST_QEMU_BINARY'))
+
+if __name__ == '__main__':
+    if os.getenv('QTEST_LOG_LEVEL'):
+        logging.basicConfig(level=int(os.getenv('QTEST_LOG_LEVEL')))
+    else:
+        logging.basicConfig(level=logging.WARN)
+    unittest.main()
diff --git a/vl.c b/vl.c
index d77dd86..dc4d825 100644
--- a/vl.c
+++ b/vl.c
@@ -123,6 +123,7 @@ int main(int argc, char **argv)
 #include "sysemu/replay.h"
 #include "qapi/qmp/qerror.h"
 #include "sysemu/iothread.h"
+#include "qapi/clone-visitor.h"
 
 #define MAX_VIRTIO_CONSOLES 1
 #define MAX_SCLP_CONSOLES 1
@@ -1556,6 +1557,11 @@ MachineInfoList *qmp_query_machines(Error **errp)
         info->name = g_strdup(mc->name);
         info->cpu_max = !mc->max_cpus ? 1 : mc->max_cpus;
         info->hotpluggable_cpus = !!mc->query_hotpluggable_cpus;
+        if (mc->always_available_buses) {
+            info->always_available_buses =
+                QAPI_CLONE(MachineBusInfoList, mc->always_available_buses);
+            info->has_always_available_buses = true;
+        }
 
         entry = g_malloc0(sizeof(*entry));
         entry->value = info;
