render / rpms / libvirt

Forked from rpms/libvirt 9 months ago
Clone
397dc2
From 17e9b949ec3876e74bcaa217810afbd46f297a65 Mon Sep 17 00:00:00 2001
397dc2
Message-Id: <17e9b949ec3876e74bcaa217810afbd46f297a65@dist-git>
397dc2
From: Michal Privoznik <mprivozn@redhat.com>
397dc2
Date: Wed, 7 Oct 2020 18:45:39 +0200
397dc2
Subject: [PATCH] conf: Parse and format HMAT
397dc2
MIME-Version: 1.0
397dc2
Content-Type: text/plain; charset=UTF-8
397dc2
Content-Transfer-Encoding: 8bit
397dc2
397dc2
To cite ACPI specification:
397dc2
397dc2
  Heterogeneous Memory Attribute Table describes the memory
397dc2
  attributes, such as memory side cache attributes and bandwidth
397dc2
  and latency details, related to the System Physical Address
397dc2
  (SPA) Memory Ranges. The software is expected to use this
397dc2
  information as hint for optimization.
397dc2
397dc2
According to our upstream discussion [1] this is exposed under
397dc2
<numa/> as <cache/> under NUMA <cell/> and <latency> or
397dc2
<bandwidth/> under numa/latencies.
397dc2
397dc2
1: https://www.redhat.com/archives/libvir-list/2020-January/msg00422.html
397dc2
397dc2
Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
397dc2
Reviewed-by: Daniel Henrique Barboza <danielhb413@gmail.com>
397dc2
(cherry picked from commit a89bbbac86383a10be0cec5a93feb7ed820871eb)
397dc2
397dc2
Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1749518
397dc2
397dc2
Conflicts:
397dc2
- src/conf/numa_conf.c: Context, because we're not using
397dc2
VIR_XPATH_NODE_AUTORESTORE() everywhere in the old code.
397dc2
397dc2
Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
397dc2
Message-Id: <f5d9028ca8eff876c2bd471460629d0ef3b20630.1602087923.git.mprivozn@redhat.com>
397dc2
Reviewed-by: Ján Tomko <jtomko@redhat.com>
397dc2
---
397dc2
 docs/formatdomain.html.in                  | 107 +++++++
397dc2
 docs/schemas/cputypes.rng                  | 110 ++++++-
397dc2
 src/conf/numa_conf.c                       | 349 ++++++++++++++++++++-
397dc2
 src/conf/numa_conf.h                       |  33 ++
397dc2
 src/libvirt_private.syms                   |   6 +
397dc2
 tests/qemuxml2argvdata/numatune-hmat.xml   |  52 +++
397dc2
 tests/qemuxml2xmloutdata/numatune-hmat.xml |   1 +
397dc2
 tests/qemuxml2xmltest.c                    |   1 +
397dc2
 8 files changed, 644 insertions(+), 15 deletions(-)
397dc2
 create mode 100644 tests/qemuxml2argvdata/numatune-hmat.xml
397dc2
 create mode 120000 tests/qemuxml2xmloutdata/numatune-hmat.xml
397dc2
397dc2
diff --git a/docs/formatdomain.html.in b/docs/formatdomain.html.in
397dc2
index 4b8d312596..bec753e37f 100644
397dc2
--- a/docs/formatdomain.html.in
397dc2
+++ b/docs/formatdomain.html.in
397dc2
@@ -1874,6 +1874,113 @@
397dc2
       using 10 for local and 20 for remote distances.
397dc2
     

397dc2
 
397dc2
+    

ACPI Heterogeneous Memory Attribute Table

397dc2
+
397dc2
+
397dc2
+...
397dc2
+<cpu>
397dc2
+  ...
397dc2
+  <numa>
397dc2
+    <cell id='0' cpus='0-3' memory='512000' unit='KiB' discard='yes'/>
397dc2
+    <cell id='1' cpus='4-7' memory='512000' unit='KiB' memAccess='shared'/>
397dc2
+    <cell id='3' cpus='0-3' memory='2097152' unit='KiB'>
397dc2
+      <cache level='1' associativity='direct' policy='writeback'>
397dc2
+        <size value='10' unit='KiB'/>
397dc2
+        <line value='8' unit='B'/>
397dc2
+      </cache>
397dc2
+    </cell>
397dc2
+    <interconnects>
397dc2
+      <latency initiator='0' target='0' type='access' value='5'/>
397dc2
+      <latency initiator='0' target='0' cache='1' type='access' value='10'/>
397dc2
+      <bandwidth initiator='0' target='0' type='access' value='204800' unit='KiB'/>
397dc2
+    </interconnects>
397dc2
+  </numa>
397dc2
+  ...
397dc2
+</cpu>
397dc2
+...
397dc2
+
397dc2
+    

397dc2
+      Since 6.6.0 the cell element can
397dc2
+      have a cache child element which describes memory side cache
397dc2
+      for memory proximity domains. The cache element has a
397dc2
+      level attribute describing the cache level and thus the
397dc2
+      element can be repeated multiple times to describe different levels of
397dc2
+      the cache.
397dc2
+    

397dc2
+
397dc2
+    

397dc2
+      The cache element then has following mandatory attributes:
397dc2
+    

397dc2
+
397dc2
+    
397dc2
+      
level
397dc2
+      
397dc2
+        Level of the cache this description refers to.
397dc2
+      
397dc2
+
397dc2
+      
associativity
397dc2
+      
397dc2
+        Describes cache associativity (accepted values are none,
397dc2
+        direct and full).
397dc2
+      
397dc2
+
397dc2
+      
policy
397dc2
+      
397dc2
+        Describes cache write associativity (accepted values are
397dc2
+        none, writeback and
397dc2
+        writethrough).
397dc2
+      
397dc2
+    
397dc2
+
397dc2
+    

