From 43dcb64a1908c5f096c993844cf66016ea8aa455 Mon Sep 17 00:00:00 2001
From: l00576878 <lihuhua@huawei.com>
Date: Wed, 31 Jul 2024 19:59:08 +0800
Subject: [PATCH libvirt-9.10] Add tmm command word into virsh tool to call get
 tmm memory info API. It makes virsh can use tmm main commmand to show tmm
 memory info on console. This command requires specific kernel and a kernel
 driver to make sure its regular function. If runnning environment missing the
 above reliance, this command will show error result on console.

---
 include/libvirt/libvirt-host.h      |   2 +
 scripts/apibuild.py                 |   1 +
 scripts/check-aclrules.py           |   1 +
 src/driver-hypervisor.h             |   5 ++
 src/libvirt-host.c                  |  36 ++++++++
 src/libvirt_public.syms             |   1 +
 src/qemu/qemu_driver.c              | 128 ++++++++++++++++++++++++++++
 src/remote/remote_daemon_dispatch.c |  22 +++++
 src/remote/remote_driver.c          |  28 ++++++
 src/remote/remote_protocol.x        |  17 +++-
 tools/virsh-host.c                  |  98 +++++++++++++++++++++
 11 files changed, 338 insertions(+), 1 deletion(-)

diff --git a/include/libvirt/libvirt-host.h b/include/libvirt/libvirt-host.h
index 3112f2b..af7dede 100644
--- a/include/libvirt/libvirt-host.h
+++ b/include/libvirt/libvirt-host.h
@@ -1016,5 +1016,7 @@ int virNodeAllocPages(virConnectPtr conn,
                       unsigned int cellCount,
                       unsigned int flags);
 
+char *virConnectGetTmmMemoryInfo(virConnectPtr conn,
+                                 unsigned int detail);
 
 #endif /* LIBVIRT_HOST_H */
diff --git a/scripts/apibuild.py b/scripts/apibuild.py
index 3ecc3ea..f1cfa5a 100755
--- a/scripts/apibuild.py
+++ b/scripts/apibuild.py
@@ -109,6 +109,7 @@ ignored_functions = {
     "virDomainMigrateConfirm3Params": "private function for migration",
     "virDomainMigratePrepareTunnel3Params": "private function for tunnelled migration",
     "virErrorCopyNew": "private",
+    "virConnectGetTmmMemoryInfo": "private function for tmm",
 }
 
 # The version in the .sym file might different from
diff --git a/scripts/check-aclrules.py b/scripts/check-aclrules.py
index e39dbd2..e6bcf00 100755
--- a/scripts/check-aclrules.py
+++ b/scripts/check-aclrules.py
@@ -54,6 +54,7 @@ permitted = {
     "localOnly": True,
     "domainQemuAttach": True,
     "domainHotpatchManage": True,
+    "connectGetTmmMemoryInfo": True,
 }
 
 # XXX this vzDomainMigrateConfirm3Params looks
diff --git a/src/driver-hypervisor.h b/src/driver-hypervisor.h
index e54af05..619a091 100644
--- a/src/driver-hypervisor.h
+++ b/src/driver-hypervisor.h
@@ -1457,6 +1457,10 @@ typedef char *
 
 typedef struct _virHypervisorDriver virHypervisorDriver;
 
