From 6b2333022503d9abed152a7c3e96884de564acb9 Mon Sep 17 00:00:00 2001
From: Xie Tongchen <xietongchen1@huawei.com>
Date: Wed, 19 Nov 2025 19:29:16 +0800
Subject: [PATCH 1/7] ham migration: add ham migration feature

Offering: NA
CVE:
Reference: NA
DTS/AR: xxx
Contribute to Upstream(Y/N): N
Description:
Add ham migration feature

Signed-off-by: Xie Tongchen <xietongchen1@huawei.com>
---
 include/libvirt/libvirt-domain.h    |   8 +
 include/libvirt/libvirt-qemu.h      |   3 +
 src/conf/domain_conf.c              |   7 +
 src/conf/domain_conf.h              |   1 +
 src/driver-hypervisor.h             |   7 +
 src/libvirt-qemu.c                  |  64 ++++
 src/libvirt_private.syms            |   6 +
 src/libvirt_qemu.syms               |   5 +
 src/qemu/meson.build                |   1 +
 src/qemu/qemu_driver.c              |  64 +++-
 src/qemu/qemu_ham.c                 | 576 ++++++++++++++++++++++++++++
 src/qemu/qemu_ham.h                 |  38 ++
 src/qemu/qemu_migration.c           |  29 ++
 src/qemu/qemu_migration.h           |   1 +
 src/qemu/qemu_migration_params.c    |   6 +
 src/qemu/qemu_migration_params.h    |   1 +
 src/qemu_protocol-structs           |   9 +
 src/remote/qemu_protocol.x          |  19 +-
 src/remote/remote_daemon.c          |   4 +
 src/remote/remote_daemon_dispatch.c |  32 ++
 src/remote/remote_driver.c          |  25 ++
 src/util/meson.build                |   1 +
 src/util/virham.c                   | 223 +++++++++++
 src/util/virham.h                   |  21 +
 tools/virsh-domain.c                |  12 +
 25 files changed, 1161 insertions(+), 2 deletions(-)
 create mode 100644 src/qemu/qemu_ham.c
 create mode 100644 src/qemu/qemu_ham.h
 create mode 100644 src/util/virham.c
 create mode 100644 src/util/virham.h

diff --git a/include/libvirt/libvirt-domain.h b/include/libvirt/libvirt-domain.h
index 713562b6c3..a6f97e0b5f 100644
--- a/include/libvirt/libvirt-domain.h
+++ b/include/libvirt/libvirt-domain.h
@@ -1115,6 +1115,12 @@ typedef enum {
      * Since: 9.10.0
      */
     VIR_MIGRATE_ONECOPY = (1 << 22),
+
+    /* Support ham migration by LDST.
+     *
+     * Since: 9.10.0
+     */
+    VIR_MIGRATE_LDST = (1 << 27),
 } virDomainMigrateFlags;
 
 
@@ -4203,6 +4209,8 @@ typedef enum {
      * suspended and it can later be resumed using virDomainMigrate* APIs with
      * VIR_MIGRATE_POSTCOPY_RESUME flag. (Since: 8.5.0) */
     VIR_DOMAIN_ABORT_JOB_POSTCOPY = 1 << 0,
+    /* Interrupt ham migration. (Since: 9.10.0) */
+    VIR_DOMAIN_ABORT_JOB_HAM = 1 << 1,
 } virDomainAbortJobFlagsValues;
 
 int virDomainAbortJobFlags(virDomainPtr dom,
diff --git a/include/libvirt/libvirt-qemu.h b/include/libvirt/libvirt-qemu.h
index a26498ab89..cd847933b2 100644
--- a/include/libvirt/libvirt-qemu.h
+++ b/include/libvirt/libvirt-qemu.h
@@ -51,6 +51,9 @@ int virDomainQemuMonitorCommandWithFiles(virDomainPtr domain,
                                          char **result,
                                          unsigned int flags);
 
+int virDomainQemuMonitorCommandAsync(virDomainPtr domain, const char *cmd,
+                                     char **result, int asyncJob);
+
 virDomainPtr virDomainQemuAttach(virConnectPtr domain,
                                  unsigned int pid_value,
                                  unsigned int flags);
diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c
index 3a64a11b14..d0071aaf20 100644
--- a/src/conf/domain_conf.c
+++ b/src/conf/domain_conf.c
@@ -4106,6 +4106,7 @@ static void virDomainObjDispose(void *obj)
 
     VIR_DEBUG("obj=%p", dom);
     virCondDestroy(&dom->cond);
+    virCondDestroy(&dom->hamCond);
     virDomainDefFree(dom->def);
     virDomainDefFree(dom->newDef);
 
@@ -4136,6 +4137,12 @@ virDomainObjNew(virDomainXMLOption *xmlopt)
         goto error;
     }
 