397dc2
+      The cache element has two mandatory child elements then:
397dc2
+      size and line which describe cache size and
397dc2
+      cache line size. Both elements accept two attributes: value
397dc2
+      and unit which set the value of corresponding cache
397dc2
+      attribute.
397dc2
+    

397dc2
+
397dc2
+    

397dc2
+      The NUMA description has an optional interconnects element that
397dc2
+      describes the normalized memory read/write latency, read/write bandwidth
397dc2
+      between Initiator Proximity Domains (Processor or I/O) and Target
397dc2
+      Proximity Domains (Memory).
397dc2
+    

397dc2
+
397dc2
+    

397dc2
+      The interconnects element can have zero or more
397dc2
+      latency child elements to describe latency between two
397dc2
+      memory nodes and zero or more bandwidth child elements to
397dc2
+      describe bandwidth between two memory nodes. Both these have the
397dc2
+      following mandatory attributes:
397dc2
+    

397dc2
+
397dc2
+    
397dc2
+      
initiator
397dc2
+      
Refers to the source NUMA node
397dc2
+
397dc2
+      
target
397dc2
+      
Refers to the target NUMA node
397dc2
+
397dc2
+      
type
397dc2
+      
The type of the access. Accepted values: access,
397dc2
+      read, write
397dc2
+
397dc2
+      
value
397dc2
+      
The actual value. For latency this is delay in nanoseconds, for
397dc2
+      bandwidth this value is in kibibytes per second. Use additional
397dc2
+      unit attribute to change the units.
397dc2
+    
397dc2
+
397dc2
+    

397dc2
+      To describe latency from one NUMA node to a cache of another NUMA node
397dc2
+      the latency element has optional cache
397dc2
+      attribute which in combination with target attribute creates
397dc2
+      full reference to distant NUMA node's cache level. For instance,
397dc2
+      target='0' cache='1' refers to the first level cache of NUMA
397dc2
+      node 0.
397dc2
+    

397dc2
+
397dc2
     

Events configuration

397dc2
 
397dc2
     

397dc2
diff --git a/docs/schemas/cputypes.rng b/docs/schemas/cputypes.rng
397dc2
index a1682a1003..ba30dbf9ff 100644
397dc2
--- a/docs/schemas/cputypes.rng
397dc2
+++ b/docs/schemas/cputypes.rng
397dc2
@@ -102,9 +102,14 @@
397dc2
 
397dc2
   <define name="cpuNuma">
397dc2
     <element name="numa">
397dc2
-      <oneOrMore>
397dc2
-        <ref name="numaCell"/>
397dc2
-      </oneOrMore>
397dc2
+      <interleave>
397dc2
+        <oneOrMore>
397dc2
+          <ref name="numaCell"/>
397dc2
+        </oneOrMore>
397dc2
+        <optional>
397dc2
+          <ref name="numaInterconnects"/>
397dc2
+        </optional>
397dc2
+      </interleave>
397dc2
     </element>
397dc2
   </define>
397dc2
 
397dc2
@@ -148,6 +153,9 @@
397dc2
           </oneOrMore>
397dc2
         </element>
397dc2
       </optional>
397dc2
+      <zeroOrMore>
397dc2
+        <ref name="numaCache"/>
397dc2
+      </zeroOrMore>
397dc2
     </element>
397dc2
   </define>
397dc2
 
397dc2
@@ -162,6 +170,102 @@
397dc2
     </element>
397dc2
   </define>
397dc2
 
397dc2
+  <define name="numaCache">
397dc2
+    <element name="cache">
397dc2
+      <attribute name="level">
397dc2
+        <ref name="unsignedInt"/>
397dc2
+      </attribute>
397dc2
+      <attribute name="associativity">
397dc2
+        <choice>
397dc2
+          <value>none</value>
397dc2
+          <value>direct</value>
397dc2
+          <value>full</value>
397dc2
+        </choice>
397dc2
+      </attribute>
397dc2
+      <attribute name="policy">
397dc2
+        <choice>
397dc2
+          <value>none</value>
397dc2
+          <value>writeback</value>
397dc2
+          <value>writethrough</value>
397dc2
+        </choice>
397dc2
+      </attribute>
397dc2
+      <interleave>
397dc2
+        <element name="size">
397dc2
+          <attribute name="value">
397dc2
+            <ref name="unsignedInt"/>
397dc2
+          </attribute>
397dc2
+          <attribute name="unit">
397dc2
+            <ref name="unit"/>
397dc2
+          </attribute>
397dc2
+        </element>
397dc2
+        <element name="line">
397dc2
+          <attribute name="value">
397dc2
+            <ref name="unsignedInt"/>
397dc2
+          </attribute>
397dc2
+          <attribute name="unit">
397dc2
+            <ref name="unit"/>
397dc2
+          </attribute>
397dc2
+        </element>
397dc2
+      </interleave>
397dc2
+    </element>
397dc2
+  </define>
397dc2
+
397dc2
+  <define name="numaInterconnects">
397dc2
+    <element name="interconnects">
397dc2
+      <interleave>
397dc2
+        <zeroOrMore>
397dc2
+          <element name="latency">
397dc2
+            <attribute name="initiator">
397dc2
+              <ref name="unsignedInt"/>
397dc2
+            </attribute>
397dc2
+            <attribute name="target">
397dc2
+              <ref name="unsignedInt"/>
397dc2
+            </attribute>
397dc2
+            <optional>
397dc2
+              <attribute name="cache">
397dc2
+                <ref name="unsignedInt"/>
397dc2
+              </attribute>
397dc2
+            </optional>
397dc2
+            <attribute name="type">
397dc2
+              <choice>
397dc2
+                <value>access</value>
397dc2
+                <value>read</value>
397dc2
+                <value>write</value>
397dc2
+              </choice>
397dc2
+            </attribute>
397dc2
+            <attribute name="value">
397dc2
+              <ref name="unsignedInt"/>
397dc2
+            </attribute>
397dc2
+            <empty/>
397dc2
+          </element>
397dc2
+        </zeroOrMore>
397dc2
+        <zeroOrMore>
397dc2
+          <element name="bandwidth">
397dc2
+            <attribute name="initiator">
397dc2
+              <ref name="unsignedInt"/>
397dc2
+            </attribute>
397dc2
+            <attribute name="target">
397dc2
+              <ref name="unsignedInt"/>
397dc2
+            </attribute>
397dc2
+            <attribute name="type">
397dc2
+              <choice>
397dc2
+                <value>access</value>
397dc2
+                <value>read</value>
397dc2
+                <value>write</value>
397dc2
+              </choice>
397dc2
+            </attribute>
397dc2
+            <attribute name="value">
397dc2
+              <ref name="unsignedInt"/>
397dc2
+            </attribute>
397dc2
+            <attribute name="unit">
397dc2
+              <ref name="unit"/>
397dc2
+            </attribute>
397dc2
+          </element>
397dc2
+        </zeroOrMore>
397dc2
+      </interleave>
397dc2
+    </element>
397dc2
+  </define>
397dc2
+
397dc2
   
