[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[RFC PATCH v3 2/3] libxl: Allow PCI device passthrough using -device Qemu command line


  • To: xen-devel@xxxxxxxxxxxxxxxxxxxx
  • From: "Thierry Escande" <thierry.escande@xxxxxxxxxx>
  • Date: Fri, 10 Apr 2026 15:03:57 +0000
  • Authentication-results: eu.smtp.expurgate.cloud; dkim=pass header.s=mte1 header.d=mandrillapp.com header.i="@mandrillapp.com" header.h="From:Subject:To:Cc:Message-Id:In-Reply-To:References:Feedback-ID:Date:MIME-Version:Content-Type:Content-Transfer-Encoding"; dkim=pass header.s=mte1 header.d=vates.tech header.i="thierry.escande@xxxxxxxxxx" header.h="From:Subject:To:Cc:Message-Id:In-Reply-To:References:Feedback-ID:Date:MIME-Version:Content-Type:Content-Transfer-Encoding"
  • Cc: "Thierry Escande" <thierry.escande@xxxxxxxxxx>, "Anthony PERARD" <anthony.perard@xxxxxxxxxx>, "Juergen Gross" <jgross@xxxxxxxx>, "Daniel P . Berrangé" <berrange@xxxxxxxxxx>, "Marek Marczykowski-Górecki" <marmarek@xxxxxxxxxxxxxxxxxxxxxx>
  • Delivery-date: Fri, 10 Apr 2026 15:04:00 +0000
  • Feedback-id: 30504962:30504962.20260410:md
  • List-id: Xen developer discussion <xen-devel.lists.xenproject.org>

This change makes use of the new option 'hotplug' for host PCI devices
passthrough'd to the guest. If hotplug=0 is used in the pci device
configuration table, the device will be attached to the guest using the
Qemu command line as '-device xen-pci-passthrough,hostaddr=...'

The host device configuration is passed to the -device option as a json
array, just like it's done for hotplug using QMP. The json array is
created by a new internal function libxl__device_pci_get_qmp_json() that
is also used by pci_add_qmp_device_add().

Then, instead of sending the 'device_add' command, the device_add
callback is called to perform the 'query-pci' check to make sure the
passthrough'd device is present.

In the same way at shutdown, the device is not removed using QMP and
only the pci_remove_done() function is called.

As with QMP, the use of the 'hotplug=0' option honors the 'seize' option
by adding the PCI device to the assignable list if needed. This mimics
what is done in libxl__device_pci_add() with regards to seize option and
the assignable PCI device list. This allows to display a proper error
message if the device is not assignable before Qemu starts.
To do so the function pciback_dev_is_assigned() has been renamed as
libxl__pciback_dev_is_assigned() and made available internally, as well
as libxl__device_pci_assignable_add(). Also, libxl_pci_assignable() is
now exported in libxl.h and renamed as libxl_device_pci_assignable()
since its prototype looks like the other libxl_device_pci_*() APIs.

Example use:
 pci = [ "00:03.0,seize=1,hotplug=0" ]

Signed-off-by: Thierry Escande <thierry.escande@xxxxxxxxxx>
---
v2:
- Add support for YAJL json parser

v3:
- Move code block for device command line parameters creation to a
  correct place.
- Better handling of PCI device assignation check to display the correct
  error message if the device is not assignable.
---
 tools/include/libxl.h             |  1 +
 tools/libs/light/libxl_dm.c       | 85 +++++++++++++++++++++++++++++++
 tools/libs/light/libxl_internal.h |  7 +++
 tools/libs/light/libxl_pci.c      | 57 ++++++++++++++-------
 4 files changed, 132 insertions(+), 18 deletions(-)

diff --git a/tools/include/libxl.h b/tools/include/libxl.h
index 7c098edab6..efd2664a90 100644
--- a/tools/include/libxl.h
+++ b/tools/include/libxl.h
@@ -2666,6 +2666,7 @@ int libxl_device_pci_assignable_add(libxl_ctx *ctx, 
libxl_device_pci *pci, int r
 int libxl_device_pci_assignable_remove(libxl_ctx *ctx, libxl_device_pci *pci, 
int rebind);
 libxl_device_pci *libxl_device_pci_assignable_list(libxl_ctx *ctx, int *num);
 void libxl_device_pci_assignable_list_free(libxl_device_pci *list, int num);
+bool libxl_device_pci_assignable(libxl_ctx *ctx, libxl_device_pci *pci);
 
 /* CPUID handling */
 int libxl_cpuid_parse_config(libxl_cpuid_policy_list *cpuid, const char* str);
diff --git a/tools/libs/light/libxl_dm.c b/tools/libs/light/libxl_dm.c
index 511ec76a65..28e4adbd4f 100644
--- a/tools/libs/light/libxl_dm.c
+++ b/tools/libs/light/libxl_dm.c
@@ -1798,6 +1798,91 @@ static int libxl__build_device_model_args_new(libxl__gc 
*gc,
             break;
         }
 
+        if (guest_config->num_pcidevs) {
+            libxl_device_pci *pci;
+            libxl__json_object *qmp_json;
+            char *json_str;
+#ifdef HAVE_LIBJSONC
+            json_object *jso;
+            const char *buf;
+#elif defined(HAVE_LIBYAJL)
+            yajl_gen hand;
+            /* memory for 'buf' is owned by 'hand' */
+            const unsigned char *buf;
+            libxl_yajl_length len;
+#else
+#           error Missing JSON library
+#endif
+
+            for (i = 0; i < guest_config->num_pcidevs; i++) {
+                pci = &guest_config->pcidevs[i];
+
+                if (pci->hotplug)
+                    continue;
+
+                if (pci->seize && !libxl__pciback_dev_is_assigned(gc, pci)) {
+                    rc = libxl__device_pci_assignable_add(gc, pci, 1);
+                    if (rc)
+                        return rc;
+                }
+
+                if (!libxl_device_pci_assignable(libxl__gc_owner(gc), pci)) {
+                    LOGD(ERROR, guest_domid, "PCI device %x:%x:%x.%x is not 
assignable",
+                         pci->domain, pci->bus, pci->dev, pci->func);
+                    return ERROR_FAIL;
+                }
+
+                qmp_json = libxl__device_pci_get_qmp_json(gc, pci);
+
+#ifdef HAVE_LIBJSONC
+                rc = libxl__json_object_to_json_object(gc, &jso, qmp_json);
+                if (rc)
+                    return rc;
+
+                buf = json_object_to_json_string_ext(jso,
+                                                     JSON_C_TO_STRING_PLAIN);
+                if (!buf) {
+                    json_object_put(jso);
+                    return ERROR_NOMEM;
+                }
+#elif defined(HAVE_LIBYAJL)
+                hand = libxl_yajl_gen_alloc(NULL);
+                if (!hand) {
+                    return ERROR_NOMEM;
+                }
+#if HAVE_YAJL_V2
+                /* Disable beautify for data sent to QEMU */
+                yajl_gen_config(hand, yajl_gen_beautify, 0);
+#endif
+
+                rc = libxl__json_object_to_yajl_gen(gc, hand, qmp_json);
+                if (rc) {
+                    yajl_gen_free(hand);
+                    return rc;
+                }
+
+                rc = yajl_gen_get_buf(hand, &buf, &len);
+                if (rc != yajl_gen_status_ok) {
+                    yajl_gen_free(hand);
+                    return rc;
+                }
+#endif
+
+                json_str = libxl__strdup(gc, (const char *)buf);
+                if (json_str)
+                    flexarray_vappend(dm_args, "-device", json_str, NULL);
+
+#ifdef HAVE_LIBJSONC
+                json_object_put(jso);
+#elif defined(HAVE_LIBYAJL)
+                yajl_gen_free(hand);
+#endif
+
+                if (!json_str)
+                    return ERROR_NOMEM;
+            }
+        }
+
         if (state->dm_runas) {
             if (qemu_opts->have_runwith_user) {
                 flexarray_append_pair(dm_args, "-run-with",
diff --git a/tools/libs/light/libxl_internal.h 
b/tools/libs/light/libxl_internal.h
index b65e0064b9..cab2ab4526 100644
--- a/tools/libs/light/libxl_internal.h
+++ b/tools/libs/light/libxl_internal.h
@@ -1729,6 +1729,13 @@ _hidden int libxl__device_pci_setdefault(libxl__gc *gc, 
uint32_t domid,
                                          libxl_device_pci *pci, bool hotplug);
 _hidden bool libxl__is_igd_vga_passthru(libxl__gc *gc,
                                         const libxl_domain_config *d_config);
+_hidden libxl__json_object *libxl__device_pci_get_qmp_json(libxl__gc *gc,
+                                                         libxl_device_pci 
*pci);
+_hidden int libxl__pciback_dev_is_assigned(libxl__gc *gc,
+                                           libxl_device_pci *pci);
+_hidden int libxl__device_pci_assignable_add(libxl__gc *gc,
+                                             libxl_device_pci *pci,
+                                             int rebind);
 
 /* from libxl_dtdev */
 
diff --git a/tools/libs/light/libxl_pci.c b/tools/libs/light/libxl_pci.c
index 49d272d0de..07d005e71d 100644
--- a/tools/libs/light/libxl_pci.c
+++ b/tools/libs/light/libxl_pci.c
@@ -686,7 +686,7 @@ out:
     return rc;
 }
 
-static int pciback_dev_is_assigned(libxl__gc *gc, libxl_device_pci *pci)
+int libxl__pciback_dev_is_assigned(libxl__gc *gc, libxl_device_pci *pci)
 {
     char * spath;
     int rc;
@@ -755,9 +755,9 @@ static int pciback_dev_unassign(libxl__gc *gc, 
libxl_device_pci *pci)
     return 0;
 }
 
-static int libxl__device_pci_assignable_add(libxl__gc *gc,
-                                            libxl_device_pci *pci,
-                                            int rebind)
+int libxl__device_pci_assignable_add(libxl__gc *gc,
+                                     libxl_device_pci *pci,
+                                     int rebind)
 {
     libxl_ctx *ctx = libxl__gc_owner(gc);
     unsigned dom, bus, dev, func;
@@ -798,7 +798,7 @@ static int libxl__device_pci_assignable_add(libxl__gc *gc,
     }
 
     /* Check to see if it's already assigned to pciback */
-    rc = pciback_dev_is_assigned(gc, pci);
+    rc = libxl__pciback_dev_is_assigned(gc, pci);
     if ( rc < 0 ) {
         return ERROR_FAIL;
     }
@@ -913,7 +913,7 @@ static int libxl__device_pci_assignable_remove(libxl__gc 
*gc,
     }
 
     /* Unbind from pciback */
-    if ( (rc = pciback_dev_is_assigned(gc, pci)) < 0 ) {
+    if ( (rc = libxl__pciback_dev_is_assigned(gc, pci)) < 0 ) {
         return ERROR_FAIL;
     } else if ( rc ) {
         pciback_dev_unassign(gc, pci);
@@ -1098,16 +1098,10 @@ out:
     pci_add_dm_done(egc, pas, rc); /* must be last */
 }
 
-static void pci_add_qmp_device_add(libxl__egc *egc, pci_add_state *pas)
+libxl__json_object *libxl__device_pci_get_qmp_json(libxl__gc *gc,
+                                                   libxl_device_pci *pci)
 {
-    STATE_AO_GC(pas->aodev->ao);
     libxl__json_object *args = NULL;
-    int rc;
-
-    /* Convenience aliases */
-    libxl_domid domid = pas->domid;
-    libxl_device_pci *pci = &pas->pci;
-    libxl__ev_qmp *const qmp = &pas->qmp;
 
     libxl__qmp_param_add_string(gc, &args, "driver",
                                 "xen-pci-passthrough");
@@ -1134,11 +1128,30 @@ static void pci_add_qmp_device_add(libxl__egc *egc, 
pci_add_state *pas)
     if (pci->permissive)
         libxl__qmp_param_add_bool(gc, &args, "permissive", true);
 
+    return args;
+}
+
+static void pci_add_qmp_device_add(libxl__egc *egc, pci_add_state *pas)
+{
+    STATE_AO_GC(pas->aodev->ao);
+    libxl__json_object *args = NULL;
+    int rc = 0;
+
+    /* Convenience aliases */
+    libxl_domid domid = pas->domid;
+    libxl_device_pci *pci = &pas->pci;
+    libxl__ev_qmp *const qmp = &pas->qmp;
+
+    args = libxl__device_pci_get_qmp_json(gc, pci);
+
     qmp->ao = pas->aodev->ao;
     qmp->domid = domid;
     qmp->payload_fd = -1;
     qmp->callback = pci_add_qmp_device_add_cb;
-    rc = libxl__ev_qmp_send(egc, qmp, "device_add", args);
+    if (pci->hotplug)
+        rc = libxl__ev_qmp_send(egc, qmp, "device_add", args);
+    else
+        pci_add_qmp_device_add_cb(egc, qmp, NULL, 0);
     if (rc) goto out;
     return;
 
@@ -1509,7 +1522,7 @@ int libxl_device_pci_add(libxl_ctx *ctx, uint32_t domid,
     return AO_INPROGRESS;
 }
 
-static bool libxl_pci_assignable(libxl_ctx *ctx, libxl_device_pci *pci)
+bool libxl_device_pci_assignable(libxl_ctx *ctx, libxl_device_pci *pci)
 {
     libxl_device_pci *pcis;
     int num;
@@ -1572,13 +1585,13 @@ void libxl__device_pci_add(libxl__egc *egc, uint32_t 
domid,
     rc = libxl__device_pci_setdefault(gc, domid, pci, !starting);
     if (rc) goto out;
 
-    if (pci->seize && !pciback_dev_is_assigned(gc, pci)) {
+    if (pci->seize && !libxl__pciback_dev_is_assigned(gc, pci)) {
         rc = libxl__device_pci_assignable_add(gc, pci, 1);
         if ( rc )
             goto out;
     }
 
-    if (!libxl_pci_assignable(ctx, pci)) {
+    if (!libxl_device_pci_assignable(ctx, pci)) {
         LOGD(ERROR, domid, "PCI device %x:%x:%x.%x is not assignable",
              pci->domain, pci->bus, pci->dev, pci->func);
         rc = ERROR_FAIL;
@@ -1820,6 +1833,14 @@ static void do_pci_remove(libxl__egc *egc, 
pci_remove_state *prs)
     libxl_domain_type type = libxl__domain_type(gc, domid);
     libxl_device_pci *pci = &prs->pci;
     int rc, num;
+
+    /* Passthrough'd device has been passed to Qemu command line so there is
+     * no need to remove it via QMP */
+    if (!pci->hotplug) {
+        pci_remove_done(egc, prs, 0);
+        return;
+    }
+
     pcis = libxl_device_pci_list(ctx, domid, &num);
     if (!pcis) {
         rc = ERROR_FAIL;
-- 
2.53.0



--
Thierry Escande | Vates XCP-ng Developer

XCP-ng & Xen Orchestra - Vates solutions

web: https://vates.tech




 


Rackspace

Lists.xenproject.org is hosted with RackSpace, monitoring our
servers 24x7x365 and backed by RackSpace's Fanatical Support®.