+    if (virCondInit(&domain->hamCond) < 0) {
+        virReportSystemError(errno, "%s",
+                             _("failed to initialize domain ham condition"));
+        goto error;
+    }
+
     if (xmlopt->privateData.alloc) {
         domain->privateData = (xmlopt->privateData.alloc)(xmlopt->config.priv);
         if (!domain->privateData)
diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h
index d70c2633a7..bf82a2f481 100644
--- a/src/conf/domain_conf.h
+++ b/src/conf/domain_conf.h
@@ -3250,6 +3250,7 @@ struct _virDomainStateReason {
 struct _virDomainObj {
     virObjectLockable parent;
     virCond cond;
+    virCond hamCond;
 
     virDomainJobObj *job;
 
diff --git a/src/driver-hypervisor.h b/src/driver-hypervisor.h
index 619a091ffa..cc2c771337 100644
--- a/src/driver-hypervisor.h
+++ b/src/driver-hypervisor.h
@@ -900,6 +900,12 @@ typedef int
                                            char **result,
                                            unsigned int flags);
 
+typedef int
+(*virDrvDomainQemuMonitorCommandAsync)(virDomainPtr domain,
+                                       const char *cmd,
+                                       char **result,
+                                       int asyncJob);
+
 typedef char *
 (*virDrvDomainQemuAgentCommand)(virDomainPtr domain,
                                 const char *cmd,
@@ -1644,6 +1650,7 @@ struct _virHypervisorDriver {
     virDrvDomainSnapshotDelete domainSnapshotDelete;
     virDrvDomainQemuMonitorCommand domainQemuMonitorCommand;
     virDrvDomainQemuMonitorCommandWithFiles domainQemuMonitorCommandWithFiles;
+    virDrvDomainQemuMonitorCommandAsync domainQemuMonitorCommandAsync;
     virDrvDomainQemuAttach domainQemuAttach;
     virDrvDomainQemuAgentCommand domainQemuAgentCommand;
     virDrvConnectDomainQemuMonitorEventRegister connectDomainQemuMonitorEventRegister;
diff --git a/src/libvirt-qemu.c b/src/libvirt-qemu.c
index 2898b71983..7a3a006776 100644
--- a/src/libvirt-qemu.c
+++ b/src/libvirt-qemu.c
@@ -190,6 +190,70 @@ virDomainQemuMonitorCommandWithFiles(virDomainPtr domain,
     return -1;
 }
 
+/**
+ * virDomainQemuMonitorCommandAsync:
+ * @domain: a domain object
+ * @cmd: the QEMU monitor command string
+ * @result: a string returned by @cmd
+ * @asyncJob: enum of type virDomainAsyncJob
+ *
+ * This API is QEMU specific, so it will only work with hypervisor
+ * connections to the QEMU driver.
+ *
+ * Send an arbitrary monitor command @cmd to @domain through the
+ * QEMU monitor. There are several requirements to safely and
+ * successfully use this API:
+ *
+ *   - A @cmd that queries state without making any modifications is safe
+ *   - A @cmd that alters state that is also tracked by libvirt is unsafe,
+ *     and may cause libvirtd to crash
+ *   - A @cmd that alters state not tracked by the current version of
+ *     libvirt is possible as a means to test new QEMU features before
+ *     they have support in libvirt, but no guarantees are made to safety
+ *
+ * When @domain is running in async job, @asyncJob is considered to be set
+ * as same as the domain's async job.
+ *
+ * If successful, @result will be filled with the string output of the
+ * @cmd, and the caller must free this string.
+ *
+ * Returns 0 in case of success, -1 in case of failure
+ *
+ * Since: 9.10.0
+ */
+int
+virDomainQemuMonitorCommandAsync(virDomainPtr domain, const char *cmd,
+                                 char **result, int asyncJob)
+{
+    virConnectPtr conn;
+
+    VIR_DOMAIN_DEBUG(domain, "cmd=%s, result=%p, asyncJob=%d",
+                     cmd, result, asyncJob);
+
+    virResetLastError();
+
+    virCheckDomainReturn(domain, -1);
+    conn = domain->conn;
+
+    virCheckNonNullArgGoto(result, error);
+    virCheckReadOnlyGoto(conn->flags, error);
+
+    if (conn->driver->domainQemuMonitorCommandAsync) {
+        int ret;
+        ret = conn->driver->domainQemuMonitorCommandAsync(domain, cmd, result,
+                                                          asyncJob);
+        if (ret < 0)
+            goto error;
+        return ret;
+    }
+
+    virReportUnsupportedError();
+
+ error:
+    virDispatchError(conn);
+    return -1;
+}
+
 /**
  * virDomainQemuAttach:
  * @conn: pointer to a hypervisor connection
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index 6ab78d504b..60c819a17f 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -2459,6 +2459,12 @@ virGICVersionTypeToString;
 virGSListStringFree;
 
 
+# util/virham.h
+virHamClearAll;
+virHamGetClearReqInit;
+virHamRackIpcSyncSendAndRecv;
+
+
 # util/virhash.h
 virHashAddEntry;
 virHashAtomicNew;
diff --git a/src/libvirt_qemu.syms b/src/libvirt_qemu.syms
index 182658bd0d..78a8d4d44b 100644
--- a/src/libvirt_qemu.syms
+++ b/src/libvirt_qemu.syms
@@ -35,3 +35,8 @@ LIBVIRT_QEMU_8.2.0 {
     global:
         virDomainQemuMonitorCommandWithFiles;
 } LIBVIRT_QEMU_1.2.3;
+
+LIBVIRT_QEMU_9.10.0 {
+    global:
+        virDomainQemuMonitorCommandAsync;
+} LIBVIRT_QEMU_8.2.0;
diff --git a/src/qemu/meson.build b/src/qemu/meson.build
index 9d5b4da35d..90ba7810d4 100644
--- a/src/qemu/meson.build
+++ b/src/qemu/meson.build
@@ -43,6 +43,7 @@ qemu_driver_sources = [
   'qemu_vhost_user_gpu.c',
   'qemu_virtiofs.c',
   'qemu_hotpatch.c',
+  'qemu_ham.c',
 ]
 
 driver_source_files += files(qemu_driver_sources)
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
index df275c403c..00b876c688 100644
--- a/src/qemu/qemu_driver.c
+++ b/src/qemu/qemu_driver.c
@@ -52,6 +52,7 @@
 #include "qemu_saveimage.h"
 #include "qemu_snapshot.h"
 #include "qemu_validate.h"
+#include "qemu_ham.h"
 
 #include "virerror.h"
 #include "virlog.h"
@@ -12180,6 +12181,29 @@ qemuDomainAbortJobPostcopy(virDomainObj *vm,
 }
 
 
+#define HAM_CANCELLED_TIMEOUT (1000ull * 300)
+
+static int
+qemuHamWaitForCancelled(virDomainObj *vm)
+{
+    unsigned long long now = 0;
+    unsigned long long then = 0;
+
+    if (virTimeMillisNow(&now) < 0)
+        return -1;
+
+    then = now + HAM_CANCELLED_TIMEOUT;
+
+    if (virCondWaitUntil(&vm->hamCond, &vm->parent.lock, then) < 0) {
+        virReportSystemError(errno, "%s",
+                             _("failed to wait for domain ham condition"));
+        return -1;
+    }
+
+    return 0;
+}
+
+
 static int
 qemuDomainAbortJobFlags(virDomainPtr dom,
                         unsigned int flags)
@@ -12187,10 +12211,12 @@ qemuDomainAbortJobFlags(virDomainPtr dom,
     virDomainObj *vm;
     int ret = -1;
     qemuDomainObjPrivate *priv;
+    bool isHam = false;
 
     VIR_DEBUG("flags=0x%x", flags);
 
-    virCheckFlags(VIR_DOMAIN_ABORT_JOB_POSTCOPY, -1);
+    virCheckFlags(VIR_DOMAIN_ABORT_JOB_POSTCOPY |
+                  VIR_DOMAIN_ABORT_JOB_HAM, -1);
 
     if (!(vm = qemuDomainObjFromDomain(dom)))
         goto cleanup;
@@ -12206,6 +12232,9 @@ qemuDomainAbortJobFlags(virDomainPtr dom,
 
     priv = vm->privateData;
 
+    isHam = flags & VIR_DOMAIN_ABORT_JOB_HAM &&
+            vm->job->asyncJob == VIR_ASYNC_JOB_MIGRATION_OUT;
+
     if (flags & VIR_DOMAIN_ABORT_JOB_POSTCOPY &&
         (vm->job->asyncJob != VIR_ASYNC_JOB_MIGRATION_OUT ||
          !virDomainObjIsPostcopy(vm, vm->job))) {
@@ -12271,6 +12300,9 @@ qemuDomainAbortJobFlags(virDomainPtr dom,
  endjob:
     virDomainObjEndJob(vm);
 
+    if (isHam && ret == 0)
+        ret = qemuHamWaitForCancelled(vm);
+
  cleanup:
     virDomainObjEndAPI(&vm);
     return ret;
@@ -13438,6 +13470,35 @@ qemuDomainQemuMonitorCommand(virDomainPtr domain,
 }
 
 
+static int
+qemuDomainQemuMonitorCommandAsync(virDomainPtr domain,
+                                  const char *cmd,
+                                  char **result,
+                                  int asyncJob)
+{
+    virQEMUDriver *driver = domain->conn->privateData;
+    virDomainObj *vm = NULL;
+    int ret = -1;
+
+    if (!(vm = qemuDomainObjFromDomain(domain)))
+        goto cleanup;
+
+    if (virDomainQemuMonitorCommandAsyncEnsureACL(domain->conn, vm->def) < 0)
+        goto cleanup;
+
+    if (virDomainObjCheckActive(vm) < 0)
+        goto cleanup;
+
+    qemuDomainObjTaint(driver, vm, VIR_DOMAIN_TAINT_CUSTOM_MONITOR, NULL);
+
+    ret = qemuDomainSendQemuMonitorCommandAsync(vm, cmd, result, asyncJob);
+
+ cleanup:
+    virDomainObjEndAPI(&vm);
+    return ret;
+}
+
+
 static int
 qemuDomainOpenConsole(virDomainPtr dom,
                       const char *dev_name,
@@ -20307,6 +20368,7 @@ static virHypervisorDriver qemuHypervisorDriver = {
     .domainSnapshotDelete = qemuDomainSnapshotDelete, /* 0.8.0 */
     .domainQemuMonitorCommand = qemuDomainQemuMonitorCommand, /* 0.8.3 */
     .domainQemuMonitorCommandWithFiles = qemuDomainQemuMonitorCommandWithFiles, /* 8.2.0 */
+    .domainQemuMonitorCommandAsync = qemuDomainQemuMonitorCommandAsync, /* 9.10.0 */
     .domainQemuAttach = NULL, /* 0.9.4 - 5.5.0 */
     .domainQemuAgentCommand = qemuDomainQemuAgentCommand, /* 0.10.0 */
     .connectDomainQemuMonitorEventRegister = qemuConnectDomainQemuMonitorEventRegister, /* 1.2.3 */
diff --git a/src/qemu/qemu_ham.c b/src/qemu/qemu_ham.c
new file mode 100644
index 0000000000..4be3f9bd4c
--- /dev/null
+++ b/src/qemu/qemu_ham.c
@@ -0,0 +1,576 @@
+/*
+ * qemu_ham.c: QEMU ham migration handling
+ *
+ * Copyright (C) 2025 Huawei Technologies Co., Ltd
+ */
+
+#include "qemu_domain.h"
+#include "qemu_ham.h"
+
+#include "virlog.h"
+
+#define VIR_FROM_THIS VIR_FROM_QEMU
+
+VIR_LOG_INIT("qemu.qemu_ham");
+
+typedef struct _qemuHamRamBlock qemuHamRamBlock;
+struct _qemuHamRamBlock {
+    unsigned int numaId;
+    unsigned long long hva;
+    unsigned long long size;
+};
+
+typedef struct _qemuHamRamInfo qemuHamRamInfo;
+struct _qemuHamRamInfo {
+    int pid;
+    unsigned int scna;
+    unsigned int num;
+    qemuHamRamBlock *blocks;
+};
+
+static void
+qemuHamRamInfoFree(qemuHamRamInfo *ramInfo)
+{
+    if (!ramInfo)
+        return;
+
+    VIR_FREE(ramInfo->blocks);
+    VIR_FREE(ramInfo);
+}
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(qemuHamRamInfo, qemuHamRamInfoFree);
+
+void
+qemuHamMigrationInfoFree(qemuHamMigrationInfo *hamInfo)
+{
+    if (!hamInfo)
+        return;
+
+    VIR_FREE(hamInfo->name);
+    VIR_FREE(hamInfo->srcHostname);
+    VIR_FREE(hamInfo->dstHostname);
+    VIR_FREE(hamInfo);
+}
+
+int
+qemuDomainSendQemuMonitorCommandAsync(virDomainObj *vm,
+                                      const char *cmd,
+                                      char **result,
+                                      virDomainAsyncJob asyncJob)
+{
+    qemuDomainObjPrivate *priv = vm->privateData;
+    int ret;
+
+    if (qemuDomainObjEnterMonitorAsync(vm, asyncJob) < 0)
+        return -1;
+
+    ret = qemuMonitorArbitraryCommand(priv->mon, cmd, -1, result, false);
+    qemuDomainObjExitMonitor(vm);
+
+    return ret;
+}
+
+static virJSONValue *
+qemuHamSendQemuMonitorCommand(virDomainObj *vm,
+                              virDomainPtr domain,
+                              const char *commandName,
+                              virJSONValue *arguments,
+                              virDomainAsyncJob asyncJob)
+{
+    g_autoptr(virJSONValue) command = NULL;
+    g_autoptr(virJSONValue) args = virJSONValueCopy(arguments);
+    g_autofree char *monitorCmd = NULL;
+    g_autofree char *result = NULL;
+    int ret = -1;
+
+    if (virJSONValueObjectAdd(&command,
+                              "s:execute", commandName,
+                              "A:arguments", &args,
+                              NULL) < 0) {
+        virReportError(VIR_ERR_INTERNAL_ERROR,
+                       _("failed to wrap execute command '%1$s' and arguments into a QMP command wrapper"),
+                       commandName);
+        return NULL;
+    }
+
+    if (!(monitorCmd = virJSONValueToString(command, false))) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("failed to convert QMP command json object to string"));
+        return NULL;
+    }
+
+    if (vm)
+        ret = qemuDomainSendQemuMonitorCommandAsync(vm, monitorCmd, &result, asyncJob);
+
+    if (domain)
+        ret = domain->conn->driver->domainQemuMonitorCommandAsync(domain, monitorCmd, &result, asyncJob);
+
+    if (ret < 0) {
+        virReportError(VIR_ERR_INTERNAL_ERROR,
+                       _("failed to get result of QMP command: %1$s"), monitorCmd);
+        return NULL;
+    }
+
+    return virJSONValueFromString(result);
+}
+
+static qemuHamRamInfo *
+qemuHamGetRamInfo(virDomainPtr domain)
+{
+    g_autoptr(qemuHamRamInfo) ramInfo = g_new0(qemuHamRamInfo, 1);
+    g_autoptr(virJSONValue) result = NULL;
+    virJSONValue *data = NULL;
+    virJSONValue *blocksArray = NULL;
+    int i;
+
+    if (!(result = qemuHamSendQemuMonitorCommand(NULL, domain, "query-ramblock",
+                                                 NULL, VIR_ASYNC_JOB_MIGRATION_IN))) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("unable to execute QMP command 'query-ramblock'"));
+        return NULL;
+    }
+
+    if (!(data = virJSONValueObjectGetObject(result, "return"))) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("'return' member missing"));
+        return NULL;
+    }
+
+    if (virJSONValueObjectGetNumberInt(data, "pid", &ramInfo->pid) < 0) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("'pid' missing in return value"));
+        return NULL;
+    }
+
+    if (!(blocksArray = virJSONValueObjectGetArray(data, "block"))) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("'block' was missing or not an array"));
+        return NULL;
+    }
+
+    if ((ramInfo->num = virJSONValueArraySize(blocksArray)) <= 0) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("'block' array is empty"));
+        return NULL;
+    }
+
+    ramInfo->blocks = g_new0(qemuHamRamBlock, ramInfo->num);
+
+    for (i = 0; i < ramInfo->num; i++) {
+        virJSONValue *entry = virJSONValueArrayGet(blocksArray, i);
+        qemuHamRamBlock *block = ramInfo->blocks + i;
+
+        if (!entry) {
+            virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("array element missing"));
+            return NULL;
+        }
+
+        if (virJSONValueObjectGetNumberUlong(entry, "hva", &block->hva) < 0) {
+            virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("found block wihtout hva"));
+            return NULL;
+        }
+
+        if (virJSONValueObjectGetNumberUlong(entry, "size", &block->size) < 0) {
+            virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("found block wihtout size"));
+            return NULL;
+        }
+    }
+
+    return g_steal_pointer(&ramInfo);
+}
+
+static char *
+qemuHamGetBorrowReq(qemuHamMigrationInfo *hamInfo, qemuHamRamInfo *ramInfo)
+{
+    g_autoptr(virJSONValue) borrowReq = virJSONValueNewObject();
+    g_autoptr(virJSONValue) valist = virJSONValueNewArray();
+    int i;
+
+    if (virJSONValueObjectAppendString(borrowReq, "action", "borrow") < 0) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("failed to add action to borrow request json object"));
+        return NULL;
+    }
+
+    if (virJSONValueObjectAppendString(borrowReq, "srcHostname", hamInfo->srcHostname) < 0) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("failed to add srcHostname to borrow request json object"));
+        return NULL;
+    }
+
+    if (virJSONValueObjectAppendNumberInt(borrowReq, "srcPid", hamInfo->srcPid) < 0) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("failed to add srcPid to borrow request json object"));
+        return NULL;
+    }
+
+    if (virJSONValueObjectAppendString(borrowReq, "dstHostname", hamInfo->dstHostname) < 0) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("failed to add dstHostname to borrow request json object"));
+        return NULL;
+    }
+
+    if (virJSONValueObjectAppendNumberInt(borrowReq, "dstPid", hamInfo->dstPid) < 0) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("failed to add dstPid to borrow request json object"));
+        return NULL;
+    }
+
+    for (i = 0; i < ramInfo->num; i++) {
+        g_autoptr(virJSONValue) va = virJSONValueNewObject();
+        qemuHamRamBlock *block = ramInfo->blocks + i;
+
+        if (virJSONValueObjectAppendNumberUlong(va, "start", block->hva) < 0) {
+            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                           _("failed to add start to va json object"));
+            return NULL;
+        }
+
+        if (virJSONValueObjectAppendNumberUlong(va, "length", block->size) < 0) {
+            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                           _("failed to add length to va json object"));
+            return NULL;
+        }
+
+        if (virJSONValueArrayAppend(valist, &va) < 0) {
+            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                           _("failed to add va to valist json array"));
+            return NULL;
+        }
+    }
+
+    if (virJSONValueObjectAppend(borrowReq, "valist", &valist) < 0) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("failed to add valist to borrow request json object"));
+        return NULL;
+    }
+
+    return virJSONValueToString(borrowReq, false);
+}
+
+static int
+qemuHamGetBorrowInfo(qemuHamMigrationInfo *hamInfo,
+                     qemuHamRamInfo *ramInfo,
+                     const char *respStr)
+{
+    g_autoptr(virJSONValue) resp = NULL;
+    virJSONValue *message = NULL;
+    virJSONValue *numaIdsArray = NULL;
+    const char *name;
+    int code;
+    int i;
+
+    if (!(resp = virJSONValueFromString(respStr))) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("failed to parse borrow info response to json object"));
+        return -1;
+    }
+
+    if (virJSONValueObjectGetNumberInt(resp, "code", &code) < 0) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("'code' missing in borrow info response"));
+        return -1;
+    }
+
+    if (code != 200) {
+        virReportError(VIR_ERR_INTERNAL_ERROR,
+                       _("borrow info request failed with code %1$d"), code);
+        return -1;
+    }
+
+    if (!(message = virJSONValueObjectGet(resp, "message"))) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("'message' missing in borrow info response"));
+        return -1;
+    }
+
+    if (!(name = virJSONValueObjectGetString(message, "name"))) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("'name' missing in message json object"));
+        return -1;
+    }
+
+    hamInfo->name = g_strdup(name);
+
+    if (virJSONValueObjectGetNumberUint(message, "scna", &ramInfo->scna) < 0) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("'scna' missing in message json object"));
+        return -1;
+    }
+
+    if (!(numaIdsArray = virJSONValueObjectGetArray(message, "numaIds"))) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("'numaIds' was missing or not an array"));
+        return -1;
+    }
+
+    if (ramInfo->num != virJSONValueArraySize(numaIdsArray)) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("number of numa mismatch with number of block"));
+        return -1;
+    }
+
+    for (i = 0; i < ramInfo->num; i++) {
+        virJSONValue *entry = virJSONValueArrayGet(numaIdsArray, i);
+        qemuHamRamBlock *block = ramInfo->blocks + i;
+
+        if (!entry) {
+            virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("array element missing"));
+            return -1;
+        }
+
+        if (virJSONValueGetNumberUint(entry, &block->numaId) < 0) {
+            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                           _("missing or invalid element of numaIds array"));
+            return -1;
+        }
+    }
+
+    return 0;
+}
+
+static int
+qemuHamSendRamInfo(qemuHamRamInfo *ramInfo, virDomainObj *vm)
+{
+    g_autoptr(virJSONValue) arguments = virJSONValueNewObject();
+    g_autoptr(virJSONValue) blocksArray = virJSONValueNewArray();
+    g_autoptr(virJSONValue) result = NULL;
+    int i;
+
+    if (virJSONValueObjectAppendNumberInt(arguments, "pid", ramInfo->pid) < 0) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("failed to add pid to arguments json object"));
+        return -1;
+    }
+
+    if (virJSONValueObjectAppendNumberUint(arguments, "scna", ramInfo->scna) < 0) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("failed to add scna to arguments json object"));
+        return -1;
+    }
+
+    for (i = 0; i < ramInfo->num; i++) {
+        g_autoptr(virJSONValue) entry = virJSONValueNewObject();
+        qemuHamRamBlock *block = ramInfo->blocks + i;
+
+        if (virJSONValueObjectAppendNumberUint(entry, "numa-id", block->numaId) < 0) {
+            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                           _("failed to add numa id to json object"));
+            return -1;
+        }
+
+        if (virJSONValueObjectAppendNumberUlong(entry, "size", block->size) < 0) {
+            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                           _("failed to add size to json object"));
+            return -1;
+        }
+
+        if (virJSONValueArrayAppend(blocksArray, &entry) < 0) {
+            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                           _("failed to add entry to block json array"));
+            return -1;
+        }
+    }
+
+    if (virJSONValueObjectAppend(arguments, "block", &blocksArray) < 0) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("failed to add block to arguments json object"));
+        return -1;
+    }
+
+    if (!(result = qemuHamSendQemuMonitorCommand(vm, NULL, "recv-rmtnuma", arguments,
+                                                 VIR_ASYNC_JOB_MIGRATION_OUT))) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("unable to execute QMP command 'recv-rmtnuma'"));
+        return -1;
+    }
+
+    if (virJSONValueObjectHasKey(result, "error")) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("execute QMP command 'recv-rmtnuma' failed"));
+        return -1;
+    }
+
+    return 0;
+}
+
+int
+qemuHamRollbackPages(virDomainObj *vm)
+{
+    g_autoptr(virJSONValue) result = NULL;
+
+    if (!(result = qemuHamSendQemuMonitorCommand(vm, NULL, "rollback-pages", NULL,
+                                                 VIR_ASYNC_JOB_MIGRATION_OUT))) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("unable to execute QMP command 'rollback-pages'"));
+        return -1;
+    }
+
+    if (virJSONValueObjectHasKey(result, "error")) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("execute QMP command 'rollback-pages' failed"));
+        return -1;
+    }
+
+    return 0;
+}
+
+static char *
+qemuHamGetClearReq(qemuHamMigrationInfo *hamInfo, virHamClearType type)
+{
+    g_autofree char *clearReqInit = NULL;
+    g_autoptr(virJSONValue) clearReq = NULL;
+
+    if (!(clearReqInit = virHamGetClearReqInit(type, hamInfo->srcHostname))) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("failed to add action, type and srcHostname to clear request"));
+        return NULL;
+    }
+
+    if (!(clearReq = virJSONValueFromString(clearReqInit))) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("failed to create clear request json object"));
+        return NULL;
+    }
+
+    if (virJSONValueObjectAppendString(clearReq, "name", hamInfo->name) < 0) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("failed to add name to clear request json object"));
+        return NULL;
+    }
+
+    if (virJSONValueObjectAppendNumberInt(clearReq, "srcPid", hamInfo->srcPid) < 0) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("failed to add srcPid to clear request json object"));
+        return NULL;
+    }
+
+    if (virJSONValueObjectAppendString(clearReq, "dstHostname", hamInfo->dstHostname) < 0) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("failed to add dstHostname to clear request json object"));
+        return NULL;
+    }
+
+    if (virJSONValueObjectAppendNumberInt(clearReq, "dstPid", hamInfo->dstPid) < 0) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("failed to add dstPid to clear request json object"));
+        return NULL;
+    }
+
+    return virJSONValueToString(clearReq, false);
+}
+
+void
+qemuHamSendClearReq(qemuHamMigrationInfo *hamInfo, virHamClearType type)
+{
+    g_autofree char *req = NULL;
+
+    if (!(req = qemuHamGetClearReq(hamInfo, type))) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("failed to create clear request"));
+        return;
+    }
+    VIR_INFO("Ham migration clear borrowed numa request: %s", NULLSTR(req));
+
+    if (!virHamRackIpcSyncSendAndRecv(req))
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("failed to clear borrowed numa"));
+}
+
+int
+qemuHamMigrationPrepare(qemuHamMigrationInfo *hamInfo,
+                        virConnectPtr dconn,
+                        virDomainObj *vm,
+                        char *cookieout,
+                        int cookieoutlen)
+{
+    g_autoptr(xmlXPathContext) ctxt = NULL;
+    virDomainPtr ddomain = NULL;
+    g_autofree char *dname = NULL;
+    g_autofree char *duuidstr = NULL;
+    unsigned char duuid[VIR_UUID_BUFLEN];
+    g_autoptr(qemuHamRamInfo) ramInfo = NULL;
+    g_autofree char *req = NULL;
+    g_autofree char *resp = NULL;
+    int ret = -1;
+
+    if (cookieout && cookieoutlen &&
+        cookieout[cookieoutlen-1] != '\0') {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("migration cookie was not NULL terminated"));
+        return -1;
+    }
+
+    if (!virXMLParseStringCtxt(cookieout, _("(qemu_migration_cookie)"), &ctxt)) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("failed to parse xml context"));
+        return -1;
+    }
+
+    if (!(dname = virXPathString("string(./name[1])", ctxt))) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("missing name element in migration data"));
+        return -1;
+    }
+
+    if (!(duuidstr = virXPathString("string(./uuid[1])", ctxt))) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("missing uuid element in migration data"));
+        return -1;
+    }
+
+    if (virUUIDParse(duuidstr, duuid) < 0) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("malformed uuid element"));
+        return -1;
+    }
+
+    if (!(hamInfo->dstHostname = virXPathString("string(./hostname[1])", ctxt))) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("missing hostname element in migration data"));
+        return -1;
+    }
+
+    ddomain = virGetDomain(dconn, dname, duuid, -1);
+
+    VIR_DEBUG("Begin to get ram info of domain on destination for ham migration");
+    if (!(ramInfo = qemuHamGetRamInfo(ddomain))) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("failed to get ram info of domain on destination"));
+        goto cleanup;
+    }
+
+    hamInfo->dstPid = ramInfo->pid;
+
+    VIR_DEBUG("Begin to get borrowed numa info for ham migration");
+    if (!(req = qemuHamGetBorrowReq(hamInfo, ramInfo))) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("failed to create borrow numa request"));
+        goto cleanup;
+    }
+    VIR_INFO("Ham migration borrow numa request: %s", NULLSTR(req));
+
+    if (!(resp = virHamRackIpcSyncSendAndRecv(req))) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("failed to get borrow response from RackAgentIpcServer"));
+        goto cleanup;
+    }
+    VIR_INFO("Ham migration borrow numa response: %s", NULLSTR(resp));
+
+    if (qemuHamGetBorrowInfo(hamInfo, ramInfo, resp) < 0) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("failed to get borrow info"));
+        goto cleanup;
+    }
+
+    VIR_DEBUG("Send ram info to domain on source for ham migration");
+    if (qemuHamSendRamInfo(ramInfo, vm) < 0) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("failed to send ram info to domain on source"));
+        goto cleanup;
+    }
+
+    ret = 0;
+
+ cleanup:
+    if (ddomain)
+        virObjectUnref(ddomain);
+
+    return ret;
+}
diff --git a/src/qemu/qemu_ham.h b/src/qemu/qemu_ham.h
new file mode 100644
index 0000000000..5ea9c0a875
--- /dev/null
+++ b/src/qemu/qemu_ham.h
@@ -0,0 +1,38 @@
+/*
+ * qemu_ham.h: QEMU ham migration handling
+ *
+ * Copyright (C) 2025 Huawei Technologies Co., Ltd
+ */
+
+#pragma once
+
+#include "virconftypes.h"
+#include "virdomainjob.h"
+#include "virham.h"
+
+typedef struct _qemuHamMigrationInfo qemuHamMigrationInfo;
+struct _qemuHamMigrationInfo {
+    char *name;
+    char *srcHostname;
+    char *dstHostname;
+    int srcPid;
+    int dstPid;
+};
+
+void qemuHamMigrationInfoFree(qemuHamMigrationInfo *hamInfo);
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(qemuHamMigrationInfo, qemuHamMigrationInfoFree);
+
+int qemuDomainSendQemuMonitorCommandAsync(virDomainObj *vm,
+                                          const char *cmd,
+                                          char **result,
+                                          virDomainAsyncJob asyncJob);
+
+int qemuHamMigrationPrepare(qemuHamMigrationInfo *hamInfo,
+                            virConnectPtr dconn,
+                            virDomainObj *vm,
+                            char *cookieout,
+                            int cookieoutlen);
+
+int qemuHamRollbackPages(virDomainObj *vm);
+
+void qemuHamSendClearReq(qemuHamMigrationInfo *hamInfo, virHamClearType type);
diff --git a/src/qemu/qemu_migration.c b/src/qemu/qemu_migration.c
index 616aa043fd..9cc394ff4e 100644
--- a/src/qemu/qemu_migration.c
+++ b/src/qemu/qemu_migration.c
@@ -39,6 +39,7 @@
 #include "qemu_slirp.h"
 #include "qemu_block.h"
 #include "qemu_tpm.h"