397dc2
   <define name="memoryKB">
397dc2
     <data type="unsignedLong"/>
397dc2
diff --git a/src/conf/numa_conf.c b/src/conf/numa_conf.c
397dc2
index a805336d16..5c764190c3 100644
397dc2
--- a/src/conf/numa_conf.c
397dc2
+++ b/src/conf/numa_conf.c
397dc2
@@ -59,9 +59,37 @@ VIR_ENUM_IMPL(virDomainMemoryAccess,
397dc2
               "private",
397dc2
 );
397dc2
 
397dc2
+VIR_ENUM_IMPL(virDomainCacheAssociativity,
397dc2
+              VIR_DOMAIN_CACHE_ASSOCIATIVITY_LAST,
397dc2
+              "none",
397dc2
+              "direct",
397dc2
+              "full",
397dc2
+);
397dc2
+
397dc2
+VIR_ENUM_IMPL(virDomainCachePolicy,
397dc2
+              VIR_DOMAIN_CACHE_POLICY_LAST,
397dc2
+              "none",
397dc2
+              "writeback",
397dc2
+              "writethrough",
397dc2
+);
397dc2
+
397dc2
+VIR_ENUM_IMPL(virDomainMemoryLatency,
397dc2
+              VIR_DOMAIN_MEMORY_LATENCY_LAST,
397dc2
+              "none",
397dc2
+              "access",
397dc2
+              "read",
397dc2
+              "write"
397dc2
+);
397dc2
+
397dc2
 typedef struct _virDomainNumaDistance virDomainNumaDistance;
397dc2
 typedef virDomainNumaDistance *virDomainNumaDistancePtr;
397dc2
 
397dc2
+typedef struct _virDomainNumaCache virDomainNumaCache;
397dc2
+typedef virDomainNumaCache *virDomainNumaCachePtr;
397dc2
+
397dc2
+typedef struct _virDomainNumaInterconnect virDomainNumaInterconnect;
397dc2
+typedef virDomainNumaInterconnect *virDomainNumaInterconnectPtr;
397dc2
+
397dc2
 typedef struct _virDomainNumaNode virDomainNumaNode;
397dc2
 typedef virDomainNumaNode *virDomainNumaNodePtr;
397dc2
 
397dc2
@@ -86,9 +114,30 @@ struct _virDomainNuma {
397dc2
             unsigned int cellid;
397dc2
         } *distances;           /* remote node distances */
397dc2
         size_t ndistances;
397dc2
+
397dc2
+        struct _virDomainNumaCache {
397dc2
+            unsigned int level; /* cache level */
397dc2
+            unsigned int size;  /* cache size */
397dc2
+            unsigned int line;  /* line size, !!! in bytes !!! */
397dc2
+            virDomainCacheAssociativity associativity; /* cache associativity */
397dc2
+            virDomainCachePolicy policy; /* cache policy */
397dc2
+        } *caches;
397dc2
+        size_t ncaches;
397dc2
     } *mem_nodes;           /* guest node configuration */
397dc2
     size_t nmem_nodes;
397dc2
 
397dc2
+    struct _virDomainNumaInterconnect {
397dc2
+        virDomainNumaInterconnectType type;  /* whether structure describes latency
397dc2
+                                                or bandwidth */
397dc2
+        unsigned int initiator; /* the initiator NUMA node */
397dc2
+        unsigned int target;    /* the target NUMA node */
397dc2
+        unsigned int cache;     /* the target cache on @target; if 0 then the
397dc2
+                                   memory on @target */
397dc2
+        virDomainMemoryLatency accessType;  /* what type of access is defined */
397dc2
+        unsigned long value;    /* value itself */
397dc2
+    } *interconnects;
397dc2
+    size_t ninterconnects;
397dc2
+
397dc2
     /* Future NUMA tuning related stuff should go here. */
