From 5cab3a84bbdd836d0a4dd773dd43bd4526477ee7 Mon Sep 17 00:00:00 2001
From: t30040867 <tujipei@huawei.com>
Date: Thu, 30 May 2024 15:07:26 +0800
Subject: [PATCH] merge

---
 docs/schemas/domaincommon.rng       |  68 ++++++++------
 include/libvirt/libvirt-host.h      |   2 +
 scripts/apibuild.py                 |   1 +
 scripts/check-aclrules.py           |   1 +
 src/conf/domain_conf.c              |  23 ++++-
 src/conf/domain_conf.h              |   3 +
 src/driver-hypervisor.h             |   5 +
 src/libvirt-host.c                  |  31 +++++++
 src/libvirt_public.syms             |   1 +
 src/qemu/qemu_command.c             |   3 +
 src/qemu/qemu_driver.c              | 139 ++++++++++++++++++++++++++++
 src/remote/remote_daemon_dispatch.c |  23 +++++
 src/remote/remote_driver.c          |  29 ++++++
 src/remote/remote_protocol.x        |  16 +++-
 tools/virsh-host.c                  |  99 ++++++++++++++++++++
 15 files changed, 410 insertions(+), 34 deletions(-)

diff --git a/docs/schemas/domaincommon.rng b/docs/schemas/domaincommon.rng
index 764f826..a0a6412 100644
--- a/docs/schemas/domaincommon.rng
+++ b/docs/schemas/domaincommon.rng
@@ -460,38 +460,48 @@
 
   <define name="launchSecurity">
     <element name="launchSecurity">
-      <attribute name="type">
-        <value>sev</value>
-      </attribute>
-      <interleave>
-        <element name="cbitpos">
-          <data type='unsignedInt'/>
-        </element>
-        <element name="reducedPhysBits">
-          <data type='unsignedInt'/>
-        </element>
-        <element name="policy">
-          <ref name='hexuint'/>
-        </element>
-        <optional>
-          <element name="handle">
-            <ref name='unsignedInt'/>
-          </element>
-        </optional>
-        <optional>
-          <element name="dhCert">
-            <data type="string"/>
-          </element>
-        </optional>
-        <optional>
-          <element name="session">
-            <data type="string"/>
-          </element>
-        </optional>
-      </interleave>
+      <choice>
+        <group>
+          <attribute name="type">
+              <value>sev</value>
+          </attribute>
+          <interleave>
+            <element name="cbitpos">
+              <data type='unsignedInt'/>
+            </element>
+            <element name="reducedPhysBits">
+              <data type='unsignedInt'/>
+            </element>
+            <element name="policy">
+              <ref name='hexuint'/>
+            </element>
+            <optional>
+              <element name="handle">
+                <ref name='unsignedInt'/>
+              </element>
+            </optional>
+            <optional>
+              <element name="dhCert">
+                <data type="string"/>
+              </element>
+            </optional>
+            <optional>
+              <element name="session">
+                <data type="string"/>
+              </element>
+            </optional>
+          </interleave>
+        </group>
+        <group>
+          <attribute name="type">
+              <value>cvm</value>
+          </attribute>
+        </group>
+      </choice>
     </element>
   </define>
 
+
   <!--
       Enable or disable perf events for the domain. For each
       of the events the following rules apply:
diff --git a/include/libvirt/libvirt-host.h b/include/libvirt/libvirt-host.h
index 6972834..2cec0bf 100644
--- a/include/libvirt/libvirt-host.h
+++ b/include/libvirt/libvirt-host.h
@@ -820,5 +820,7 @@ int virNodeAllocPages(virConnectPtr conn,
                       unsigned int cellCount,
                       unsigned int flags);
 
+char *virTmmGetMemoryInfo(virConnectPtr conn,
+                          unsigned int detail);
 
 #endif /* LIBVIRT_HOST_H */