+#include "qemu_ham.h"
 
 #include "domain_audit.h"
 #include "virlog.h"
@@ -58,6 +59,7 @@
 #include "virprocess.h"
 #include "virdomainsnapshotobjlist.h"
 #include "virutil.h"
+#include "virham.h"
 
 #define VIR_FROM_THIS VIR_FROM_QEMU
 
@@ -5533,6 +5535,7 @@ qemuMigrationSrcPerformPeer2Peer3(virQEMUDriver *driver,
     int maxparams = 0;
     size_t i;
     bool offline = !!(flags & VIR_MIGRATE_OFFLINE);
+    g_autoptr(qemuHamMigrationInfo) hamInfo = NULL;
 
     VIR_DEBUG("driver=%p, sconn=%p, dconn=%p, dconnuri=%s, vm=%p, xmlin=%s, "
               "dname=%s, uri=%s, graphicsuri=%s, listenAddress=%s, "
@@ -5679,6 +5682,19 @@ qemuMigrationSrcPerformPeer2Peer3(virQEMUDriver *driver,
         goto finish;
     }
 
+    if (flags & VIR_MIGRATE_LDST) {
+        VIR_INFO("Prepare for ham migration");
+        hamInfo = g_new0(qemuHamMigrationInfo, 1);
+        hamInfo->srcHostname = virGetHostname();
+        hamInfo->srcPid = vm->pid;
+        if (qemuHamMigrationPrepare(hamInfo, dconn, vm, cookieout, cookieoutlen) < 0) {
+            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                           _("failed to prepare for ham migration"));
+            virErrorPreserveLast(&orig_err);
+            goto finish;
+        }
+    }
+
     /* Perform the migration.  The driver isn't supposed to return
      * until the migration is complete. The src VM should remain
      * running, but in paused state until the destination can
@@ -5836,6 +5852,19 @@ qemuMigrationSrcPerformPeer2Peer3(virQEMUDriver *driver,
         ret = -1;
     }
 
+    if ((flags & VIR_MIGRATE_LDST) && hamInfo && hamInfo->name) {
+        VIR_INFO("Cleanup for ham migration");
+        if (ret < 0) {
+            if (qemuHamRollbackPages(vm) < 0)
+                virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                               _("failed to rollback memory pages for ham migration"));
+            qemuHamSendClearReq(hamInfo, VIR_HAM_CLEAR_MIGRATE_FAILURE);
+            virCondBroadcast(&vm->hamCond);
+        } else {
+            qemuHamSendClearReq(hamInfo, VIR_HAM_CLEAR_MIGRATE_SUCCESS);
+        }
+    }
+
     virObjectUnref(st);
 
     virErrorRestore(&orig_err);
diff --git a/src/qemu/qemu_migration.h b/src/qemu/qemu_migration.h
index 6da44189ce..393dee51bc 100644
--- a/src/qemu/qemu_migration.h
+++ b/src/qemu/qemu_migration.h
@@ -64,6 +64,7 @@
      VIR_MIGRATE_ZEROCOPY | \
      VIR_MIGRATE_RETURNPATH | \
      VIR_MIGRATE_ONECOPY | \
+     VIR_MIGRATE_LDST | \
      0)
 
 /* All supported migration parameters and their types. */
diff --git a/src/qemu/qemu_migration_params.c b/src/qemu/qemu_migration_params.c
index 289c3a628f..0c56da974b 100644
--- a/src/qemu/qemu_migration_params.c
+++ b/src/qemu/qemu_migration_params.c
@@ -100,6 +100,7 @@ VIR_ENUM_IMPL(qemuMigrationCapability,
               "return-path",
               "zero-copy-send",
               "onecopy",
+              "ldst",
 );
 
 
@@ -201,6 +202,11 @@ static const qemuMigrationParamsFlagMapItem qemuMigrationParamsFlagMap[] = {
      VIR_MIGRATE_ONECOPY,
      QEMU_MIGRATION_CAP_ONECOPY,
      QEMU_MIGRATION_SOURCE},
+
+     {QEMU_MIGRATION_FLAG_REQUIRED,
+     VIR_MIGRATE_LDST,
+     QEMU_MIGRATION_CAP_LDST,
+     QEMU_MIGRATION_SOURCE},
 };
 
 /* Translation from VIR_MIGRATE_PARAM_* typed parameters to
diff --git a/src/qemu/qemu_migration_params.h b/src/qemu/qemu_migration_params.h
index b704291d3e..ada417d15b 100644
--- a/src/qemu/qemu_migration_params.h
+++ b/src/qemu/qemu_migration_params.h
@@ -41,6 +41,7 @@ typedef enum {
     QEMU_MIGRATION_CAP_RETURN_PATH,
     QEMU_MIGRATION_CAP_ZERO_COPY_SEND,
     QEMU_MIGRATION_CAP_ONECOPY,
+    QEMU_MIGRATION_CAP_LDST,
 
     QEMU_MIGRATION_CAP_LAST
 } qemuMigrationCapability;
diff --git a/src/qemu_protocol-structs b/src/qemu_protocol-structs
index ea0854385f..daed2ced4a 100644
--- a/src/qemu_protocol-structs
+++ b/src/qemu_protocol-structs
@@ -55,6 +55,14 @@ struct qemu_domain_monitor_command_with_files_args {
 struct qemu_domain_monitor_command_with_files_ret {
         remote_nonnull_string      result;
 };
+struct qemu_domain_monitor_command_async_args {
+        remote_nonnull_domain      dom;
+        remote_nonnull_string      cmd;
+        int                        asyncJob;
+};
+struct qemu_domain_monitor_command_async_ret {
+        remote_nonnull_string      result;
+};
 enum qemu_procedure {
         QEMU_PROC_DOMAIN_MONITOR_COMMAND = 1,
         QEMU_PROC_DOMAIN_ATTACH = 2,
@@ -63,4 +71,5 @@ enum qemu_procedure {
         QEMU_PROC_CONNECT_DOMAIN_MONITOR_EVENT_DEREGISTER = 5,
         QEMU_PROC_DOMAIN_MONITOR_EVENT = 6,
         QEMU_PROC_DOMAIN_MONITOR_COMMAND_WITH_FILES       = 7,
+        QEMU_PROC_DOMAIN_MONITOR_COMMAND_ASYNC            = 8,
 };
diff --git a/src/remote/qemu_protocol.x b/src/remote/qemu_protocol.x
index c7f3abfcbf..02788d6bc3 100644
--- a/src/remote/qemu_protocol.x
+++ b/src/remote/qemu_protocol.x
@@ -89,6 +89,16 @@ struct qemu_domain_monitor_command_with_files_ret {
     remote_nonnull_string result;
 };
 
+struct qemu_domain_monitor_command_async_args {
+    remote_nonnull_domain dom;
+    remote_nonnull_string cmd;
+    int asyncJob;
+};
+
+struct qemu_domain_monitor_command_async_ret {
+    remote_nonnull_string result;
+};
+
 
 /* Define the program number, protocol version and procedure numbers here. */
 const QEMU_PROGRAM = 0x20008087;