397dc2
 };
397dc2
 
397dc2
@@ -368,9 +417,13 @@ virDomainNumaFree(virDomainNumaPtr numa)
397dc2
 
397dc2
         if (numa->mem_nodes[i].ndistances > 0)
397dc2
             VIR_FREE(numa->mem_nodes[i].distances);
397dc2
+
397dc2
+        VIR_FREE(numa->mem_nodes[i].caches);
397dc2
     }
397dc2
     VIR_FREE(numa->mem_nodes);
397dc2
 
397dc2
+    VIR_FREE(numa->interconnects);
397dc2
+
397dc2
     VIR_FREE(numa);
397dc2
 }
397dc2
 
397dc2
@@ -841,6 +894,97 @@ virDomainNumaDefNodeDistanceParseXML(virDomainNumaPtr def,
397dc2
     return ret;
397dc2
 }
397dc2
 
397dc2
+
397dc2
+static int
397dc2
+virDomainNumaDefNodeCacheParseXML(virDomainNumaPtr def,
397dc2
+                                  xmlXPathContextPtr ctxt,
397dc2
+                                  unsigned int cur_cell)
397dc2
+{
397dc2
+    g_autofree xmlNodePtr *nodes = NULL;
397dc2
+    int n;
397dc2
+    size_t i;
397dc2
+
397dc2
+    if ((n = virXPathNodeSet("./cache", ctxt, &nodes)) < 0)
397dc2
+        return -1;
397dc2
+
397dc2
+    def->mem_nodes[cur_cell].caches = g_new0(virDomainNumaCache, n);
397dc2
+
397dc2
+    for (i = 0; i < n; i++) {
397dc2
+        VIR_XPATH_NODE_AUTORESTORE(ctxt);
397dc2
+        virDomainNumaCachePtr cache = &def->mem_nodes[cur_cell].caches[i];
397dc2
+        g_autofree char *tmp = NULL;
397dc2
+        unsigned int level;
397dc2
+        int associativity;
397dc2
+        int policy;
397dc2
+        unsigned long long size;
397dc2
+        unsigned long long line;
397dc2
+
397dc2
+        if (!(tmp = virXMLPropString(nodes[i], "level"))) {
397dc2
+            virReportError(VIR_ERR_XML_ERROR,
397dc2
+                           _("Missing 'level' attribute in cache "
397dc2
+                             "element for NUMA node %d"),
397dc2
+                           cur_cell);
397dc2
+            return -1;
397dc2
+        }
397dc2
+
397dc2
+        if (virStrToLong_uip(tmp, NULL, 10, &level) < 0 ||
397dc2
+            level == 0) {
397dc2
+            virReportError(VIR_ERR_XML_ERROR,
397dc2
+                           _("Invalid 'level' attribute in cache "
397dc2
+                             "element for NUMA node %d"),
397dc2
+                           cur_cell);
397dc2
+            return -1;
397dc2
+        }
397dc2
+        VIR_FREE(tmp);
397dc2
+
397dc2
+        if (!(tmp = virXMLPropString(nodes[i], "associativity"))) {
397dc2
+            virReportError(VIR_ERR_XML_ERROR,
397dc2
+                           _("Missing 'associativity' attribute in cache "
397dc2
+                             "element for NUMA node %d"),
397dc2
+                           cur_cell);
397dc2
+            return -1;
397dc2
+        }
397dc2
+
397dc2
+        if ((associativity = virDomainCacheAssociativityTypeFromString(tmp)) < 0) {
397dc2
+            virReportError(VIR_ERR_XML_ERROR,
397dc2
+                           _("Invalid cache associativity '%s'"),
397dc2
+                           tmp);
397dc2
+            return -1;
397dc2
+        }
397dc2
+        VIR_FREE(tmp);
397dc2
+
397dc2
+        if (!(tmp = virXMLPropString(nodes[i], "policy"))) {
397dc2
+            virReportError(VIR_ERR_XML_ERROR,
397dc2
+                           _("Missing 'policy' attribute in cache "
397dc2
+                             "element for NUMA node %d"),
397dc2
+                           cur_cell);
397dc2
+        }
397dc2
+
397dc2
+        if ((policy = virDomainCachePolicyTypeFromString(tmp)) < 0) {
397dc2
+            virReportError(VIR_ERR_XML_ERROR,
397dc2
+                           _("Invalid cache policy '%s'"),
397dc2
+                           tmp);
397dc2
+            return -1;
397dc2
+        }
397dc2
+        VIR_FREE(tmp);
397dc2
+
397dc2
+        ctxt->node = nodes[i];
397dc2
+        if (virDomainParseMemory("./size/@value", "./size/unit",
397dc2
+                                 ctxt, &size, true, false) < 0)
397dc2
+            return -1;
397dc2
+
397dc2
+        if (virParseScaledValue("./line/@value", "./line/unit",
397dc2
+                                ctxt, &line, 1, ULLONG_MAX, true) < 0)
397dc2
+            return -1;
397dc2
+
397dc2
+        *cache = (virDomainNumaCache){level, size, line, associativity, policy};
397dc2
+        def->mem_nodes[cur_cell].ncaches++;
397dc2
+    }
397dc2
+
397dc2
+    return 0;
397dc2
+}
397dc2
+
397dc2
+
397dc2
 int
397dc2
 virDomainNumaDefParseXML(virDomainNumaPtr def,
397dc2
                          xmlXPathContextPtr ctxt)