diff --git a/scripts/apibuild.py b/scripts/apibuild.py
index c98bcf6..7b7caeb 100755
--- a/scripts/apibuild.py
+++ b/scripts/apibuild.py
@@ -107,6 +107,7 @@ ignored_functions = {
     "virDomainMigrateConfirm3Params": "private function for migration",
     "virDomainMigratePrepareTunnel3Params": "private function for tunnelled migration",
     "virErrorCopyNew": "private",
+    "virTmmGetMemoryInfo": "private function for tmm",
 }
 
 ignored_macros = {
diff --git a/scripts/check-aclrules.py b/scripts/check-aclrules.py
index e196f81..eae75fd 100755
--- a/scripts/check-aclrules.py
+++ b/scripts/check-aclrules.py
@@ -54,6 +54,7 @@ whitelist = {
     "localOnly": True,
     "domainQemuAttach": True,
     "domainHotpatchManage": True,
+    "tmmGetMemoryInfo": True,
 }
 
 # XXX this vzDomainMigrateConfirm3Params looks
diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c
index 5a04d1b..a5177dc 100644
--- a/src/conf/domain_conf.c
+++ b/src/conf/domain_conf.c
@@ -1272,6 +1272,7 @@ VIR_ENUM_IMPL(virDomainLaunchSecurity,
               VIR_DOMAIN_LAUNCH_SECURITY_LAST,
               "",
               "sev",
+              "cvm",
 );
 
 static virClassPtr virDomainObjClass;
@@ -16821,6 +16822,7 @@ virDomainSEVDefParseXML(xmlNodePtr sevNode,
     def->sectype = virDomainLaunchSecurityTypeFromString(type);
     switch ((virDomainLaunchSecurity) def->sectype) {
     case VIR_DOMAIN_LAUNCH_SECURITY_SEV:
+    case VIR_DOMAIN_LAUNCH_SECURITY_CVM:
         break;
     case VIR_DOMAIN_LAUNCH_SECURITY_NONE:
     case VIR_DOMAIN_LAUNCH_SECURITY_LAST:
@@ -22161,9 +22163,17 @@ virDomainDefParseXML(xmlDocPtr xml,
 
     /* Check for SEV feature */
     if ((node = virXPathNode("./launchSecurity", ctxt)) != NULL) {
-        def->sev = virDomainSEVDefParseXML(node, ctxt);
-        if (!def->sev)
-            goto error;
+        tmp = virXMLPropString(node, "type");
+        if((virDomainLaunchSecurity)virDomainLaunchSecurityTypeFromString(tmp) == VIR_DOMAIN_LAUNCH_SECURITY_CVM) {
+            def->cvm = true;
+        } else {
+            def->sev = virDomainSEVDefParseXML(node, ctxt);
+            if(!def->sev) {
+                VIR_FREE(tmp);
+                goto error;
+            }
+        }
+        VIR_FREE(tmp);
     }
 
     /* analysis of memory devices */
@@ -29826,7 +29836,12 @@ virDomainDefFormatInternalSetRootName(virDomainDefPtr def,
     if (def->keywrap)
         virDomainKeyWrapDefFormat(buf, def->keywrap);
 
-    virDomainSEVDefFormat(buf, def->sev);
+    if (def->cvm) {
+        virBufferAddLit(buf, "<launchSecurity type='cvm'>\n");
+        virBufferAddLit(buf, "</launchSecurity>\n");
+    } else {
+        virDomainSEVDefFormat(buf, def->sev);
+    }
 
     virBufferAdjustIndent(buf, -2);
     virBufferAsprintf(buf, "</%s>\n", rootname);
diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h
index 6d56ef0..c7abd61 100644
--- a/src/conf/domain_conf.h
+++ b/src/conf/domain_conf.h
@@ -2365,6 +2365,7 @@ struct _virDomainKeyWrapDef {
 typedef enum {
     VIR_DOMAIN_LAUNCH_SECURITY_NONE,
     VIR_DOMAIN_LAUNCH_SECURITY_SEV,
+    VIR_DOMAIN_LAUNCH_SECURITY_CVM,
 
     VIR_DOMAIN_LAUNCH_SECURITY_LAST,
 } virDomainLaunchSecurity;
@@ -2578,6 +2579,8 @@ struct _virDomainDef {
 
     /* SEV-specific domain */
     virDomainSEVDefPtr sev;
+    // support for cvm
+    bool cvm;
 
     /* Application-specific custom metadata */
     xmlNodePtr metadata;
diff --git a/src/driver-hypervisor.h b/src/driver-hypervisor.h
index 82f8089..aa84813 100644
--- a/src/driver-hypervisor.h
+++ b/src/driver-hypervisor.h
@@ -1402,6 +1402,10 @@ typedef int
 typedef struct _virHypervisorDriver virHypervisorDriver;
 typedef virHypervisorDriver *virHypervisorDriverPtr;
 
+typedef char *
+(*virDrvTmmGetMemoryInfo)(virConnectPtr conn,
+                          bool detail);
+
 /**
  * _virHypervisorDriver:
  *
@@ -1664,4 +1668,5 @@ struct _virHypervisorDriver {
     virDrvDomainBackupGetXMLDesc domainBackupGetXMLDesc;
     virDrvDomainHotpatchManage domainHotpatchManage;
     virDrvDomainStartDirtyRateCalc domainStartDirtyRateCalc;
+    virDrvTmmGetMemoryInfo tmmGetMemoryInfo;
 };
diff --git a/src/libvirt-host.c b/src/libvirt-host.c
index bc3d1d2..e9585cc 100644
--- a/src/libvirt-host.c
+++ b/src/libvirt-host.c
@@ -1754,3 +1754,34 @@ virNodeGetSEVInfo(virConnectPtr conn,
     virDispatchError(conn);
     return -1;
 }
+
+/*
+ * virGetTmmMemoryInfo:
+ * @conn: pointer to the hypervisor connection
+ *
+ * If Tmm enable, then will fill the cotents of tmmMemoryInfo
+ *
+ * Returns string ptr in case of success, and NULL in case of failure.
+ */
+char *
+virTmmGetMemoryInfo(virConnectPtr conn,
+                    unsigned int detail)
+{
+    VIR_DEBUG("conn=%p", conn);
+
+    virResetLastError();
+
+    virCheckConnectReturn(conn, NULL);
+
+    if (conn->driver->tmmGetMemoryInfo) {
+        char *ret;
+        ret = conn->driver->tmmGetMemoryInfo(conn, detail);
+        if (!ret)
+            goto error;
+        return ret;
+    }
+
+ error:
+    virDispatchError(conn);
+    return NULL;
+}
\ No newline at end of file
diff --git a/src/libvirt_public.syms b/src/libvirt_public.syms
index f006516..66e6fbd 100644
--- a/src/libvirt_public.syms
+++ b/src/libvirt_public.syms
@@ -877,5 +877,6 @@ LIBVIRT_6.2.0 {
     global:
         virDomainHotpatchManage;
         virDomainStartDirtyRateCalc;
+        virTmmGetMemoryInfo;
 } LIBVIRT_6.0.0;
 # .... define new API here using predicted next version number ....
diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c
index bae0606..49bcab0 100644
--- a/src/qemu/qemu_command.c
+++ b/src/qemu/qemu_command.c
@@ -7263,6 +7263,9 @@ qemuBuildMachineCommandLine(virCommandPtr cmd,
 
     if (def->sev)
         virBufferAddLit(&buf, ",memory-encryption=sev0");
+    //support for cvm
+    if (def->cvm)
+        virBufferAddLit(&buf, ",kvm-type=cvm");
 
     if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_BLOCKDEV)) {
         if (priv->pflash0)
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
index 9cb14b0..98c55a3 100644
--- a/src/qemu/qemu_driver.c
+++ b/src/qemu/qemu_driver.c
@@ -23369,6 +23369,144 @@ qemuDomainStartDirtyRateCalc(virDomainPtr dom,
     return ret;
 }
 
+static int
+qemuTmmInfoListAppend(char ***targetInfoStrList,
+                      char **infoStrList,
+                      int targetNumaNum,
+                      int *startIndex,
+                      int maxListSize)
+{
+    char *numStart;
+    int numaNode, index, ret = 0;
+
+    for (index = *startIndex; index < maxListSize; index++) {
+        if (strlen(infoStrList[index]) == 0)
+            break;
+
+        numStart = strstr(infoStrList[index], "node ");
+        if (numStart)
+            virSkipToDigit((const char **)(&numStart));
+
+        ret = virStrToLong_i(numStart, &numStart, 10, &numaNode);
+        if (ret < 0) {
+            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                           _("Failed to get current numa node"));
+            return ret;
+        }
+
+        if (numaNode == targetNumaNum) {
+            ret = virStringListAdd(targetInfoStrList, infoStrList[index]);
+            if (ret < 0) {
+                virReportError(VIR_ERR_INTERNAL_ERROR, "%s [%d]",
+                               _("Failed to get add info list member"), index);
+                return ret;
+            }
+        } else {
+            break;
+        }
+    }
+
+    *startIndex = index;
+
+    return ret;
+}
+
+static char *
+qemuTmmDetailInfoFormat(char *baseMeminfo,
+                        char *slabInfo)
+{
+    int ret, i = 0, j = 0;
+    char *numStart, *numListStart, *format = NULL;
+    char **baseMeminfoSplits = virStringSplit(baseMeminfo, "\n", 0);
+    char **slabInfoSplits = virStringSplit(slabInfo, "\n", 0);
+    char **resultStrList = NULL;
+    int numaSize, numaIndex, headNumaNode;
+    ssize_t meminfoListSize = virStringListLength((const char * const *)baseMeminfoSplits);
+    ssize_t slabInfoSize = virStringListLength((const char * const *)slabInfoSplits);
+
+    numStart = strchr(baseMeminfoSplits[i], ':');
+    if (numStart)
+        virSkipToDigit((const char **)(&numStart));
+
+    numListStart = strchr(baseMeminfoSplits[i], '(');
+    if (numListStart)
+        virSkipToDigit((const char **)(&numListStart));
+
+    ret = virStrToLong_i(numStart, &numStart, 10, &numaSize);
+    if (ret < 0) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("Failed to get available numa size"));
+        goto cleanup;
+    }
+
+    ret = virStringListAdd(&resultStrList, baseMeminfoSplits[i++]);
+    if (ret < 0) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s [%d]",
+                       _("Failed to get add base memory info list member"), (i - 1));
+        goto cleanup;
+    }
+
+    for (numaIndex = 0; numaIndex < numaSize; numaIndex++, numListStart++) {
+        ret = virStrToLong_i(numListStart, &numListStart, 10, &headNumaNode);
+        if (ret < 0) {
+            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                           _("Failed to get current numa node"));
+            goto cleanup;
+        }
+
+        ret = qemuTmmInfoListAppend(&resultStrList, baseMeminfoSplits, headNumaNode, &i, meminfoListSize);
+        if (ret < 0)
+            goto cleanup;
+        ret = qemuTmmInfoListAppend(&resultStrList, slabInfoSplits, headNumaNode, &j, slabInfoSize);
+        if (ret < 0)
+            goto cleanup;
+    }
+
+    format = virStringListJoin((const char **)resultStrList, "\n");
+
+ cleanup:
+    virStringListFree(baseMeminfoSplits);
+    virStringListFree(slabInfoSplits);
+    virStringListFree(resultStrList);
+    return format;
+}
+
+static char *
+qemuTmmGetMemoryInfo(virConnectPtr conn G_GNUC_UNUSED,
+                     bool detail)
+{
+    int maxLen = 10 * 1024;
+    char *meminfo = NULL;
+    g_autofree char *formatInfo = NULL;
+    g_autofree char *baseMeminfo = NULL;
+    g_autofree char *slabInfo = NULL;
+    g_autofree char *buddyInfo = NULL;
+
+    if (virFileReadAll("/sys/kernel/tmm/memory_info", maxLen, &baseMeminfo) < 0)
+        goto end;
+    if (detail && virFileReadAll("/sys/kernel/tmm/slab_info", maxLen, &slabInfo) < 0)
+        goto end;
+    if (detail && virFileReadAll("/sys/kernel/tmm/buddy_info", maxLen, &buddyInfo) < 0)
+        goto end;
+
+    if (detail) {
+        if (!virStringIsEmpty(baseMeminfo) && !virStringIsEmpty(slabInfo)) {
+            formatInfo = qemuTmmDetailInfoFormat(baseMeminfo, slabInfo);
+            if (formatInfo == NULL)
+                goto end;
+        } else {
+            formatInfo = g_strdup_printf(_("%s%s"), baseMeminfo, slabInfo);
+        }
+
+        meminfo = g_strdup_printf(_("%s\n%s"), formatInfo, buddyInfo);
+    } else {
+        meminfo = g_strdup(baseMeminfo);
+    }
+
+end:
+    return meminfo;
+}
+
 
 static virHypervisorDriver qemuHypervisorDriver = {
     .name = QEMU_DRIVER_NAME,
@@ -23611,6 +23749,7 @@ static virHypervisorDriver qemuHypervisorDriver = {
     .domainBackupGetXMLDesc = qemuDomainBackupGetXMLDesc, /* 6.0.0 */
     .domainHotpatchManage = qemuDomainHotpatchManage, /* 6.2.0 */
     .domainStartDirtyRateCalc = qemuDomainStartDirtyRateCalc, /* 6.2.0 */
+    .tmmGetMemoryInfo = qemuTmmGetMemoryInfo, /* 6.2.0 */
 };
 
 
diff --git a/src/remote/remote_daemon_dispatch.c b/src/remote/remote_daemon_dispatch.c
index 0bbcc05..7e54f86 100644
--- a/src/remote/remote_daemon_dispatch.c
+++ b/src/remote/remote_daemon_dispatch.c
@@ -7268,6 +7268,29 @@ remoteDispatchNetworkPortGetParameters(virNetServerPtr server G_GNUC_UNUSED,
     return rv;
 }
 
+static int
+remoteDispatchTmmGetMemoryInfo(virNetServerPtr server G_GNUC_UNUSED,
+                               virNetServerClientPtr client,
+                               virNetMessagePtr msg G_GNUC_UNUSED,
+                               virNetMessageErrorPtr rerr,
+                               remote_tmm_get_memory_info_args *args,
+                               remote_tmm_get_memory_info_ret *ret)
+{
+    int rv = -1;
+    char *meminfo = NULL;
+    virConnectPtr conn = remoteGetHypervisorConn(client);
+
+    if (conn && (meminfo = virTmmGetMemoryInfo(conn, args->detail))) {
+        rv = 0;
+        ret->meminfo = meminfo;
+    }
+
+    if (rv < 0)
+        virNetMessageSaveError(rerr);
+
+    return rv;
+}
+
 
 /*----- Helpers. -----*/
 
diff --git a/src/remote/remote_driver.c b/src/remote/remote_driver.c
index 9c272b4..1237aa8 100644
--- a/src/remote/remote_driver.c
+++ b/src/remote/remote_driver.c
@@ -8253,6 +8253,34 @@ remoteDomainGetGuestInfo(virDomainPtr dom,
     return rv;
 }
 
+static char *
+remoteTmmGetMemoryInfo(virConnectPtr conn,
+                       bool detail)
+{
+    char *rv = NULL;
+    struct private_data *priv = conn->privateData;
+    remote_tmm_get_memory_info_args args;
+    remote_tmm_get_memory_info_ret ret;
+
+    remoteDriverLock(priv);
+
+    args.detail = detail;
+
+    memset(&ret, 0, sizeof(ret));
+
+    if (call(conn, priv, 0, REMOTE_PROC_TMM_GET_MEMORY_INFO,
+             (xdrproc_t)xdr_remote_tmm_get_memory_info_args, (char *)&args,
+             (xdrproc_t)xdr_remote_tmm_get_memory_info_ret, (char *)&ret) < 0) {
+        goto done;
+    }
+
+    rv = ret.meminfo;
+
+ done:
+    remoteDriverUnlock(priv);
+    return rv;
+}
+
 /* get_nonnull_domain and get_nonnull_network turn an on-wire
  * (name, uuid) pair into virDomainPtr or virNetworkPtr object.
  * These can return NULL if underlying memory allocations fail,
@@ -8686,6 +8714,7 @@ static virHypervisorDriver hypervisor_driver = {
     .domainBackupGetXMLDesc = remoteDomainBackupGetXMLDesc, /* 6.0.0 */
     .domainHotpatchManage = remoteDomainHotpatchManage, /* 6.2.0 */
     .domainStartDirtyRateCalc = remoteDomainStartDirtyRateCalc, /* 6.2.0 */
+    .tmmGetMemoryInfo = remoteTmmGetMemoryInfo /* 6.2.0 */
 };
 
 static virNetworkDriver network_driver = {
diff --git a/src/remote/remote_protocol.x b/src/remote/remote_protocol.x
index d89cc1a..c47c9a0 100644
--- a/src/remote/remote_protocol.x
+++ b/src/remote/remote_protocol.x
@@ -3789,6 +3789,14 @@ struct remote_domain_start_dirty_rate_calc_args {
     unsigned int flags;
 };
 
+struct remote_tmm_get_memory_info_args {
+    unsigned int detail;
+};
+
+struct remote_tmm_get_memory_info_ret {
+    remote_nonnull_string meminfo;
+};
+
 /*----- Protocol. -----*/
 
 /* Define the program number, protocol version and procedure numbers here. */
@@ -6698,5 +6706,11 @@ enum remote_procedure {
      * @generate: both
      * @acl: domain:read
      */
-    REMOTE_PROC_DOMAIN_HOTPATCH_MANAGE = 800
+    REMOTE_PROC_DOMAIN_HOTPATCH_MANAGE = 800,
+
+    /**
+     * @generate: none
+     * @acl: connect:read
+     */
+    REMOTE_PROC_TMM_GET_MEMORY_INFO = 900
 };
diff --git a/tools/virsh-host.c b/tools/virsh-host.c
index 67d5466..322134c 100644
--- a/tools/virsh-host.c
+++ b/tools/virsh-host.c
@@ -1799,6 +1799,99 @@ cmdHypervisorCPUBaseline(vshControl *ctl,
     return ret;
 }
 
+/*
+ * "securememinfo" command
+ */
+
+static const vshCmdInfo info_tmm[] = {
+    {.name = "help",
+     .data = N_("Interaction with the tmm")
+    },
+    {.name = "desc",
+     .data = N_("Call the host kernel dev which is provided for virsh to use receiving tmm informations.")
+    },
+    {.name = NULL}
+};
+
+static const vshCmdOptDef opts_tmm[] = {
+    {.name = "dev",
+     .type = VSH_OT_DATA,
+     .flags = VSH_OFLAG_REQ,
+     .help = N_("Device name of host kernel dev")
+    },
+    {.name = "detail",
+     .type = VSH_OT_BOOL,
+     .help = N_("print detailed info if this option contained in cmd")
+    },
+    {.name = NULL}
+};
+
+static bool
+virshGetTmmMemoryInfo(vshControl *ctl,
+                      const vshCmd *cmd)
+{
+    char *tmmMemoryInfo = NULL;
+    bool detail;
+    virshControlPtr priv = ctl->privData;
+
+    detail = vshCommandOptBool(cmd, "detail");
+    if (!(tmmMemoryInfo = virTmmGetMemoryInfo(priv->conn, (unsigned int)detail))) {
+        vshError(ctl, _("Get tmm_memory_info failed"));
+        return false;
+    }
+
+    vshPrintExtra(ctl, _("%s"), tmmMemoryInfo);
+
+    VIR_FREE(tmmMemoryInfo);
+    return true;
+}
+
+typedef bool
+(*virshTmmFunc)(vshControl *ctl,
+                const vshCmd *cmd);
+
+struct _virshTmmFuncInfo {
+    const char *devName;
+    virshTmmFunc funcPtr;
+};
+
+typedef struct _virshTmmFuncInfo virshTmmFuncInfo;
+
+static virshTmmFuncInfo virshTmmFuncMap[] = {
+    {"tmm_memory_info", virshGetTmmMemoryInfo},
+};
+
+static bool
+virshTmmRunFunc(vshControl *ctl,
+                const char *devName,
+                const vshCmd *cmd)
+{
+    int funcIndex;
+
+    for (funcIndex = 0; funcIndex < sizeof(virshTmmFuncMap) / sizeof(virshTmmFuncInfo); funcIndex++) {
+        if (strcmp(devName, virshTmmFuncMap[funcIndex].devName) == 0) {
+            virshTmmFuncMap[funcIndex].funcPtr(ctl, cmd);
+            return true;
+        }
+    }
+
+    vshError(ctl, _("Invalid dev name"));
+    return false;
+}
+
+static bool
+cmdTmm(vshControl *ctl, const vshCmd *cmd)
+{
+    const char *devName = NULL;
+
+    if (vshCommandOptStringReq(ctl, cmd, "dev", &devName) < 0)
+        return false;
+
+    if (!virshTmmRunFunc(ctl, devName, cmd))
+        return false;
+
+    return true;
+}
 
 const vshCmdDef hostAndHypervisorCmds[] = {
     {.name = "allocpages",
@@ -1927,5 +2020,11 @@ const vshCmdDef hostAndHypervisorCmds[] = {
      .info = info_version,
      .flags = 0
     },
+    {.name = "tmm",
+     .handler = cmdTmm,
+     .opts = opts_tmm,
+     .info = info_tmm,
+     .flags = 0
+    },
     {.name = NULL}
 };
-- 
2.33.0

