diff --git a/docs/qmp-full-introspection.txt b/docs/qmp-full-introspection.txt
new file mode 100644
index 0000000..1617df7
--- /dev/null
+++ b/docs/qmp-full-introspection.txt
@@ -0,0 +1,97 @@
+= Full introspection support for QMP =
+
+
+== Purpose ==
+
+Add a new monitor command for management to  query QMP schema
+information, it returns a range of schema structs, which contain the
+useful metadata to help management to check supported features, QMP
+commands detail, etc.
+
+== Usage ==
+
+Json schema:
+  { 'type': 'NameInfo', 'data': {'*name': 'str'} }
+  { 'command': 'query-name', 'returns': 'NameInfo' }
+
+Execute QMP command:
+
+  { "execute": "query-qmp-schema" }
+
+Returns:
+
+  { "return": [
+      {
+          "name": "query-name",
+          "type": "command",
+          "returns": {
+              "name": "NameInfo",
+              "type": "type",
+              "data": [
+                  {
+                      "name": "name",
+                      "optional": true,
+                      "recursive": false,
+                      "type": "str"
+                  }
+              ]
+          }
+      },
+      ...
+   }
+
+The whole schema information will be returned in one go, it contains
+all the schema entries. It doesn't support to be filtered by type
+or name. Currently it takes about 5 seconds to return about 1.5M string.
+
+== 'DataObject' union ==
+
+{ 'union': 'DataObject',
+  'base': 'DataObjectBase',
+  'discriminator': 'type',
+  'data': {
+    'anonymous-struct': 'DataObjectAnonymousStruct',
+    'command': 'DataObjectCommand',
+    'enumeration': 'DataObjectEnumeration',
+    'reference-type': 'String',
+    'type': 'DataObjectType',
+    'unionobj': 'DataObjectUnion' } }
+
+Currently we have schema difinitions for type, command, enumeration,
+union. Some arbitrary structs (dictionary, list or string) and native
+types are also used in the body of definitions.
+
+Here we use "DataObject" union to abstract all above schema. We want
+to provide more useful metadata, and used some enum/unions to indicate
+the dynamic type. In the output, some simple data is processed too
+unwieldy. In another side, some complex data is described clearly.
+It's also caused by some limitation of QAPI infrastructure.
+
+So we define 'DataObject' to be an union, it always has an object name
+except anonymous struct.
+
+'command', 'enumeration', 'type', 'unionobj' are common schema type,
+'union' is a build-in type, so I used unionobj here.
+
+'reference-type' will be used to describe native types and unextended
+types.
+
+'anonymous-struct' will be used to describe arbitrary structs
+(dictionary, list or string).
+
+== Avoid dead loop in recursive extending ==
+
+We have four types (ImageInfo, BlockStats, PciDeviceInfo, ObjectData)
+that uses themself in their own define data directly or indirectly,
+we will not repeatedly extend them to avoid dead loop.
+
+We use a string to record the visit path, type index of each node
+will be saved to the string, indexes are split by ':'.
+
+Push index to visit_path_str before extending, and pop index from
+visit_path_str after extending.
+
+If the type was already extended in parent node, we don't extend it
+again to avoid dead loop.
+
+'recursive' indicates if the type is extended or not.
diff --git a/qapi-schema.json b/qapi-schema.json
index c3c939c..db500ab 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -4235,3 +4235,153 @@
 # Since: 1.7
 ##
 { 'command': 'blockdev-add', 'data': { 'options': 'BlockdevOptions' } }
+
+##
+# @DataObjectBase
+#
+# Base of @DataObject
+#
+# @name: #optional @DataObject name
+# @type: @DataObject type
+#
+# Since: 1.8
+##
+{ 'type': 'DataObjectBase',
+  'data': { '*name': 'str', 'type': 'str' } }
+
+##
+# @DataObjectMemberType
+#
+# Type of @DabaObjectMember
+#
+# @reference: reference string
+# @anonymous: arbitrary struct
+# @extend: the @DataObjectMember
+#
+# Since: 1.8
+##
+{ 'union': 'DataObjectMemberType',
+  'discriminator': {},
+  'data': { 'reference': 'str',
+            'anonymous': 'DataObject',
+            'extend': 'DataObject' } }
+
+##
+# @DataObjectMember
+#
+# General member of @DataObject
+#
+# @type: type of @DataObjectMember
+# @name: #optional name
+# @optional: #optional key to indicate if the @DataObjectMember is optional
+# @recursive: #optional key to indicate if it's defined recursively
+#
+# Since: 1.8
+##
+{ 'type': 'DataObjectMember',
+  'data': { 'type': 'DataObjectMemberType', '*name': 'str',
+            '*optional': 'bool', '*recursive': 'bool' } }
+
+##
+# @DataObjectAnonymousStruct
+#
+# Arbitrary struct, it can be dictionary, list or string
+#
+# @data: content of arbitrary struct
+#
+# Since: 1.8
+##
+{ 'type': 'DataObjectAnonymousStruct',
+  'data': { 'data': [ 'DataObjectMember' ] } }
+
+##
+# @DataObjectCommand
+#
+# QMP Command schema
+#
+# @data: QMP command content
+# @returns: returns of executing command
+# @gen: a key to suppress code generation
+#
+# Since: 1.8
+##
+{ 'type': 'DataObjectCommand',
+  'data': { '*data': [ 'DataObjectMember' ],
+            '*returns': 'DataObject',
+            '*gen': 'bool' } }
+
+##
+# @DataObjectEnumeration
+#
+# Enumeration schema
+#
+# @data: enumeration content, it's a string list
+#
+# Since: 1.8
+##
+{ 'type': 'DataObjectEnumeration',
+  'data': { 'data': [ 'str' ] } }
+
+##
+# @DataObjectType
+#
+# Type schema
+#
+# @data: type content
+#
+# Since: 1.8
+##
+{ 'type': 'DataObjectType',
+  'data': { 'data': [ 'DataObjectMember' ] } }
+
+##
+# @DataObjectUnion
+#
+# Union schema
+#
+# @data: union content
+# @base: union base
+# @discriminator: union discriminator
+#
+# Since: 1.8
+##
+{ 'type': 'DataObjectUnion',
+  'data': { 'data': [ 'DataObjectMember' ], '*base': 'str',
+            '*discriminator': 'str' } }
+
+##
+# @DataObject
+#
+# Dynamic data struct, it can be command, enumeration, type, union, arbitrary
+# struct or native type.
+#
+# @anonymous-struct: arbitrary struct, it can be dictionary, list or string
+# @command: QMP command schema
+# @enumeration: enumeration schema
+# @reference-type: native type or unextended type
+# @type: type schema, it will be extended
+# @unionobj: union schema
+#
+# Since: 1.8
+##
+{ 'union': 'DataObject',
+  'base': 'DataObjectBase',
+  'discriminator': 'type',
+  'data': {
+    'anonymous-struct': 'DataObjectAnonymousStruct',
+    'command': 'DataObjectCommand',
+    'enumeration': 'DataObjectEnumeration',
+    'reference-type': 'String',
+    'type': 'DataObjectType',
+    'unionobj': 'DataObjectUnion' } }
+
+##
+# @query-qmp-schema
+#
+# Query QMP schema information
+#
+# @returns: list of @DataObject
+#
+# Since: 1.8
+##
+{ 'command': 'query-qmp-schema', 'returns': ['DataObject'] }
diff --git a/qmp-commands.hx b/qmp-commands.hx
index fba15cd..cf9c4aa 100644
--- a/qmp-commands.hx
+++ b/qmp-commands.hx
@@ -3237,7 +3237,48 @@ Example:
             "broadcast-allowed": false
         }
       ]