397dc2
@@ -867,6 +1011,7 @@ virDomainNumaDefParseXML(virDomainNumaPtr def,
397dc2
     def->nmem_nodes = n;
397dc2
 
397dc2
     for (i = 0; i < n; i++) {
397dc2
+        VIR_XPATH_NODE_AUTORESTORE(ctxt);
397dc2
         int rc;
397dc2
         unsigned int cur_cell = i;
397dc2
 
397dc2
@@ -953,7 +1098,109 @@ virDomainNumaDefParseXML(virDomainNumaPtr def,
397dc2
 
397dc2
         /* Parse NUMA distances info */
397dc2
         if (virDomainNumaDefNodeDistanceParseXML(def, ctxt, cur_cell) < 0)
397dc2
+            goto cleanup;
397dc2
+
397dc2
+        /* Parse cache info */
397dc2
+        if (virDomainNumaDefNodeCacheParseXML(def, ctxt, cur_cell) < 0)
397dc2
+            goto cleanup;
397dc2
+    }
397dc2
+
397dc2
+    VIR_FREE(nodes);
397dc2
+    if ((n = virXPathNodeSet("./cpu/numa[1]/interconnects[1]/latency|"
397dc2
+                             "./cpu/numa[1]/interconnects[1]/bandwidth", ctxt, &nodes)) < 0)
397dc2
+        goto cleanup;
397dc2
+
397dc2
+    def->interconnects = g_new0(virDomainNumaInterconnect, n);
397dc2
+    for (i = 0; i < n; i++) {
397dc2
+        virDomainNumaInterconnectType type;
397dc2
+        unsigned int initiator;
397dc2
+        unsigned int target;
397dc2
+        unsigned int cache = 0;
397dc2
+        int accessType;
397dc2
+        unsigned long long value;
397dc2
+
397dc2
+        if (virXMLNodeNameEqual(nodes[i], "latency")) {
397dc2
+            type = VIR_DOMAIN_NUMA_INTERCONNECT_TYPE_LATENCY;
397dc2
+
397dc2
+            if (!(tmp = virXMLPropString(nodes[i], "value"))) {
397dc2
+                virReportError(VIR_ERR_XML_ERROR, "%s",
397dc2
+                               _("Missing 'value' attribute in NUMA interconnects"));
397dc2
                 goto cleanup;
397dc2
+            }
397dc2
+
397dc2
+            if (virStrToLong_ullp(tmp, NULL, 10, &value) < 0) {
397dc2
+                virReportError(VIR_ERR_XML_ERROR, "%s",
397dc2
+                               _("Invalid 'value' attribute in NUMA interconnects"));
397dc2
+                goto cleanup;
397dc2
+            }
397dc2
+            VIR_FREE(tmp);
397dc2
+        } else if (virXMLNodeNameEqual(nodes[i], "bandwidth")) {
397dc2
+            VIR_XPATH_NODE_AUTORESTORE(ctxt);
397dc2
+            type = VIR_DOMAIN_NUMA_INTERCONNECT_TYPE_BANDWIDTH;
397dc2
+
397dc2
+            ctxt->node = nodes[i];
397dc2
+
397dc2
+            if (virDomainParseMemory("./@value", "./@unit", ctxt, &value, true, false) < 0)
397dc2
+                goto cleanup;
397dc2
+        } else {
397dc2
+            /* Ignore yet unknown child elements. */
397dc2
+            continue;
397dc2
+        }
397dc2
+
397dc2
+        if (!(tmp = virXMLPropString(nodes[i], "initiator"))) {
397dc2
+            virReportError(VIR_ERR_XML_ERROR, "%s",
397dc2
+                           _("Missing 'initiator' attribute in NUMA interconnects"));
397dc2
+            goto cleanup;
397dc2
+        }
397dc2
+
397dc2
+        if (virStrToLong_uip(tmp, NULL, 10, &initiator) < 0) {
397dc2
+            virReportError(VIR_ERR_XML_ERROR, "%s",
397dc2
+                           _("Invalid 'initiator' attribute in NUMA interconnects"));
397dc2
+            goto cleanup;
397dc2
+        }
397dc2
+        VIR_FREE(tmp);
397dc2
+
397dc2
+        if (!(tmp = virXMLPropString(nodes[i], "target"))) {
397dc2
+            virReportError(VIR_ERR_XML_ERROR, "%s",
397dc2
+                           _("Missing 'target' attribute in NUMA interconnects"));
397dc2
+            goto cleanup;
397dc2
+        }
397dc2
+
397dc2
+        if (virStrToLong_uip(tmp, NULL, 10, &target) < 0) {
397dc2
+            virReportError(VIR_ERR_XML_ERROR, "%s",
397dc2
+                           _("Invalid 'target' attribute in NUMA interconnects"));
397dc2
+            goto cleanup;
397dc2
+        }
397dc2
+        VIR_FREE(tmp);
397dc2
+
397dc2
+
397dc2
+        /* cache attribute is optional */
397dc2
+        if ((tmp = virXMLPropString(nodes[i], "cache"))) {
397dc2
+            if (virStrToLong_uip(tmp, NULL, 10, &cache) < 0 ||
397dc2
+                cache == 0) {
397dc2
+                virReportError(VIR_ERR_XML_ERROR, "%s",
397dc2
+                               _("Invalid 'cache' attribute in NUMA interconnects"));
397dc2
+                goto cleanup;
397dc2
+            }
397dc2
+        }
397dc2
+        VIR_FREE(tmp);
397dc2
+
397dc2
+        if (!(tmp = virXMLPropString(nodes[i], "type"))) {
397dc2
+            virReportError(VIR_ERR_XML_ERROR, "%s",
397dc2
+                           _("Missing 'type' attribute in NUMA interconnects"));
397dc2
+            goto cleanup;
397dc2
+        }
397dc2
+
397dc2
+        if ((accessType = virDomainMemoryLatencyTypeFromString(tmp)) <= 0) {
397dc2
+            virReportError(VIR_ERR_XML_ERROR, "%s",
397dc2
+                           _("Invalid 'type' attribute in NUMA interconnects"));
397dc2
+            goto cleanup;
397dc2
+        }
397dc2
+        VIR_FREE(tmp);
397dc2
+
397dc2
+        def->interconnects[i] = (virDomainNumaInterconnect) {type, initiator, target,
397dc2
+                                                             cache, accessType, value};
397dc2
+        def->ninterconnects++;
397dc2
     }
397dc2
 
397dc2
     ret = 0;
397dc2
@@ -983,6 +1230,7 @@ virDomainNumaDefFormatXML(virBufferPtr buf,
397dc2
     for (i = 0; i < ncells; i++) {
397dc2
         virBitmapPtr cpumask = virDomainNumaGetNodeCpumask(def, i);
397dc2
         int ndistances;
397dc2
+        size_t ncaches;
397dc2
 
397dc2
         memAccess = virDomainNumaGetNodeMemoryAccessMode(def, i);
397dc2
         discard = virDomainNumaGetNodeDiscard(def, i);
397dc2
@@ -1009,30 +1257,107 @@ virDomainNumaDefFormatXML(virBufferPtr buf,
397dc2
                               virTristateBoolTypeToString(discard));
397dc2
 
397dc2
         ndistances = def->mem_nodes[i].ndistances;
397dc2
-        if (ndistances == 0) {
397dc2
+        ncaches = def->mem_nodes[i].ncaches;
397dc2
+        if (ndistances == 0 && ncaches == 0) {
397dc2
             virBufferAddLit(buf, "/>\n");
397dc2
         } else {
397dc2
             size_t j;
397dc2
-            virDomainNumaDistancePtr distances = def->mem_nodes[i].distances;
397dc2
 
397dc2
             virBufferAddLit(buf, ">\n");
397dc2
             virBufferAdjustIndent(buf, 2);
397dc2
-            virBufferAddLit(buf, "<distances>\n");
397dc2
-            virBufferAdjustIndent(buf, 2);
397dc2
-            for (j = 0; j < ndistances; j++) {
397dc2
-                if (distances[j].value) {
397dc2
-                    virBufferAddLit(buf, "
397dc2
-                    virBufferAsprintf(buf, " id='%d'", distances[j].cellid);
397dc2
-                    virBufferAsprintf(buf, " value='%d'", distances[j].value);
397dc2
-                    virBufferAddLit(buf, "/>\n");
397dc2
+
397dc2
+            if (ndistances) {
397dc2
+                virDomainNumaDistancePtr distances = def->mem_nodes[i].distances;
397dc2
+
397dc2
+                virBufferAddLit(buf, "<distances>\n");
397dc2
+                virBufferAdjustIndent(buf, 2);
397dc2
+                for (j = 0; j < ndistances; j++) {
397dc2
+                    if (distances[j].value) {
397dc2
+                        virBufferAddLit(buf, "
397dc2
+                        virBufferAsprintf(buf, " id='%d'", distances[j].cellid);
397dc2
+                        virBufferAsprintf(buf, " value='%d'", distances[j].value);
397dc2
+                        virBufferAddLit(buf, "/>\n");
397dc2
+                    }
397dc2
                 }
397dc2
+                virBufferAdjustIndent(buf, -2);
397dc2
+                virBufferAddLit(buf, "</distances>\n");
397dc2
+            }
397dc2
+
397dc2
+            for (j = 0; j < ncaches; j++) {
397dc2
+                virDomainNumaCachePtr cache = &def->mem_nodes[i].caches[j];
397dc2
+
397dc2
+                virBufferAsprintf(buf, "<cache level='%u'", cache->level);
397dc2
+                if (cache->associativity) {
397dc2
+                    virBufferAsprintf(buf, " associativity='%s'",
397dc2
+                                      virDomainCacheAssociativityTypeToString(cache->associativity));
397dc2
+                }
397dc2
+
397dc2
+                if (cache->policy) {
397dc2
+                    virBufferAsprintf(buf, " policy='%s'",
397dc2
+                                      virDomainCachePolicyTypeToString(cache->policy));
397dc2
+                }
397dc2
+                virBufferAddLit(buf, ">\n");
397dc2
+
397dc2
+                virBufferAdjustIndent(buf, 2);
397dc2
+                virBufferAsprintf(buf,
397dc2
+                                  "<size value='%u' unit='KiB'/>\n",
397dc2
+                                  cache->size);
397dc2
+
397dc2
+                if (cache->line) {
397dc2
+                    virBufferAsprintf(buf,
397dc2
+                                      "<line value='%u' unit='B'/>\n",
397dc2
+                                      cache->line);
397dc2
+                }
397dc2
+
397dc2
+                virBufferAdjustIndent(buf, -2);
397dc2
+                virBufferAddLit(buf, "</cache>\n");
397dc2
             }
397dc2
-            virBufferAdjustIndent(buf, -2);
397dc2
-            virBufferAddLit(buf, "</distances>\n");
397dc2
             virBufferAdjustIndent(buf, -2);
397dc2
             virBufferAddLit(buf, "</cell>\n");
397dc2
         }
397dc2
     }
397dc2
+
397dc2
+    if (def->ninterconnects) {
397dc2
+        virBufferAddLit(buf, "<interconnects>\n");
397dc2
+        virBufferAdjustIndent(buf, 2);
397dc2
+    }
397dc2
+
397dc2
+    for (i = 0; i < def->ninterconnects; i++) {
397dc2
+        virDomainNumaInterconnectPtr l = &def->interconnects[i];
397dc2
+
397dc2
+        switch (l->type) {
397dc2
+        case VIR_DOMAIN_NUMA_INTERCONNECT_TYPE_LATENCY:
397dc2
+            virBufferAddLit(buf, "
397dc2
+            break;
397dc2
+        case VIR_DOMAIN_NUMA_INTERCONNECT_TYPE_BANDWIDTH:
397dc2
+            virBufferAddLit(buf, "
397dc2
+        }
397dc2
+
397dc2
+        virBufferAsprintf(buf,
397dc2
+                          " initiator='%u' target='%u'",
397dc2
+                          l->initiator, l->target);
397dc2
+
397dc2
+        if (l->cache > 0) {
397dc2
+            virBufferAsprintf(buf,
397dc2
+                              " cache='%u'",
397dc2
+                              l->cache);
397dc2
+        }
397dc2
+
397dc2
+        virBufferAsprintf(buf,
397dc2
+                          " type='%s' value='%lu'",
397dc2
+                          virDomainMemoryLatencyTypeToString(l->accessType),
397dc2
+                          l->value);
397dc2
+
397dc2
+        if (l->type == VIR_DOMAIN_NUMA_INTERCONNECT_TYPE_BANDWIDTH)
397dc2
+            virBufferAddLit(buf, " unit='KiB'");
397dc2
+        virBufferAddLit(buf, "/>\n");
397dc2
+    }
397dc2
+
397dc2
+    if (def->ninterconnects) {
397dc2
+        virBufferAdjustIndent(buf, -2);
397dc2
+        virBufferAddLit(buf, "</interconnects>\n");
397dc2
+    }
397dc2
+
397dc2
     virBufferAdjustIndent(buf, -2);
397dc2
     virBufferAddLit(buf, "</numa>\n");
397dc2
 
397dc2
diff --git a/src/conf/numa_conf.h b/src/conf/numa_conf.h
397dc2
index 6808439a7c..5043c5a6d4 100644
397dc2
--- a/src/conf/numa_conf.h
397dc2
+++ b/src/conf/numa_conf.h
397dc2
@@ -52,6 +52,39 @@ typedef enum {
397dc2
 } virDomainMemoryAccess;
397dc2
 VIR_ENUM_DECL(virDomainMemoryAccess);
397dc2
 
397dc2
+typedef enum {
397dc2
+    VIR_DOMAIN_CACHE_ASSOCIATIVITY_NONE,    /* No associativity */
397dc2
+    VIR_DOMAIN_CACHE_ASSOCIATIVITY_DIRECT,  /* Direct mapped cache */
397dc2
+    VIR_DOMAIN_CACHE_ASSOCIATIVITY_FULL,    /* Fully associative cache */
397dc2
+
397dc2
+    VIR_DOMAIN_CACHE_ASSOCIATIVITY_LAST
397dc2
+} virDomainCacheAssociativity;
397dc2
+VIR_ENUM_DECL(virDomainCacheAssociativity);
397dc2
+
397dc2
+typedef enum {
397dc2
+    VIR_DOMAIN_CACHE_POLICY_NONE,           /* No policy */
397dc2
+    VIR_DOMAIN_CACHE_POLICY_WRITEBACK,      /* Write-back policy */
397dc2
+    VIR_DOMAIN_CACHE_POLICY_WRITETHROUGH,   /* Write-through policy */
397dc2
+
397dc2
+    VIR_DOMAIN_CACHE_POLICY_LAST
397dc2
+} virDomainCachePolicy;
397dc2
+VIR_ENUM_DECL(virDomainCachePolicy);
397dc2
+
397dc2
+typedef enum {
397dc2
+    VIR_DOMAIN_NUMA_INTERCONNECT_TYPE_LATENCY,
397dc2
+    VIR_DOMAIN_NUMA_INTERCONNECT_TYPE_BANDWIDTH,
397dc2
+} virDomainNumaInterconnectType;
397dc2
+
397dc2
+typedef enum {
397dc2
+    VIR_DOMAIN_MEMORY_LATENCY_NONE = 0, /* No memory latency defined */
397dc2
+    VIR_DOMAIN_MEMORY_LATENCY_ACCESS,   /* Access latency */
397dc2
+    VIR_DOMAIN_MEMORY_LATENCY_READ,     /* Read latency */
397dc2
+    VIR_DOMAIN_MEMORY_LATENCY_WRITE,    /* Write latency */
397dc2
+
397dc2
+    VIR_DOMAIN_MEMORY_LATENCY_LAST
397dc2
+} virDomainMemoryLatency;
397dc2
+VIR_ENUM_DECL(virDomainMemoryLatency);
397dc2
+
397dc2
 
397dc2
 virDomainNumaPtr virDomainNumaNew(void);
397dc2
 void virDomainNumaFree(virDomainNumaPtr numa);
397dc2
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
397dc2
index acb25eb8c8..de95e3b116 100644
397dc2
--- a/src/libvirt_private.syms
397dc2
+++ b/src/libvirt_private.syms
397dc2
@@ -808,8 +808,14 @@ virNodeDeviceDeleteVport;
397dc2
 virNodeDeviceGetParentName;
397dc2
 
397dc2
 # conf/numa_conf.h
397dc2
+virDomainCacheAssociativityTypeFromString;
397dc2
+virDomainCacheAssociativityTypeToString;
397dc2
+virDomainCachePolicyTypeFromString;
397dc2
+virDomainCachePolicyTypeToString;
397dc2
 virDomainMemoryAccessTypeFromString;
397dc2
 virDomainMemoryAccessTypeToString;
397dc2
+virDomainMemoryLatencyTypeFromString;
397dc2
+virDomainMemoryLatencyTypeToString;
397dc2
 virDomainNumaCheckABIStability;
397dc2
 virDomainNumaEquals;
397dc2
 virDomainNumaFree;
397dc2
diff --git a/tests/qemuxml2argvdata/numatune-hmat.xml b/tests/qemuxml2argvdata/numatune-hmat.xml
397dc2
new file mode 100644
397dc2
index 0000000000..83f0b56c9b
397dc2
--- /dev/null
397dc2
+++ b/tests/qemuxml2argvdata/numatune-hmat.xml
397dc2
@@ -0,0 +1,52 @@
397dc2
+<domain type='qemu'>
397dc2
+  <name>QEMUGuest</name>
397dc2
+  <uuid>c7a5fdb2-cdaf-9455-926a-d65c16db1809</uuid>
397dc2
+  <memory unit='KiB'>8388608</memory>
397dc2
+  <currentMemory unit='KiB'>8388608</currentMemory>
397dc2
+  <vcpu placement='static'>12</vcpu>
397dc2
+  <os>
397dc2
+    <type arch='x86_64' machine='pc'>hvm</type>
397dc2
+    <boot dev='hd'/>
397dc2
+  </os>
397dc2
+  <features>
397dc2
+    <acpi/>
397dc2
+    <apic/>
397dc2
+    <pae/>
397dc2
+  </features>
397dc2
+  <cpu>
397dc2
+    <numa>
397dc2
+      <cell id='0' cpus='0-3' memory='2097152' unit='KiB'>
397dc2
+        <cache level='1' associativity='direct' policy='writeback'>
397dc2
+          <size value='10' unit='KiB'/>
397dc2
+          <line value='8' unit='B'/>
397dc2
+        </cache>
397dc2
+      </cell>
397dc2
+      <cell id='1' cpus='4-7' memory='2097152' unit='KiB'/>
397dc2
+      <cell id='2' cpus='8-11' memory='2097152' unit='KiB'/>
397dc2
+      <cell id='3' memory='2097152' unit='KiB'/>
397dc2
+      <cell id='4' memory='2097152' unit='KiB'/>
397dc2
+      <cell id='5' memory='2097152' unit='KiB'/>
397dc2
+      <interconnects>
397dc2
+        <latency initiator='0' target='0' type='access' value='5'/>
397dc2
+        <latency initiator='0' target='0' cache='1' type='access' value='10'/>
397dc2
+        <bandwidth initiator='0' target='0' type='access' value='204800' unit='KiB'/>
397dc2
+      </interconnects>
397dc2
+    </numa>
397dc2
+  </cpu>
397dc2
+  <clock offset='utc'/>
397dc2
+  <on_poweroff>destroy</on_poweroff>
397dc2
+  <on_reboot>restart</on_reboot>
397dc2
+  <on_crash>restart</on_crash>
397dc2
+  <devices>
397dc2
+    <emulator>/usr/bin/qemu-system-x86_64</emulator>
397dc2
+    <controller type='usb' index='0'>
397dc2
+      <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'/>
397dc2
+    </controller>
397dc2
+    <controller type='pci' index='0' model='pci-root'/>
397dc2
+    <input type='mouse' bus='ps2'/>
397dc2
+    <input type='keyboard' bus='ps2'/>
397dc2
+    <memballoon model='virtio'>
397dc2
+      <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
397dc2
+    </memballoon>
397dc2
+  </devices>
397dc2
+</domain>
397dc2
diff --git a/tests/qemuxml2xmloutdata/numatune-hmat.xml b/tests/qemuxml2xmloutdata/numatune-hmat.xml
397dc2
new file mode 120000
397dc2
index 0000000000..6903a80ab1
397dc2
--- /dev/null
397dc2
+++ b/tests/qemuxml2xmloutdata/numatune-hmat.xml
397dc2
@@ -0,0 +1 @@
397dc2
+../qemuxml2argvdata/numatune-hmat.xml
397dc2
\ No newline at end of file
397dc2
diff --git a/tests/qemuxml2xmltest.c b/tests/qemuxml2xmltest.c
397dc2
index 1ddeba30f0..de1d720e1d 100644
397dc2
--- a/tests/qemuxml2xmltest.c
397dc2
+++ b/tests/qemuxml2xmltest.c
397dc2
@@ -1106,6 +1106,7 @@ mymain(void)
397dc2
     DO_TEST("numatune-memnode-no-memory", QEMU_CAPS_OBJECT_MEMORY_FILE);
397dc2
     DO_TEST("numatune-distances", QEMU_CAPS_NUMA, QEMU_CAPS_NUMA_DIST);
397dc2
     DO_TEST("numatune-no-vcpu", QEMU_CAPS_NUMA);
397dc2
+    DO_TEST("numatune-hmat", NONE);
397dc2
 
397dc2
     DO_TEST("bios-nvram", NONE);
397dc2
     DO_TEST("bios-nvram-os-interleave", NONE);
397dc2
-- 
397dc2
2.29.2
397dc2