@@ -169,5 +179,12 @@ enum qemu_procedure {
      * @priority: low
      * @acl: domain:write
      */
-    QEMU_PROC_DOMAIN_MONITOR_COMMAND_WITH_FILES = 7
+    QEMU_PROC_DOMAIN_MONITOR_COMMAND_WITH_FILES = 7,
+
+    /**
+     * @generate: none
+     * @priority: low
+     * @acl: domain:write
+     */
+    QEMU_PROC_DOMAIN_MONITOR_COMMAND_ASYNC = 8
 };
diff --git a/src/remote/remote_daemon.c b/src/remote/remote_daemon.c
index 657c053f6f..b236adf667 100644
--- a/src/remote/remote_daemon.c
+++ b/src/remote/remote_daemon.c
@@ -55,6 +55,7 @@
 #include "virsystemd.h"
 #include "virhostuptime.h"
 #include "virdaemon.h"
+#include "virham.h"
 
 #include "driver.h"
 
@@ -1193,6 +1194,9 @@ int main(int argc, char **argv) {
         goto cleanup;
     }
 
+    VIR_DEBUG("Clear all borrowed numa nodes of ham migration");
+    virHamClearAll(virGetHostname());
+
 #if defined(__linux__) && defined(NETLINK_ROUTE)
     /* Register the netlink event service for NETLINK_ROUTE */
     if (virNetlinkEventServiceStart(NETLINK_ROUTE, 0) < 0) {
diff --git a/src/remote/remote_daemon_dispatch.c b/src/remote/remote_daemon_dispatch.c
index 10f343843a..df6b01f6e4 100644
--- a/src/remote/remote_daemon_dispatch.c
+++ b/src/remote/remote_daemon_dispatch.c
@@ -4739,6 +4739,38 @@ qemuDispatchDomainMonitorCommandWithFiles(virNetServer *server G_GNUC_UNUSED,
 }
 
 
+static int
+qemuDispatchDomainMonitorCommandAsync(virNetServer *server G_GNUC_UNUSED,
+                                      virNetServerClient *client,
+                                      virNetMessage *msg G_GNUC_UNUSED,
+                                      struct virNetMessageError *rerr,
+                                      qemu_domain_monitor_command_async_args *args,
+                                      qemu_domain_monitor_command_async_ret *ret)
+{
+    virDomainPtr dom = NULL;
+    int rv = -1;
+    virConnectPtr conn = remoteGetHypervisorConn(client);
+
+    if (!conn)
+        goto cleanup;
+
+    if (!(dom = get_nonnull_domain(conn, args->dom)))
+        goto cleanup;
+
+    if (virDomainQemuMonitorCommandAsync(dom, args->cmd, &ret->result,
+                                         args->asyncJob) < 0)
+        goto cleanup;
+
+    rv = 0;
+
+ cleanup:
+    if (rv < 0)
+        virNetMessageSaveError(rerr);
+    virObjectUnref(dom);
+    return rv;
+}
+
+
 static int
 remoteDispatchDomainMigrateBegin3(virNetServer *server G_GNUC_UNUSED,
                                   virNetServerClient *client,
diff --git a/src/remote/remote_driver.c b/src/remote/remote_driver.c
index 82d4c968ea..ab66af5bdb 100644
--- a/src/remote/remote_driver.c
+++ b/src/remote/remote_driver.c
@@ -5553,6 +5553,30 @@ remoteDomainQemuMonitorCommandWithFiles(virDomainPtr domain,
 }
 
 
+static int
+remoteDomainQemuMonitorCommandAsync(virDomainPtr domain, const char *cmd,
+                                    char **result, int asyncJob)
+{
+    qemu_domain_monitor_command_async_args args;
+    g_auto(qemu_domain_monitor_command_async_ret) ret = {0};
+    struct private_data *priv = domain->conn->privateData;
+    VIR_LOCK_GUARD lock = remoteDriverLock(priv);
+
+    make_nonnull_domain(&args.dom, domain);
+    args.cmd = (char *)cmd;
+    args.asyncJob = asyncJob;
+
+    if (call(domain->conn, priv, REMOTE_CALL_QEMU, QEMU_PROC_DOMAIN_MONITOR_COMMAND_ASYNC,
+             (xdrproc_t) xdr_qemu_domain_monitor_command_async_args, (char *) &args,
+             (xdrproc_t) xdr_qemu_domain_monitor_command_async_ret, (char *) &ret) == -1)
+        return -1;
+
+    *result = g_steal_pointer(&ret.result);
+
+    return 0;
+}
+
+
 static char *
 remoteDomainMigrateBegin3(virDomainPtr domain,
                           const char *xmlin,
@@ -7786,6 +7810,7 @@ static virHypervisorDriver hypervisor_driver = {
     .domainSnapshotDelete = remoteDomainSnapshotDelete, /* 0.8.0 */
     .domainQemuMonitorCommand = remoteDomainQemuMonitorCommand, /* 0.8.3 */
     .domainQemuMonitorCommandWithFiles = remoteDomainQemuMonitorCommandWithFiles, /* 8.2.0 */
+    .domainQemuMonitorCommandAsync = remoteDomainQemuMonitorCommandAsync, /* 9.10.0 */
     .domainQemuAttach = remoteDomainQemuAttach, /* 0.9.4 */
     .domainQemuAgentCommand = remoteDomainQemuAgentCommand, /* 0.10.0 */
     .connectDomainQemuMonitorEventRegister = remoteConnectDomainQemuMonitorEventRegister, /* 1.2.3 */
diff --git a/src/util/meson.build b/src/util/meson.build
index 91f5b0d45e..9fa767f40e 100644
--- a/src/util/meson.build
+++ b/src/util/meson.build
@@ -111,6 +111,7 @@ util_sources = [
   'virvsock.c',
   'virxml.c',
   'virub.c',
+  'virham.c',
 ]
 
 util_public_sources = files(
diff --git a/src/util/virham.c b/src/util/virham.c
new file mode 100644
index 0000000000..a9a2f94059
--- /dev/null
+++ b/src/util/virham.c
@@ -0,0 +1,223 @@
+/*
+ * virham.c: rack ipc functions for ham migration
+ *
+ * Copyright (C) 2025 Huawei Technologies Co., Ltd
+ */
+
+#include <dlfcn.h>
+
+#include "viralloc.h"
+#include "virerror.h"
+#include "virfile.h"
+#include "virham.h"
+#include "virjson.h"
+#include "virlog.h"
+
+#define VIR_FROM_THIS VIR_FROM_NONE
+
+VIR_LOG_INIT("util.ham");
+
+/* rack ipc path for ham migration */
+#define VIR_HAM_RACK_IPC_PATH "/usr/local/softbus/ctrlbus/lib/librack_com.so"
+/* rack ipc timeout(seconds) for ham migration */
+#define VIR_HAM_RACK_IPC_TIMEOUT (60 * 30)
+
+typedef int (*virHamRackIpcClientStart)(uint16_t timeout);
+
+typedef struct _virHamRackIpcData virHamRackIpcData;
+struct _virHamRackIpcData {
+    uint8_t *buffer;
+    uint32_t length;
+};
+
+typedef int (*virHamRackIpcSyncSend)(virHamRackIpcData *sendData, virHamRackIpcData *recvData);
+
+typedef void (*virHamRackIpcCallback)(void *ctx,
+                                      void *recv,
+                                      uint32_t len,
+                                      int32_t result);
+
+typedef struct _virHamRackIpcCallbackDef virHamRackIpcCallbackDef;
+struct _virHamRackIpcCallbackDef {
+    virHamRackIpcCallback cb;
+    void *cbCtx;
+};
+
+typedef int (*virHamRackIpcAsyncSend)(virHamRackIpcData *sendData, virHamRackIpcCallbackDef *callback);
+
+typedef struct _virHamRackIpcClient virHamRackIpcClient;
+struct _virHamRackIpcClient {
+    virHamRackIpcClientStart start;
+    virHamRackIpcSyncSend syncSend;
+    virHamRackIpcAsyncSend asyncSend;
+};
+
+static virHamRackIpcClient *rackIpcClient;
+
+static void
+virHamRackIpcDataFree(virHamRackIpcData *data)
+{
+    if (!data)
+        return;
+
+    VIR_FREE(data->buffer);
+    VIR_FREE(data);
+}
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(virHamRackIpcData, virHamRackIpcDataFree);
+
+static int
+virHamRackIpcInitialize(void)
+{
+    void *handle = NULL;
+    int code;
+
+    if (!virFileExists(VIR_HAM_RACK_IPC_PATH)) {
+        VIR_WARN("Ham rack ipc file doesn't exist");
+        return -1;
+    }
+
+    if (!(handle = dlopen(VIR_HAM_RACK_IPC_PATH, RTLD_NOW | RTLD_LOCAL))) {
+        virReportError(VIR_ERR_INTERNAL_ERROR,
+                       _("failed to load rack ipc file: %1$s"), dlerror());
+        return -1;
+    }
+
+    rackIpcClient = g_new0(virHamRackIpcClient, 1);
+
+    if (!(rackIpcClient->start = dlsym(handle, "RackStartIpcClientWithTimeout"))) {
+        virReportError(VIR_ERR_INTERNAL_ERROR,
+                       _("failed to find symbol 'RackStartIpcClientWithTimeout': %1$s"), dlerror());
+        goto error;
+    }
+
+    if (!(rackIpcClient->syncSend = dlsym(handle, "RackSyncSendForHam"))) {
+        virReportError(VIR_ERR_INTERNAL_ERROR,
+                       _("failed to find symbol 'RackSyncSendForHam': %1$s"), dlerror());
+        goto error;
+    }
+
+    if (!(rackIpcClient->asyncSend = dlsym(handle, "RackAsyncSendForHam"))) {
+        virReportError(VIR_ERR_INTERNAL_ERROR,
+                       _("failed to find symbol 'RackAsyncSendForHam': %1$s"), dlerror());
+        goto error;
+    }
+
+    if ((code = rackIpcClient->start(VIR_HAM_RACK_IPC_TIMEOUT)) != 0) {
+        virReportError(VIR_ERR_INTERNAL_ERROR,
+                       _("failed to start RackIpcClient with code %1$d"), code);
+        goto error;
+    }
+
+    return 0;
+
+ error:
+    dlclose(handle);
+    VIR_FREE(rackIpcClient);
+    return -1;
+}
+
+char *
+virHamRackIpcSyncSendAndRecv(const char *req)
+{
+    g_autoptr(virHamRackIpcData) sendData = g_new0(virHamRackIpcData, 1);
+    g_autoptr(virHamRackIpcData) recvData = g_new0(virHamRackIpcData, 1);
+    int code;
+    g_autofree char *resp = NULL;
+
+    if (!rackIpcClient && virHamRackIpcInitialize() < 0) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("failed to initialize RackIpcClient"));
+        return NULL;
+    }
+
+    sendData->length = strlen(req);
+    sendData->buffer = g_malloc0(sendData->length);
+    memcpy(sendData->buffer, req, sendData->length);
+
+    if ((code = rackIpcClient->syncSend(sendData, recvData)) != 0) {
+        virReportError(VIR_ERR_INTERNAL_ERROR,
+                       _("failed to communicate with RackAgentIpcServer with code %1$d"), code);
+        return NULL;
+    }
+
+    if (!(resp = g_new0(char, recvData->length + 1))) {
+        virReportError(VIR_ERR_INTERNAL_ERROR,
+                       _("failed to allocate memory of the length %1$u"), recvData->length);
+        return NULL;
+    }
+
+    memcpy(resp, recvData->buffer, recvData->length);
+    /* MatrixVirt returned length does not include the string terminator */
+    resp[recvData->length] = '\0';
+
+    return g_steal_pointer(&resp);
+}
+
+char *
+virHamGetClearReqInit(virHamClearType type, const char *hostname)
+{
+    g_autoptr(virJSONValue) clearReq = virJSONValueNewObject();
+
+    if (virJSONValueObjectAppendString(clearReq, "action", "clear") < 0) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("failed to add action to clear request json object"));
+        return NULL;
+    }
+
+    if (virJSONValueObjectAppendNumberInt(clearReq, "type", type) < 0) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("failed to add type to clear request json object"));
+        return NULL;
+    }
+
+    if (virJSONValueObjectAppendString(clearReq, "srcHostname", hostname) < 0) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("failed to add srcHostname to clear request json object"));
+        return NULL;
+    }
+
+    return virJSONValueToString(clearReq, false);
+}
+
+static int
+virHamRackIpcAsyncSendAndRecv(const char *req, virHamRackIpcCallbackDef *callback)
+{
+    g_autoptr(virHamRackIpcData) sendData = g_new0(virHamRackIpcData, 1);
+    int code;
+
+    if (!rackIpcClient && virHamRackIpcInitialize() < 0) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("failed to initialize RackIpcClient"));
+        return -1;
+    }
+
+    sendData->length = strlen(req);
+    sendData->buffer = g_malloc0(sendData->length);
+    memcpy(sendData->buffer, req, sendData->length);
+
+    if ((code = rackIpcClient->asyncSend(sendData, callback)) != 0) {
+        virReportError(VIR_ERR_INTERNAL_ERROR,
+                       _("failed to send request to RackAgentIpcServer with code %1$d"), code);
+        return -1;
+    }
+
+    return 0;
+}
+
+void
+virHamClearAll(const char *hostname)
+{
+    g_autofree char *req = NULL;
+    virHamRackIpcCallbackDef *callback = g_new0(virHamRackIpcCallbackDef, 1);
+
+    if (!(req = virHamGetClearReqInit(VIR_HAM_CLEAR_ALL, hostname))) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("failed to create clear request"));
+        return;
+    }
+    VIR_INFO("Ham migration clear all borrowed numa nodes request: %s", NULLSTR(req));
+
+    if (virHamRackIpcAsyncSendAndRecv(req, callback) < 0)
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("failed to clear all borrowed numa nodes"));
+}
diff --git a/src/util/virham.h b/src/util/virham.h
new file mode 100644
index 0000000000..1de59900c9
--- /dev/null
+++ b/src/util/virham.h
@@ -0,0 +1,21 @@
+/*
+ * virham.h: rack ipc functions for ham migration
+ *
+ * Copyright (C) 2025 Huawei Technologies Co., Ltd
+ */
+
+#pragma once
+
+typedef enum {
+    VIR_HAM_CLEAR_ALL = 0,              /* Clear all resource */
+    VIR_HAM_CLEAR_MIGRATE_SUCCESS,      /* Clear specific resource when ham migration succeed */
+    VIR_HAM_CLEAR_MIGRATE_FAILURE,      /* Clear specific resource when ham migration failed */
+
+    VIR_HAM_CLEAR_LAST
+} virHamClearType;
+
+char *virHamRackIpcSyncSendAndRecv(const char *req);
+
+char *virHamGetClearReqInit(virHamClearType type, const char *hostname);
+
+void virHamClearAll(const char *hostname);
diff --git a/tools/virsh-domain.c b/tools/virsh-domain.c
index af55e01f08..e4b751289c 100644
--- a/tools/virsh-domain.c
+++ b/tools/virsh-domain.c
@@ -6527,6 +6527,10 @@ static const vshCmdOptDef opts_domjobabort[] = {
      .type = VSH_OT_BOOL,
      .help = N_("interrupt post-copy migration")
     },