+typedef char *
+(*virDrvConnectGetTmmMemoryInfo)(virConnectPtr conn,
+                                 bool detail);
+
 /**
  * _virHypervisorDriver:
  *
@@ -1728,4 +1732,5 @@ struct _virHypervisorDriver {
     virDrvDomainStartDirtyRateCalc domainStartDirtyRateCalc;
     virDrvDomainFDAssociate domainFDAssociate;
     virDrvDomainHotpatchManage domainHotpatchManage;
+    virDrvConnectGetTmmMemoryInfo connectGetTmmMemoryInfo;
 };
diff --git a/src/libvirt-host.c b/src/libvirt-host.c
index e67b368..e763d5c 100644
--- a/src/libvirt-host.c
+++ b/src/libvirt-host.c
@@ -1829,3 +1829,39 @@ virNodeGetSEVInfo(virConnectPtr conn,
     virDispatchError(conn);
     return -1;
 }
+
+/*
+ * virConnectGetTmmMemoryInfo:
+ * @conn: pointer to the hypervisor connection
+ * @detail: whether libvirtd return detailed tmm memory information;
+ * the default value is 0 which means don't return detailed tmm memory information.
+ *
+ * If Tmm enable, then will fill the cotents of string buffer with tmm memory information.
+ *
+ * Returns string ptr in case of success, and NULL in case of failure.
+ *
+ * Since: 9.7.0
+ */
+char *
+virConnectGetTmmMemoryInfo(virConnectPtr conn,
+                           unsigned int detail)
+{
+    VIR_DEBUG("conn=%p", conn);
+
+    virResetLastError();
+
+    virCheckConnectReturn(conn, NULL);
+
+    if (conn->driver->connectGetTmmMemoryInfo) {
+        char *ret;
+        ret = conn->driver->connectGetTmmMemoryInfo(conn, detail);
+        if (!ret)
+            goto error;
+        return ret;
+    }
+
+    virReportUnsupportedError();
+ error:
+    virDispatchError(conn);
+    return NULL;
+}
diff --git a/src/libvirt_public.syms b/src/libvirt_public.syms
index 8b38fe9..72efec0 100644
--- a/src/libvirt_public.syms
+++ b/src/libvirt_public.syms
@@ -941,6 +941,7 @@ LIBVIRT_9.7.0 {
     global:
         virNetworkGetMetadata;
         virNetworkSetMetadata;
+        virConnectGetTmmMemoryInfo;
 } LIBVIRT_9.0.0;
 
 # .... define new API here using predicted next version number ....
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
index abacb0d..421ad7e 100644
--- a/src/qemu/qemu_driver.c
+++ b/src/qemu/qemu_driver.c
@@ -20010,6 +20010,133 @@ qemuDomainHotpatchManage(virDomainPtr domain,
     return ret;
 }
 