-   }
+
+EQMP
+    {
+        .name       = "query-qmp-schema",
+        .args_type  = "",
+        .mhandler.cmd_new = qmp_marshal_input_query_qmp_schema,
+    },
+
+
+SQMP
+query-qmp-schema
+----------------
+
+query qmp schema information
+
+Return a json-object with the following information:
+
+- "name": qmp schema name (json-string)
+- "type": qmp schema type, it can be 'comand', 'type', 'enum', 'union'
+- "returns": return data of qmp command (json-object, optional)
+
+Example:
+
+-> { "execute": "query-qmp-schema" }
+-> { "return": [
+     {
+         "name": "query-name",
+         "type": "command",
+         "returns": {
+             "name": "NameInfo",
+             "type": "type",
+             "data": [
+                 {
+                     "name": "name",
+                     "optional": true,
+                     "recursive": false,
+                     "type": "str"
+                 }
+             ]
+         }
+     }
+  }
 
 EQMP
 
diff --git a/qmp.c b/qmp.c
index 1d7a04d..e9bba06 100644
--- a/qmp.c
+++ b/qmp.c
@@ -25,6 +25,8 @@
 #include "sysemu/blockdev.h"
 #include "qom/qom-qobject.h"
 #include "hw/boards.h"
+#include "qmp-schema.h"
+#include "qapi/qmp/qjson.h"
 
 NameInfo *qmp_query_name(Error **errp)
 {
@@ -486,6 +488,386 @@ CpuDefinitionInfoList *qmp_query_cpu_definitions(Error **errp)
     return arch_query_cpu_definitions(errp);
 }
 
+/*
+ * use a passed string to record the visit path, schema index of
+ * each extended node will be appended to the string, indexes are
+ * split by ':'
+ */
+static char visit_path_str[1024];
+
+/* push the type index into visit_path_str */
+static void push_id(int id)
+{
+    char *end = strrchr(visit_path_str, ':');
+    char type_idx[256];
+    int num;
+
+    num = sprintf(type_idx, "%d:", id);
+
+    if (end) {
+        /* avoid overflow */
+        assert(end - visit_path_str + 1 + num < sizeof(visit_path_str));
+        sprintf(end + 1, "%d:", id);
+    } else {
+        sprintf(visit_path_str, "%d:", id);
+    }
+}
+
+/* pop the type index from visit_path_str */
+static void pop_id(void)
+{
+    char *p = strrchr(visit_path_str, ':');
+
+    assert(p != NULL);
+    *p = '\0';
+    p = strrchr(visit_path_str, ':');
+    if (p) {
+        *(p + 1) = '\0';
+    } else {
+        visit_path_str[0] = '\0';
+    }
+}
+
+static const char *qstring_copy_str(QObject *data)
+{
+    QString *qstr;
+
+    if (!data) {
+        return NULL;
+    }
+    qstr = qobject_to_qstring(data);
+    if (qstr) {
+        return qstring_get_str(qstr);
+    } else {
+        return NULL;
+    }
+}
+
+static QObject *get_definition(const char *str, bool update_path)
+{
+    QObject *data, *value;
+    QDict *qdict;
+    int i;
+
+    if (!strcmp(str, "str") || !strcmp(str, "int") ||
+        !strcmp(str, "number") || !strcmp(str, "bool") ||
+        !strcmp(str, "int8") || !strcmp(str, "int16") ||
+        !strcmp(str, "int32") || !strcmp(str, "int64") ||
+        !strcmp(str, "uint8") || !strcmp(str, "uint16") ||
+        !strcmp(str, "uint32") || !strcmp(str, "uint64") ||
+        !strcmp(str, "visitor") || !strcmp(str, "**") ||
+        !strcmp(str, "size")) {
+        return NULL;
+    }
+
+    for (i = 0; qmp_schema_table[i]; i++) {
+        data = qobject_from_json(qmp_schema_table[i]);
+        qdict = qobject_to_qdict(data);
+        assert(qdict != NULL);
+
+        if (qdict_get(qdict, "enum")) {
+            value = qdict_get(qdict, "enum");
+        } else if (qdict_get(qdict, "type")) {
+            value = qdict_get(qdict, "type");
+        } else if (qdict_get(qdict, "union")) {
+            value = qdict_get(qdict, "union");
+        } else {
+            continue;
+        }
+
+        if (!strcmp(str, qstring_copy_str(value)) && update_path) {
+            char *start, *end;
+            char cur_idx[256];
+            char type_idx[256];
+
+            start = visit_path_str;
+            sprintf(type_idx, "%d", i);
+            while (start) {
+                end = strchr(start, ':');
+                if (!end) {
+                    break;
+                }
+                snprintf(cur_idx, end - start + 1, "%s", start);
+                start = end + 1;
+                /* if the type was already extended in parent node,
+                 * we don't extend it again to avoid dead loop. */
+                if (!strcmp(cur_idx, type_idx)) {
+                    return NULL;
+                }
+            }
+            /* push index to visit_path_str before extending */
+            push_id(i);
+        }
+        if (!strcmp(str, qstring_copy_str(value))) {
+
+            return qobject_from_json(qmp_schema_table[i]);
+        }
+    }
+    return NULL;
+}
+
+static DataObject *visit_qobj_dict(QObject *data);
+static DataObject *visit_qobj_list(QObject *data);
+
+/* extend defined type to json object */
+static DataObject *extend_type(const char *str)
+{
+    QObject *data;
+    DataObject *obj;
+
+    data = get_definition(str, true);
+
+    if (data) {
+        obj = visit_qobj_dict(data);
+        pop_id();
+    } else {
+        obj = g_malloc0(sizeof(struct DataObject));
+        obj->kind = DATA_OBJECT_KIND_REFERENCE_TYPE;
+        obj->reference_type = g_malloc0(sizeof(String));
+        obj->reference_type->str = g_strdup(str);
+    }
+
+    return obj;
+}
+
+static DataObjectMemberList *list_to_memberlist(QObject *data)
+{
+    DataObjectMemberList *mem_list, *entry, *last_entry;
+    QList *qlist;
+    const QListEntry *lent;
+
+        qlist = qobject_to_qlist(data);
+
+        mem_list = NULL;
+        for (lent = qlist_first(qlist); lent; lent = qlist_next(lent)) {
+            entry = g_malloc0(sizeof(DataObjectMemberList *));
+            entry->value = g_malloc0(sizeof(DataObjectMember));
+            entry->value->type = g_malloc0(sizeof(DataObjectMemberType));
+
+            if (get_definition(qstring_copy_str(lent->value), false)) {
+                entry->value->type->kind = DATA_OBJECT_MEMBER_TYPE_KIND_EXTEND;
+                entry->value->has_recursive = true;
+                entry->value->recursive = true;
+                entry->value->type->extend =
+                    extend_type(qstring_copy_str(lent->value));
+            } else {
+                entry->value->type->kind =
+                    DATA_OBJECT_MEMBER_TYPE_KIND_REFERENCE;
+                entry->value->has_recursive = true;
+                entry->value->recursive = false;
+                entry->value->type->reference =
+                    g_strdup(qstring_copy_str(lent->value));
+            }
+
+            entry->next = NULL;
+            if (!mem_list) {
+                mem_list = entry;
+            } else {
+                last_entry->next = entry;
+            }
+            last_entry = entry;
+        }
+        return mem_list;
+}
+
+static DataObjectMemberList *dict_to_memberlist(QObject *data)
+{
+    DataObjectMemberList *mem_list, *entry, *last_entry;
+    QDict *qdict;
+    const QDictEntry *dent;
+
+        qdict = qobject_to_qdict(data);
+
+        mem_list = NULL;
+        for (dent = qdict_first(qdict); dent; dent = qdict_next(qdict, dent)) {
+            entry = g_malloc0(sizeof(DataObjectMemberList *));
+            entry->value = g_malloc0(sizeof(DataObjectMember));
+
+            entry->value->type = g_malloc0(sizeof(DataObjectMemberType));
+
+            if (dent->value->type->code == QTYPE_QDICT) {
+                entry->value->type->kind = DATA_OBJECT_MEMBER_TYPE_KIND_EXTEND;
+                entry->value->type->extend = visit_qobj_dict(dent->value);
+            } else if (dent->value->type->code == QTYPE_QLIST) {
+                entry->value->type->kind = DATA_OBJECT_MEMBER_TYPE_KIND_EXTEND;
+                entry->value->type->extend = visit_qobj_list(dent->value);
+            } else if (get_definition(qstring_copy_str(dent->value), false)) {
+                entry->value->type->kind = DATA_OBJECT_MEMBER_TYPE_KIND_EXTEND;
+                entry->value->has_recursive = true;
+                entry->value->recursive = true;
+                entry->value->type->extend =
+                    extend_type(qstring_copy_str(dent->value));
+            } else {
+                entry->value->type->kind =
+                    DATA_OBJECT_MEMBER_TYPE_KIND_REFERENCE;
+                entry->value->has_recursive = true;
+                entry->value->recursive = false;
+                entry->value->type->reference =
+                    g_strdup(qstring_copy_str(dent->value));
+            }
+            entry->value->has_optional = true;
+            entry->value->has_name = true;
+            if (dent->key[0] == '*') {
+                entry->value->optional = true;
+                entry->value->name = g_strdup(dent->key + 1);
+            } else {
+                entry->value->name = g_strdup(dent->key);
+            }
+
+            entry->next = NULL;
+            if (!mem_list) {
+                mem_list = entry;
+            } else {
+                last_entry->next = entry;
+            }
+            last_entry = entry;
+        }
+        return mem_list;
+}
+
+static DataObject *visit_qobj_list(QObject *data)
+{
+    DataObject *obj;
+
+    obj = g_malloc0(sizeof(struct DataObject));
+    obj->kind = DATA_OBJECT_KIND_ANONYMOUS_STRUCT;
+    obj->anonymous_struct = g_malloc0(sizeof(struct
+                                             DataObjectAnonymousStruct));
+    obj->anonymous_struct->data = list_to_memberlist(data);
+
+    return obj;
+}
+
+static strList *get_str_list(QObject *data)
+{
+    strList *str_list, *last_str_entry, *str_entry;
+    QList *qlist;
+    const QListEntry *lent;
+
+    qlist = qobject_to_qlist(data);
+    str_list = NULL;
+    for (lent = qlist_first(qlist); lent; lent = qlist_next(lent)) {
+        str_entry = g_malloc0(sizeof(strList *));
+        str_entry->value = g_strdup(qstring_copy_str(lent->value));
+        str_entry->next = NULL;
+        if (!str_list) {
+            str_list = str_entry;
+        } else {
+            last_str_entry->next = str_entry;
+        }
+        last_str_entry = str_entry;
+    }
+
+    return str_list;
+}
+
+static DataObject *visit_qobj_dict(QObject *data)
+{
+    DataObject *obj;
+    QObject *subdata;
+    QDict *qdict;
+
+    qdict = qobject_to_qdict(data);
+    assert(qdict != NULL);
+    obj = g_malloc0(sizeof(*obj));
+
+    if (qdict_get(qdict, "command")) {
+        obj->kind = DATA_OBJECT_KIND_COMMAND;
+        obj->has_name = true;
+        obj->name = g_strdup(qstring_copy_str(qdict_get(qdict, "command")));
+        obj->command = g_malloc0(sizeof(struct DataObjectCommand));
+
+        subdata = qdict_get(qdict, "data");
+        if (subdata && subdata->type->code == QTYPE_QDICT) {
+            obj->command->has_data = true;
+            obj->command->data = dict_to_memberlist(subdata);
+        } else if (subdata && subdata->type->code == QTYPE_QLIST) {
+            abort();
+        } else if (subdata) {
+            obj->command->has_data = true;
+            obj->command->data =
+                dict_to_memberlist(get_definition(qstring_copy_str(subdata),
+                                                  true));
+            pop_id();
+        }
+
+        subdata = qdict_get(qdict, "returns");
+        if (subdata && subdata->type->code == QTYPE_QDICT) {
+            abort();
+        } else if (subdata && subdata->type->code == QTYPE_QLIST) {
+            obj->command->has_returns = true;
+            obj->command->returns = visit_qobj_list(subdata);
+        } else if (subdata && subdata->type->code == QTYPE_QSTRING) {
+            obj->command->has_returns = true;
+            obj->command->returns = extend_type(qstring_copy_str(subdata));
+        }
+
+        subdata = qdict_get(qdict, "gen");
+        if (subdata && subdata->type->code == QTYPE_QSTRING) {
+            obj->command->has_gen = true;
+            if (!strcmp(qstring_copy_str(subdata), "no")) {
+                obj->command->gen = false;
+            } else {
+                obj->command->gen = true;
+            }
+        }
+    } else if (qdict_get(qdict, "union")) {
+        obj->kind = DATA_OBJECT_KIND_UNIONOBJ;
+        obj->has_name = true;
+        obj->name = g_strdup(qstring_copy_str(qdict_get(qdict, "union")));
+        obj->unionobj = g_malloc0(sizeof(struct DataObjectUnion));
+        subdata = qdict_get(qdict, "data");
+        obj->unionobj->data = dict_to_memberlist(subdata);
+    } else if (qdict_get(qdict, "type")) {
+        obj->kind = DATA_OBJECT_KIND_TYPE;
+        obj->has_name = true;
+        obj->name = g_strdup(qstring_copy_str(qdict_get(qdict, "type")));
+        obj->type = g_malloc0(sizeof(struct DataObjectType));
+        subdata = qdict_get(qdict, "data");
+        obj->type->data = dict_to_memberlist(subdata);
+    } else if (qdict_get(qdict, "enum")) {
+        obj->kind = DATA_OBJECT_KIND_ENUMERATION;
+        obj->has_name = true;
+        obj->name = g_strdup(qstring_copy_str(qdict_get(qdict, "enum")));
+        obj->enumeration = g_malloc0(sizeof(struct DataObjectEnumeration));
+        subdata = qdict_get(qdict, "data");
+        obj->enumeration->data = get_str_list(subdata);
+    } else {
+        obj->kind = DATA_OBJECT_KIND_ANONYMOUS_STRUCT;
+        obj->anonymous_struct = g_malloc0(sizeof(struct
+                                                 DataObjectAnonymousStruct));
+        obj->anonymous_struct->data = dict_to_memberlist(data);
+    }
+
+    return obj;
+}
+
+DataObjectList *qmp_query_qmp_schema(Error **errp)
+{
+    DataObjectList *list, *last_entry, *entry;
+    QObject *data;
+    int i;
+
+    list = NULL;
+    for (i = 0; qmp_schema_table[i]; i++) {
+        data = qobject_from_json(qmp_schema_table[i]);
+        assert(data != NULL);
+
+        entry = g_malloc0(sizeof(DataObjectList *));
+        memset(visit_path_str, 0, sizeof(visit_path_str));
+        entry->value = visit_qobj_dict(data);
+        entry->next = NULL;
+        if (!list) {
+            list = entry;
+        } else {
+            last_entry->next = entry;
+        }
+        last_entry = entry;
+    }
+
+    return list;
+}
+
 void qmp_add_client(const char *protocol, const char *fdname,
                     bool has_skipauth, bool skipauth, bool has_tls, bool tls,
                     Error **errp)
