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

7548c0
 
7548c0
+    

ACPI Heterogeneous Memory Attribute Table

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

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

7548c0
+
7548c0
+    

7548c0
+      The cache element then has following mandatory attributes:
7548c0
+    

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

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

7548c0
+
7548c0
+    

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

7548c0
+
7548c0
+    

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

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

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

7548c0
+
7548c0
     

Events configuration

7548c0
 
7548c0
     

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