+    {.name = "ham",
+     .type = VSH_OT_BOOL,
+     .help = N_("interrupt ham migration")
+    },
     {.name = NULL}
 };
 
@@ -6543,6 +6547,9 @@ cmdDomjobabort(vshControl *ctl, const vshCmd *cmd)
     if (vshCommandOptBool(cmd, "postcopy"))
         flags |= VIR_DOMAIN_ABORT_JOB_POSTCOPY;
 
+    if (vshCommandOptBool(cmd, "ham"))
+        flags |= VIR_DOMAIN_ABORT_JOB_HAM;
+
     if (flags == 0)
         rc = virDomainAbortJob(dom);
     else
@@ -11137,6 +11144,10 @@ static const vshCmdOptDef opts_migrate[] = {
      .type = VSH_OT_BOOL,
      .help = N_("enable one-copy migration")
     },
+    {.name = "ldst",
+     .type = VSH_OT_BOOL,
+     .help = N_("enable ham migration")
+    },
     {.name = NULL}
 };
 
@@ -11188,6 +11199,7 @@ doMigrate(void *opaque)
         { "suspend", VIR_MIGRATE_PAUSED },
         { "return-path", VIR_MIGRATE_RETURNPATH },
         { "onecopy", VIR_MIGRATE_ONECOPY },
+        { "ldst", VIR_MIGRATE_LDST },
     };
 
 #ifndef WIN32
-- 
2.51.0.windows.1