From ee85abc4af033c860e9a4e818d3bb6986a1f7d42 Mon Sep 17 00:00:00 2001 From: wh02252983 Date: Fri, 14 Nov 2025 15:30:21 +0800 Subject: [PATCH] add migration support on hct device --- ...pi-build-c-add-srat-and-slit-acpi-ta.patch | 109 + ...support-live-migrate-for-vpsp-device.patch | 401 ++++ ...6-csv-introduce-host-data-str-option.patch | 132 ++ ...support-issuing-the-launch-finish-ex.patch | 181 ++ ...hct-support-start-with-ccp-ko-driver.patch | 345 ++++ ...-support-vfio-pci-multiple-processes.patch | 1786 +++++++++++++++++ ...ort-live-migration-function-for-virt.patch | 397 ++++ ...virtual-machine-paused-due-to-obtain.patch | 166 ++ ...ort-abort-migration-when-hct-excepti.patch | 169 ++ qemu.spec | 16 +- 10 files changed, 3701 insertions(+), 1 deletion(-) create mode 100644 0523-hw-riscv-virt-acpi-build-c-add-srat-and-slit-acpi-ta.patch create mode 100644 0524-hw-misc-psp-support-live-migrate-for-vpsp-device.patch create mode 100644 0525-target-i386-csv-introduce-host-data-str-option.patch create mode 100644 0526-target-i386-csv-support-issuing-the-launch-finish-ex.patch create mode 100644 0527-hw-vfio-hct-support-start-with-ccp-ko-driver.patch create mode 100644 0528-hw-vfio-hct-support-vfio-pci-multiple-processes.patch create mode 100644 0529-hw-vfio-hct-support-live-migration-function-for-virt.patch create mode 100644 0530-hw-vfio-hct-fix-virtual-machine-paused-due-to-obtain.patch create mode 100644 0531-hw-vfio-hct-support-abort-migration-when-hct-excepti.patch diff --git a/0523-hw-riscv-virt-acpi-build-c-add-srat-and-slit-acpi-ta.patch b/0523-hw-riscv-virt-acpi-build-c-add-srat-and-slit-acpi-ta.patch new file mode 100644 index 0000000..73f41b5 --- /dev/null +++ b/0523-hw-riscv-virt-acpi-build-c-add-srat-and-slit-acpi-ta.patch @@ -0,0 +1,109 @@ +From 92ddeb71acc6716e75339b8b0b8b1791bf9f0be6 Mon Sep 17 00:00:00 2001 +From: Haibo Xu +Date: Mon, 29 Jan 2024 17:42:00 +0800 +Subject: [PATCH] hw/riscv/virt-acpi-build.c: Add SRAT and SLIT ACPI tables + +Enable ACPI NUMA support by adding the following 2 ACPI tables: +SRAT: provides the association for memory/Harts and Proximity Domains +SLIT: provides the relative distance between Proximity Domains + +The SRAT RINTC Affinity Structure definition[1] was based on the recently +approved ACPI CodeFirst ECR[2]. + +[1] https://github.com/riscv-non-isa/riscv-acpi/issues/25 +[2] https://mantis.uefi.org/mantis/view.php?id=2433 + +Signed-off-by: Haibo Xu +Reviewed-by: Andrew Jones +Message-ID: <20240129094200.3581037-1-haibo1.xu@intel.com> +Signed-off-by: Alistair Francis +--- + hw/riscv/virt-acpi-build.c | 60 ++++++++++++++++++++++++++++++++++++++ + 1 file changed, 60 insertions(+) + +diff --git a/hw/riscv/virt-acpi-build.c b/hw/riscv/virt-acpi-build.c +index 4b5a8b600b..99beaaa1cd 100644 +--- a/hw/riscv/virt-acpi-build.c ++++ b/hw/riscv/virt-acpi-build.c +@@ -596,11 +596,61 @@ static void build_madt(GArray *table_data, + acpi_table_end(linker, &table); + } + ++/* ++ * ACPI spec, Revision 6.5+ ++ * 5.2.16 System Resource Affinity Table (SRAT) ++ * REF: https://github.com/riscv-non-isa/riscv-acpi/issues/25 ++ * https://drive.google.com/file/d/1YTdDx2IPm5IeZjAW932EYU-tUtgS08tX/view ++ */ ++static void ++build_srat(GArray *table_data, BIOSLinker *linker, RISCVVirtState *vms) ++{ ++ int i; ++ uint64_t mem_base; ++ MachineClass *mc = MACHINE_GET_CLASS(vms); ++ MachineState *ms = MACHINE(vms); ++ const CPUArchIdList *cpu_list = mc->possible_cpu_arch_ids(ms); ++ AcpiTable table = { .sig = "SRAT", .rev = 3, .oem_id = vms->oem_id, ++ .oem_table_id = vms->oem_table_id }; ++ ++ acpi_table_begin(&table, table_data); ++ build_append_int_noprefix(table_data, 1, 4); /* Reserved */ ++ build_append_int_noprefix(table_data, 0, 8); /* Reserved */ ++ ++ for (i = 0; i < cpu_list->len; ++i) { ++ uint32_t nodeid = cpu_list->cpus[i].props.node_id; ++ /* ++ * 5.2.16.8 RINTC Affinity Structure ++ */ ++ build_append_int_noprefix(table_data, 7, 1); /* Type */ ++ build_append_int_noprefix(table_data, 20, 1); /* Length */ ++ build_append_int_noprefix(table_data, 0, 2); /* Reserved */ ++ build_append_int_noprefix(table_data, nodeid, 4); /* Proximity Domain */ ++ build_append_int_noprefix(table_data, i, 4); /* ACPI Processor UID */ ++ /* Flags, Table 5-70 */ ++ build_append_int_noprefix(table_data, 1 /* Flags: Enabled */, 4); ++ build_append_int_noprefix(table_data, 0, 4); /* Clock Domain */ ++ } ++ ++ mem_base = vms->memmap[VIRT_DRAM].base; ++ for (i = 0; i < ms->numa_state->num_nodes; ++i) { ++ if (ms->numa_state->nodes[i].node_mem > 0) { ++ build_srat_memory(table_data, mem_base, ++ ms->numa_state->nodes[i].node_mem, i, ++ MEM_AFFINITY_ENABLED); ++ mem_base += ms->numa_state->nodes[i].node_mem; ++ } ++ } ++ ++ acpi_table_end(linker, &table); ++} ++ + static void virt_acpi_build(RISCVVirtState *s, AcpiBuildTables *tables) + { + GArray *table_offsets; + unsigned dsdt, xsdt; + GArray *tables_blob = tables->table_data; ++ MachineState *ms = MACHINE(s); + + table_offsets = g_array_new(false, true, + sizeof(uint32_t)); +@@ -636,6 +686,16 @@ static void virt_acpi_build(RISCVVirtState *s, AcpiBuildTables *tables) + s->oem_table_id); + } + ++ if (ms->numa_state->num_nodes > 0) { ++ acpi_add_table(table_offsets, tables_blob); ++ build_srat(tables_blob, tables->linker, s); ++ if (ms->numa_state->have_numa_distance) { ++ acpi_add_table(table_offsets, tables_blob); ++ build_slit(tables_blob, tables->linker, ms, s->oem_id, ++ s->oem_table_id); ++ } ++ } ++ + /* XSDT is pointed to by RSDP */ + xsdt = tables_blob->len; + build_xsdt(tables_blob, tables->linker, table_offsets, s->oem_id, +-- +2.39.3 + diff --git a/0524-hw-misc-psp-support-live-migrate-for-vpsp-device.patch b/0524-hw-misc-psp-support-live-migrate-for-vpsp-device.patch new file mode 100644 index 0000000..c91fcfb --- /dev/null +++ b/0524-hw-misc-psp-support-live-migrate-for-vpsp-device.patch @@ -0,0 +1,401 @@ +From 5f8dfa658010bcc5df0ef1226d90c79baa8f15c1 Mon Sep 17 00:00:00 2001 +From: xiongmengbiao +Date: Thu, 7 Aug 2025 17:48:54 +0800 +Subject: [PATCH] hw/misc/psp: support live migrate for vpsp device + +This patch adds live migration support by: +1. Introducing VMStateDescription for migration +2. Implementing pre-save to backup device state: + - Key images via VPSP_OP_BACKUP_KEY + - Command context via VPSP_OP_BACKUP_CTX +3. Implementing post-load to restore state: + - Command context via VPSP_OP_RESTORE_CTX + - Key images via VPSP_OP_RESTORE_KEY + +Signed-off-by: xiongmengbiao +--- + hw/i386/pc.c | 1 + + hw/misc/psp.c | 296 ++++++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 297 insertions(+) + +diff --git a/hw/i386/pc.c b/hw/i386/pc.c +index 2bf0341182..6fffaf6832 100644 +--- a/hw/i386/pc.c ++++ b/hw/i386/pc.c +@@ -839,6 +839,7 @@ static void mem2_init(MachineState *ms, MemoryRegion *system_memory) + + sprintf(mr_name, "mem2-%d", i); + memory_region_init_ram_ptr(mem2_mr[i], NULL, mr_name, HUGEPAGE_SIZE, ram); ++ vmstate_register_ram_global(mem2_mr[i]); + memory_region_add_subregion(system_memory, ms->ram2_base + (i * HUGEPAGE_SIZE), mem2_mr[i]); + } + +diff --git a/hw/misc/psp.c b/hw/misc/psp.c +index c2af21d34c..1c0dd57501 100644 +--- a/hw/misc/psp.c ++++ b/hw/misc/psp.c +@@ -19,17 +19,27 @@ + #include "exec/address-spaces.h" + #include "exec/ramblock.h" + #include "hw/i386/e820_memory_layout.h" ++#include "migration/migration.h" ++#include "migration/misc.h" + #include + + #define TYPE_PSP_DEV "psp" + OBJECT_DECLARE_SIMPLE_TYPE(PSPDevState, PSP_DEV) + ++#define VPSP_MIGRATE_VERSION 1 ++#define HUGEPAGE_SIZE (1024*1024*2) ++ ++static int vpsp_dev_pre_save(void *opaque); ++static int vpsp_dev_post_load(void *opaque, int version_id); ++ + struct PSPDevState { + /* Private */ + DeviceState pdev; + + /* Public */ + Notifier shutdown_notifier; ++ NotifierWithReturn precopy_notifier; ++ + int dev_fd; + uint8_t enabled; + +@@ -41,6 +51,30 @@ struct PSPDevState { + uint32_t vid; + /* pinned hugepage numbers */ + int hp_num; ++ ++ uint32_t img_len; ++ uint8_t *key_img; ++ uint32_t ctx_len; ++ uint8_t *cmd_ctx; ++}; ++ ++static const VMStateDescription vmstate_vpsp_dev = { ++ .name = "vpsp-dev", ++ .version_id = VPSP_MIGRATE_VERSION, ++ .minimum_version_id = VPSP_MIGRATE_VERSION, ++ .pre_save = vpsp_dev_pre_save, ++ .post_load = vpsp_dev_post_load, ++ .fields = (VMStateField[]) { ++ VMSTATE_UINT32(img_len, PSPDevState), ++ VMSTATE_VBUFFER_ALLOC_UINT32(key_img, ++ PSPDevState, 0, 0, ++ img_len), ++ VMSTATE_UINT32(ctx_len, PSPDevState), ++ VMSTATE_VBUFFER_ALLOC_UINT32(cmd_ctx, ++ PSPDevState, 0, 0, ++ ctx_len), ++ VMSTATE_END_OF_LIST() ++ } + }; + + #define PSP_DEV_PATH "/dev/hygon_psp_config" +@@ -57,8 +91,22 @@ enum VPSP_DEV_CTRL_OPCODE { + VPSP_OP_SET_DEFAULT_VID_PERMISSION, + VPSP_OP_GET_DEFAULT_VID_PERMISSION, + VPSP_OP_SET_GPA, ++ VPSP_OP_BACKUP_KEY, ++ VPSP_OP_RESTORE_KEY, ++ VPSP_OP_BACKUP_CTX, ++ VPSP_OP_RESTORE_CTX, + }; + ++typedef struct key_img_ctl { ++ unsigned int img_len; ++ void *key_img_ptr; ++} __attribute__ ((packed)) key_img_ctl_t; ++ ++typedef struct cmd_ctx_ctl { ++ unsigned int buffer_len; ++ void *cmd_ctx_ptr; ++} __attribute__ ((packed)) cmd_ctx_ctl_t; ++ + struct psp_dev_ctrl { + unsigned char op; + unsigned char resv[3]; +@@ -70,10 +118,212 @@ struct psp_dev_ctrl { + uint64_t gpa_start; + uint64_t gpa_end; + } gpa; ++ key_img_ctl_t key_img_ctl; ++ cmd_ctx_ctl_t cmd_ctx_ctl; + unsigned char reserved[128]; + } __attribute__ ((packed)) data; + }; + ++static int vpsp_dev_backup_key_img(struct PSPDevState *state) ++{ ++ int ret = 0; ++ uint32_t img_buffer_len = 0; ++ struct psp_dev_ctrl ctrl = { 0 }; ++ ++ if (state && state->dev_fd) { ++ if (state->enabled && state->vid) { ++ ctrl.op = VPSP_OP_BACKUP_KEY; ++ ++ // get actual key image buffer length ++ ctrl.data.key_img_ctl.img_len = 0; ++ if (ioctl(state->dev_fd, PSP_IOC_VPSP_OPT, &ctrl) < 0) { ++ error_report("ioctl VPSP_OP_BACKUP_KEY: %d", -errno); ++ return -1; ++ } ++ ++ img_buffer_len = ctrl.data.key_img_ctl.img_len; ++ // no key images need to migrate, but it unlikely ++ if (img_buffer_len == 0) ++ return 0; ++ ++ // free last key images, it's unlikely ++ if (state->key_img) ++ g_free(state->key_img); ++ ++ state->key_img = g_malloc0(img_buffer_len); ++ if (!state->key_img) { ++ error_report("g_malloc0 failed: %d", -errno); ++ return -1; ++ } ++ ++ // get key images backup buffer ++ ctrl.data.key_img_ctl.img_len = img_buffer_len; ++ ctrl.data.key_img_ctl.key_img_ptr = state->key_img; ++ ret = ioctl(state->dev_fd, PSP_IOC_VPSP_OPT, &ctrl); ++ if (ret < 0) { ++ error_report("ioctl VPSP_OP_BACKUP_KEY: %d", -errno); ++ goto end; ++ } ++ ++ state->img_len = ctrl.data.key_img_ctl.img_len; ++ } ++ } ++ ++ ret = 0; ++end: ++ return ret; ++} ++ ++static int vpsp_dev_restore_key_img(struct PSPDevState *state) ++{ ++ int ret = 0; ++ struct psp_dev_ctrl ctrl = { 0 }; ++ ++ if (state && state->dev_fd) { ++ if (state->enabled && state->vid && state->img_len) { ++ if (!state->key_img) { ++ error_report("PSPDevState load invalid, key_img is null\n"); ++ return -1; ++ } ++ ++ ctrl.op = VPSP_OP_RESTORE_KEY; ++ ctrl.data.key_img_ctl.img_len = state->img_len; ++ ctrl.data.key_img_ctl.key_img_ptr = state->key_img; ++ if (ioctl(state->dev_fd, PSP_IOC_VPSP_OPT, &ctrl) < 0) { ++ error_report("ioctl PSP_IOC_VPSP_OPT: %d", -errno); ++ return -1; ++ } ++ ++ // release key_img buffer, for migrate again ++ g_free(state->key_img); ++ state->key_img = NULL; ++ state->img_len = 0; ++ } ++ } ++ ++ return ret; ++} ++ ++static int vpsp_dev_backup_cmd_ctx(struct PSPDevState *state) ++{ ++ int ret = 0; ++ uint32_t buffer_len = 0; ++ struct psp_dev_ctrl ctrl = { 0 }; ++ ++ if (state && state->dev_fd) { ++ if (state->enabled && state->vid) { ++ ctrl.op = VPSP_OP_BACKUP_CTX; ++ ++ // get actual cmd context serialization buffer length ++ ctrl.data.cmd_ctx_ctl.buffer_len = 0; ++ if (ioctl(state->dev_fd, PSP_IOC_VPSP_OPT, &ctrl) < 0) { ++ error_report("ioctl VPSP_OP_BACKUP_CTX: %d", -errno); ++ return -1; ++ } ++ ++ buffer_len = ctrl.data.cmd_ctx_ctl.buffer_len; ++ // no cmd ctx need to migrate ++ if (buffer_len == 0) ++ return 0; ++ ++ // free last cmd_ctx buffer, it's unlikely ++ if (state->cmd_ctx) ++ g_free(state->cmd_ctx); ++ ++ state->cmd_ctx = g_malloc0(buffer_len); ++ if (!state->cmd_ctx) { ++ error_report("g_malloc0 failed: %d", -errno); ++ return -1; ++ } ++ ++ ctrl.data.cmd_ctx_ctl.buffer_len = buffer_len; ++ ctrl.data.cmd_ctx_ctl.cmd_ctx_ptr = state->cmd_ctx; ++ ret = ioctl(state->dev_fd, PSP_IOC_VPSP_OPT, &ctrl); ++ if (ret < 0) { ++ error_report("ioctl VPSP_OP_BACKUP_CTX: %d", -errno); ++ goto end; ++ } ++ state->ctx_len = ctrl.data.cmd_ctx_ctl.buffer_len; ++ } ++ } ++ ++ ret = 0; ++end: ++ return ret; ++} ++ ++static int vpsp_dev_restore_cmd_ctx(struct PSPDevState *state) ++{ ++ int ret = 0; ++ struct psp_dev_ctrl ctrl = { 0 }; ++ ++ if (state && state->dev_fd) { ++ if (state->enabled && state->vid && state->ctx_len) { ++ if (!state->cmd_ctx) { ++ error_report("PSPDevState load invalid, cmd_ctx is null\n"); ++ return -1; ++ } ++ ++ ctrl.op = VPSP_OP_RESTORE_CTX; ++ ctrl.data.cmd_ctx_ctl.buffer_len = state->ctx_len; ++ ctrl.data.cmd_ctx_ctl.cmd_ctx_ptr = state->cmd_ctx; ++ ++ if (ioctl(state->dev_fd, PSP_IOC_VPSP_OPT, &ctrl) < 0) { ++ error_report("ioctl PSP_IOC_VPSP_OPT: %d", -errno); ++ return -1; ++ } ++ ++ // release cmd ctx buffer, for migrate again ++ g_free(state->cmd_ctx); ++ state->cmd_ctx = NULL; ++ state->ctx_len = 0; ++ } ++ } ++ ++ return ret; ++} ++ ++static int vpsp_dev_pre_save(void *opaque) ++{ ++ int ret = 0; ++ struct PSPDevState *state = opaque; ++ ++ /** ++ * Back up the key image in the final stage ++ * to ensure the key image is up-to-date ++ */ ++ ret = vpsp_dev_backup_key_img(opaque); ++ if (ret) ++ goto end; ++ ++end: ++ if (ret && state->key_img) { ++ g_free(state->key_img); ++ state->key_img = NULL; ++ state->img_len = 0; ++ } ++ return ret; ++} ++ ++static int vpsp_dev_post_load(void *opaque, int version_id) ++{ ++ int ret = 0; ++ ++ /** ++ * During load, there are no sequencing requirements ++ * between restore of the key image and cmd_ctx ++ */ ++ ret = vpsp_dev_restore_cmd_ctx(opaque); ++ if (ret) ++ return ret; ++ ++ ret = vpsp_dev_restore_key_img(opaque); ++ if (ret) ++ return ret; ++ ++ return ret; ++} ++ + static MemoryRegion *find_memory_region_by_name(MemoryRegion *root, const char *name) { + MemoryRegion *subregion; + MemoryRegion *result; +@@ -91,6 +341,47 @@ static MemoryRegion *find_memory_region_by_name(MemoryRegion *root, const char * + return NULL; + } + ++static int precopy_state_notifier(NotifierWithReturn *notifier, void *data) ++{ ++ int ret = 0, i; ++ PrecopyNotifyData *pnd = data; ++ char mr_name[128] = {0}; ++ MemoryRegion *find_mr = NULL; ++ PSPDevState *state = container_of(notifier, PSPDevState, precopy_notifier); ++ ++ if (pnd->reason != PRECOPY_NOTIFY_COMPLETE) ++ goto end; ++ ++ /** ++ * The host kernel will then check each cmd_ctx ++ * to confirm all cmd_ctx are completed. ++ */ ++ ret = vpsp_dev_backup_cmd_ctx(state); ++ if (ret) ++ goto end; ++ ++ for (i = 0 ; i < state->hp_num; ++i) { ++ sprintf(mr_name, "mem2-%d", i); ++ find_mr = find_memory_region_by_name(get_system_memory(), mr_name); ++ if (!find_mr) { ++ error_report("fail to find memory region by name %s.", mr_name); ++ ret = -ENOMEM; ++ goto end; ++ } ++ ++ /* ensure mem2 memoryregion is migrated during downtime */ ++ memory_region_set_dirty(find_mr, 0, HUGEPAGE_SIZE); ++ } ++ ++end: ++ if (ret && state->cmd_ctx) { ++ g_free(state->cmd_ctx); ++ state->cmd_ctx = NULL; ++ state->ctx_len = 0; ++ } ++ return ret; ++} ++ + static int pin_user_hugepage(int fd, uint64_t vaddr) + { + int ret; +@@ -269,6 +560,9 @@ static void psp_dev_realize(DeviceState *dev, Error **errp) + state->shutdown_notifier.notify = psp_dev_shutdown_notify; + qemu_register_shutdown_notifier(&state->shutdown_notifier); + ++ state->precopy_notifier.notify = precopy_state_notifier; ++ precopy_add_notifier(&state->precopy_notifier); ++ + return; + del_vid: + ctrl.op = VPSP_OP_VID_DEL; +@@ -288,6 +582,8 @@ static void psp_dev_class_init(ObjectClass *klass, void *data) + + dc->desc = "PSP Device"; + dc->realize = psp_dev_realize; ++ dc->vmsd = &vmstate_vpsp_dev; ++ + set_bit(DEVICE_CATEGORY_MISC, dc->categories); + device_class_set_props(dc, psp_dev_properties); + } +-- +2.39.3 + diff --git a/0525-target-i386-csv-introduce-host-data-str-option.patch b/0525-target-i386-csv-introduce-host-data-str-option.patch new file mode 100644 index 0000000..fd9063e --- /dev/null +++ b/0525-target-i386-csv-introduce-host-data-str-option.patch @@ -0,0 +1,132 @@ +From 0992e1b6afe7dbd41ca62b1279fa2fb1b5bf1095 Mon Sep 17 00:00:00 2001 +From: hanliyang +Date: Tue, 23 Sep 2025 14:19:59 +0800 +Subject: [PATCH] target/i386: csv: Introduce host-data=str option + +Hygon-SIG: commit none hygon csv: Introduce host-data=str option + +This feature only applied to Hygon CSV. + +The host can supply base64-encoded data to the Hygon CSV guest context. +This data can inform the guest how to configure its environment, and the +host-data will be signed in the attestation report by the firmware. The +guest user can request an attestation report and validate the host-data. + +Signed-off-by: hanliyang +Cc: hygon-arch@list.openanolis.cn +--- + qapi/qom.json | 4 +++- + qemu-options.hx | 5 ++++- + target/i386/sev.c | 37 +++++++++++++++++++++++++++++++++++++ + 3 files changed, 44 insertions(+), 2 deletions(-) + +diff --git a/qapi/qom.json b/qapi/qom.json +index a54bb86012..e1f246a35e 100644 +--- a/qapi/qom.json ++++ b/qapi/qom.json +@@ -872,6 +872,7 @@ + # support on Hygon CPUs (since 8.2) + # @secret-file: the file guest owner's secret, only support on Hygon + # CPUs (since 8.2) ++# @host-data: the host supplied data (encoded with base64) (since 8.2) + # + # Since: 2.12 + ## +@@ -886,7 +887,8 @@ + '*kernel-hashes': 'bool', + '*user-id': 'str', + '*secret-header-file': 'str', +- '*secret-file': 'str' } } ++ '*secret-file': 'str', ++ '*host-data': 'str' } } + + ## + # @ThreadContextProperties: +diff --git a/qemu-options.hx b/qemu-options.hx +index e6a7fe35ef..a1a42bb2c3 100644 +--- a/qemu-options.hx ++++ b/qemu-options.hx +@@ -5645,7 +5645,7 @@ SRST + -object secret,id=sec0,keyid=secmaster0,format=base64,\\ + data=$SECRET,iv=$(secret_file = g_strdup(value); + } + ++static char * ++sev_guest_get_host_data(Object *obj, Error **errp) ++{ ++ SevGuestState *s = SEV_GUEST(obj); ++ ++ return g_strdup(s->host_data); ++} ++ ++static void ++sev_guest_set_host_data(Object *obj, const char *value, Error **errp) ++{ ++ SevGuestState *s = SEV_GUEST(obj); ++ g_autofree guchar *blob; ++ gsize len; ++ ++ s->host_data = g_strdup(value); ++ ++ blob = qbase64_decode(s->host_data, -1, &len, errp); ++ ++ if (!blob) { ++ return; ++ } ++ ++ // The host-data length must be 64 bytes ++ if (len != 64) { ++ error_setg(errp, "parameter length of %" G_GSIZE_FORMAT ++ " not equal to 64", len); ++ return; ++ } ++} ++ + static char * + sev_guest_get_sev_device(Object *obj, Error **errp) + { +@@ -2764,6 +2796,11 @@ sev_guest_class_init(ObjectClass *oc, void *data) + sev_guest_set_secret_file); + object_class_property_set_description(oc, "secret-file", + "file of the guest owner's secret"); ++ object_class_property_add_str(oc, "host-data", ++ sev_guest_get_host_data, ++ sev_guest_set_host_data); ++ object_class_property_set_description(oc, "host-data", ++ "base64-encoded host supplied data"); + } + + static void +-- +2.39.3 + diff --git a/0526-target-i386-csv-support-issuing-the-launch-finish-ex.patch b/0526-target-i386-csv-support-issuing-the-launch-finish-ex.patch new file mode 100644 index 0000000..05ae8aa --- /dev/null +++ b/0526-target-i386-csv-support-issuing-the-launch-finish-ex.patch @@ -0,0 +1,181 @@ +From e896f101fff367cfb53d536e0ce8564d3acf20a1 Mon Sep 17 00:00:00 2001 +From: hanliyang +Date: Wed, 24 Sep 2025 20:28:15 +0800 +Subject: [PATCH] target/i386: csv: Support issuing the LAUNCH_FINISH_EX API + with host-data for CSV3 guest + +Hygon-SIG: commit none hygon csv: Support issuing the LAUNCH_FINISH_EX API with host-data for CSV3 guest + +THe CSV3 guest can issue LAUNCH_FINISH_EX API only if the host-data +exists and the KVM_CAP_HYGON_COCO_EXT_CSV3_LFINISH_EX capability is +enabled. + +Signed-off-by: hanliyang +Cc: hygon-arch@list.openanolis.cn +--- + linux-headers/linux/kvm.h | 10 +++++++++ + target/i386/csv-sysemu-stub.c | 4 ++++ + target/i386/csv.c | 40 +++++++++++++++++++++++++++++++++++ + target/i386/csv.h | 1 + + target/i386/sev.c | 8 +++++++ + target/i386/trace-events | 1 + + 6 files changed, 64 insertions(+) + +diff --git a/linux-headers/linux/kvm.h b/linux-headers/linux/kvm.h +index 656b6163e6..35f054f3d5 100644 +--- a/linux-headers/linux/kvm.h ++++ b/linux-headers/linux/kvm.h +@@ -925,6 +925,8 @@ struct kvm_enable_cap { + #define KVM_CAP_HYGON_COCO_EXT_CSV3_MULT_LUP_DATA (1 << 1) + /* support request to inject secret to CSV3 guest */ + #define KVM_CAP_HYGON_COCO_EXT_CSV3_INJ_SECRET (1 << 2) ++/* support finish launch process by CSV3_CMD_LAUNCH_FINISH_EX firmware API */ ++#define KVM_CAP_HYGON_COCO_EXT_CSV3_LFINISH_EX (1 << 3) + + #define KVM_EXIT_HYPERCALL_VALID_MASK (1 << KVM_HC_MAP_GPA_RANGE) + +@@ -1479,6 +1481,7 @@ enum csv3_cmd_id { + KVM_CSV3_HANDLE_MEMORY, + + KVM_CSV3_SET_GUEST_PRIVATE_MEMORY = 0xc8, ++ KVM_CSV3_LAUNCH_FINISH_EX = 0xc9, + + KVM_CSV3_NR_MAX, + }; +@@ -1489,6 +1492,13 @@ struct kvm_csv3_launch_encrypt_data { + __u32 len; + }; + ++#define KVM_CSV3_HOST_DATA_SIZE 64 ++ ++struct kvm_csv3_launch_finish_ex { ++ __u8 host_data[KVM_CSV3_HOST_DATA_SIZE]; ++ __u8 pad[16]; ++}; ++ + struct kvm_csv3_init_data { + __u64 nodemask; + }; +diff --git a/target/i386/csv-sysemu-stub.c b/target/i386/csv-sysemu-stub.c +index ce4850f5e4..f023458832 100644 +--- a/target/i386/csv-sysemu-stub.c ++++ b/target/i386/csv-sysemu-stub.c +@@ -30,6 +30,10 @@ int csv3_launch_encrypt_vmcb(void) + g_assert_not_reached(); + } + ++void csv3_launch_finish_ex(char *host_data) ++ g_assert_not_reached(); ++} ++ + int csv3_shared_region_dma_map(uint64_t start, uint64_t end) + { + return 0; +diff --git a/target/i386/csv.c b/target/i386/csv.c +index 27cd84d912..1c6c9a7453 100644 +--- a/target/i386/csv.c ++++ b/target/i386/csv.c +@@ -12,6 +12,7 @@ + */ + + #include "qemu/osdep.h" ++#include "qemu/base64.h" + #include "qemu/error-report.h" + #include "qapi/error.h" + #include "sysemu/kvm.h" +@@ -198,6 +199,45 @@ err: + return ret; + } + ++void ++csv3_launch_finish_ex(char *host_data) ++{ ++ int ret, fw_error; ++ g_autofree guchar *blob; ++ gsize len; ++ struct kvm_csv3_launch_finish_ex *finish_ex = NULL; ++ ++ if (!host_data) { ++ exit(1); ++ } ++ ++ blob = qbase64_decode(host_data, -1, &len, NULL); ++ ++ if (!blob) { ++ exit(1); ++ } ++ ++ if (len != KVM_CSV3_HOST_DATA_SIZE) { ++ error_report("%s: host_data length of %" G_GSIZE_FORMAT ++ " not equal to %d", ++ __func__, len, KVM_CSV3_HOST_DATA_SIZE); ++ exit(1); ++ } ++ ++ finish_ex = g_malloc0(sizeof(struct kvm_csv3_launch_finish_ex)); ++ memcpy(finish_ex->host_data, blob, len); ++ ++ trace_kvm_csv3_launch_finish_ex(host_data); ++ ret = csv3_ioctl(KVM_CSV3_LAUNCH_FINISH_EX, finish_ex, &fw_error); ++ if (ret) { ++ error_report("%s: CSV3 LAUNCH_FINISH_EX ret=%d fw_error=%d '%s'", ++ __func__, ret, fw_error, fw_error_to_str(fw_error)); ++ exit(1); ++ } ++ ++ g_free(finish_ex); ++} ++ + int csv3_shared_region_dma_map(uint64_t start, uint64_t end) + { + MemoryRegionSection section; +diff --git a/target/i386/csv.h b/target/i386/csv.h +index d6af8b9c80..45e64cd3e0 100644 +--- a/target/i386/csv.h ++++ b/target/i386/csv.h +@@ -119,6 +119,7 @@ extern struct Csv3GuestState csv3_guest; + extern struct ConfidentialGuestMemoryEncryptionOps csv3_memory_encryption_ops; + extern int csv3_init(uint32_t policy, int fd, void *state, struct sev_ops *ops); + extern int csv3_launch_encrypt_vmcb(void); ++void csv3_launch_finish_ex(char *host_data); + + int csv3_load_data(uint64_t gpa, uint8_t *ptr, uint64_t len, Error **errp); + +diff --git a/target/i386/sev.c b/target/i386/sev.c +index f781eac134..b734d9dc56 100644 +--- a/target/i386/sev.c ++++ b/target/i386/sev.c +@@ -971,6 +971,13 @@ sev_launch_finish(SevGuestState *sev) + { + int ret, error; + ++ if (csv3_enabled() && ++ sev->host_data && ++ (kvm_hygon_coco_ext_inuse & KVM_CAP_HYGON_COCO_EXT_CSV3_LFINISH_EX)) { ++ csv3_launch_finish_ex(sev->host_data); ++ goto common_finish; ++ } ++ + trace_kvm_sev_launch_finish(); + ret = sev_ioctl(sev->sev_fd, KVM_SEV_LAUNCH_FINISH, 0, &error); + if (ret) { +@@ -979,6 +986,7 @@ sev_launch_finish(SevGuestState *sev) + exit(1); + } + ++common_finish: + sev_set_guest_state(sev, SEV_STATE_RUNNING); + + /* add migration blocker */ +diff --git a/target/i386/trace-events b/target/i386/trace-events +index 5d4a709a39..fb28980ff9 100644 +--- a/target/i386/trace-events ++++ b/target/i386/trace-events +@@ -27,3 +27,4 @@ kvm_csv3_send_encrypt_context(void *dst, int len) "trans %p len %d" + kvm_csv3_receive_encrypt_data(void *dst, int len, void *hdr, int hdr_len) "trans %p len %d hdr %p hdr_len %d" + kvm_csv3_receive_encrypt_context(void *dst, int len, void *hdr, int hdr_len) "trans %p len %d hdr %p hdr_len %d" + kvm_csv3_set_guest_private_memory(void) "" ++kvm_csv3_launch_finish_ex(char *host_data) "host_data %s" +-- +2.39.3 + diff --git a/0527-hw-vfio-hct-support-start-with-ccp-ko-driver.patch b/0527-hw-vfio-hct-support-start-with-ccp-ko-driver.patch new file mode 100644 index 0000000..caa905e --- /dev/null +++ b/0527-hw-vfio-hct-support-start-with-ccp-ko-driver.patch @@ -0,0 +1,345 @@ +From 90e366d29e08d35d80f6e288cfa0d091f68d2ba3 Mon Sep 17 00:00:00 2001 +From: Yabin Li +Date: Wed, 30 Oct 2024 21:08:12 +0800 +Subject: [PATCH] hw/vfio/hct: support start with ccp.ko driver + +this patch allowed hct device start with ccp.ko +which with ccp-mdev feature. + +Signed-off-by: Yabin Li +Signed-off-by: yangdepei +--- + hw/vfio/hct.c | 177 +++++++++++++++++++++++++++++++++++++++++++------- + 1 file changed, 152 insertions(+), 25 deletions(-) + +diff --git a/hw/vfio/hct.c b/hw/vfio/hct.c +index 953d7b03a8..411a86508e 100644 +--- a/hw/vfio/hct.c ++++ b/hw/vfio/hct.c +@@ -13,6 +13,7 @@ + #include + #include + #include ++#include + + #include "qemu/osdep.h" + #include "qemu/queue.h" +@@ -36,13 +37,18 @@ + #define PATH_MAX 4096 + #define TYPE_HCT_DEV "hct" + #define PCI_HCT_DEV(obj) OBJECT_CHECK(HCTDevState, (obj), TYPE_HCT_DEV) +-#define HCT_MMIO_SIZE (1 << 20) + #define HCT_MAX_PASID (1 << 8) + + #define PCI_VENDOR_ID_HYGON_CCP 0x1d94 + #define PCI_DEVICE_ID_HYGON_CCP 0x1468 + ++#define VFIO_DEVICE_CCP_SET_MODE _IO(VFIO_TYPE, VFIO_BASE + 32) ++#define VFIO_DEVICE_CCP_GET_MODE _IO(VFIO_TYPE, VFIO_BASE + 33) ++ + #define HCT_SHARE_DEV "/dev/hct_share" ++#define CCP_SHARE_DEV "/dev/ccp_share" ++#define PCI_DRV_HCT_DIR "/sys/bus/pci/drivers/hct" ++#define PCI_DRV_CCP_DIR "/sys/bus/pci/drivers/ccp" + + #define DEF_VERSION_STRING "0.1" + #define HCT_VERSION_STR_02 "0.2" +@@ -79,6 +85,7 @@ static volatile struct hct_data { + uint8_t hct_version[VERSION_SIZE]; + uint8_t ccp_index[MAX_CCP_CNT]; + uint8_t ccp_cnt; ++ uint8_t driver; + } hct_data; + + typedef struct SharedDevice { +@@ -92,7 +99,9 @@ typedef struct HctDevState { + MemoryRegion mmio; + MemoryRegion shared; + MemoryRegion pasid; ++ uint64_t map_size[PCI_NUM_REGIONS]; + void *maps[PCI_NUM_REGIONS]; ++ char *ccp_dev_path; + } HCTDevState; + + struct hct_dev_ctrl { +@@ -110,12 +119,23 @@ struct hct_dev_ctrl { + }; + }; + ++enum ccp_dev_used_mode { ++ _KERNEL_SPACE_USED = 0, ++ _USER_SPACE_USED, ++}; ++ + enum MDEV_USED_TYPE { + MDEV_USED_FOR_HOST, + MDEV_USED_FOR_VM, + MDEV_USED_UNDEF + }; + ++enum hct_ccp_driver_mode_type { ++ HCT_CCP_DRV_MOD_UNINIT = 0, ++ HCT_CCP_DRV_MOD_HCT, ++ HCT_CCP_DRV_MOD_CCP, ++}; ++ + static int hct_get_sysfs_value(const char *path, int *val) + { + FILE *fp = NULL; +@@ -154,7 +174,7 @@ static int pasid_get_and_init(HCTDevState *state) + void *base = (void *)hct_data.pasid_memory; + struct hct_dev_ctrl ctrl; + unsigned long *gid = NULL; +- int ret; ++ int ret = 0; + + ctrl.op = HCT_SHARE_OP_GET_PASID; + ret = ioctl(hct_data.hct_fd, HCT_SHARE_OP, &ctrl); +@@ -194,23 +214,28 @@ static const MemoryRegionOps hct_mmio_ops = { + static void vfio_hct_detach_device(HCTDevState *state) + { + vfio_detach_device(&state->vdev); +- g_free(state->vdev.name); + } + + static void vfio_hct_exit(PCIDevice *dev) + { + HCTDevState *state = PCI_HCT_DEV(dev); + +- vfio_hct_detach_device(state); ++ if (hct_data.driver == HCT_CCP_DRV_MOD_HCT) ++ vfio_hct_detach_device(state); + + if (hct_data.hct_fd) { + qemu_close(hct_data.hct_fd); + hct_data.hct_fd = 0; + } ++ if (state->vdev.fd) { ++ qemu_close(state->vdev.fd); ++ state->vdev.fd = 0; ++ } + } + + static Property vfio_hct_properties[] = { + DEFINE_PROP_STRING("sysfsdev", HCTDevState, vdev.sysfsdev), ++ DEFINE_PROP_STRING("path", HCTDevState, ccp_dev_path), + DEFINE_PROP_END_OF_LIST(), + }; + +@@ -244,14 +269,15 @@ static int vfio_hct_region_mmap(HCTDevState *state) + error_report("vfio mmap fail\n"); + goto out; + } ++ state->map_size[i] = info->size; + } + g_free(info); + } + +- memory_region_init_io(&state->mmio, OBJECT(state), &hct_mmio_ops, state, +- "hct mmio", HCT_MMIO_SIZE); +- memory_region_init_ram_device_ptr(&state->mmio, OBJECT(state), "hct mmio", +- HCT_MMIO_SIZE, ++ memory_region_init_io(&state->mmio, OBJECT(state), &hct_mmio_ops, ++ state, "hct mmio", state->map_size[HCT_REG_BAR_IDX]); ++ memory_region_init_ram_device_ptr(&state->mmio, OBJECT(state), ++ "hct mmio", state->map_size[HCT_REG_BAR_IDX], + state->maps[HCT_REG_BAR_IDX]); + + memory_region_init_io(&state->shared, OBJECT(state), &hct_mmio_ops, state, +@@ -291,11 +317,67 @@ static int hct_check_duplicated_index(int index) + return 0; + } + ++static int hct_ccp_dev_get_index(HCTDevState *state) ++{ ++ char fpath[PATH_MAX] = {0}; ++ char *ptr = NULL; ++ uint32_t loops= 0; ++ uint32_t max_loops = 10000; ++ int ccp_idx; ++ int fd; ++ int ret; ++ ++ if (!state->ccp_dev_path) { ++ error_report("state->ccp_dev_path is NULL."); ++ return -1; ++ } ++ ++ ptr = strstr(state->ccp_dev_path, "ccp"); ++ if (!ptr) ++ return -1; ++ ++ ccp_idx = atoi(ptr + strlen("ccp")); ++ if (hct_check_duplicated_index(ccp_idx)) ++ return -1; ++ ++ fd = qemu_open_old(state->ccp_dev_path, O_RDWR); ++ if (fd < 0) { ++ error_report("fail to open %s, errno %d.", fpath, errno); ++ return -1; ++ } ++ ++ while ((ret = ioctl(fd, VFIO_DEVICE_CCP_SET_MODE, _USER_SPACE_USED)) < 0 ++ && errno == EAGAIN) { ++ if (++loops > max_loops) { ++ error_report("loops = %u, configure user mode fail.\n", loops); ++ break; ++ } ++ usleep(10); ++ } ++ if (ret < 0) { ++ error_report("configure user mode for %s fail, errno %d", fpath, errno); ++ close(fd); ++ return -1; ++ } ++ ++ state->vdev.fd = fd; ++ state->sdev.shared_memory_offset = ccp_idx; ++ return 0; ++} ++ + static int hct_get_ccp_index(HCTDevState *state) + { + char path[PATH_MAX] = {0}; + int mdev_used, index; + ++ if (hct_data.driver == HCT_CCP_DRV_MOD_CCP) ++ return hct_ccp_dev_get_index(state); ++ ++ if (!state->vdev.sysfsdev) { ++ error_report("state->vdev.sysfsdev is NULL."); ++ return -1; ++ } ++ + if (memcmp((void *)hct_data.hct_version, HCT_VERSION_STR_06, + sizeof(HCT_VERSION_STR_06)) >= 0) { + snprintf(path, PATH_MAX, "%s/vendor/use", state->vdev.sysfsdev); +@@ -437,6 +519,31 @@ static MemoryListener hct_memory_listener = { + .region_del = hct_listener_region_del, + }; + ++static int hct_get_used_driver_walk(const char *path) ++{ ++ const char filter[] = "0000:*"; ++ struct dirent *e = NULL; ++ DIR *dir = NULL; ++ int ret = -EINVAL; ++ ++ dir = opendir(path); ++ if (dir == NULL) ++ return -1; ++ ++ while ((e = readdir(dir)) != NULL) { ++ if (e->d_name[0] == '.') ++ continue; ++ ++ if (fnmatch(filter, e->d_name, 0) == 0) { ++ ret = 0; ++ break; ++ } ++ } ++ ++ closedir(dir); ++ return ret; ++} ++ + static void hct_data_uninit(HCTDevState *state) + { + if (hct_data.hct_fd) { +@@ -444,6 +551,11 @@ static void hct_data_uninit(HCTDevState *state) + hct_data.hct_fd = 0; + } + ++ if (state->vdev.fd) { ++ qemu_close(state->vdev.fd); ++ state->vdev.fd = 0; ++ } ++ + if (hct_data.pasid) { + hct_data.pasid = 0; + } +@@ -463,13 +575,23 @@ static void hct_data_uninit(HCTDevState *state) + + static int hct_data_init(HCTDevState *state) + { ++ const char *hct_shr_name = NULL; + int ret; + + if (hct_data.init == 0) { + +- hct_data.hct_fd = qemu_open_old(HCT_SHARE_DEV, O_RDWR); ++ ret = hct_get_used_driver_walk(PCI_DRV_HCT_DIR); ++ if (ret == 0) { ++ hct_data.driver = HCT_CCP_DRV_MOD_HCT; ++ hct_shr_name = HCT_SHARE_DEV; ++ } else { ++ hct_data.driver = HCT_CCP_DRV_MOD_CCP; ++ hct_shr_name = CCP_SHARE_DEV; ++ } ++ ++ hct_data.hct_fd = qemu_open_old(hct_shr_name, O_RDWR); + if (hct_data.hct_fd < 0) { +- error_report("fail to open %s, errno %d.", HCT_SHARE_DEV, errno); ++ error_report("fail to open %s, errno %d.", hct_shr_name, errno); + ret = -errno; + goto out; + } +@@ -521,35 +643,40 @@ static void vfio_hct_realize(PCIDevice *pci_dev, Error **errp) + Error *err = NULL; + HCTDevState *state = PCI_HCT_DEV(pci_dev); + +- /* parsing mdev device name from startup scripts */ +- mdevid = g_path_get_basename(state->vdev.sysfsdev); +- state->vdev.name = g_strdup_printf("%s", mdevid); +- + ret = hct_data_init(state); + if (ret < 0) { +- g_free(state->vdev.name); ++ error_setg(errp, "hct data initialization failed."); + goto out; + } + +- ret = vfio_attach_device(state->vdev.name, &state->vdev, +- pci_device_iommu_address_space(pci_dev), &err); ++ if (hct_data.driver == HCT_CCP_DRV_MOD_HCT) { ++ mdevid = g_path_get_basename(state->vdev.sysfsdev); ++ state->vdev.name = g_strdup_printf("%s", mdevid); + +- if (ret) { +- error_report("attach device failed, name = %s", state->vdev.name); +- goto data_uninit_out; +- } ++ ret = vfio_attach_device(state->vdev.name, &state->vdev, ++ pci_device_iommu_address_space(pci_dev), &err); ++ if(ret){ ++ error_setg(errp, "attach device failed, name = %s.", state->vdev.name); ++ g_free(state->vdev.name); ++ goto data_uninit_out; ++ } + +- state->vdev.ops = &vfio_ccp_ops; +- state->vdev.dev = &state->sdev.dev.qdev; ++ state->vdev.ops = &vfio_ccp_ops; ++ state->vdev.dev = &state->sdev.dev.qdev; ++ g_free(state->vdev.name); ++ } + + ret = vfio_hct_region_mmap(state); +- if (ret < 0) ++ if (ret < 0) { ++ error_setg(errp, "hct vfio region mmap failed."); + goto detach_device_out; ++ } + + return; + + detach_device_out: +- vfio_hct_detach_device(state); ++ if (hct_data.driver == HCT_CCP_DRV_MOD_HCT) ++ vfio_hct_detach_device(state); + + data_uninit_out: + hct_data_uninit(state); +-- +2.39.3 + diff --git a/0528-hw-vfio-hct-support-vfio-pci-multiple-processes.patch b/0528-hw-vfio-hct-support-vfio-pci-multiple-processes.patch new file mode 100644 index 0000000..f3dbc18 --- /dev/null +++ b/0528-hw-vfio-hct-support-vfio-pci-multiple-processes.patch @@ -0,0 +1,1786 @@ +From 9baee81199c5b1a1b636159c218e413bdb2fd45e Mon Sep 17 00:00:00 2001 +From: Xiangyu Xu +Date: Thu, 9 Oct 2025 11:00:56 +0800 +Subject: [PATCH] hw/vfio/hct: support vfio-pci multiple processes. + +Signed-off-by: Xiangyu Xu +Signed-off-by: yangdepei +--- + hw/vfio/hct.c | 1560 +++++++++++++++++++++++++++++++++++++++++++++---- + 1 file changed, 1459 insertions(+), 101 deletions(-) + +diff --git a/hw/vfio/hct.c b/hw/vfio/hct.c +index 411a86508e..a9a79b626a 100644 +--- a/hw/vfio/hct.c ++++ b/hw/vfio/hct.c +@@ -12,8 +12,12 @@ + #include + #include + #include ++#include ++#include + #include + #include ++#include ++#include + + #include "qemu/osdep.h" + #include "qemu/queue.h" +@@ -29,6 +33,180 @@ + #include "qapi/error.h" + #include "hw/qdev-properties.h" + ++// ======================== g_id API ==================== ++ ++#define HCT_DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d)) ++#define HCT_BITMAP_SIZE(nr) HCT_DIV_ROUND_UP(nr, CHAR_BIT * sizeof(unsigned long)) ++ ++static unsigned long g_id = 0; ++ ++enum { ++ BITS_PER_WORD = sizeof(unsigned long) * CHAR_BIT ++}; ++#define WORD_OFFSET(b) ((b) / BITS_PER_WORD) ++#define BIT_OFFSET(b) ((b) % BITS_PER_WORD) ++ ++#ifndef MAX_PATH ++#define MAX_PATH 4096 ++#endif ++ ++/* ++ * Each HCT and QEMU process allocates a unique GID through the shared memory hct_gid_bitmap. ++ * The HCT process uses bits 0-1023 of the bitmap, while the QEMU process uses bits 1024-2047. ++ * After a QEMU process allocates a bit_pos from the bitmap, it first locks the range of bytes ++ * from bit_pos * 8 to (bit_pos + 1) * 8 in the shared memory hct_gid_locks. Then it calculates ++ * the GID: ++ * bit_pos is incremented by 1 (since GID cannot be 0), then subtracted by 1024 (to correct ++ * for the starting offset of the bitmap), resulting in a number in the range of 1-1024. ++ * This number is then left-shifted by HCT_QEMU_GIDS_SHIFT_BITS (18) to obtain the final GID. ++ */ ++#define HCT_GID_BITMAP_SHM_NAME "hct_gid_bitmap" ++#define HCT_GID_LOCK_FILE "hct_gid_locks" ++ ++#define HCT_QEMU_GIDS_BITMAP_MAX_BIT 2048 ++#define HCT_QEMU_GIDS_BITMAP_MIN_BIT 1024 ++#define HCT_QEMU_GIDS_SHIFT_BITS 18 ++#define HCT_GIDS_PER_BLOCK 8 ++ ++static void hct_clear_bit(unsigned long *bitmap, int n); ++static uint32_t hct_get_bit(unsigned long *bitmap, int n); ++ ++/** ++ * File-based bitmap structure for multi-process shared g_id allocation ++ */ ++struct hct_gid_bitmap { ++ char name[MAX_PATH]; ++ unsigned int len; ++ unsigned long *bitmap; ++ int shm_fd; ++ int lock_fd; ++}; ++ ++/// Global bitmap instance for g_id management ++struct hct_gid_bitmap *g_hct_gid_bitmap; ++ ++/** ++ * @brief Allocate a new hct_gid_bitmap structure with shared memory storage ++ * @details Creates shared memory if not exists, or opens existing one ++ * @return pointer to allocated hct_gid_bitmap on success, NULL on failure ++ */ ++static struct hct_gid_bitmap* hct_gid_bitmap_alloc(void); ++ ++/** ++ * @brief Free hct_gid_bitmap structure and cleanup resources ++ * @param bitmap pointer to hct_gid_bitmap to free ++ */ ++static void hct_gid_bitmap_free(struct hct_gid_bitmap *bitmap); ++ ++/** ++ * @brief Allocate a g_id from the 1024-bit bitmap, left-shift by 8 bits ++ * @param bitmap pointer to hct_gid_bitmap structure ++ * @param gid pointer to store allocated g_id ++ * @return 0 on success, -EINVAL on failure ++ */ ++static int hct_g_ids_alloc(struct hct_gid_bitmap *bitmap, unsigned long *gid); ++ ++/** ++ * @brief Free a g_id and clear corresponding bit in bitmap ++ * @param bitmap pointer to hct_gid_bitmap structure ++ * @param gid g_id to free ++ */ ++static void hct_g_ids_free(struct hct_gid_bitmap *bitmap, unsigned long gid); ++ ++/** ++ * @brief Check if g_id is locked by trying to acquire exclusive lock on its file region ++ * @param bitmap pointer to hct_gid_bitmap structure ++ * @param gid g_id to check lock status ++ * @return 0 if can lock (process dead), -EINVAL if cannot lock (process alive) ++ */ ++static int hct_g_ids_lock_state_lock(struct hct_gid_bitmap *bitmap, unsigned long gid); ++ ++/** ++ * @brief Walk through bitmap and check for abnormally exited processes ++ * @details Clean up orphaned g_ids by checking file locks ++ * @param bitmap pointer to hct_gid_bitmap structure ++ */ ++static void hct_g_ids_lock_state_walk(struct hct_gid_bitmap *bitmap); ++ ++// ======================== g_id API end ==================== ++ ++ ++// ======================== HCT IPC API ==================== ++ ++// HCT IPC TLV field type definitions ++#define HCT_IPC_FIELD_COMMAND 1 /* command */ ++#define HCT_IPC_FIELD_CONTAINER_FD 2 /* container fd */ ++#define HCT_IPC_FIELD_GROUP_FD 3 /* group fd */ ++#define HCT_IPC_FIELD_DEVICE_FD 4 /* device fd */ ++#define HCT_IPC_FIELD_GROUP_INFO 5 /* group info */ ++#define HCT_IPC_FIELD_DEVICE_INFO 6 /* device info */ ++#define HCT_IPC_FIELD_DEVICE_NAMES 7 /* device names */ ++#define HCT_IPC_FIELD_VCCP_PATH 9 /* vccp device file path */ ++#define HCT_IPC_FIELD_VCCP_CONTENT 10 /* vccp device file content */ ++#define HCT_IPC_FIELD_ERROR_REASON 11 /* request failed reason */ ++ ++// Error code definitions ++#define HCT_SUCCESS 0 /* success */ ++#define HCT_ERROR_CONNECT (-1) /* connect error */ ++#define HCT_ERROR_RECEIVE (-2) /* receive error */ ++#define HCT_ERROR_INVALID_DATA (-3) /* invalid data */ ++ ++// Constants ++#define HCT_DAEMON_PID_FILE "/var/run/hctd.pid" /* daemon pid file */ ++#define HCT_DAEMON_SOCK_PATH "/var/run/hctd.sock" /* daemon socket path */ ++ ++#define PCI_ADDR_MAX 20 /* pci address max length */ ++#define DEVICE_NAME_MAX 32 /* device name max length */ ++ ++// daemon client command type ++enum hct_daemon_req_cmd { ++ HCT_CMD_GET_ALL_DEVICES = 0x01, /* libhct request: get all devices information */ ++ HCT_CMD_GET_DEVICE_BY_NAME = 0x02, /* qemu request: get device info via vccp file */ ++}; ++ ++typedef struct hct_vccp_req { ++ const char *path; ++ const char *content; ++} hct_vccp_req; ++ ++// TLV structure ++typedef struct { ++ uint16_t type; ++ uint16_t length; ++ void *value; ++} hct_tlv_t; ++ ++// CCP device management structure ++typedef struct { ++ char pci_addr[PCI_ADDR_MAX]; /* PCI address (e.g., 0000:01:00.0) */ ++ int group_id; /* VFIO group ID */ ++ int group_fd; /* VFIO group FD */ ++ int device_fd; /* VFIO device FD */ ++ int group_index; /* Index in group array of the group this device belongs to */ ++} hct_ccp_device_t; ++ ++// Group information structure ++typedef struct { ++ int group_id; ++ int group_fd; ++ int device_count; /* Number of devices in this group */ ++} hct_group_info_t; ++ ++// Overall client device information container ++typedef struct { ++ int container_fd; /* VFIO container file descriptor */ ++ hct_group_info_t *groups; /* VFIO group information array */ ++ int group_count; /* Number of groups */ ++ hct_ccp_device_t *devices; /* VFIO device information array */ ++ int device_count; /* Number of devices */ ++} hct_client_info_t; ++ ++// Internal constants for client implementation ++#define MAX_TLV_BUFFER_SIZE 2048 /* max tlv buffer size */ ++#define MAX_FD_COUNT 128 /* max fd count */ ++ ++// ======================== HCT IPC API end ==================== ++ + #define MAX_CCP_CNT 48 + #define DEF_CCP_CNT_MAX 16 + #define PAGE_SIZE 4096 +@@ -45,6 +223,10 @@ + #define VFIO_DEVICE_CCP_SET_MODE _IO(VFIO_TYPE, VFIO_BASE + 32) + #define VFIO_DEVICE_CCP_GET_MODE _IO(VFIO_TYPE, VFIO_BASE + 33) + ++#define SHM_DIR "/dev/shm/" ++#define HCT_GLOBAL_SHARE_SHM_NAME "hct_global_share" ++#define HCT_GLOBAL_SHARE_SHM_PATH SHM_DIR HCT_GLOBAL_SHARE_SHM_NAME ++ + #define HCT_SHARE_DEV "/dev/hct_share" + #define CCP_SHARE_DEV "/dev/ccp_share" + #define PCI_DRV_HCT_DIR "/sys/bus/pci/drivers/hct" +@@ -78,6 +260,8 @@ + static volatile struct hct_data { + int init; + int hct_fd; ++ int hct_shm_fd; ++ int vfio_container_fd; + unsigned long pasid; + unsigned long hct_shared_size; + uint8_t *pasid_memory; +@@ -102,6 +286,10 @@ typedef struct HctDevState { + uint64_t map_size[PCI_NUM_REGIONS]; + void *maps[PCI_NUM_REGIONS]; + char *ccp_dev_path; ++ int container_fd; /* vfio container fd */ ++ int group_fd; /* vfio group fd */ ++ int group_id; /* vfio group id */ ++ int lock_fd; /* vccp flock fd for this device only */ + } HCTDevState; + + struct hct_dev_ctrl { +@@ -134,8 +322,37 @@ enum hct_ccp_driver_mode_type { + HCT_CCP_DRV_MOD_UNINIT = 0, + HCT_CCP_DRV_MOD_HCT, + HCT_CCP_DRV_MOD_CCP, ++ HCT_CCP_DRV_MOD_VFIO_PCI, + }; + ++ ++/* @brief vfio-pci mapping function */ ++static int vfio_hct_dma_map_vfio_pci(int container_fd, void *vaddr, uint64_t iova, uint64_t size); ++ ++/* @brief vfio-pci unmapping function */ ++static int vfio_hct_dma_unmap_vfio_pci(int container_fd, uint64_t iova, uint64_t size); ++ ++/* @brief vfio-pci init from daemon function */ ++static int vfio_hct_init_from_daemon(HCTDevState *state); ++ ++/* @brief hct data uninit function */ ++static void hct_data_uninit(HCTDevState *state); ++ ++/* @brief hct get error string function */ ++static const char *hct_get_error_string(int error_code); ++ ++/* @brief hct find device by pci addr function */ ++static hct_ccp_device_t* hct_find_device_by_pci_addr(hct_client_info_t *client_info, const char *pci_addr); ++ ++/* @brief hct client cleanup function */ ++static void hct_client_cleanup(hct_client_info_t *client_info); ++ ++/* @brief hct client send cmd function */ ++static int hct_client_send_cmd(const char *socket_path, hct_client_info_t *client_info, enum hct_daemon_req_cmd cmd, void *req_data); ++ ++/* @brief hct create user shared memory function */ ++static int hct_create_user_shared_memory(const char *name, size_t size); ++ + static int hct_get_sysfs_value(const char *path, int *val) + { + FILE *fp = NULL; +@@ -172,32 +389,27 @@ static int hct_get_sysfs_value(const char *path, int *val) + static int pasid_get_and_init(HCTDevState *state) + { + void *base = (void *)hct_data.pasid_memory; +- struct hct_dev_ctrl ctrl; + unsigned long *gid = NULL; + int ret = 0; + +- ctrl.op = HCT_SHARE_OP_GET_PASID; +- ret = ioctl(hct_data.hct_fd, HCT_SHARE_OP, &ctrl); +- if (ret < 0) { +- ret = -errno; +- error_report("get pasid fail, errno: %d.", errno); ++ g_hct_gid_bitmap = hct_gid_bitmap_alloc(); ++ if (!g_hct_gid_bitmap) { ++ error_report("Failed to allocate hct_gid_bitmap"); + goto out; + } + +- hct_data.pasid = (unsigned long)ctrl.pasid; +- *(unsigned long *)base = (unsigned long)ctrl.pasid; +- +- ctrl.op = HCT_SHARE_OP_GET_ID; +- ret = ioctl(hct_data.hct_fd, HCT_SHARE_OP, &ctrl); ++ gid = (unsigned long *)((unsigned long)base + HCT_PASID_MEM_GID_OFFSET); ++ ret = hct_g_ids_alloc(g_hct_gid_bitmap, &g_id); ++ *gid = g_id; + if (ret < 0) { +- ret = -errno; +- error_report("get gid fail, errno: %d", errno); ++ error_report("Failed to allocate g_id, ret=%d", ret); + goto out; ++ } else { ++ hct_data.pasid = *gid >> HCT_QEMU_GIDS_SHIFT_BITS; ++ *(unsigned long *)base = (unsigned long)hct_data.pasid; ++ return 0; + } + +- gid = (unsigned long *)((unsigned long)base + HCT_PASID_MEM_GID_OFFSET); +- *(unsigned long *)gid = (unsigned long)ctrl.id; +- + out: + return ret; + } +@@ -227,15 +439,34 @@ static void vfio_hct_exit(PCIDevice *dev) + qemu_close(hct_data.hct_fd); + hct_data.hct_fd = 0; + } ++ + if (state->vdev.fd) { + qemu_close(state->vdev.fd); + state->vdev.fd = 0; + } ++ ++ /* Release vccp file lock */ ++ if (state->lock_fd >= 0) { ++ flock(state->lock_fd, LOCK_UN); ++ close(state->lock_fd); ++ state->lock_fd = -1; ++ } ++ ++ if (hct_data.driver == HCT_CCP_DRV_MOD_VFIO_PCI) { ++ state->container_fd = -1; ++ state->group_fd = -1; ++ } ++ ++ hct_data.ccp_cnt--; ++ ++ if (hct_data.ccp_cnt == 0) { ++ hct_data_uninit(state); ++ } + } + + static Property vfio_hct_properties[] = { + DEFINE_PROP_STRING("sysfsdev", HCTDevState, vdev.sysfsdev), +- DEFINE_PROP_STRING("path", HCTDevState, ccp_dev_path), ++ DEFINE_PROP_STRING("dev", HCTDevState, ccp_dev_path), + DEFINE_PROP_END_OF_LIST(), + }; + +@@ -317,34 +548,19 @@ static int hct_check_duplicated_index(int index) + return 0; + } + +-static int hct_ccp_dev_get_index(HCTDevState *state) ++static int hct_ccp_set_mode(HCTDevState *state) + { + char fpath[PATH_MAX] = {0}; +- char *ptr = NULL; + uint32_t loops= 0; + uint32_t max_loops = 10000; +- int ccp_idx; + int fd; + int ret; + +- if (!state->ccp_dev_path) { +- error_report("state->ccp_dev_path is NULL."); +- return -1; +- } +- +- ptr = strstr(state->ccp_dev_path, "ccp"); +- if (!ptr) +- return -1; +- +- ccp_idx = atoi(ptr + strlen("ccp")); +- if (hct_check_duplicated_index(ccp_idx)) +- return -1; +- +- fd = qemu_open_old(state->ccp_dev_path, O_RDWR); +- if (fd < 0) { +- error_report("fail to open %s, errno %d.", fpath, errno); ++ if (state->vdev.fd <= 0) { ++ error_report("fail to get device fd %d.", state->vdev.fd); + return -1; + } ++ fd = state->vdev.fd; + + while ((ret = ioctl(fd, VFIO_DEVICE_CCP_SET_MODE, _USER_SPACE_USED)) < 0 + && errno == EAGAIN) { +@@ -360,47 +576,76 @@ static int hct_ccp_dev_get_index(HCTDevState *state) + return -1; + } + +- state->vdev.fd = fd; +- state->sdev.shared_memory_offset = ccp_idx; + return 0; + } + + static int hct_get_ccp_index(HCTDevState *state) + { + char path[PATH_MAX] = {0}; +- int mdev_used, index; ++ char vccp_content[256] = {0}; ++ FILE *fp = NULL; ++ int mdev_used = 0, index = 0; + + if (hct_data.driver == HCT_CCP_DRV_MOD_CCP) +- return hct_ccp_dev_get_index(state); ++ if(hct_ccp_set_mode(state)) { ++ return -1; ++ } + +- if (!state->vdev.sysfsdev) { +- error_report("state->vdev.sysfsdev is NULL."); +- return -1; +- } ++ if (hct_data.driver == HCT_CCP_DRV_MOD_HCT) { ++ if (!state->vdev.sysfsdev) { ++ error_report("state->vdev.sysfsdev is NULL."); ++ return -1; ++ } ++ ++ if (memcmp((void *)hct_data.hct_version, HCT_VERSION_STR_06, ++ sizeof(HCT_VERSION_STR_06)) >= 0) { ++ snprintf(path, PATH_MAX, "%s/vendor/use", state->vdev.sysfsdev); ++ if (hct_get_sysfs_value(path, &mdev_used)) { ++ error_report("get %s sysfs value fail.\n", path); ++ return -1; ++ } else if (mdev_used != MDEV_USED_FOR_VM) { ++ error_report("The value of file node(%s) is %d, should be MDEV_USED_FOR_VM(%d), pls check.\n", ++ path, mdev_used, MDEV_USED_FOR_VM); ++ return -1; ++ } ++ } + +- if (memcmp((void *)hct_data.hct_version, HCT_VERSION_STR_06, +- sizeof(HCT_VERSION_STR_06)) >= 0) { +- snprintf(path, PATH_MAX, "%s/vendor/use", state->vdev.sysfsdev); +- if (hct_get_sysfs_value(path, &mdev_used)) { ++ snprintf(path, PATH_MAX, "%s/vendor/id", state->vdev.sysfsdev); ++ if (hct_get_sysfs_value(path, &index)) { + error_report("get %s sysfs value fail.\n", path); + return -1; +- } else if (mdev_used != MDEV_USED_FOR_VM) { +- error_report("The value of file node(%s) is %d, should be MDEV_USED_FOR_VM(%d), pls check.\n", +- path, mdev_used, MDEV_USED_FOR_VM); ++ } ++ ++ if (hct_check_duplicated_index(index)) ++ return -1; ++ ++ state->sdev.shared_memory_offset = index; ++ } else if (hct_data.driver == HCT_CCP_DRV_MOD_CCP || hct_data.driver == HCT_CCP_DRV_MOD_VFIO_PCI) { ++ fp = fopen(state->ccp_dev_path, "r"); ++ if (!fp) { ++ error_report("Failed to open vccp file %s to get index: %s", state->ccp_dev_path, strerror(errno)); + return -1; + } +- } ++ if (fgets(vccp_content, sizeof(vccp_content), fp) == NULL) { ++ error_report("Failed to read content from vccp file %s", state->ccp_dev_path); ++ fclose(fp); ++ return -1; ++ } ++ fclose(fp); + +- snprintf(path, PATH_MAX, "%s/vendor/id", state->vdev.sysfsdev); +- if (hct_get_sysfs_value(path, &index)) { +- error_report("get %s sysfs value fail.\n", path); +- return -1; ++ if (sscanf(state->ccp_dev_path, "/dev/hct/vccp%*d_%d", &index) != 1) { ++ error_report("Invalid vccp filename format for vfio-pci: %s", state->ccp_dev_path); ++ return -1; ++ } ++ state->sdev.shared_memory_offset = index; ++ if (hct_check_duplicated_index(index)) { ++ return -1; ++ } ++ } else { ++ error_report("Invalid driver mode %d, vccp path %s.\n", hct_data.driver, state->ccp_dev_path); ++ return -1; + } + +- if (hct_check_duplicated_index(index)) +- return -1; +- +- state->sdev.shared_memory_offset = index; + return 0; + } + +@@ -436,7 +681,7 @@ static int hct_shared_memory_init(void) + + hct_data.hct_shared_memory = mmap(NULL, hct_data.hct_shared_size, + PROT_READ | PROT_WRITE, MAP_SHARED, +- hct_data.hct_fd, 0); ++ hct_data.hct_shm_fd, 0); + if (hct_data.hct_shared_memory == MAP_FAILED) { + ret = -errno; + error_report("map hct shared memory fail\n"); +@@ -474,13 +719,24 @@ static void hct_listener_region_add(MemoryListener *listener, + (iova - section->offset_within_address_space); + llsize = int128_sub(llend, int128_make64(iova)); + +- ctrl.op = HCT_SHARE_OP_DMA_MAP; +- ctrl.iova = iova | (hct_data.pasid << PASID_OFFSET); +- ctrl.vaddr = (uint64_t)vaddr; +- ctrl.size = llsize; +- ret = ioctl(hct_data.hct_fd, HCT_SHARE_OP, &ctrl); +- if (ret < 0) +- error_report("VFIO_MAP_DMA: %d, iova=%lx", -errno, iova); ++ /* according to host running mode to select different DMA mapping mode */ ++ if (hct_data.driver == HCT_CCP_DRV_MOD_VFIO_PCI) { ++ iova = iova | (hct_data.pasid << PASID_OFFSET); ++ ret = vfio_hct_dma_map_vfio_pci(hct_data.vfio_container_fd, vaddr, iova, llsize); ++ if (ret < 0) { ++ error_report("VFIO_PCI_MAP_DMA: %d, iova=%lx", ret, iova); ++ } ++ } else { ++ /* host running hct/ccp mdev mode: use hct/ccp module mapping */ ++ ctrl.op = HCT_SHARE_OP_DMA_MAP; ++ ctrl.iova = iova | (hct_data.pasid << PASID_OFFSET); ++ ctrl.vaddr = (uint64_t)vaddr; ++ ctrl.size = llsize; ++ ret = ioctl(hct_data.hct_fd, HCT_SHARE_OP, &ctrl); ++ if (ret < 0) { ++ error_report("HCT_MAP_DMA: %d, iova=%lx", -errno, iova); ++ } ++ } + } + + static void hct_listener_region_del(MemoryListener *listener, +@@ -506,12 +762,24 @@ static void hct_listener_region_del(MemoryListener *listener, + + llsize = int128_sub(llend, int128_make64(iova)); + +- ctrl.op = HCT_SHARE_OP_DMA_UNMAP; +- ctrl.iova = iova | (hct_data.pasid << PASID_OFFSET); +- ctrl.size = llsize; +- ret = ioctl(hct_data.hct_fd, HCT_SHARE_OP, &ctrl); +- if (ret < 0) +- error_report("VFIO_UNMAP_DMA: %d", -errno); ++ /* according to host running mode to select different DMA unmapping mode */ ++ if (hct_data.driver == HCT_CCP_DRV_MOD_VFIO_PCI) { ++ /* use vfio-pci directly unmapping */ ++ iova = iova | ((uint64_t)hct_data.pasid << PASID_OFFSET); ++ ret = vfio_hct_dma_unmap_vfio_pci(hct_data.vfio_container_fd, iova, llsize); ++ if (ret < 0) { ++ error_report("VFIO_PCI_UNMAP_DMA: %d", ret); ++ } ++ } else { ++ /* host running hct/ccp mdev mode: use hct/ccp module unmapping */ ++ ctrl.op = HCT_SHARE_OP_DMA_UNMAP; ++ ctrl.iova = iova | (hct_data.pasid << PASID_OFFSET); ++ ctrl.size = llsize; ++ ret = ioctl(hct_data.hct_fd, HCT_SHARE_OP, &ctrl); ++ if (ret < 0) { ++ error_report("HCT_UNMAP_DMA: %d", -errno); ++ } ++ } + } + + static MemoryListener hct_memory_listener = { +@@ -551,16 +819,26 @@ static void hct_data_uninit(HCTDevState *state) + hct_data.hct_fd = 0; + } + +- if (state->vdev.fd) { +- qemu_close(state->vdev.fd); +- state->vdev.fd = 0; ++ if (hct_data.driver == HCT_CCP_DRV_MOD_HCT) { ++ if (state->vdev.fd) { ++ qemu_close(state->vdev.fd); ++ state->vdev.fd = 0; ++ } + } + ++ if (hct_data.hct_shm_fd) { ++ qemu_close(hct_data.hct_shm_fd); ++ hct_data.hct_shm_fd = 0; ++ } + if (hct_data.pasid) { + hct_data.pasid = 0; + } + + if (hct_data.pasid_memory) { ++ if (g_id) { ++ hct_g_ids_free(g_hct_gid_bitmap, g_id); ++ g_id = 0; ++ } + munmap(hct_data.pasid_memory, PAGE_SIZE); + hct_data.pasid_memory = NULL; + } +@@ -571,43 +849,118 @@ static void hct_data_uninit(HCTDevState *state) + } + + memory_listener_unregister(&hct_memory_listener); ++ ++ hct_data.init = 0; ++ hct_data.driver = HCT_CCP_DRV_MOD_UNINIT; + } + + static int hct_data_init(HCTDevState *state) + { ++ + const char *hct_shr_name = NULL; +- int ret; ++ int ret = 0; + + if (hct_data.init == 0) { +- +- ret = hct_get_used_driver_walk(PCI_DRV_HCT_DIR); +- if (ret == 0) { +- hct_data.driver = HCT_CCP_DRV_MOD_HCT; +- hct_shr_name = HCT_SHARE_DEV; +- } else { +- hct_data.driver = HCT_CCP_DRV_MOD_CCP; +- hct_shr_name = CCP_SHARE_DEV; ++ /* ++ * Check driver type based on parameters. ++ * sysfsdev: mdev mode (hct or ccp) ++ * dev(ccp_dev_path): vfio-pci or ccp mode (via vccp files) ++ */ ++ if (state->ccp_dev_path) { ++ FILE *fp = fopen(state->ccp_dev_path, "r"); ++ if (fp) { ++ int type_char = fgetc(fp); ++ fclose(fp); ++ if (type_char == 'v') { ++ hct_data.driver = HCT_CCP_DRV_MOD_VFIO_PCI; ++ } else if (type_char == 'c') { ++ hct_data.driver = HCT_CCP_DRV_MOD_CCP; ++ } else { ++ error_report("hct: invalid vccp file content in %s", state->ccp_dev_path); ++ return -EINVAL; ++ } ++ } else { ++ error_report("hct: cannot open vccp file %s", state->ccp_dev_path); ++ return -EIO; ++ } ++ } else { ++ /* Default to legacy mdev mode check if no params given */ ++ ret = hct_get_used_driver_walk(PCI_DRV_HCT_DIR); ++ if (ret == 0) { ++ hct_data.driver = HCT_CCP_DRV_MOD_HCT; ++ hct_shr_name = HCT_SHARE_DEV; ++ hct_data.hct_fd = qemu_open_old(hct_shr_name, O_RDWR); ++ if (hct_data.hct_fd < 0) { ++ error_report("fail to open %s, errno %d.", hct_shr_name, errno); ++ goto out; ++ } ++ ++ /* The hct.ko version number needs not to be less than 0.2. */ ++ ret = hct_api_version_check(); ++ if (ret) { ++ goto out; ++ } ++ /* close fd for ioctl in kernel module and open the real share memory file below. */ ++ qemu_close(hct_data.hct_fd); ++ ++ } else { ++ /* This case is now handled by the unified logic below, assuming vccp path */ ++ error_report("hct: sysfsdev is only supported hct driver mode."); ++ } ++ } ++ } ++ if (hct_data.driver == HCT_CCP_DRV_MOD_VFIO_PCI || hct_data.driver == HCT_CCP_DRV_MOD_CCP) { ++ /* host running vfio-pci/ccp mode: get device information from daemon via vccp file */ ++ ret = vfio_hct_init_from_daemon(state); ++ if (ret < 0) { ++ error_report("Failed to initialize device info %d", ret); ++ return ret; + } +- +- hct_data.hct_fd = qemu_open_old(hct_shr_name, O_RDWR); +- if (hct_data.hct_fd < 0) { +- error_report("fail to open %s, errno %d.", hct_shr_name, errno); +- ret = -errno; +- goto out; ++ } ++ if (hct_data.init == 0) { ++ hct_shr_name = HCT_GLOBAL_SHARE_SHM_PATH; ++ hct_data.hct_shm_fd = qemu_open_old(hct_shr_name, O_RDWR); ++ if (hct_data.hct_shm_fd < 0) { ++ if (errno == 2) { ++ ret = hct_create_user_shared_memory(HCT_GLOBAL_SHARE_SHM_NAME, HCT_SHARED_MEMORY_SIZE); ++ if (!ret) { ++ hct_data.hct_shm_fd = qemu_open_old(hct_shr_name, O_RDWR); ++ if (hct_data.hct_shm_fd < 0) { ++ ret = -errno; ++ } ++ } ++ } else { ++ ret = -errno; ++ } ++ if (ret < 0) { ++ error_report("fail to open %s, errno %d.", hct_shr_name, errno); ++ goto out; ++ } + } +- +- /* The hct.ko version number needs not to be less than 0.2. */ +- ret = hct_api_version_check(); +- if (ret) +- goto out; ++ hct_data.hct_shared_size = HCT_SHARED_MEMORY_SIZE; + + /* assign a page to the virtual BAR3 of each CCP. */ + ret = hct_shared_memory_init(); + if (ret) + goto out; + ++ /* Open fd for ioctl */ ++ if (hct_data.driver == HCT_CCP_DRV_MOD_HCT) { ++ hct_data.hct_fd = qemu_open_old(HCT_SHARE_DEV, O_RDWR); ++ if (hct_data.hct_fd < 0) { ++ error_report("fail to open %s, errno %d.", HCT_SHARE_DEV, errno); ++ goto unmap_shared_memory_exit; ++ } ++ } else if (hct_data.driver == HCT_CCP_DRV_MOD_CCP) { ++ hct_data.hct_fd = qemu_open_old(CCP_SHARE_DEV, O_RDWR); ++ if (hct_data.hct_fd < 0) { ++ error_report("fail to open %s, errno %d.", CCP_SHARE_DEV, errno); ++ goto unmap_shared_memory_exit; ++ } ++ } ++ + hct_data.pasid_memory = mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, +- MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); ++ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (hct_data.pasid_memory < 0) + goto unmap_shared_memory_exit; + +@@ -616,8 +969,7 @@ static int hct_data_init(HCTDevState *state) + if (ret < 0) + goto unmap_pasid_memory_exit; + +- /* perform DMA_MAP and DMA_UNMAP operations on all memories of the +- * virtual machine. */ ++ /* perform DMA_MAP and DMA_UNMAP operations on all memories of the virtual machine. */ + memory_listener_register(&hct_memory_listener, &address_space_memory); + + hct_data.init = 1; +@@ -635,14 +987,66 @@ out: + return ret; + } + ++#define VFIO_GET_REGION_ADDR(x) ((uint64_t) x << 40ULL) ++ ++/* @brief set bus master to avoid ccp stuck in vfio-pci mode */ ++static int pci_vfio_set_bus_master(int dev_fd) ++{ ++ uint16_t reg = 0; ++ int ret = 0; ++ ++ ret = pread(dev_fd, ®, sizeof(reg), ++ VFIO_GET_REGION_ADDR(VFIO_PCI_CONFIG_REGION_INDEX) + ++ PCI_COMMAND); ++ if (ret != sizeof(reg)) { ++ error_report("Cannot read command from PCI config space!\n"); ++ return -1; ++ } ++ ++ reg |= PCI_COMMAND_MASTER; ++ ++ ret = pwrite(dev_fd, ®, sizeof(reg), ++ VFIO_GET_REGION_ADDR(VFIO_PCI_CONFIG_REGION_INDEX) + ++ PCI_COMMAND); ++ ++ if (ret != sizeof(reg)) { ++ error_report("Cannot write command to PCI config space!\n"); ++ return -1; ++ } ++ ++ return 0; ++} ++ + /* When device is loaded */ + static void vfio_hct_realize(PCIDevice *pci_dev, Error **errp) + { +- int ret; ++ int ret = 0; + char *mdevid; + Error *err = NULL; + HCTDevState *state = PCI_HCT_DEV(pci_dev); + ++ state->lock_fd = -1; ++ ++ /* ++ * In vfio-pci/ccp mode, lock this device's vccp file to prevent ++ * multiple QEMU instances from using the same vccp group. ++ */ ++ if (state->ccp_dev_path && !state->vdev.sysfsdev) { ++ state->lock_fd = open(state->ccp_dev_path, O_RDONLY | O_CLOEXEC); ++ if (state->lock_fd < 0) { ++ error_setg(errp, "hct: cannot open vccp file %s: %s", ++ state->ccp_dev_path, strerror(errno)); ++ return; ++ } ++ if (flock(state->lock_fd, LOCK_EX | LOCK_NB) != 0) { ++ error_setg(errp, "hct: cannot lock vccp file %s, another QEMU may be using this vccp group.", ++ state->ccp_dev_path); ++ close(state->lock_fd); ++ state->lock_fd = -1; ++ return; ++ } ++ } ++ + ret = hct_data_init(state); + if (ret < 0) { + error_setg(errp, "hct data initialization failed."); +@@ -664,6 +1068,8 @@ static void vfio_hct_realize(PCIDevice *pci_dev, Error **errp) + state->vdev.ops = &vfio_ccp_ops; + state->vdev.dev = &state->sdev.dev.qdev; + g_free(state->vdev.name); ++ } else if(hct_data.driver == HCT_CCP_DRV_MOD_VFIO_PCI) { ++ pci_vfio_set_bus_master(state->vdev.fd); + } + + ret = vfio_hct_region_mmap(state); +@@ -718,3 +1124,955 @@ static const TypeInfo pci_hct_info = { + static void hct_register_types(void) { type_register_static(&pci_hct_info); } + + type_init(hct_register_types); ++ ++/* @brief vfio-pci mode DMA mapping function */ ++static int vfio_hct_dma_map_vfio_pci(int container_fd, void *vaddr, uint64_t iova, uint64_t size) ++{ ++ struct vfio_iommu_type1_dma_map dma_map = { 0 }; ++ int ret = 0; ++ ++ if (container_fd < 0) { ++ error_report("Invalid container fd for vfio-pci mapping"); ++ return -1; ++ } ++ ++ if (!vaddr || !size) { ++ error_report("Invalid parameters for vfio-pci mapping"); ++ return -1; ++ } ++ ++ dma_map.argsz = sizeof(dma_map); ++ dma_map.vaddr = (uint64_t)vaddr; ++ dma_map.size = size; ++ dma_map.iova = (uint64_t)iova; ++ dma_map.flags = VFIO_DMA_MAP_FLAG_READ | VFIO_DMA_MAP_FLAG_WRITE; ++ ++ ret = ioctl(container_fd, VFIO_IOMMU_MAP_DMA, &dma_map); ++ if (ret) { ++ if (errno == EEXIST) { ++ error_report("Memory segment is already mapped in vfio-pci mode, container_fd=%d, iova=%lx, size=%lx", container_fd, iova, size); ++ ret = 0; ++ } else { ++ error_report("Cannot set up DMA remapping in vfio-pci mode, error %i (%s)", ++ errno, strerror(errno)); ++ } ++ } ++ ++ return ret; ++} ++ ++static int vfio_hct_dma_unmap_vfio_pci(int container_fd, uint64_t iova, uint64_t size) ++{ ++ struct vfio_iommu_type1_dma_unmap dma_unmap = { 0 }; ++ int ret = 0; ++ ++ if (container_fd < 0) { ++ error_report("Invalid container fd for vfio-pci unmapping"); ++ return -1; ++ } ++ ++ if (!iova || !size) { ++ error_report("Invalid parameters for vfio-pci unmapping, iova %lu, size %lu\n", iova, size); ++ return -1; ++ } ++ ++ dma_unmap.argsz = sizeof(dma_unmap); ++ dma_unmap.size = size; ++ dma_unmap.iova = (uint64_t)iova; ++ ++ ret = ioctl(container_fd, VFIO_IOMMU_UNMAP_DMA, &dma_unmap); ++ if (ret < 0) { ++ error_report("Cannot unmap DMA in vfio-pci mode, error %i (%s)", ++ errno, strerror(errno)); ++ } ++ ++ return ret; ++} ++ ++/* @brief get vfio file descriptor from daemon */ ++static int vfio_hct_init_from_daemon(HCTDevState *state) ++{ ++ hct_client_info_t client_info; ++ hct_vccp_req req_data; ++ hct_ccp_device_t *device_info = NULL; ++ hct_group_info_t *group_info = NULL; ++ char vccp_content[256] = {0}; ++ char bdf[PCI_ADDR_MAX] = {0}; ++ FILE *fp = NULL; ++ int ret = 0; ++ char type_char = 'c'; ++ ++ fp = fopen(state->ccp_dev_path, "r"); ++ if (!fp) { ++ error_report("Failed to open vccp file %s: %s", state->ccp_dev_path, strerror(errno)); ++ return -EINVAL; ++ } ++ ++ if (fgets(vccp_content, sizeof(vccp_content), fp) == NULL) { ++ error_report("Failed to read content from vccp file %s", state->ccp_dev_path); ++ fclose(fp); ++ return -EINVAL; ++ } ++ fclose(fp); ++ ++ vccp_content[strcspn(vccp_content, "\n")] = '\0'; ++ ++ if (sscanf(vccp_content, "%c", &type_char) != 1) { ++ error_report("Invalid vccp file format %s", state->ccp_dev_path); ++ return -EINVAL; ++ } ++ ++ if (type_char == 'v') { ++ if (sscanf(vccp_content, "v %*d %*d %15s", bdf) != 1) { ++ error_report("Invalid vfio-pci vccp file %s", state->ccp_dev_path); ++ return -EINVAL; ++ } ++ } ++ ++ req_data.path = state->ccp_dev_path; ++ req_data.content = vccp_content; ++ ret = hct_client_send_cmd(HCT_DAEMON_SOCK_PATH, &client_info, HCT_CMD_GET_DEVICE_BY_NAME, &req_data); ++ if (ret != HCT_SUCCESS) { ++ error_report("Failed to send command: %s", hct_get_error_string(ret)); ++ return ret; ++ } ++ ++ /* find specified CCP device */ ++ if (hct_data.driver == HCT_CCP_DRV_MOD_CCP) { ++ if (client_info.device_count != 1 || !client_info.devices) { ++ error_report("CCP mode: Expected 1 device, but received %d", ++ client_info.device_count); ++ hct_client_cleanup(&client_info); ++ return -ENODEV; ++ } ++ device_info = &client_info.devices[0]; ++ ++ state->vdev.fd = device_info->device_fd; ++ } else { /* VFIO-PCI mode */ ++ device_info = hct_find_device_by_pci_addr(&client_info, bdf); ++ if (!device_info) { ++ error_report("Device %s not found", bdf); ++ hct_client_cleanup(&client_info); ++ return -ENODEV; ++ } ++ /* get corresponding group information */ ++ group_info = &client_info.groups[device_info->group_index]; ++ ++ /* use returned file descriptor */ ++ state->container_fd = client_info.container_fd; ++ state->group_fd = group_info->group_fd; ++ state->vdev.fd = device_info->device_fd; ++ state->group_id = group_info->group_id; ++ hct_data.vfio_container_fd = state->container_fd; ++ } ++ ++ ++ /* note: do not call hct_client_cleanup, because we need to keep FD open */ ++ /* only clean up dynamic allocated memory, do not close FD */ ++ if (client_info.groups) { ++ free(client_info.groups); ++ } ++ if (client_info.devices) { ++ free(client_info.devices); ++ } ++ ++ return 0; ++} ++ ++/** ++ * @brief Parse single TLV record ++ * @param buffer The buffer to parse ++ * @param buffer_len The length of the buffer ++ * @param offset The offset to parse ++ * @param tlv The TLV to parse ++ * @return 0 on success, -1 on failure ++ */ ++static int hct_parse_tlv(const char *buffer, size_t buffer_len, size_t *offset, hct_tlv_t *tlv) ++{ ++ if (*offset + sizeof(uint16_t) * 2 > buffer_len) { ++ return -1; ++ } ++ ++ memcpy(&tlv->type, buffer + *offset, sizeof(uint16_t)); ++ *offset += sizeof(uint16_t); ++ ++ memcpy(&tlv->length, buffer + *offset, sizeof(uint16_t)); ++ *offset += sizeof(uint16_t); ++ ++ if (*offset + tlv->length > buffer_len) { ++ return -1; ++ } ++ ++ if (tlv->length > 0) { ++ tlv->value = malloc(tlv->length); ++ if (!tlv->value) { ++ return -1; ++ } ++ memcpy(tlv->value, buffer + *offset, tlv->length); ++ *offset += tlv->length; ++ } else { ++ tlv->value = NULL; ++ } ++ ++ return 0; ++} ++ ++/** ++ * @brief Add a TLV to the buffer ++ * @param buffer The buffer to add the TLV to ++ * @param current_len The current length of the buffer ++ * @param max_len The maximum length of the buffer ++ * @param type The type of the TLV ++ * @param value The value of the TLV ++ * @param length The length of the TLV ++ * @return 0 on success, -1 on failure ++ */ ++static int hct_add_tlv_to_buffer(char *buffer, size_t *current_len, size_t max_len, uint16_t type, const void *value, uint16_t length) ++{ ++ if (*current_len + sizeof(type) + sizeof(length) + length > max_len) { ++ error_report("Buffer overflow in TLV add\n"); ++ return -1; ++ } ++ ++ memcpy(buffer + *current_len, &type, sizeof(type)); ++ *current_len += sizeof(type); ++ ++ memcpy(buffer + *current_len, &length, sizeof(length)); ++ *current_len += sizeof(length); ++ ++ if (length > 0 && value) { ++ memcpy(buffer + *current_len, value, length); ++ *current_len += length; ++ } ++ ++ return 0; ++} ++ ++/** ++ * @brief Send command to daemon ++ * @param sock The socket to send to ++ * @param cmd The command to send ++ * @param device_names Array of device names (for HCT_CMD_GET_DEVICE_BY_NAME) ++ * @param device_count Number of device names ++ * @return 0 on success, -1 on failure ++ */ ++static int hct_send_command(int sock, enum hct_daemon_req_cmd cmd, void *req_data) ++{ ++ char buffer[2048] = {0}; ++ size_t buffer_len = 0; ++ struct hct_vccp_req *vccp_req = NULL; ++ ++ /* add command TLV */ ++ if (hct_add_tlv_to_buffer(buffer, &buffer_len, sizeof(buffer), ++ HCT_IPC_FIELD_COMMAND, &cmd, sizeof(cmd)) < 0) { ++ error_report("Failed to add command TLV\n"); ++ return -1; ++ } ++ ++ /* add device names TLV if present */ ++ if (cmd == HCT_CMD_GET_DEVICE_BY_NAME) { ++ vccp_req = req_data; ++ if (hct_add_tlv_to_buffer(buffer, &buffer_len, sizeof(buffer), ++ HCT_IPC_FIELD_VCCP_PATH, vccp_req->path, ++ strlen(vccp_req->path) + 1) < 0) { ++ error_report("Failed to add vccp path TLV\n"); ++ return -1; ++ } ++ if (hct_add_tlv_to_buffer(buffer, &buffer_len, sizeof(buffer), ++ HCT_IPC_FIELD_VCCP_CONTENT, vccp_req->content, ++ strlen(vccp_req->content) + 1) < 0) { ++ error_report("Failed to add vccp content TLV\n"); ++ return -1; ++ } ++ } ++ ++ /* send command */ ++ if (send(sock, buffer, buffer_len, 0) < 0) { ++ error_report("Failed to send command: %s\n", strerror(errno)); ++ return -1; ++ } ++ ++ return 0; ++} ++ ++/** ++ * @brief Send command request to HCT daemon and get response ++ * @param socket_path The path to the socket ++ * @param client_info The client information ++ * @param cmd The command to send ++ * @param device_names Array of device names (for HCT_CMD_GET_DEVICE_BY_NAME) ++ * @param device_count Number of device names ++ * @return 0 on success, -1 on failure ++ */ ++static int hct_client_send_cmd(const char *socket_path, hct_client_info_t *client_info, ++ enum hct_daemon_req_cmd cmd, void *req_data) ++{ ++ char cmsgbuf[CMSG_SPACE(sizeof(int) * MAX_FD_COUNT)] = {0}; ++ char buffer[MAX_TLV_BUFFER_SIZE]; ++ char error_reason[256] = {0}; ++ int fds[MAX_FD_COUNT] = {0}; ++ struct sockaddr_un addr; ++ struct msghdr msg; ++ struct iovec iov; ++ hct_tlv_t tlv; ++ struct cmsghdr *cmsg = NULL; ++ int *fdptr = NULL; ++ ssize_t received = 0; ++ size_t offset = 0; ++ size_t fd_size = 0; ++ int current_group_index = 0; ++ int fd_index = 0; ++ int sock = 0; ++ int has_pending_group_info = 0; ++ int has_pending_device_info = 0; ++ int device_index = 0; ++ ++ /* Temporary variables to hold info before FD */ ++ struct { ++ int group_id; ++ int device_count; ++ } pending_group_info = {0}; ++ struct { ++ char pci_addr[16]; ++ int group_id; ++ } pending_device_info = {0}; ++ ++ ++ if (!socket_path || !client_info) { ++ return HCT_ERROR_INVALID_DATA; ++ } ++ ++ memset(client_info, 0, sizeof(hct_client_info_t)); ++ sock = socket(AF_UNIX, SOCK_STREAM, 0); ++ if (sock < 0) { ++ error_report("Failed to create socket: %s\n", strerror(errno)); ++ return HCT_ERROR_CONNECT; ++ } ++ ++ memset(&addr, 0, sizeof(addr)); ++ addr.sun_family = AF_UNIX; ++ strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1); ++ ++ if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { ++ error_report("Failed to connect to %s: %s\n", socket_path, strerror(errno)); ++ close(sock); ++ return HCT_ERROR_CONNECT; ++ } ++ ++ if (hct_send_command(sock, cmd, req_data) < 0) { ++ error_report("[HCT] Failed to send command"); ++ close(sock); ++ return HCT_ERROR_CONNECT; ++ } ++ ++ memset(&msg, 0, sizeof(msg)); ++ iov.iov_base = buffer; ++ iov.iov_len = sizeof(buffer); ++ ++ msg.msg_iov = &iov; ++ msg.msg_iovlen = 1; ++ msg.msg_control = cmsgbuf; ++ msg.msg_controllen = sizeof(cmsgbuf); ++ ++ received = recvmsg(sock, &msg, 0); ++ if (received <= 0) { ++ error_report("Failed to receive message: %s\n", strerror(errno)); ++ close(sock); ++ return HCT_ERROR_RECEIVE; ++ } ++ ++ /* Extract file descriptors from control message */ ++ for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg)) { ++ if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) { ++ fd_size = cmsg->cmsg_len - CMSG_LEN(0); ++ fdptr = (int *)CMSG_DATA(cmsg); ++ memcpy(fds, fdptr, fd_size); ++ } ++ } ++ ++ offset = 0; ++ fd_index = 0; ++ current_group_index = -1; ++ ++ while (offset < received) { ++ memset(&tlv, 0, sizeof(tlv)); ++ if (hct_parse_tlv(buffer, received, &offset, &tlv) < 0) { ++ error_report("[HCT] Failed to parse TLV data"); ++ hct_client_cleanup(client_info); ++ close(sock); ++ return HCT_ERROR_INVALID_DATA; ++ } ++ ++ /* Process different TLV types according to order */ ++ switch (tlv.type) { ++ case HCT_IPC_FIELD_CONTAINER_FD: ++ client_info->container_fd = fds[fd_index]; ++ fd_index++; ++ break; ++ ++ case HCT_IPC_FIELD_GROUP_INFO: ++ /* Store group info for next group FD */ ++ if (tlv.length == sizeof(pending_group_info)) { ++ memcpy(&pending_group_info, tlv.value, sizeof(pending_group_info)); ++ has_pending_group_info = 1; ++ } else { ++ error_report("Invalid group info size\n"); ++ } ++ break; ++ ++ case HCT_IPC_FIELD_GROUP_FD: ++ if (!has_pending_group_info) { ++ error_report("Received group FD without group info"); ++ break; ++ } ++ ++ /* New group detected, allocate space */ ++ current_group_index++; ++ client_info->group_count = current_group_index + 1; ++ ++ /* Reallocate groups array */ ++ client_info->groups = realloc(client_info->groups, sizeof(hct_group_info_t) * client_info->group_count); ++ if (!client_info->groups) { ++ error_report("Failed to allocate memory for groups"); ++ hct_client_cleanup(client_info); ++ close(sock); ++ return HCT_ERROR_INVALID_DATA; ++ } ++ ++ /* Initialize new group with real group_id */ ++ memset(&client_info->groups[current_group_index], 0, sizeof(hct_group_info_t)); ++ client_info->groups[current_group_index].group_id = pending_group_info.group_id; ++ client_info->groups[current_group_index].group_fd = fds[fd_index]; ++ client_info->groups[current_group_index].device_count = 0; ++ ++ fd_index++; ++ has_pending_group_info = 0; /* Clear pending info */ ++ break; ++ ++ case HCT_IPC_FIELD_DEVICE_INFO: ++ /* Store device info for next device FD */ ++ if (tlv.length == sizeof(pending_device_info)) { ++ memcpy(&pending_device_info, tlv.value, sizeof(pending_device_info)); ++ has_pending_device_info = 1; ++ } else { ++ error_report("Invalid device info size\n"); ++ } ++ break; ++ ++ case HCT_IPC_FIELD_DEVICE_FD: ++ /* In CCP mode, we only receive DEVICE_FD, no preceding INFO TLVs */ ++ if (hct_data.driver == HCT_CCP_DRV_MOD_VFIO_PCI) { ++ if (!has_pending_device_info) { ++ error_report("VFIO mode: Received device FD without device info"); ++ break; ++ } ++ if (current_group_index < 0) { ++ error_report("Received device FD without group context"); ++ break; ++ } ++ } ++ ++ /* Allocate space for new device */ ++ client_info->device_count++; ++ client_info->devices = realloc(client_info->devices, sizeof(hct_ccp_device_t) * client_info->device_count); ++ if (!client_info->devices) { ++ error_report("Failed to allocate memory for devices"); ++ hct_client_cleanup(client_info); ++ close(sock); ++ return HCT_ERROR_INVALID_DATA; ++ } ++ ++ /* Initialize new device with real pci_addr */ ++ device_index = client_info->device_count - 1; ++ memset(&client_info->devices[device_index], 0, sizeof(hct_ccp_device_t)); ++ client_info->devices[device_index].device_fd = fds[fd_index]; ++ ++ if (hct_data.driver != HCT_CCP_DRV_MOD_CCP) { ++ client_info->devices[device_index].group_index = current_group_index; ++ ++ /* Use real pci_addr from device info */ ++ strncpy(client_info->devices[device_index].pci_addr, ++ pending_device_info.pci_addr, ++ sizeof(client_info->devices[device_index].pci_addr) - 1); ++ client_info->devices[device_index].pci_addr[ ++ sizeof(client_info->devices[device_index].pci_addr) - 1] = '\0'; ++ ++ /* Update group device count */ ++ client_info->groups[current_group_index].device_count++; ++ } ++ ++ fd_index++; ++ has_pending_device_info = 0; /* Clear pending info */ ++ break; ++ case HCT_IPC_FIELD_ERROR_REASON: ++ error_report("IPC return error"); ++ break; ++ ++ default: ++ error_report("Unknown TLV type: %d", tlv.type); ++ break; ++ } ++ ++ if (tlv.value) { ++ free(tlv.value); ++ } ++ } ++ ++ if (error_reason[0] != '\0') { ++ error_report("Rejected request"); ++ hct_client_cleanup(client_info); ++ close(sock); ++ return HCT_ERROR_INVALID_DATA; ++ } ++ ++ close(sock); ++ ++ return HCT_SUCCESS; ++} ++ ++/** ++ * @brief Cleanup HCT client resources ++ * @param client_info The client information ++ */ ++static void hct_client_cleanup(hct_client_info_t *client_info) ++{ ++ if (!client_info) { ++ return; ++ } ++ ++ if (client_info->devices) { ++ free(client_info->devices); ++ client_info->devices = NULL; ++ } ++ ++ if (client_info->groups) { ++ free(client_info->groups); ++ client_info->groups = NULL; ++ } ++ ++ if (client_info->container_fd >= 0) { ++ client_info->container_fd = -1; ++ } ++ ++ client_info->device_count = 0; ++ client_info->group_count = 0; ++} ++ ++/** ++ * @brief Get error description string ++ * @param error_code The error code ++ * @return The error description string ++ */ ++static const char *hct_get_error_string(int error_code) ++{ ++ switch (error_code) { ++ case HCT_SUCCESS: ++ return "Success"; ++ case HCT_ERROR_CONNECT: ++ return "Failed to connect"; ++ case HCT_ERROR_RECEIVE: ++ return "Failed to receive data"; ++ case HCT_ERROR_INVALID_DATA: ++ return "Invalid data received"; ++ default: ++ return "Unknown error"; ++ } ++} ++ ++/** ++ * @brief Find device by PCI address ++ * @param client_info The client information ++ * @param pci_addr The PCI address of the device ++ * @return The device information ++ */ ++static hct_ccp_device_t* hct_find_device_by_pci_addr(hct_client_info_t *client_info, const char *pci_addr) ++{ ++ int i = 0; ++ ++ if (!client_info || !pci_addr || !client_info->devices) { ++ return NULL; ++ } ++ ++ for (i = 0; i < client_info->device_count; i++) { ++ if (strcmp(client_info->devices[i].pci_addr, pci_addr) == 0) { ++ return &client_info->devices[i]; ++ } ++ } ++ ++ return NULL; ++} ++ ++/** ++ * @brief Allocate a global bitmap instance for g_id management ++ * @return The allocated bitmap instance, or NULL on failure ++ */ ++static struct hct_gid_bitmap* hct_gid_bitmap_alloc(void) ++{ ++ struct hct_gid_bitmap *gid_bitmap = NULL; ++ mode_t oldmod = 0; ++ size_t total_size = 0; ++ int shm_fd = -1; ++ int lock_fd = -1; ++ ++ gid_bitmap = (struct hct_gid_bitmap *)calloc(1, sizeof(struct hct_gid_bitmap)); ++ if (!gid_bitmap) { ++ error_report("Failed to allocate hct_gid_bitmap structure\n"); ++ return NULL; ++ } ++ ++ strncpy(gid_bitmap->name, HCT_GID_BITMAP_SHM_NAME, MAX_PATH - 1); ++ gid_bitmap->name[MAX_PATH - 1] = '\0'; ++ gid_bitmap->shm_fd = -1; ++ gid_bitmap->lock_fd = -1; ++ ++ total_size = HCT_BITMAP_SIZE(HCT_QEMU_GIDS_BITMAP_MAX_BIT) * sizeof(unsigned long); ++ gid_bitmap->len = total_size; ++ ++ oldmod = umask(0); ++ shm_fd = shm_open(HCT_GID_BITMAP_SHM_NAME, O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC, 0666); ++ umask(oldmod); ++ ++ if (shm_fd < 0 && errno == EEXIST) { ++ // Shared memory already exists, try to open it ++ shm_fd = shm_open(HCT_GID_BITMAP_SHM_NAME, O_RDWR | O_CLOEXEC, 0666); ++ } ++ ++ if (shm_fd < 0) { ++ error_report("Failed to create/open shared memory %s, errno %d\n", HCT_GID_BITMAP_SHM_NAME, errno); ++ goto cleanup; ++ } ++ ++ gid_bitmap->shm_fd = shm_fd; ++ if (ftruncate(shm_fd, total_size) != 0) { ++ error_report("Failed to set shared memory size, errno %d\n", errno); ++ goto cleanup; ++ } ++ ++ gid_bitmap->bitmap = (unsigned long *)mmap(NULL, total_size, PROT_READ | PROT_WRITE, ++ MAP_SHARED, shm_fd, 0); ++ if (gid_bitmap->bitmap == MAP_FAILED) { ++ error_report("Failed to mmap shared memory, errno %d\n", errno); ++ goto cleanup; ++ } ++ ++ lock_fd = shm_open(HCT_GID_LOCK_FILE, O_CREAT | O_EXCL | O_RDWR, 0666); ++ if (lock_fd < 0) { ++ if (errno == EEXIST) { ++ lock_fd = shm_open(HCT_GID_LOCK_FILE, O_RDWR, 0); ++ if (lock_fd == -1) { ++ error_report("Failed to shm_open lock file %s, errno %d\n", HCT_GID_LOCK_FILE, errno); ++ goto cleanup; ++ } ++ } else { ++ error_report("Failed to shm_open lock file %s, errno %d\n", HCT_GID_LOCK_FILE, errno); ++ goto cleanup; ++ } ++ } else { ++ if (ftruncate(lock_fd, HCT_QEMU_GIDS_BITMAP_MAX_BIT * 8) != 0) { ++ error_report("Failed to ftruncate lock shm %s, errno %d\n", HCT_GID_LOCK_FILE, errno); ++ goto cleanup; ++ } ++ if (fchmod(lock_fd, 0666) == -1) { ++ error_report("fchmod failed\n"); ++ } ++ } ++ ++ gid_bitmap->lock_fd = lock_fd; ++ g_hct_gid_bitmap = gid_bitmap; ++ ++ return gid_bitmap; ++ ++cleanup: ++ hct_gid_bitmap_free(gid_bitmap); ++ return NULL; ++} ++ ++/** ++ * @brief Free a global bitmap instance for g_id management ++ * @param bitmap The bitmap instance to free ++ */ ++static void hct_gid_bitmap_free(struct hct_gid_bitmap *bitmap) ++{ ++ if (!bitmap) ++ return; ++ ++ if (bitmap->bitmap && bitmap->bitmap != MAP_FAILED) { ++ munmap(bitmap->bitmap, bitmap->len); ++ } ++ ++ if (bitmap->shm_fd >= 0) { ++ close(bitmap->shm_fd); ++ } ++ ++ if (bitmap->lock_fd >= 0) { ++ close(bitmap->lock_fd); ++ } ++ ++ if (g_hct_gid_bitmap == bitmap) { ++ g_hct_gid_bitmap = NULL; ++ } ++ ++ free(bitmap); ++} ++ ++/** ++ * @brief Allocate a g_id from the 1024-bit bitmap, left-shift by 8 bits ++ * @param bitmap The bitmap instance to allocate from ++ * @return The allocated g_id, or -1 if no g_id is available ++ */ ++static inline int _hct_gid_bitmap_try_alloc(struct hct_gid_bitmap *bitmap) { ++ unsigned long bit_pos = 0; ++ unsigned long old_val = 0, new_val = 0; ++ volatile unsigned long *word_ptr = NULL; ++ unsigned long mask = 0; ++ ++ for (bit_pos = HCT_QEMU_GIDS_BITMAP_MIN_BIT; bit_pos < HCT_QEMU_GIDS_BITMAP_MAX_BIT; bit_pos++) { ++ if (!hct_get_bit(bitmap->bitmap, bit_pos)) { ++ word_ptr = &bitmap->bitmap[WORD_OFFSET(bit_pos)]; ++ mask = 1UL << BIT_OFFSET(bit_pos); ++ old_val = *word_ptr; ++ if (!(old_val & mask)) { ++ new_val = old_val | mask; ++ if (__atomic_compare_exchange_n(word_ptr, &old_val, new_val, ++ false, __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE)) { ++ return bit_pos; ++ } ++ } ++ } ++ } ++ return -1; ++} ++ ++/** ++ * @brief Allocate a g_id from the 1024-bit bitmap, left-shift by 8 bits ++ * @param bitmap The bitmap instance to allocate from ++ * @param gid The allocated g_id ++ * @return 0 on success, -1 if no g_id is available ++ */ ++static int hct_g_ids_alloc(struct hct_gid_bitmap *bitmap, unsigned long *gid) ++{ ++ unsigned long id = 0; ++ int bit_pos = -1; ++ int try_count = 0; ++ int max_try = 2; ++ int lock_ret = 0; ++ ++ if (!bitmap || !bitmap->bitmap || !gid) { ++ error_report("Invalid parameters\n"); ++ return -EINVAL; ++ } ++ ++ while (try_count < max_try) { ++ bit_pos = _hct_gid_bitmap_try_alloc(bitmap); ++ if (bit_pos >= 0) { ++ id = bit_pos + 1 - 1024; // g_id can't be 0 ++ *gid = id << HCT_QEMU_GIDS_SHIFT_BITS; ++ lock_ret = hct_g_ids_lock_state_lock(bitmap, *gid); ++ if (lock_ret == 0) { ++ return 0; ++ } else { ++ continue; ++ } ++ } else if (try_count == 0) { ++ // try to clean up orphaned g_ids ++ error_report("try to clean up orphaned g_ids\n"); ++ hct_g_ids_lock_state_walk(bitmap); ++ } ++ try_count++; ++ } ++ ++ error_report("No available g_id in bitmap or all locks busy after cleanup\n"); ++ return -EINVAL; ++} ++ ++/** ++ * @brief Free a g_id from the 1024-bit bitmap, left-shift by 8 bits ++ * @param bitmap The bitmap instance to free from ++ * @param gid The g_id to free ++ */ ++static void hct_g_ids_free(struct hct_gid_bitmap *bitmap, unsigned long gid) ++{ ++ unsigned long id = 0; ++ unsigned long bit_pos = 0; ++ off_t offset = 0; ++ struct flock lock; ++ ++ if (!bitmap || !bitmap->bitmap) { ++ error_report("Invalid bitmap parameters\n"); ++ return; ++ } ++ ++ if (gid == 0) { ++ error_report("Invalid g_id=0\n"); ++ return; ++ } ++ ++ // Extract original id from g_id ++ id = gid >> HCT_QEMU_GIDS_SHIFT_BITS; ++ if (id == 0 || id > HCT_QEMU_GIDS_BITMAP_MAX_BIT) { ++ error_report("Invalid g_id=0x%lx, extracted id=%lu\n", gid, id); ++ return; ++ } ++ ++ bit_pos = id - 1 + 1024; // Convert back to bit position ++ ++ offset = bit_pos * HCT_GIDS_PER_BLOCK; ++ lock.l_type = F_UNLCK; ++ lock.l_whence = SEEK_SET; ++ lock.l_start = offset; ++ lock.l_len = HCT_GIDS_PER_BLOCK; ++ if (bitmap->lock_fd >= 0) ++ fcntl(bitmap->lock_fd, F_SETLK, &lock); ++ ++ hct_clear_bit(bitmap->bitmap, bit_pos); ++} ++ ++/** ++ * @brief Lock a g_id ++ * @param bitmap The bitmap instance to lock ++ * @param gid The g_id to lock ++ * @return 0 on success, -1 if the g_id is not locked ++ */ ++static int hct_g_ids_lock_state_lock(struct hct_gid_bitmap *bitmap, unsigned long gid) ++{ ++ struct flock lock; ++ unsigned long id = 0; ++ off_t offset = 0; ++ int ret = 0; ++ ++ if (!bitmap || !bitmap->bitmap || bitmap->lock_fd < 0) { ++ error_report("Invalid bitmap\n"); ++ return -EINVAL; ++ } ++ ++ if (gid == 0) { ++ error_report("Invalid g_id=0\n"); ++ return -EINVAL; ++ } ++ ++ // Extract original id from g_id ++ id = gid >> HCT_QEMU_GIDS_SHIFT_BITS; ++ if (id == 0 || id > HCT_QEMU_GIDS_BITMAP_MAX_BIT) { ++ error_report("Invalid g_id=0x%lx, extracted id=%lu\n", gid, id); ++ return -EINVAL; ++ } ++ ++ // Calculate file offset for this g_id's lock region ++ offset = (id + 1024 - 1) * HCT_GIDS_PER_BLOCK; ++ ++ // Set up exclusive lock ++ lock.l_type = F_WRLCK; ++ lock.l_whence = SEEK_SET; ++ lock.l_start = offset; ++ lock.l_len = HCT_GIDS_PER_BLOCK; ++ ++ // Try to acquire lock (non-blocking) ++ if (fcntl(bitmap->lock_fd, F_SETLK, &lock) == 0) { ++ ret = 0; ++ } else { ++ error_report("Failed to lock g_id=0x%lx, offset%lx, errno=%d\n", gid, offset, errno); ++ ret = -EINVAL; ++ } ++ ++ return ret; ++} ++ ++/** ++ * @brief Walk the bitmap to detect orphaned g_ids ++ * @param bitmap The bitmap instance to walk ++ */ ++static void hct_g_ids_lock_state_walk(struct hct_gid_bitmap *bitmap) ++{ ++ struct flock lock; ++ unsigned long bit_pos = 0; ++ unsigned long gid = 0; ++ off_t offset = 0; ++ ++ if (!bitmap || !bitmap->bitmap || bitmap->lock_fd < 0) { ++ error_report("Invalid bitmap parameters\n"); ++ return; ++ } ++ ++ // Walk through all allocated bits in bitmap ++ for (bit_pos = HCT_QEMU_GIDS_BITMAP_MIN_BIT; bit_pos < HCT_QEMU_GIDS_BITMAP_MAX_BIT; bit_pos++) { ++ if (hct_get_bit(bitmap->bitmap, bit_pos)) { ++ gid = (bit_pos + 1) << HCT_QEMU_GIDS_SHIFT_BITS; ++ if (gid == *(unsigned long *)((unsigned long)(hct_data.pasid_memory) + HCT_PASID_MEM_GID_OFFSET)) { ++ continue; ++ } ++ if (gid >> HCT_QEMU_GIDS_SHIFT_BITS == 0) { ++ continue; ++ } ++ offset = bit_pos * HCT_GIDS_PER_BLOCK; ++ ++ // Try to acquire exclusive lock for this g_id ++ lock.l_type = F_WRLCK; ++ lock.l_whence = SEEK_SET; ++ lock.l_start = offset; ++ lock.l_len = HCT_GIDS_PER_BLOCK; ++ ++ if (fcntl(bitmap->lock_fd, F_GETLK, &lock) == -1) { ++ error_report("Failed to get lock file status.\n"); ++ return; ++ } ++ if (lock.l_type == F_UNLCK) { ++ info_report("Detected orphaned g_id=0x%lx, cleaning up\n", gid); ++ hct_clear_bit(bitmap->bitmap, bit_pos); ++ } ++ } ++ } ++} ++ ++static void hct_clear_bit(unsigned long *bitmap, int n) ++{ ++ __atomic_fetch_and(&bitmap[WORD_OFFSET(n)], ~(1UL << BIT_OFFSET(n)), __ATOMIC_RELEASE); ++} ++ ++static uint32_t hct_get_bit(unsigned long *bitmap, int n) ++{ ++ return ((bitmap[WORD_OFFSET(n)] & (0x1UL << BIT_OFFSET(n))) != 0); ++} ++ ++static int hct_create_user_shared_memory(const char *name, size_t size) ++{ ++ mode_t oldmod; ++ void *addr = NULL; ++ int shm_fd = -1; ++ int new_mem = 0; ++ ++ oldmod = umask(0); ++ shm_fd = shm_open(name, O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC, 0666); ++ umask(oldmod); ++ ++ if (shm_fd < 0) { ++ if (errno == EEXIST) { ++ error_report("Shared memory %s already exists, trying to open existing one\n", name); ++ return 0; ++ } else { ++ error_report("Failed to create/open shared memory %s, errno %d (%s)\n", name, errno, strerror(errno)); ++ return -1; ++ } ++ } else { ++ new_mem = 1; ++ } ++ ++ if (ftruncate(shm_fd, size) != 0) { ++ error_report("Failed to set shared memory size %zu, errno %d (%s)\n", size, errno, strerror(errno)); ++ close(shm_fd); ++ return -1; ++ } ++ ++ if (new_mem) { ++ addr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0); ++ if (addr == MAP_FAILED) { ++ error_report("Failed to mmap shared memory %s, errno %d (%s)\n", name, errno, strerror(errno)); ++ close(shm_fd); ++ return -1; ++ } ++ memset(addr, 0, size); ++ munmap(addr, size); ++ } ++ ++ close(shm_fd); ++ return 0; ++} +-- +2.39.3 + diff --git a/0529-hw-vfio-hct-support-live-migration-function-for-virt.patch b/0529-hw-vfio-hct-support-live-migration-function-for-virt.patch new file mode 100644 index 0000000..31559b7 --- /dev/null +++ b/0529-hw-vfio-hct-support-live-migration-function-for-virt.patch @@ -0,0 +1,397 @@ +From ef53d21a81f3312cd22f904eff5fb15e6f1b6a6b Mon Sep 17 00:00:00 2001 +From: Yabin Li +Date: Wed, 15 Oct 2025 22:01:06 +0800 +Subject: [PATCH] hw/vfio/hct: support live migration function for virtual + machines. + +Signed-off-by: Yabin Li +Signed-off-by: yangdepei +--- + hw/vfio/hct.c | 299 ++++++++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 299 insertions(+) + +diff --git a/hw/vfio/hct.c b/hw/vfio/hct.c +index a9a79b626a..febfc1c735 100644 +--- a/hw/vfio/hct.c ++++ b/hw/vfio/hct.c +@@ -18,6 +18,8 @@ + #include + #include + #include ++#include ++#include + + #include "qemu/osdep.h" + #include "qemu/queue.h" +@@ -32,6 +34,10 @@ + #include "qemu/error-report.h" + #include "qapi/error.h" + #include "hw/qdev-properties.h" ++#include "hw/virtio/vhost-vsock.h" ++#include "migration/migration.h" ++#include "migration/vmstate.h" ++#include "migration/misc.h" + + // ======================== g_id API ==================== + +@@ -216,6 +222,7 @@ typedef struct { + #define TYPE_HCT_DEV "hct" + #define PCI_HCT_DEV(obj) OBJECT_CHECK(HCTDevState, (obj), TYPE_HCT_DEV) + #define HCT_MAX_PASID (1 << 8) ++#define HCT_MIGRATE_VERSION 1 + + #define PCI_VENDOR_ID_HYGON_CCP 0x1d94 + #define PCI_DEVICE_ID_HYGON_CCP 0x1468 +@@ -257,6 +264,20 @@ typedef struct { + #define PASID_OFFSET 40 + #define HCT_PASID_MEM_GID_OFFSET 1024 + ++/* for migration */ ++#define HCT_MIG_PROTOCOL_VER 1 ++#define HCT_MIG_MSG_MAGIC 0x76484354 ++#define HCT_VMADDR_CID_HOST 2 ++#define HCT_VSOCK_PORT 12345 ++#define HCT_MIG_STATE_ONLINE 0x00 ++#define HCT_MIG_STATE_RESTRICTED 0x01 ++#define HCT_MIG_STATE_STOPPED 0x02 ++#define HCT_MIGRATION_START 0x01 ++#define HCT_CHECK_VM_READINESS 0x02 ++#define HCT_MIGRATION_DONE 0x03 ++#define HCT_MIG_MSG_ACK 0x01 ++#define HCT_MIG_MSG_ERR 0x02 ++ + static volatile struct hct_data { + int init; + int hct_fd; +@@ -283,9 +304,15 @@ typedef struct HctDevState { + MemoryRegion mmio; + MemoryRegion shared; + MemoryRegion pasid; ++ NotifierWithReturn precopy_notifier; ++ QEMUTimer *migrate_load_timer; + uint64_t map_size[PCI_NUM_REGIONS]; ++ uint32_t guest_cid; ++ uint32_t migrate_support; ++ int client_fd; + void *maps[PCI_NUM_REGIONS]; + char *ccp_dev_path; ++ char *vsock_device; + int container_fd; /* vfio container fd */ + int group_fd; /* vfio group fd */ + int group_id; /* vfio group id */ +@@ -467,6 +494,7 @@ static void vfio_hct_exit(PCIDevice *dev) + static Property vfio_hct_properties[] = { + DEFINE_PROP_STRING("sysfsdev", HCTDevState, vdev.sysfsdev), + DEFINE_PROP_STRING("dev", HCTDevState, ccp_dev_path), ++ DEFINE_PROP_STRING("vsock-device", HCTDevState, vsock_device), + DEFINE_PROP_END_OF_LIST(), + }; + +@@ -812,6 +840,255 @@ static int hct_get_used_driver_walk(const char *path) + return ret; + } + ++static int hct_get_vsock_guest_cid(HCTDevState *state) ++{ ++ Object *dev = NULL; ++ gchar *path = NULL; ++ uint32_t cid; ++ ++ if (!state->vsock_device) { ++ dev = object_resolve_path_type("", TYPE_VHOST_VSOCK, NULL); ++ if (!dev) { ++ error_report("get Object for %s failed.", TYPE_VHOST_VSOCK); ++ return -1; ++ } ++ } else { ++ path = g_strdup_printf("/machine/peripheral/%s", state->vsock_device); ++ dev = object_resolve_path(path, NULL); ++ g_free(path); ++ if (!dev) { ++ error_report("get Object for %s failed.", path); ++ return -1; ++ } ++ } ++ ++ cid = object_property_get_uint(dev, "guest-cid", NULL); ++ if (cid <= HCT_VMADDR_CID_HOST) { ++ error_report("cid = %u, invalid.", cid); ++ return -1; ++ } ++ ++ state->guest_cid = cid; ++ return 0; ++} ++ ++static int hct_client_vsock_connect_op(HCTDevState *state) ++{ ++ struct sockaddr_vm host_addr; ++ int sock_fd; ++ ++ if (state->guest_cid <= HCT_VMADDR_CID_HOST) { ++ error_report("state->guest_cid = %u, invalid.", state->guest_cid); ++ return -1; ++ } ++ ++ sock_fd = socket(AF_VSOCK, SOCK_STREAM, 0); ++ if (sock_fd < 0) { ++ perror("socket creation failed"); ++ return -1; ++ } ++ ++ memset(&host_addr, 0, sizeof(host_addr)); ++ host_addr.svm_family = AF_VSOCK; ++ host_addr.svm_cid = state->guest_cid; ++ host_addr.svm_port = HCT_VSOCK_PORT; ++ ++ if (connect(sock_fd, (struct sockaddr*)&host_addr, sizeof(host_addr)) < 0) { ++ perror("connect failed"); ++ close(sock_fd); ++ return -EAGAIN; ++ } ++ ++ state->client_fd = sock_fd; ++ return 0; ++} ++ ++static int hct_client_vsock_send_msg(int sock_fd, char *buf, size_t len) ++{ ++ struct msghdr msg; ++ struct iovec iov; ++ ++ memset(&msg, 0, sizeof(msg)); ++ iov.iov_base = buf; ++ iov.iov_len = len; ++ msg.msg_iov = &iov; ++ msg.msg_iovlen = 1; ++ return sendmsg(sock_fd, &msg, 0); ++} ++ ++static int hct_client_vsock_recv_msg(int sock_fd, char *buf, size_t len) ++{ ++ struct msghdr msg; ++ struct iovec iov; ++ ++ memset(&msg, 0, sizeof(msg)); ++ iov.iov_base = buf; ++ iov.iov_len = len; ++ msg.msg_iov = &iov; ++ msg.msg_iovlen = 1; ++ return recvmsg(sock_fd, &msg, 0); ++} ++ ++static int hct_client_send_msg(HCTDevState *state, char *buf, size_t len, int mloop) ++{ ++ int loops = 0; ++ int ret = -1; ++ ++ while ((ret = hct_client_vsock_connect_op(state)) != 0 && ret == -EAGAIN) { ++ if (!mloop) { ++ return -EAGAIN; ++ } ++ if (++loops > mloop) { ++ error_report("loops = %d, connect failed.", loops); ++ return -1; ++ } ++ usleep(20 * 1000); ++ } ++ ++ if (hct_client_vsock_send_msg(state->client_fd, (char *)buf, len) < 0) { ++ error_report("hct_client_vsock_send_msg failed."); ++ goto exit; ++ } ++ ++ memset((void *)buf, 0, len); ++ if (hct_client_vsock_recv_msg(state->client_fd, (char *)buf, len) < 0) { ++ error_report("hct_client_vsock_recv_msg failed."); ++ goto exit; ++ } ++ ++ ret = 0; ++ ++exit: ++ close(state->client_fd); ++ return ret; ++} ++ ++static int hct_migrate_precopy_notifier(NotifierWithReturn *notifier, void *data) ++{ ++ HCTDevState *state = container_of(notifier, HCTDevState, precopy_notifier); ++ MigrationState *ms = migrate_get_current(); ++ PrecopyNotifyData *pnd = data; ++ int msg[16]; ++ int MAX_CONNECT_LOOPS = 10; ++ int MAX_CHECK_LOOPS = 20; ++ int loops = 0; ++ int ret = -1; ++ ++ if (pnd->reason != PRECOPY_NOTIFY_SETUP) ++ return 0; ++ ++ qemu_mutex_unlock_iothread(); ++ ++ /* [0]:magic [1]:version [2]:op [3]:sync_state */ ++ msg[0] = HCT_MIG_MSG_MAGIC; ++ msg[1] = HCT_MIG_PROTOCOL_VER; ++ msg[2] = HCT_MIGRATION_START; ++ msg[3] = 0; ++ ret = hct_client_send_msg(state, (char *)msg, sizeof(msg), MAX_CONNECT_LOOPS); ++ if (ret != 0 || msg[0] != HCT_MIG_MSG_MAGIC) { ++ /* Perform live migration addording to a regular virtual machine. */ ++ error_report("ret:%d msg[0]:0x%x, please install a newer hct.ko" ++ " for the virtual machine.", ret, msg[0]); ++ state->migrate_support = 0; ++ ret = 0; ++ goto exit; ++ } else if (msg[3] != HCT_MIG_MSG_ACK) { ++ /* We believe that the virtual machine is not ready, ++ * so terminate the live migration. ++ */ ++ error_setg(pnd->errp, "%s[%u] msg[3]:0x%02x, invalid.\n", ++ __func__, __LINE__, msg[3]); ++ ms->state = MIGRATION_STATUS_CANCELLED; ++ goto exit; ++ } ++ ++ while (++loops <= MAX_CHECK_LOOPS) { ++ msg[0] = HCT_MIG_MSG_MAGIC; ++ msg[1] = HCT_MIG_PROTOCOL_VER; ++ msg[2] = HCT_CHECK_VM_READINESS; ++ msg[3] = 0; ++ ret = hct_client_send_msg(state, (char *)msg, sizeof(msg), MAX_CONNECT_LOOPS); ++ if (ret != 0 || msg[0] != HCT_MIG_MSG_MAGIC) { ++ error_setg(pnd->errp, "ret:%d msg[0]:0x%x, invalid.", ret, msg[0]); ++ ms->state = MIGRATION_STATUS_CANCELLED; ++ goto exit; ++ } else if (msg[3] == HCT_MIG_STATE_STOPPED) { ++ break; ++ } ++ sleep(1); ++ } ++ if (loops > MAX_CHECK_LOOPS) { ++ error_setg(pnd->errp, "%s[%u] loops:%d > MAX_CHECK_LOOPS:%d, will cancel.\n", ++ __func__, __LINE__, loops, MAX_CHECK_LOOPS); ++ ms->state = MIGRATION_STATUS_CANCELLED; ++ goto exit; ++ } ++ ++ info_report("%s: recieved HCT_MIG_MSG_ACK.", __func__); ++ ret = 0; ++ ++exit: ++ qemu_mutex_lock_iothread(); ++ return ret; ++} ++ ++static void hct_client_connect_timer_cb(void *opaque) ++{ ++ HCTDevState *state = opaque; ++ int msg[16]; ++ int MAX_LOOP_TIMES = 10; ++ static int loops = 0; ++ int ret; ++ ++ /* [0]:magic [1]:version [2]:op [3]:sync_state */ ++ msg[0] = HCT_MIG_MSG_MAGIC; ++ msg[1] = HCT_MIG_PROTOCOL_VER; ++ msg[2] = HCT_MIGRATION_DONE; ++ msg[3] = 0; ++ ret = hct_client_send_msg(state, (char *)msg, sizeof(msg), 0); ++ if (ret == -EAGAIN) { ++ if (++loops > MAX_LOOP_TIMES) { ++ error_report("%s: loops = %d, connect failed.", __func__, loops); ++ goto exit; ++ } ++ timer_mod(state->migrate_load_timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + ++ NANOSECONDS_PER_SECOND * 2); /* 2s */ ++ return; ++ } ++ ++ if (ret != 0 || msg[0] != HCT_MIG_MSG_MAGIC || msg[3] != HCT_MIG_MSG_ACK) { ++ error_report("ret:%d msg[0]:0x%x msg[3]:0x%x, invalid.\n", ret, msg[0], msg[3]); ++ goto exit; ++ } ++ info_report("%s: recieved HCT_MIG_MSG_ACK.", __func__); ++ ++exit: ++ timer_free(state->migrate_load_timer); ++ state->migrate_load_timer = NULL; ++ close(state->client_fd); ++ loops = 0; ++} ++ ++static int hct_dev_post_load(void *opaque, int version_id) ++{ ++ HCTDevState *state = opaque; ++ ++ if (!state->migrate_support) ++ return 0; ++ ++ /* When there are multiple ccp devices, each device will ++ * execute the post_load function once. ++ * We only hope that the first device can set the timer. ++ */ ++ state->migrate_support = 0; ++ state->migrate_load_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, ++ hct_client_connect_timer_cb, state); ++ timer_mod(state->migrate_load_timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + ++ NANOSECONDS_PER_SECOND * 5); /* 5s */ ++ ++ return 0; ++} ++ + static void hct_data_uninit(HCTDevState *state) + { + if (hct_data.hct_fd) { +@@ -849,6 +1126,7 @@ static void hct_data_uninit(HCTDevState *state) + } + + memory_listener_unregister(&hct_memory_listener); ++ precopy_remove_notifier(&state->precopy_notifier); + + hct_data.init = 0; + hct_data.driver = HCT_CCP_DRV_MOD_UNINIT; +@@ -969,6 +1247,15 @@ static int hct_data_init(HCTDevState *state) + if (ret < 0) + goto unmap_pasid_memory_exit; + ++ ret = hct_get_vsock_guest_cid(state); ++ if (ret < 0) ++ error_report("get the guest_cid of vsock device fail."); ++ ++ state->precopy_notifier.notify = hct_migrate_precopy_notifier; ++ precopy_add_notifier(&state->precopy_notifier); ++ state->migrate_load_timer = NULL; ++ state->migrate_support = 1; ++ + /* perform DMA_MAP and DMA_UNMAP operations on all memories of the virtual machine. */ + memory_listener_register(&hct_memory_listener, &address_space_memory); + +@@ -1091,12 +1378,24 @@ out: + return; + } + ++static const VMStateDescription vfio_hct_vmstate = { ++ .name = "vfio-hct-dev", ++ .version_id = HCT_MIGRATE_VERSION, ++ .minimum_version_id = HCT_MIGRATE_VERSION, ++ .post_load = hct_dev_post_load, ++ .fields = (VMStateField[]) { ++ VMSTATE_UINT32(migrate_support, HCTDevState), ++ VMSTATE_END_OF_LIST() ++ } ++}; ++ + static void hct_dev_class_init(ObjectClass *klass, void *data) + { + DeviceClass *dc = DEVICE_CLASS(klass); + PCIDeviceClass *pdc = PCI_DEVICE_CLASS(klass); + + dc->desc = "HCT Device"; ++ dc->vmsd = &vfio_hct_vmstate; + device_class_set_props(dc, vfio_hct_properties); + + pdc->realize = vfio_hct_realize; +-- +2.39.3 + diff --git a/0530-hw-vfio-hct-fix-virtual-machine-paused-due-to-obtain.patch b/0530-hw-vfio-hct-fix-virtual-machine-paused-due-to-obtain.patch new file mode 100644 index 0000000..c3244d5 --- /dev/null +++ b/0530-hw-vfio-hct-fix-virtual-machine-paused-due-to-obtain.patch @@ -0,0 +1,166 @@ +From d9524210fb6087021695f8248388b259e3706c05 Mon Sep 17 00:00:00 2001 +From: Yabin Li +Date: Wed, 15 Oct 2025 22:02:31 +0800 +Subject: [PATCH] hw/vfio/hct: fix virtual machine paused due to obtain VQ + failed. + +Signed-off-by: Yabin Li +Signed-off-by: yangdepei +--- + hw/vfio/hct.c | 111 +++++++++++++++----------------------------------- + 1 file changed, 33 insertions(+), 78 deletions(-) + +diff --git a/hw/vfio/hct.c b/hw/vfio/hct.c +index febfc1c735..27c5b637ad 100644 +--- a/hw/vfio/hct.c ++++ b/hw/vfio/hct.c +@@ -377,9 +377,6 @@ static void hct_client_cleanup(hct_client_info_t *client_info); + /* @brief hct client send cmd function */ + static int hct_client_send_cmd(const char *socket_path, hct_client_info_t *client_info, enum hct_daemon_req_cmd cmd, void *req_data); + +-/* @brief hct create user shared memory function */ +-static int hct_create_user_shared_memory(const char *name, size_t size); +- + static int hct_get_sysfs_value(const char *path, int *val) + { + FILE *fp = NULL; +@@ -705,19 +702,42 @@ static int hct_api_version_check(void) + + static int hct_shared_memory_init(void) + { +- int ret = 0; ++ const char *name = HCT_GLOBAL_SHARE_SHM_NAME; ++ size_t size = HCT_SHARED_MEMORY_SIZE; ++ void *vaddr = NULL; ++ int shm_fd = -1; ++ mode_t oldmod; ++ ++ oldmod = umask(0); ++ shm_fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC, 0666); ++ umask(oldmod); ++ if (shm_fd < 0 && errno == EEXIST) ++ shm_fd = shm_open(name, O_RDWR | O_CLOEXEC, 0666); ++ if (shm_fd < 0) { ++ error_report("Failed to open file %s, errno: %d.\n", name, errno); ++ return -1; ++ } ++ ++ if (ftruncate(shm_fd, size) != 0) { ++ error_report("Failed to ftruncate file %s, errno: %d\n", name, errno); ++ close(shm_fd); ++ return -1; ++ } + +- hct_data.hct_shared_memory = mmap(NULL, hct_data.hct_shared_size, +- PROT_READ | PROT_WRITE, MAP_SHARED, +- hct_data.hct_shm_fd, 0); +- if (hct_data.hct_shared_memory == MAP_FAILED) { +- ret = -errno; ++ vaddr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0); ++ if (vaddr == MAP_FAILED) { + error_report("map hct shared memory fail\n"); +- goto out; ++ close(shm_fd); ++ return -ENOMEM; + } + +-out: +- return ret; ++ if (flock(shm_fd, LOCK_EX | LOCK_NB) == 0) ++ memset(vaddr, 0, size); ++ ++ hct_data.hct_shm_fd = shm_fd; ++ hct_data.hct_shared_size = size; ++ hct_data.hct_shared_memory = vaddr; ++ return flock(shm_fd, LOCK_SH); + } + + static void hct_listener_region_add(MemoryListener *listener, +@@ -1104,7 +1124,7 @@ static void hct_data_uninit(HCTDevState *state) + } + + if (hct_data.hct_shm_fd) { +- qemu_close(hct_data.hct_shm_fd); ++ close(hct_data.hct_shm_fd); + hct_data.hct_shm_fd = 0; + } + if (hct_data.pasid) { +@@ -1196,27 +1216,6 @@ static int hct_data_init(HCTDevState *state) + } + } + if (hct_data.init == 0) { +- hct_shr_name = HCT_GLOBAL_SHARE_SHM_PATH; +- hct_data.hct_shm_fd = qemu_open_old(hct_shr_name, O_RDWR); +- if (hct_data.hct_shm_fd < 0) { +- if (errno == 2) { +- ret = hct_create_user_shared_memory(HCT_GLOBAL_SHARE_SHM_NAME, HCT_SHARED_MEMORY_SIZE); +- if (!ret) { +- hct_data.hct_shm_fd = qemu_open_old(hct_shr_name, O_RDWR); +- if (hct_data.hct_shm_fd < 0) { +- ret = -errno; +- } +- } +- } else { +- ret = -errno; +- } +- if (ret < 0) { +- error_report("fail to open %s, errno %d.", hct_shr_name, errno); +- goto out; +- } +- } +- hct_data.hct_shared_size = HCT_SHARED_MEMORY_SIZE; +- + /* assign a page to the virtual BAR3 of each CCP. */ + ret = hct_shared_memory_init(); + if (ret) +@@ -2331,47 +2330,3 @@ static uint32_t hct_get_bit(unsigned long *bitmap, int n) + { + return ((bitmap[WORD_OFFSET(n)] & (0x1UL << BIT_OFFSET(n))) != 0); + } +- +-static int hct_create_user_shared_memory(const char *name, size_t size) +-{ +- mode_t oldmod; +- void *addr = NULL; +- int shm_fd = -1; +- int new_mem = 0; +- +- oldmod = umask(0); +- shm_fd = shm_open(name, O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC, 0666); +- umask(oldmod); +- +- if (shm_fd < 0) { +- if (errno == EEXIST) { +- error_report("Shared memory %s already exists, trying to open existing one\n", name); +- return 0; +- } else { +- error_report("Failed to create/open shared memory %s, errno %d (%s)\n", name, errno, strerror(errno)); +- return -1; +- } +- } else { +- new_mem = 1; +- } +- +- if (ftruncate(shm_fd, size) != 0) { +- error_report("Failed to set shared memory size %zu, errno %d (%s)\n", size, errno, strerror(errno)); +- close(shm_fd); +- return -1; +- } +- +- if (new_mem) { +- addr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0); +- if (addr == MAP_FAILED) { +- error_report("Failed to mmap shared memory %s, errno %d (%s)\n", name, errno, strerror(errno)); +- close(shm_fd); +- return -1; +- } +- memset(addr, 0, size); +- munmap(addr, size); +- } +- +- close(shm_fd); +- return 0; +-} +-- +2.39.3 + diff --git a/0531-hw-vfio-hct-support-abort-migration-when-hct-excepti.patch b/0531-hw-vfio-hct-support-abort-migration-when-hct-excepti.patch new file mode 100644 index 0000000..5f835d9 --- /dev/null +++ b/0531-hw-vfio-hct-support-abort-migration-when-hct-excepti.patch @@ -0,0 +1,169 @@ +From 48dc9a046f5adf72b3817aa85f4147f37b28ed7b Mon Sep 17 00:00:00 2001 +From: Yabin Li +Date: Fri, 31 Oct 2025 15:27:52 +0800 +Subject: [PATCH] hw/vfio/hct: support abort migration when hct exception + +When the string 'migrate-abort-on-error' is set, the live migration +will be aborted when it encounters an hct exception. If not set, the +live migration will continue to execute. + +Signed-off-by: Yabin Li +Signed-off-by: yangdepei +--- + hw/vfio/hct.c | 64 ++++++++++++++++++++++++++++++++++++--------------- + 1 file changed, 46 insertions(+), 18 deletions(-) + +diff --git a/hw/vfio/hct.c b/hw/vfio/hct.c +index 27c5b637ad..8d0b47d264 100644 +--- a/hw/vfio/hct.c ++++ b/hw/vfio/hct.c +@@ -317,6 +317,7 @@ typedef struct HctDevState { + int group_fd; /* vfio group fd */ + int group_id; /* vfio group id */ + int lock_fd; /* vccp flock fd for this device only */ ++ bool migrate_abort_err; + } HCTDevState; + + struct hct_dev_ctrl { +@@ -492,6 +493,7 @@ static Property vfio_hct_properties[] = { + DEFINE_PROP_STRING("sysfsdev", HCTDevState, vdev.sysfsdev), + DEFINE_PROP_STRING("dev", HCTDevState, ccp_dev_path), + DEFINE_PROP_STRING("vsock-device", HCTDevState, vsock_device), ++ DEFINE_PROP_BOOL("migrate-abort-on-error", HCTDevState, migrate_abort_err, false), + DEFINE_PROP_END_OF_LIST(), + }; + +@@ -507,7 +509,7 @@ struct VFIODeviceOps vfio_ccp_ops = { + /* create BAR2, BAR3 and BAR4 space for the virtual machine. */ + static int vfio_hct_region_mmap(HCTDevState *state) + { +- int ret; ++ int ret = 0; + int i; + struct vfio_region_info *info; + +@@ -579,7 +581,7 @@ static int hct_ccp_set_mode(HCTDevState *state) + uint32_t loops= 0; + uint32_t max_loops = 10000; + int fd; +- int ret; ++ int ret = 0; + + if (state->vdev.fd <= 0) { + error_report("fail to get device fd %d.", state->vdev.fd); +@@ -677,7 +679,7 @@ static int hct_get_ccp_index(HCTDevState *state) + static int hct_api_version_check(void) + { + struct hct_dev_ctrl ctrl; +- int ret; ++ int ret = 0; + + ctrl.op = HCT_SHARE_OP_GET_VERSION; + memcpy(ctrl.version, DEF_VERSION_STRING, sizeof(DEF_VERSION_STRING)); +@@ -747,7 +749,7 @@ static void hct_listener_region_add(MemoryListener *listener, + hwaddr iova; + Int128 llend, llsize; + void *vaddr; +- int ret; ++ int ret = 0; + + iova = REAL_HOST_PAGE_ALIGN(section->offset_within_address_space); + llend = int128_make64(section->offset_within_address_space); +@@ -793,7 +795,7 @@ static void hct_listener_region_del(MemoryListener *listener, + struct hct_dev_ctrl ctrl; + hwaddr iova; + Int128 llend, llsize; +- int ret; ++ int ret = 0; + + iova = REAL_HOST_PAGE_ALIGN(section->offset_within_address_space); + llend = int128_make64(section->offset_within_address_space); +@@ -1008,7 +1010,7 @@ static int hct_migrate_precopy_notifier(NotifierWithReturn *notifier, void *data + if (ret != 0 || msg[0] != HCT_MIG_MSG_MAGIC) { + /* Perform live migration addording to a regular virtual machine. */ + error_report("ret:%d msg[0]:0x%x, please install a newer hct.ko" +- " for the virtual machine.", ret, msg[0]); ++ " for the virtual machine.", ret, msg[0]); + state->migrate_support = 0; + ret = 0; + goto exit; +@@ -1016,10 +1018,18 @@ static int hct_migrate_precopy_notifier(NotifierWithReturn *notifier, void *data + /* We believe that the virtual machine is not ready, + * so terminate the live migration. + */ +- error_setg(pnd->errp, "%s[%u] msg[3]:0x%02x, invalid.\n", +- __func__, __LINE__, msg[3]); +- ms->state = MIGRATION_STATUS_CANCELLED; +- goto exit; ++ if (state->migrate_abort_err) { ++ error_setg(pnd->errp, "%s[%u] msg[3]:0x%02x invalid, notifier fail.\n", ++ __func__, __LINE__, msg[3]); ++ ms->state = MIGRATION_STATUS_CANCELLED; ++ goto exit; ++ } else { ++ error_report("%s[%u] msg[3]:0x%02x invalid, notifier fail.\n", ++ __func__, __LINE__, msg[3]); ++ state->migrate_support = 0; ++ ret = 0; ++ goto exit; ++ } + } + + while (++loops <= MAX_CHECK_LOOPS) { +@@ -1029,19 +1039,37 @@ static int hct_migrate_precopy_notifier(NotifierWithReturn *notifier, void *data + msg[3] = 0; + ret = hct_client_send_msg(state, (char *)msg, sizeof(msg), MAX_CONNECT_LOOPS); + if (ret != 0 || msg[0] != HCT_MIG_MSG_MAGIC) { +- error_setg(pnd->errp, "ret:%d msg[0]:0x%x, invalid.", ret, msg[0]); +- ms->state = MIGRATION_STATUS_CANCELLED; +- goto exit; ++ if (state->migrate_abort_err) { ++ error_setg(pnd->errp, "%s[%u] ret:%d msg[0]:0x%x invalid, notifier fail.\n", ++ __func__, __LINE__, ret, msg[0]); ++ ms->state = MIGRATION_STATUS_CANCELLED; ++ goto exit; ++ } else { ++ error_report("%s[%u] ret:%d msg[0]:0x%x invalid, notifier fail.\n", ++ __func__, __LINE__, ret, msg[0]); ++ state->migrate_support = 0; ++ ret = 0; ++ goto exit; ++ } + } else if (msg[3] == HCT_MIG_STATE_STOPPED) { + break; + } + sleep(1); + } + if (loops > MAX_CHECK_LOOPS) { +- error_setg(pnd->errp, "%s[%u] loops:%d > MAX_CHECK_LOOPS:%d, will cancel.\n", +- __func__, __LINE__, loops, MAX_CHECK_LOOPS); +- ms->state = MIGRATION_STATUS_CANCELLED; +- goto exit; ++ if (state->migrate_abort_err) { ++ error_setg(pnd->errp, "%s[%u] loops:%d > MAX_CHECK_LOOPS:%d," ++ " the live migration will be canceled.\n", ++ __func__, __LINE__, loops, MAX_CHECK_LOOPS); ++ ms->state = MIGRATION_STATUS_CANCELLED; ++ goto exit; ++ } else { ++ error_report("%s[%u] loops:%d > MAX_CHECK_LOOPS:%d, hct live migration fail.\n", ++ __func__, __LINE__, loops, MAX_CHECK_LOOPS); ++ state->migrate_support = 0; ++ ret = 0; ++ goto exit; ++ } + } + + info_report("%s: recieved HCT_MIG_MSG_ACK.", __func__); +@@ -1058,7 +1086,7 @@ static void hct_client_connect_timer_cb(void *opaque) + int msg[16]; + int MAX_LOOP_TIMES = 10; + static int loops = 0; +- int ret; ++ int ret = 0; + + /* [0]:magic [1]:version [2]:op [3]:sync_state */ + msg[0] = HCT_MIG_MSG_MAGIC; +-- +2.39.3 + diff --git a/qemu.spec b/qemu.spec index 832e2a3..43d2216 100644 --- a/qemu.spec +++ b/qemu.spec @@ -1,4 +1,4 @@ -%define anolis_release 35 +%define anolis_release 36 %bcond_with check %global all_system_emu_support 0 @@ -808,6 +808,15 @@ Patch0519: 0519-hw-vfio-hct-sharing-ccp-resources-between-host-and-v.patch Patch0520: 0520-hw-vfio-hct-compatible-with-hct-ko-modules-in-versio.patch Patch0521: 0521-hw-vfio-hct-fix-ccp-index-error-caused-by-uninitiali.patch Patch0522: 0522-hw-vfio-hct-remove-the-operation-of-reading-the-valu.patch +Patch0523: 0523-hw-riscv-virt-acpi-build-c-add-srat-and-slit-acpi-ta.patch +Patch0524: 0524-hw-misc-psp-support-live-migrate-for-vpsp-device.patch +Patch0525: 0525-target-i386-csv-introduce-host-data-str-option.patch +Patch0526: 0526-target-i386-csv-support-issuing-the-launch-finish-ex.patch +Patch0527: 0527-hw-vfio-hct-support-start-with-ccp-ko-driver.patch +Patch0528: 0528-hw-vfio-hct-support-vfio-pci-multiple-processes.patch +Patch0529: 0529-hw-vfio-hct-support-live-migration-function-for-virt.patch +Patch0530: 0530-hw-vfio-hct-fix-virtual-machine-paused-due-to-obtain.patch +Patch0531: 0531-hw-vfio-hct-support-abort-migration-when-hct-excepti.patch ExclusiveArch: x86_64 aarch64 loongarch64 riscv64 @@ -2372,6 +2381,11 @@ useradd -r -u 107 -g qemu -G kvm -d / -s /sbin/nologin \ %endif %changelog +* Fri Nov 14 2025 wh02252983 - 2:8.2.0-36 +- support live migrate for vpsp device +- Support issuing the LAUNCH_FINISH_EX API with host-data for CSV3 guest +- add migration support on hct device + * Thu Oct 09 2025 wh02252983 - 2:8.2.0-35 - RISC-V: ACPI: Enable AIA and update RHCT - update hct device, to support latest HCT version (HCT2.1) -- Gitee