+static int
+qemuConnectTmmInfoListAppend(char **format,
+                             char **infoStrList,
+                             int targetNumaNum,
+                             int *startIndex,
+                             int maxListSize)
+{
+    char *numStart;
+    char *strPtr = NULL;
+    int numaNode, index, ret = 0;
+
+    for (index = *startIndex; index < maxListSize; index++) {
+        if (strlen(infoStrList[index]) == 0)
+            break;
+
+        numStart = strstr(infoStrList[index], "node ");
+        if (!numStart)
+            return -1;
+
+        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) {
+            strPtr = *format;
+            *format = g_strconcat(*format, "\n", infoStrList[index], NULL);
+            free(strPtr);
+        } else {
+            break;
+        }
+    }
+
+    *startIndex = index;
+
+    return ret;
+}
+
+static char *
+qemuConnectTmmDetailInfoFormat(char *baseMeminfo,
+                               char *slabInfo)
+{
+    int ret, i = 0, j = 0;
+    char *numStart, *numListStart, *format = NULL;
+    char **baseMeminfoSplits = g_strsplit(baseMeminfo, "\n", 0);
+    char **slabInfoSplits = g_strsplit(slabInfo, "\n", 0);
+    int numaSize, numaIndex, headNumaNode;
+    ssize_t meminfoListSize = g_strv_length(baseMeminfoSplits);
+    ssize_t slabInfoSize = g_strv_length(slabInfoSplits);
+
+    numStart = strchr(baseMeminfoSplits[i], ':');
+    numListStart = strchr(baseMeminfoSplits[i], '(');
+    if (!numStart || !numListStart)
+        goto cleanup;
+
+    virSkipToDigit((const char **)(&numStart));
+    ret = virStrToLong_i(numStart, &numStart, 10, &numaSize);
+    if (ret < 0) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("Failed to get available numa size"));
+        goto cleanup;
+    }
+
+    format = g_strconcat(baseMeminfoSplits[i++], NULL);
+
+    virSkipToDigit((const char **)(&numListStart));
+    for (numaIndex = 0; *numListStart && 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 = qemuConnectTmmInfoListAppend(&format, baseMeminfoSplits, headNumaNode, &i, meminfoListSize);
+        if (ret < 0)
+            goto cleanup;
+        ret = qemuConnectTmmInfoListAppend(&format, slabInfoSplits, headNumaNode, &j, slabInfoSize);
+        if (ret < 0)
+            goto cleanup;
+    }
+
+cleanup:
+    g_strfreev(baseMeminfoSplits);
+    g_strfreev(slabInfoSplits);
+    return format;
+}
+
+static char *
+qemuConnectGetTmmMemoryInfo(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 = qemuConnectTmmDetailInfoFormat(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_steal_pointer(&baseMeminfo);
+    }
+
+end:
+    return meminfo;
+}
+
 static virHypervisorDriver qemuHypervisorDriver = {
     .name = QEMU_DRIVER_NAME,
     .connectURIProbe = qemuConnectURIProbe,
@@ -20260,6 +20387,7 @@ static virHypervisorDriver qemuHypervisorDriver = {
     .domainStartDirtyRateCalc = qemuDomainStartDirtyRateCalc, /* 7.2.0 */
     .domainSetLaunchSecurityState = qemuDomainSetLaunchSecurityState, /* 8.0.0 */
     .domainFDAssociate = qemuDomainFDAssociate, /* 9.0.0 */
+    .connectGetTmmMemoryInfo = qemuConnectGetTmmMemoryInfo, /* 9.0.0 */
 };
 
 
diff --git a/src/remote/remote_daemon_dispatch.c b/src/remote/remote_daemon_dispatch.c
index 7542caa..10f3438 100644
--- a/src/remote/remote_daemon_dispatch.c
+++ b/src/remote/remote_daemon_dispatch.c
@@ -7125,6 +7125,28 @@ remoteDispatchNetworkPortGetParameters(virNetServer *server G_GNUC_UNUSED,
     return rv;
 }
 
+static int
+remoteDispatchConnectGetTmmMemoryInfo(virNetServer *server G_GNUC_UNUSED,
+                                      virNetServerClient *client,
+                                      virNetMessage *msg G_GNUC_UNUSED,
+                                      struct virNetMessageError *rerr,
+                                      remote_connect_get_tmm_memory_info_args *args,
+                                      remote_connect_get_tmm_memory_info_ret *ret)
+{
+    int rv = -1;
+    char *meminfo = NULL;
+    virConnectPtr conn = remoteGetHypervisorConn(client);
+
+    if (conn && (meminfo = virConnectGetTmmMemoryInfo(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 9350e81..4b9ad30 100644
--- a/src/remote/remote_driver.c
+++ b/src/remote/remote_driver.c
@@ -7406,6 +7406,33 @@ remoteDomainFDAssociate(virDomainPtr domain,
     return 0;
 }
 
+static char *
+remoteConnectGetTmmMemoryInfo(virConnectPtr conn,
+                              bool detail)
+{
+    char *rv = NULL;
+    struct private_data *priv = conn->privateData;
+    remote_connect_get_tmm_memory_info_args args;
+    remote_connect_get_tmm_memory_info_ret ret;
+
+    remoteDriverLock(priv);
+
+    args.detail = detail;
+
+    memset(&ret, 0, sizeof(ret));
+
+    if (call(conn, priv, 0, REMOTE_PROC_CONNECT_GET_TMM_MEMORY_INFO,
+             (xdrproc_t)xdr_remote_connect_get_tmm_memory_info_args, (char *)&args,
+             (xdrproc_t)xdr_remote_connect_get_tmm_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.
@@ -7849,6 +7876,7 @@ static virHypervisorDriver hypervisor_driver = {
     .domainStartDirtyRateCalc = remoteDomainStartDirtyRateCalc, /* 7.2.0 */
     .domainSetLaunchSecurityState = remoteDomainSetLaunchSecurityState, /* 8.0.0 */
     .domainFDAssociate = remoteDomainFDAssociate, /* 9.0.0 */
+    .connectGetTmmMemoryInfo = remoteConnectGetTmmMemoryInfo /* 9.0.0 */
 };
 
 static virNetworkDriver network_driver = {
diff --git a/src/remote/remote_protocol.x b/src/remote/remote_protocol.x
index eea11df..39069ce 100644
--- a/src/remote/remote_protocol.x
+++ b/src/remote/remote_protocol.x
@@ -3973,6 +3973,15 @@ struct remote_domain_fd_associate_args {
     remote_nonnull_string name;
     unsigned int flags;
 };
+
+struct remote_connect_get_tmm_memory_info_args {
+    unsigned int detail;
+};
+
+struct remote_connect_get_tmm_memory_info_ret {
+    remote_nonnull_string meminfo;
+};
+
 /*----- Protocol. -----*/
 
 /* Define the program number, protocol version and procedure numbers here. */
@@ -7038,5 +7047,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_CONNECT_GET_TMM_MEMORY_INFO = 900
 };
diff --git a/tools/virsh-host.c b/tools/virsh-host.c
index 6c14be8..7fdd6ae 100644
--- a/tools/virsh-host.c
+++ b/tools/virsh-host.c
@@ -1826,6 +1826,98 @@ 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;
+    virshControl *priv = ctl->privData;
+
+    detail = vshCommandOptBool(cmd, "detail");
+    if (!(tmmMemoryInfo = virConnectGetTmmMemoryInfo(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",
@@ -1960,5 +2052,11 @@ const vshCmdDef hostAndHypervisorCmds[] = {
      .info = info_version,
      .flags = 0
     },
+    {.name = "tmm",
+     .handler = cmdTmm,
+     .opts = opts_tmm,
+     .info = info_tmm,
+     .flags = 0
+    },
     {.name = NULL}
 };
-- 
2.28.0.windows.1

