[RFC PATCH v1 0/1] Thunderbolt PCIe hotplug layer

Dmitry R posted 1 patch 1 day, 10 hours ago
Patches applied successfully (tree, apply log)
git fetch https://github.com/patchew-project/qemu tags/patchew/20260710133440.2845828-1-rdmitry0911@gmail.com
Maintainers: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>, "Michael S. Tsirkin" <mst@redhat.com>, Fabiano Rosas <farosas@suse.de>, Laurent Vivier <lvivier@redhat.com>, Paolo Bonzini <pbonzini@redhat.com>
There is a newer version of this series
README.md                                   |  91 ++++
docs/specs/index.rst                        |   1 +
docs/specs/thunderbolt-pcie-hotplug.rst     |  99 ++++
hw/display/meson.build                      |   1 +
hw/display/thunderbolt-vga.c                | 275 ++++++++++
hw/pci-bridge/meson.build                   |   2 +
hw/pci-bridge/thunderbolt_pcie_pci_bridge.c | 156 ++++++
hw/pci-bridge/thunderbolt_root_port.c       | 565 ++++++++++++++++++++
tests/qtest/display-vga-test.c              |  68 +++
9 files changed, 1258 insertions(+)
create mode 100644 README.md
create mode 100644 docs/specs/thunderbolt-pcie-hotplug.rst
create mode 100644 hw/display/thunderbolt-vga.c
create mode 100644 hw/pci-bridge/thunderbolt_pcie_pci_bridge.c
create mode 100644 hw/pci-bridge/thunderbolt_root_port.c
[RFC PATCH v1 0/1] Thunderbolt PCIe hotplug layer
Posted by Dmitry R 1 day, 10 hours ago
This RFC adds a Thunderbolt-flavoured PCIe hotplug layer for QEMU.

The main goal is hotplug and hot-unplug of real and virtual devices in
guests where direct hotplug for those devices is not supported or not
usable.  macOS is the main validation target for this path: the guest can
observe endpoints through a guest-visible Thunderbolt layer even when direct
PCIe hotplug for the same endpoint is not usable.

The series intentionally keeps regular QEMU PCI endpoint devices intact.
Devices behind the new layer remain normal QEMU PCI devices; the
Thunderbolt root port controls guest-visible link presence, publishes ACPI
properties, and emits PCIe hotplug notifications.

The patch adds:

* thunderbolt-root-port, creating a thunderbolt-bus secondary bus.
* thunderbolt-pcie-pci-bridge for tunnelled PCI endpoints.
* thunderbolt-vga, a separate stdvga-compatible hotpluggable endpoint.
* README/specs documentation with VGA, USB storage, and NVMe examples.
* qtest coverage for the Thunderbolt VGA hotplug path.

The regular QEMU VGA device is not changed and remains non-hotpluggable.
The Thunderbolt display path uses the separate thunderbolt-vga device type.

Runtime validation performed so far:

* macOS guest booted from QCOW2 overlays over immutable base disk images.
* Thunderbolt VGA hotplug/removal while the standard boot VGA remains.
* NVMe hotplug/removal with guest driver instance checks.
* USB controller hotplug, USB storage mount/write/eject, controller removal,
  replug, and data persistence verification.

Build/test validation:

* ninja -C build qemu-system-x86_64 tests/qtest/display-vga-test
* meson test -C build qtest-x86_64/display-vga-test --print-errorlogs
* scripts/checkpatch.pl --no-tree --no-signoff on the full diff:
  0 errors, with expected warnings for MIT inheritance in thunderbolt-vga.c
  and possible MAINTAINERS coverage.

Open questions:

* Whether this should be split into smaller review patches after the overall
  Thunderbolt modelling direction is accepted.
* Which MAINTAINERS entry should own the Thunderbolt PCIe layer.
* Whether the guest-visible properties are sufficient for non-macOS guests
  that may also benefit from Thunderbolt-style tunnelling.

Dmitry R (1):
  hw/pci-bridge: add Thunderbolt PCIe hotplug layer

 README.md                                   |  91 ++++
 docs/specs/index.rst                        |   1 +
 docs/specs/thunderbolt-pcie-hotplug.rst     |  99 ++++
 hw/display/meson.build                      |   1 +
 hw/display/thunderbolt-vga.c                | 275 ++++++++++
 hw/pci-bridge/meson.build                   |   2 +
 hw/pci-bridge/thunderbolt_pcie_pci_bridge.c | 156 ++++++
 hw/pci-bridge/thunderbolt_root_port.c       | 565 ++++++++++++++++++++
 tests/qtest/display-vga-test.c              |  68 +++
 9 files changed, 1258 insertions(+)
 create mode 100644 README.md
 create mode 100644 docs/specs/thunderbolt-pcie-hotplug.rst
 create mode 100644 hw/display/thunderbolt-vga.c
 create mode 100644 hw/pci-bridge/thunderbolt_pcie_pci_bridge.c
 create mode 100644 hw/pci-bridge/thunderbolt_root_port.c

-- 
2.47.3
[RFC PATCH v2 0/5] Thunderbolt PCIe hotplug layer
Posted by Dmitry R 14 hours ago
This RFC adds a Thunderbolt-flavoured PCIe hotplug layer for QEMU.

The main goal is hotplug and hot-unplug of real and virtual devices in
guests where direct hotplug for those devices is not supported or not
usable.  macOS is the main validation target for this path: the guest can
observe endpoints through a guest-visible Thunderbolt layer even when direct
PCIe hotplug for the same endpoint is not usable.

In v2, the series is split so the modelling part is explicit:

* patch 1 adds the Thunderbolt root port and thunderbolt-bus.  This is the
  core Thunderbolt PCIe model: endpoint devices remain normal QEMU PCI
  devices, while the root port controls guest-visible link presence and PCIe
  hotplug signalling.
* patch 2 adds the Thunderbolt PCIe-to-PCI bridge for tunnelled PCI
  endpoints.
* patch 3 adds a separate thunderbolt-vga endpoint for display hotplug tests.
  The regular QEMU VGA device remains untouched and non-hotpluggable.
* patch 4 adds qtest coverage.
* patch 5 adds documentation and MAINTAINERS coverage.

I added myself as reviewer for the Thunderbolt PCIe files.  The files still
match the existing PCI area through their paths, so PCI maintainership is
inherited by normal get_maintainer.pl matching.

Runtime validation performed so far:

* macOS guest booted from QCOW2 overlays over immutable base disk images.
* Thunderbolt VGA hotplug/removal while the standard boot VGA remains.
* NVMe hotplug/removal with guest driver instance checks.
* USB controller hotplug, USB storage mount/write/eject, controller removal,
  replug, and data persistence verification.

Build/test validation:

* ninja -C build qemu-system-x86_64 tests/qtest/display-vga-test
* meson test -C build qtest-x86_64/display-vga-test --print-errorlogs
* scripts/checkpatch.pl --no-tree --no-signoff on the full diff:
  0 errors, with one expected warning for MIT inheritance in thunderbolt-vga.c.

Changes since v1:

* Split the single large patch into five patches.
* Clarified that the modelling piece is the Thunderbolt root port/bus.
* Added a MAINTAINERS entry with Dmitry R as reviewer.

Dmitry R (5):
  hw/pci-bridge: add Thunderbolt root port
  hw/pci-bridge: add Thunderbolt PCIe-to-PCI bridge
  hw/display: add Thunderbolt VGA endpoint
  tests/qtest: cover Thunderbolt VGA hotplug
  docs: document Thunderbolt PCIe hotplug layer

 MAINTAINERS                                 |   7 +
 README.md                                   |  91 ++++
 docs/specs/index.rst                        |   1 +
 docs/specs/thunderbolt-pcie-hotplug.rst     |  99 ++++
 hw/display/meson.build                      |   1 +
 hw/display/thunderbolt-vga.c                | 275 ++++++++++
 hw/pci-bridge/meson.build                   |   2 +
 hw/pci-bridge/thunderbolt_pcie_pci_bridge.c | 156 ++++++
 hw/pci-bridge/thunderbolt_root_port.c       | 565 ++++++++++++++++++++
 tests/qtest/display-vga-test.c              |  68 +++
 10 files changed, 1265 insertions(+)
 create mode 100644 README.md
 create mode 100644 docs/specs/thunderbolt-pcie-hotplug.rst
 create mode 100644 hw/display/thunderbolt-vga.c
 create mode 100644 hw/pci-bridge/thunderbolt_pcie_pci_bridge.c
 create mode 100644 hw/pci-bridge/thunderbolt_root_port.c

-- 
2.47.3
[RFC PATCH v2 1/5] hw/pci-bridge: add Thunderbolt root port
Posted by Dmitry R 14 hours ago
Add thunderbolt-root-port and its secondary thunderbolt-bus type. This is
the core Thunderbolt PCIe modelling piece. Devices behind the root port
remain normal QEMU PCI devices, while the root port controls
guest-visible link presence and hotplug signalling.

The connected QOM property allows the whole Thunderbolt port to be
disconnected or reconnected without changing endpoint device
implementations.

Signed-off-by: Dmitry R <rdmitry0911@gmail.com>
---
 hw/pci-bridge/meson.build             |   1 +
 hw/pci-bridge/thunderbolt_root_port.c | 565 ++++++++++++++++++++++++++
 2 files changed, 566 insertions(+)
 create mode 100644 hw/pci-bridge/thunderbolt_root_port.c

diff --git a/hw/pci-bridge/meson.build b/hw/pci-bridge/meson.build
index 2e0eb0d233..fd0698fb33 100644
--- a/hw/pci-bridge/meson.build
+++ b/hw/pci-bridge/meson.build
@@ -3,6 +3,7 @@ pci_ss.add(when: 'CONFIG_PCI_BRIDGE', if_true: files('pci_bridge_dev.c'))
 pci_ss.add(when: 'CONFIG_I82801B11', if_true: files('i82801b11.c'))
 pci_ss.add(when: 'CONFIG_IOH3420', if_true: files('ioh3420.c'))
 pci_ss.add(when: 'CONFIG_PCIE_PORT', if_true: files('pcie_root_port.c', 'gen_pcie_root_port.c'))
+pci_ss.add(when: 'CONFIG_PCIE_PORT', if_true: files('thunderbolt_root_port.c'))
 pci_ss.add(when: 'CONFIG_PCIE_PCI_BRIDGE', if_true: files('pcie_pci_bridge.c'))
 pci_ss.add(when: 'CONFIG_PXB', if_true: files('pci_expander_bridge.c'),
                                if_false: files('pci_expander_bridge_stubs.c'))
diff --git a/hw/pci-bridge/thunderbolt_root_port.c b/hw/pci-bridge/thunderbolt_root_port.c
new file mode 100644
index 0000000000..625a5dba7e
--- /dev/null
+++ b/hw/pci-bridge/thunderbolt_root_port.c
@@ -0,0 +1,565 @@
+/*
+ * Thunderbolt PCI Express Root Port
+ *
+ * This root port creates a Thunderbolt-flavoured PCIe bus. Devices on that
+ * bus remain regular QEMU PCI devices; the Thunderbolt layer controls their
+ * guest-visible connection state.
+ *
+ * Copyright (C) 2026
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "qemu/module.h"
+#include "hw/acpi/aml-build.h"
+#include "hw/acpi/pci.h"
+#include "hw/acpi/acpi_aml_interface.h"
+#include "hw/core/hotplug.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/core/qdev-properties-system.h"
+#include "hw/pci/msix.h"
+#include "hw/pci/pci_bridge.h"
+#include "hw/pci/pcie_port.h"
+#include "migration/vmstate.h"
+#include "system/system.h"
+#include "qom/object.h"
+
+#define TYPE_THUNDERBOLT_ROOT_PORT "thunderbolt-root-port"
+#define TYPE_THUNDERBOLT_BUS "thunderbolt-bus"
+
+OBJECT_DECLARE_SIMPLE_TYPE(ThunderboltRootPort, THUNDERBOLT_ROOT_PORT)
+
+#define THUNDERBOLT_ROOT_PORT_AER_OFFSET 0x100
+#define THUNDERBOLT_ROOT_PORT_ACS_OFFSET \
+    (THUNDERBOLT_ROOT_PORT_AER_OFFSET + PCI_ERR_SIZEOF)
+#define THUNDERBOLT_ROOT_PORT_MSIX_NR_VECTOR 1
+#define THUNDERBOLT_ROOT_DEFAULT_IO_RANGE 4096
+
+struct ThunderboltRootPort {
+    PCIESlot parent_obj;
+
+    bool migrate_msix;
+    PCIResReserve res_reserve;
+    bool connected;
+    DeviceListener listener;
+    Notifier machine_done;
+};
+
+static void thunderbolt_root_port_set_presence(PCIDevice *dev, bool present,
+                                               bool notify);
+static void thunderbolt_root_port_sync_connected(ThunderboltRootPort *trp,
+                                                 bool notify);
+
+static Aml *build_device_properties_dsd(Aml *properties)
+{
+    Aml *dsd;
+
+    dsd = aml_package(2);
+    aml_append(dsd, aml_touuid("DAFFD814-6EBA-4D8C-8A91-BC9BBF4AA301"));
+    aml_append(dsd, properties);
+
+    return aml_name_decl("_DSD", dsd);
+}
+
+static void append_bool_property(Aml *properties, const char *name)
+{
+    Aml *property = aml_package(2);
+
+    aml_append(property, aml_string("%s", name));
+    aml_append(property, aml_int(1));
+    aml_append(properties, property);
+}
+
+static void build_thunderbolt_root_port_dsd(Aml *scope)
+{
+    Aml *properties;
+
+    properties = aml_package(2);
+    append_bool_property(properties, "PCI-Thunderbolt");
+    append_bool_property(properties, "pci-supports-link-change");
+
+    aml_append(scope, build_device_properties_dsd(properties));
+}
+
+static void build_thunderbolt_root_port_aml(AcpiDevAmlIf *adev, Aml *scope)
+{
+    build_thunderbolt_root_port_dsd(scope);
+    build_pci_bridge_aml(adev, scope);
+}
+
+static bool thunderbolt_root_port_get_connected(Object *obj, Error **errp)
+{
+    return THUNDERBOLT_ROOT_PORT(obj)->connected;
+}
+
+static void thunderbolt_root_port_set_connected(Object *obj, bool value,
+                                                Error **errp)
+{
+    ThunderboltRootPort *trp = THUNDERBOLT_ROOT_PORT(obj);
+
+    if (trp->connected == value) {
+        return;
+    }
+
+    trp->connected = value;
+
+    if (!qdev_is_realized(DEVICE(obj))) {
+        return;
+    }
+
+    thunderbolt_root_port_sync_connected(trp, true);
+}
+
+static void thunderbolt_root_port_sync_connected(ThunderboltRootPort *trp,
+                                                 bool notify)
+{
+    PCIDevice *rp = PCI_DEVICE(trp);
+    PCIBus *bus = pci_bridge_get_sec_bus(PCI_BRIDGE(rp));
+    bool present = false;
+    int devfn;
+
+    for (devfn = 0; devfn < PCI_DEVFN_MAX; devfn++) {
+        PCIDevice *child = bus->devices[devfn];
+
+        if (!child) {
+            continue;
+        }
+
+        present = true;
+        if (trp->connected) {
+            pci_set_enabled(child, true);
+        }
+    }
+
+    if (trp->connected && present) {
+        thunderbolt_root_port_set_presence(rp, true, notify);
+    } else {
+        thunderbolt_root_port_set_presence(rp, false, notify);
+        for (devfn = 0; devfn < PCI_DEVFN_MAX; devfn++) {
+            PCIDevice *child = bus->devices[devfn];
+
+            if (child) {
+                pci_set_enabled(child, false);
+            }
+        }
+    }
+}
+
+static void thunderbolt_root_port_machine_done(Notifier *notifier, void *data)
+{
+    ThunderboltRootPort *trp = container_of(notifier, ThunderboltRootPort,
+                                            machine_done);
+
+    thunderbolt_root_port_sync_connected(trp, false);
+}
+
+static void thunderbolt_root_port_device_realize(DeviceListener *listener,
+                                                 DeviceState *dev)
+{
+    ThunderboltRootPort *trp = container_of(listener, ThunderboltRootPort,
+                                           listener);
+    PCIBus *bus;
+
+    if (!dev->parent_bus ||
+        !object_dynamic_cast(OBJECT(dev), TYPE_PCI_DEVICE) ||
+        !object_dynamic_cast(OBJECT(dev->parent_bus), TYPE_THUNDERBOLT_BUS)) {
+        return;
+    }
+
+    bus = PCI_BUS(dev->parent_bus);
+    if (bus->parent_dev != PCI_DEVICE(trp)) {
+        return;
+    }
+
+    thunderbolt_root_port_sync_connected(trp, false);
+}
+
+static void thunderbolt_root_port_instance_init(Object *obj)
+{
+    ThunderboltRootPort *trp = THUNDERBOLT_ROOT_PORT(obj);
+
+    trp->connected = true;
+    object_property_add_bool(obj, "connected",
+                             thunderbolt_root_port_get_connected,
+                             thunderbolt_root_port_set_connected);
+}
+
+static void thunderbolt_root_port_set_presence(PCIDevice *dev, bool present,
+                                               bool notify)
+{
+    uint8_t *exp_cap = dev->config + dev->exp.exp_cap;
+    uint32_t lnkcap = pci_get_long(exp_cap + PCI_EXP_LNKCAP);
+
+    if (present) {
+        pcie_cap_slot_enable_power(dev);
+        pci_word_test_and_set_mask(exp_cap + PCI_EXP_SLTSTA,
+                                   PCI_EXP_SLTSTA_PDS);
+        if (dev->cap_present & QEMU_PCIE_LNKSTA_DLLLA ||
+            (lnkcap & PCI_EXP_LNKCAP_DLLLARC)) {
+            pci_word_test_and_set_mask(exp_cap + PCI_EXP_LNKSTA,
+                                       PCI_EXP_LNKSTA_DLLLA);
+        }
+    } else {
+        pci_word_test_and_clear_mask(exp_cap + PCI_EXP_SLTSTA,
+                                     PCI_EXP_SLTSTA_PDS);
+        if (dev->cap_present & QEMU_PCIE_LNKSTA_DLLLA ||
+            (lnkcap & PCI_EXP_LNKCAP_DLLLARC)) {
+            pci_word_test_and_clear_mask(exp_cap + PCI_EXP_LNKSTA,
+                                         PCI_EXP_LNKSTA_DLLLA);
+        }
+    }
+
+    if (notify) {
+        pci_word_test_and_set_mask(exp_cap + PCI_EXP_SLTSTA,
+                                   PCI_EXP_SLTSTA_PDC);
+        pcie_cap_slot_push_attention_button(dev);
+    }
+}
+
+static void thunderbolt_root_port_pre_plug(HotplugHandler *hotplug_dev,
+                                           DeviceState *dev, Error **errp)
+{
+    pcie_cap_slot_pre_plug_cb(hotplug_dev, dev, errp);
+}
+
+static void thunderbolt_root_port_plug(HotplugHandler *hotplug_dev,
+                                       DeviceState *dev, Error **errp)
+{
+    ThunderboltRootPort *trp = THUNDERBOLT_ROOT_PORT(hotplug_dev);
+
+    if (trp->connected) {
+        pcie_cap_slot_plug_cb(hotplug_dev, dev, errp);
+        return;
+    }
+
+    pci_set_enabled(PCI_DEVICE(dev), false);
+    thunderbolt_root_port_set_presence(PCI_DEVICE(hotplug_dev), false, false);
+}
+
+static void thunderbolt_root_port_unplug_request(HotplugHandler *hotplug_dev,
+                                                 DeviceState *dev,
+                                                 Error **errp)
+{
+    pcie_cap_slot_unplug_request_cb(hotplug_dev, dev, errp);
+}
+
+static void thunderbolt_root_port_unplug(HotplugHandler *hotplug_dev,
+                                         DeviceState *dev, Error **errp)
+{
+    pcie_cap_slot_unplug_cb(hotplug_dev, dev, errp);
+}
+
+static uint8_t thunderbolt_root_port_aer_vector(const PCIDevice *dev)
+{
+    return 0;
+}
+
+static void thunderbolt_root_port_aer_vector_update(PCIDevice *dev)
+{
+    PCIERootPortClass *rpc = PCIE_ROOT_PORT_GET_CLASS(dev);
+
+    if (rpc->aer_vector) {
+        pcie_aer_root_set_vector(dev, rpc->aer_vector(dev));
+    }
+}
+
+static int thunderbolt_root_port_interrupts_init(PCIDevice *dev, Error **errp)
+{
+    int rc;
+
+    rc = msix_init_exclusive_bar(dev, THUNDERBOLT_ROOT_PORT_MSIX_NR_VECTOR,
+                                 0, errp);
+    if (rc < 0) {
+        assert(rc == -ENOTSUP);
+    } else {
+        msix_vector_use(dev, 0);
+    }
+
+    return rc;
+}
+
+static void thunderbolt_root_port_interrupts_uninit(PCIDevice *dev)
+{
+    msix_uninit_exclusive_bar(dev);
+}
+
+static void thunderbolt_root_port_write_config(PCIDevice *dev,
+                                               uint32_t address,
+                                               uint32_t val, int len)
+{
+    uint32_t root_cmd =
+        pci_get_long(dev->config + dev->exp.aer_cap + PCI_ERR_ROOT_COMMAND);
+    uint16_t slt_ctl;
+    uint16_t slt_sta;
+
+    pcie_cap_slot_get(dev, &slt_ctl, &slt_sta);
+
+    pci_bridge_write_config(dev, address, val, len);
+    thunderbolt_root_port_aer_vector_update(dev);
+    pcie_cap_slot_write_config(dev, slt_ctl, slt_sta, address, val, len);
+    pcie_aer_write_config(dev, address, val, len);
+    pcie_aer_root_write_config(dev, address, val, len, root_cmd);
+}
+
+static void thunderbolt_root_port_reset_hold(Object *obj, ResetType type)
+{
+    ThunderboltRootPort *trp = THUNDERBOLT_ROOT_PORT(obj);
+    PCIDevice *dev = PCI_DEVICE(obj);
+    DeviceState *qdev = DEVICE(obj);
+
+    thunderbolt_root_port_aer_vector_update(dev);
+    pcie_cap_root_reset(dev);
+    pcie_cap_deverr_reset(dev);
+    pcie_cap_slot_reset(dev);
+    pcie_cap_arifwd_reset(dev);
+    pcie_acs_reset(dev);
+    pcie_aer_root_reset(dev);
+    pci_bridge_reset(qdev);
+    pci_bridge_disable_base_limit(dev);
+    thunderbolt_root_port_sync_connected(trp, false);
+}
+
+static void thunderbolt_root_port_realize_pci(PCIDevice *dev, Error **errp)
+{
+    PCIEPort *p = PCIE_PORT(dev);
+    PCIESlot *s = PCIE_SLOT(dev);
+    PCIDeviceClass *dc = PCI_DEVICE_GET_CLASS(dev);
+    PCIERootPortClass *rpc = PCIE_ROOT_PORT_GET_CLASS(dev);
+    int rc;
+
+    pci_config_set_interrupt_pin(dev->config, 1);
+    pci_bridge_initfn(dev, TYPE_THUNDERBOLT_BUS);
+    pcie_port_init_reg(dev);
+
+    rc = pci_bridge_ssvid_init(dev, rpc->ssvid_offset, dc->vendor_id,
+                               rpc->ssid, errp);
+    if (rc < 0) {
+        error_append_hint(errp, "Can't init SSV ID, error %d\n", rc);
+        goto err_bridge;
+    }
+
+    if (rpc->interrupts_init) {
+        rc = rpc->interrupts_init(dev, errp);
+        if (rc < 0) {
+            goto err_bridge;
+        }
+    }
+
+    rc = pcie_cap_init(dev, rpc->exp_offset, PCI_EXP_TYPE_ROOT_PORT,
+                       p->port, errp);
+    if (rc < 0) {
+        error_append_hint(errp, "Can't add Root Port capability, "
+                          "error %d\n", rc);
+        goto err_int;
+    }
+
+    pcie_cap_arifwd_init(dev);
+    pcie_cap_deverr_init(dev);
+    pcie_cap_slot_init(dev, s);
+    qbus_set_hotplug_handler(BUS(pci_bridge_get_sec_bus(PCI_BRIDGE(dev))),
+                             OBJECT(dev));
+    pcie_cap_root_init(dev);
+
+    pcie_chassis_create(s->chassis);
+    rc = pcie_chassis_add_slot(s);
+    if (rc < 0) {
+        error_setg(errp, "Can't add chassis slot, error %d", rc);
+        goto err_pcie_cap;
+    }
+
+    rc = pcie_aer_init(dev, PCI_ERR_VER, rpc->aer_offset,
+                       PCI_ERR_SIZEOF, errp);
+    if (rc < 0) {
+        goto err;
+    }
+    pcie_aer_root_init(dev);
+    thunderbolt_root_port_aer_vector_update(dev);
+
+    if (rpc->acs_offset) {
+        pcie_acs_init(dev, rpc->acs_offset);
+    }
+
+    THUNDERBOLT_ROOT_PORT(dev)->listener.realize =
+        thunderbolt_root_port_device_realize;
+    device_listener_register(&THUNDERBOLT_ROOT_PORT(dev)->listener);
+
+    THUNDERBOLT_ROOT_PORT(dev)->machine_done.notify =
+        thunderbolt_root_port_machine_done;
+    qemu_add_machine_init_done_notifier(
+        &THUNDERBOLT_ROOT_PORT(dev)->machine_done);
+
+    return;
+
+err:
+    pcie_chassis_del_slot(s);
+err_pcie_cap:
+    pcie_cap_exit(dev);
+err_int:
+    if (rpc->interrupts_uninit) {
+        rpc->interrupts_uninit(dev);
+    }
+err_bridge:
+    pci_bridge_exitfn(dev);
+}
+
+static void thunderbolt_root_port_exit(PCIDevice *dev)
+{
+    PCIERootPortClass *rpc = PCIE_ROOT_PORT_GET_CLASS(dev);
+    PCIESlot *s = PCIE_SLOT(dev);
+
+    pcie_aer_exit(dev);
+    pcie_chassis_del_slot(s);
+    pcie_cap_exit(dev);
+    if (rpc->interrupts_uninit) {
+        rpc->interrupts_uninit(dev);
+    }
+    device_listener_unregister(&THUNDERBOLT_ROOT_PORT(dev)->listener);
+    qemu_remove_machine_init_done_notifier(
+        &THUNDERBOLT_ROOT_PORT(dev)->machine_done);
+    pci_bridge_exitfn(dev);
+}
+
+static void thunderbolt_root_port_realize(DeviceState *dev, Error **errp)
+{
+    ThunderboltRootPort *trp = THUNDERBOLT_ROOT_PORT(dev);
+    PCIESlot *slot = PCIE_SLOT(dev);
+    PCIERootPortClass *rpc = PCIE_ROOT_PORT_GET_CLASS(dev);
+    Error *local_err = NULL;
+    int rc;
+
+    rpc->parent_realize(dev, &local_err);
+    if (local_err) {
+        error_propagate(errp, local_err);
+        return;
+    }
+
+    if (slot->hide_native_hotplug_cap &&
+        trp->res_reserve.io == (uint64_t)-1 && slot->hotplug) {
+        trp->res_reserve.io = THUNDERBOLT_ROOT_DEFAULT_IO_RANGE;
+    }
+
+    rc = pci_bridge_qemu_reserve_cap_init(PCI_DEVICE(dev), 0,
+                                          trp->res_reserve, errp);
+    if (rc < 0) {
+        rpc->parent_class.exit(PCI_DEVICE(dev));
+        return;
+    }
+
+    if (!trp->res_reserve.io) {
+        PCIDevice *pdev = PCI_DEVICE(dev);
+
+        pci_word_test_and_clear_mask(pdev->wmask + PCI_COMMAND,
+                                     PCI_COMMAND_IO);
+        pdev->wmask[PCI_IO_BASE] = 0;
+        pdev->wmask[PCI_IO_LIMIT] = 0;
+    }
+}
+
+static bool thunderbolt_root_port_test_migrate_msix(void *opaque,
+                                                    int version_id)
+{
+    ThunderboltRootPort *trp = opaque;
+
+    return trp->migrate_msix;
+}
+
+static const VMStateDescription vmstate_thunderbolt_root_port = {
+    .name = "thunderbolt-root-port",
+    .priority = MIG_PRI_PCI_BUS,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .post_load = pcie_cap_slot_post_load,
+    .fields = (const VMStateField[]) {
+        VMSTATE_PCI_DEVICE(parent_obj.parent_obj.parent_obj.parent_obj,
+                           ThunderboltRootPort),
+        VMSTATE_STRUCT(parent_obj.parent_obj.parent_obj.parent_obj.exp.aer_log,
+                       ThunderboltRootPort, 0, vmstate_pcie_aer_log,
+                       PCIEAERLog),
+        VMSTATE_MSIX_TEST(parent_obj.parent_obj.parent_obj.parent_obj,
+                          ThunderboltRootPort,
+                          thunderbolt_root_port_test_migrate_msix),
+        VMSTATE_BOOL(connected, ThunderboltRootPort),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static const Property thunderbolt_root_port_props[] = {
+    DEFINE_PROP_BOOL("x-migrate-msix", ThunderboltRootPort,
+                     migrate_msix, true),
+    DEFINE_PROP_UINT32("bus-reserve", ThunderboltRootPort,
+                       res_reserve.bus, -1),
+    DEFINE_PROP_SIZE("io-reserve", ThunderboltRootPort,
+                     res_reserve.io, -1),
+    DEFINE_PROP_SIZE("mem-reserve", ThunderboltRootPort,
+                     res_reserve.mem_non_pref, -1),
+    DEFINE_PROP_SIZE("pref32-reserve", ThunderboltRootPort,
+                     res_reserve.mem_pref_32, -1),
+    DEFINE_PROP_SIZE("pref64-reserve", ThunderboltRootPort,
+                     res_reserve.mem_pref_64, -1),
+    DEFINE_PROP_PCIE_LINK_SPEED("x-speed", PCIESlot,
+                                speed, PCIE_LINK_SPEED_16),
+    DEFINE_PROP_PCIE_LINK_WIDTH("x-width", PCIESlot,
+                                width, PCIE_LINK_WIDTH_32),
+};
+
+static void thunderbolt_root_port_class_init(ObjectClass *klass,
+                                             const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+    PCIERootPortClass *rpc = PCIE_ROOT_PORT_CLASS(klass);
+    AcpiDevAmlIfClass *adevc = ACPI_DEV_AML_IF_CLASS(klass);
+    ResettableClass *rc = RESETTABLE_CLASS(klass);
+    HotplugHandlerClass *hc = HOTPLUG_HANDLER_CLASS(klass);
+
+    k->vendor_id = PCI_VENDOR_ID_REDHAT;
+    k->device_id = PCI_DEVICE_ID_REDHAT_PCIE_RP;
+    k->config_write = thunderbolt_root_port_write_config;
+    k->realize = thunderbolt_root_port_realize_pci;
+    k->exit = thunderbolt_root_port_exit;
+    dc->desc = "Thunderbolt PCI Express Root Port";
+    dc->vmsd = &vmstate_thunderbolt_root_port;
+    device_class_set_props(dc, thunderbolt_root_port_props);
+    device_class_set_parent_realize(dc, thunderbolt_root_port_realize,
+                                    &rpc->parent_realize);
+    set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories);
+    rc->phases.hold = thunderbolt_root_port_reset_hold;
+
+    hc->pre_plug = thunderbolt_root_port_pre_plug;
+    hc->plug = thunderbolt_root_port_plug;
+    hc->unplug = thunderbolt_root_port_unplug;
+    hc->unplug_request = thunderbolt_root_port_unplug_request;
+
+    rpc->aer_vector = thunderbolt_root_port_aer_vector;
+    rpc->interrupts_init = thunderbolt_root_port_interrupts_init;
+    rpc->interrupts_uninit = thunderbolt_root_port_interrupts_uninit;
+    rpc->aer_offset = THUNDERBOLT_ROOT_PORT_AER_OFFSET;
+    rpc->acs_offset = THUNDERBOLT_ROOT_PORT_ACS_OFFSET;
+    adevc->build_dev_aml = build_thunderbolt_root_port_aml;
+}
+
+static const TypeInfo thunderbolt_root_port_info = {
+    .name          = TYPE_THUNDERBOLT_ROOT_PORT,
+    .parent        = TYPE_PCIE_ROOT_PORT,
+    .instance_size = sizeof(ThunderboltRootPort),
+    .instance_init = thunderbolt_root_port_instance_init,
+    .class_init    = thunderbolt_root_port_class_init,
+    .interfaces = (const InterfaceInfo[]) {
+        { TYPE_ACPI_DEV_AML_IF },
+        { },
+    },
+};
+
+static const TypeInfo thunderbolt_bus_info = {
+    .name   = TYPE_THUNDERBOLT_BUS,
+    .parent = TYPE_PCIE_BUS,
+};
+
+static void thunderbolt_root_port_register_types(void)
+{
+    type_register_static(&thunderbolt_bus_info);
+    type_register_static(&thunderbolt_root_port_info);
+}
+
+type_init(thunderbolt_root_port_register_types)
-- 
2.47.3
[RFC PATCH v2 2/5] hw/pci-bridge: add Thunderbolt PCIe-to-PCI bridge
Posted by Dmitry R 14 hours ago
Add thunderbolt-pcie-pci-bridge for PCI endpoints tunnelled through the
Thunderbolt PCIe layer. The bridge exposes Thunderbolt-related ACPI
properties while leaving downstream PCI devices as regular QEMU devices.

Signed-off-by: Dmitry R <rdmitry0911@gmail.com>
---
 hw/pci-bridge/meson.build                   |   1 +
 hw/pci-bridge/thunderbolt_pcie_pci_bridge.c | 156 ++++++++++++++++++++
 2 files changed, 157 insertions(+)
 create mode 100644 hw/pci-bridge/thunderbolt_pcie_pci_bridge.c

diff --git a/hw/pci-bridge/meson.build b/hw/pci-bridge/meson.build
index fd0698fb33..8bb58ec84c 100644
--- a/hw/pci-bridge/meson.build
+++ b/hw/pci-bridge/meson.build
@@ -5,6 +5,7 @@ pci_ss.add(when: 'CONFIG_IOH3420', if_true: files('ioh3420.c'))
 pci_ss.add(when: 'CONFIG_PCIE_PORT', if_true: files('pcie_root_port.c', 'gen_pcie_root_port.c'))
 pci_ss.add(when: 'CONFIG_PCIE_PORT', if_true: files('thunderbolt_root_port.c'))
 pci_ss.add(when: 'CONFIG_PCIE_PCI_BRIDGE', if_true: files('pcie_pci_bridge.c'))
+pci_ss.add(when: 'CONFIG_PCIE_PCI_BRIDGE', if_true: files('thunderbolt_pcie_pci_bridge.c'))
 pci_ss.add(when: 'CONFIG_PXB', if_true: files('pci_expander_bridge.c'),
                                if_false: files('pci_expander_bridge_stubs.c'))
 pci_ss.add(when: 'CONFIG_XIO3130', if_true: files('xio3130_upstream.c', 'xio3130_downstream.c'))
diff --git a/hw/pci-bridge/thunderbolt_pcie_pci_bridge.c b/hw/pci-bridge/thunderbolt_pcie_pci_bridge.c
new file mode 100644
index 0000000000..afbf4871f4
--- /dev/null
+++ b/hw/pci-bridge/thunderbolt_pcie_pci_bridge.c
@@ -0,0 +1,156 @@
+/*
+ * Thunderbolt PCI Express to PCI Bridge
+ *
+ * This is the regular generic PCIe-to-PCI bridge used as a Thunderbolt PCIe
+ * tunnel endpoint. Downstream PCI devices are described normally so existing
+ * macOS drivers that are not IOPCITunnelCompatible can still bind to them.
+ *
+ * Copyright (C) 2026
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "qemu/module.h"
+#include "hw/acpi/aml-build.h"
+#include "hw/acpi/pcihp.h"
+#include "hw/acpi/acpi_aml_interface.h"
+#include "hw/pci/pci.h"
+#include "hw/pci/pci_bridge.h"
+#include "qom/object.h"
+
+#define TYPE_THUNDERBOLT_PCIE_PCI_BRIDGE "thunderbolt-pcie-pci-bridge"
+
+static Aml *build_device_properties_dsd(Aml *properties)
+{
+    Aml *dsd;
+
+    dsd = aml_package(2);
+    aml_append(dsd, aml_touuid("DAFFD814-6EBA-4D8C-8A91-BC9BBF4AA301"));
+    aml_append(dsd, properties);
+
+    return aml_name_decl("_DSD", dsd);
+}
+
+static void append_bool_property(Aml *properties, const char *name)
+{
+    Aml *property = aml_package(2);
+
+    aml_append(property, aml_string("%s", name));
+    aml_append(property, aml_int(1));
+    aml_append(properties, property);
+}
+
+static void build_tunnelled_device_dsd(Aml *scope)
+{
+    Aml *properties = aml_package(1);
+
+    append_bool_property(properties, "IOPCITunnelled");
+
+    aml_append(scope, build_device_properties_dsd(properties));
+}
+
+static void build_tunnelled_device_acpi_properties(Aml *scope)
+{
+    build_tunnelled_device_dsd(scope);
+}
+
+static Aml *build_pci_static_endpoint_dsm(PCIDevice *pdev)
+{
+    Aml *method;
+    Aml *params;
+    Aml *pkg;
+
+    g_assert(pdev->acpi_index != 0);
+
+    method = aml_method("_DSM", 4, AML_SERIALIZED);
+    params = aml_local(0);
+    pkg = aml_package(1);
+    aml_append(pkg, aml_int(pdev->acpi_index));
+    aml_append(method, aml_store(pkg, params));
+    aml_append(method,
+        aml_return(aml_call5("EDSM", aml_arg(0), aml_arg(1), aml_arg(2),
+                             aml_arg(3), params)));
+
+    return method;
+}
+
+static bool thunderbolt_bridge_devfn_ignored(int devfn, const PCIBus *bus)
+{
+    const PCIDevice *pdev = bus->devices[devfn];
+
+    if (PCI_FUNC(devfn) && IS_PCI_BRIDGE(pdev) && DEVICE(pdev)->hotplugged) {
+        return true;
+    }
+
+    return false;
+}
+
+static void build_tunnelled_pci_bus_devices(Aml *parent_scope, PCIBus *bus)
+{
+    int devfn;
+
+    for (devfn = 0; devfn < ARRAY_SIZE(bus->devices); devfn++) {
+        PCIDevice *pdev = bus->devices[devfn];
+        int adr = PCI_SLOT(devfn) << 16 | PCI_FUNC(devfn);
+        Aml *dev;
+
+        if (!pdev || thunderbolt_bridge_devfn_ignored(devfn, bus)) {
+            continue;
+        }
+
+        dev = aml_device("S%.02X", devfn);
+        aml_append(dev, aml_name_decl("_ADR", aml_int(adr)));
+
+        call_dev_aml_func(DEVICE(pdev), dev);
+
+        if (pdev->acpi_index &&
+            !object_property_get_bool(OBJECT(pdev), "hotpluggable",
+                                      &error_abort)) {
+            aml_append(dev, build_pci_static_endpoint_dsm(pdev));
+        }
+
+        aml_append(parent_scope, dev);
+    }
+}
+
+static void build_thunderbolt_pcie_pci_bridge_aml(AcpiDevAmlIf *adev,
+                                                  Aml *scope)
+{
+    PCIBridge *br = PCI_BRIDGE(adev);
+
+    build_tunnelled_device_acpi_properties(scope);
+
+    if (!DEVICE(br)->hotplugged) {
+        PCIBus *sec_bus = pci_bridge_get_sec_bus(br);
+
+        build_tunnelled_pci_bus_devices(scope, sec_bus);
+
+        if (object_property_find(OBJECT(sec_bus), ACPI_PCIHP_PROP_BSEL)) {
+            build_append_pcihp_slots(scope, sec_bus);
+        }
+    }
+}
+
+static void thunderbolt_pcie_pci_bridge_class_init(ObjectClass *klass,
+                                                   const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    AcpiDevAmlIfClass *adevc = ACPI_DEV_AML_IF_CLASS(klass);
+
+    dc->desc = "Thunderbolt PCI Express to PCI Bridge";
+    adevc->build_dev_aml = build_thunderbolt_pcie_pci_bridge_aml;
+}
+
+static const TypeInfo thunderbolt_pcie_pci_bridge_info = {
+    .name       = TYPE_THUNDERBOLT_PCIE_PCI_BRIDGE,
+    .parent     = "pcie-pci-bridge",
+    .class_init = thunderbolt_pcie_pci_bridge_class_init,
+};
+
+static void thunderbolt_pcie_pci_bridge_register_types(void)
+{
+    type_register_static(&thunderbolt_pcie_pci_bridge_info);
+}
+
+type_init(thunderbolt_pcie_pci_bridge_register_types)
-- 
2.47.3
[RFC PATCH v2 3/5] hw/display: add Thunderbolt VGA endpoint
Posted by Dmitry R 14 hours ago
Add thunderbolt-vga as a separate stdvga-compatible hotpluggable endpoint
for Thunderbolt display hotplug tests.

The regular QEMU VGA device is intentionally left untouched and remains
non-hotpluggable. thunderbolt-vga.c keeps the MIT license because it is
derived from the existing stdvga implementation in hw/display/vga-pci.c,
which carries the same license.

Signed-off-by: Dmitry R <rdmitry0911@gmail.com>
---
 hw/display/meson.build       |   1 +
 hw/display/thunderbolt-vga.c | 275 +++++++++++++++++++++++++++++++++++
 2 files changed, 276 insertions(+)
 create mode 100644 hw/display/thunderbolt-vga.c

diff --git a/hw/display/meson.build b/hw/display/meson.build
index ffecedbf70..7074d2b5f9 100644
--- a/hw/display/meson.build
+++ b/hw/display/meson.build
@@ -19,6 +19,7 @@ system_ss.add(when: 'CONFIG_SSD0323', if_true: files('ssd0323.c'))
 system_ss.add(when: 'CONFIG_XEN_BUS', if_true: files('xenfb.c'))
 
 system_ss.add(when: 'CONFIG_VGA_PCI', if_true: files('vga-pci.c'))
+system_ss.add(when: 'CONFIG_VGA_PCI', if_true: files('thunderbolt-vga.c'))
 system_ss.add(when: 'CONFIG_VGA_ISA', if_true: files('vga-isa.c'))
 system_ss.add(when: 'CONFIG_VGA_MMIO', if_true: files('vga-mmio.c'))
 system_ss.add(when: 'CONFIG_VMWARE_VGA', if_true: files('vmware_vga.c'))
diff --git a/hw/display/thunderbolt-vga.c b/hw/display/thunderbolt-vga.c
new file mode 100644
index 0000000000..4eabe357c2
--- /dev/null
+++ b/hw/display/thunderbolt-vga.c
@@ -0,0 +1,275 @@
+/*
+ * Thunderbolt hotpluggable PCI VGA device.
+ *
+ * This is a stdvga-compatible endpoint with a separate QOM type for
+ * Thunderbolt hotplug experiments.  The regular "VGA" type remains unchanged.
+ *
+ * Copyright (c) 2003 Fabrice Bellard
+ * SPDX-License-Identifier: MIT
+ */
+
+#include "qemu/osdep.h"
+#include "hw/core/loader.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/display/edid.h"
+#include "hw/pci/pci_device.h"
+#include "qemu/module.h"
+#include "qom/object.h"
+#include "ui/console.h"
+#include "ui/pixel_ops.h"
+#include "vga_int.h"
+
+#define TYPE_PCI_VGA "pci-vga"
+#define TYPE_THUNDERBOLT_VGA "thunderbolt-vga"
+
+OBJECT_DECLARE_SIMPLE_TYPE(ThunderboltVGAState, THUNDERBOLT_VGA)
+
+enum thunderbolt_vga_pci_flags {
+    THUNDERBOLT_VGA_FLAG_ENABLE_MMIO = 1,
+    THUNDERBOLT_VGA_FLAG_ENABLE_QEXT = 2,
+    THUNDERBOLT_VGA_FLAG_ENABLE_EDID = 3,
+};
+
+struct ThunderboltVGAState {
+    PCIDevice dev;
+    VGACommonState vga;
+    uint32_t flags;
+    qemu_edid_info edid_info;
+    MemoryRegion mmio;
+    MemoryRegion mrs[4];
+    uint8_t edid[384];
+};
+
+static uint64_t thunderbolt_vga_ioport_read(void *ptr, hwaddr addr,
+                                            unsigned size)
+{
+    VGACommonState *s = ptr;
+    uint64_t ret = 0;
+
+    switch (size) {
+    case 1:
+        ret = vga_ioport_read(s, addr + 0x3c0);
+        break;
+    case 2:
+        ret  = vga_ioport_read(s, addr + 0x3c0);
+        ret |= vga_ioport_read(s, addr + 0x3c1) << 8;
+        break;
+    }
+    return ret;
+}
+
+static void thunderbolt_vga_ioport_write(void *ptr, hwaddr addr,
+                                         uint64_t val, unsigned size)
+{
+    VGACommonState *s = ptr;
+
+    switch (size) {
+    case 1:
+        vga_ioport_write(s, addr + 0x3c0, val);
+        break;
+    case 2:
+        vga_ioport_write(s, addr + 0x3c0, val & 0xff);
+        vga_ioport_write(s, addr + 0x3c1, (val >> 8) & 0xff);
+        break;
+    }
+}
+
+static const MemoryRegionOps thunderbolt_vga_ioport_ops = {
+    .read = thunderbolt_vga_ioport_read,
+    .write = thunderbolt_vga_ioport_write,
+    .valid.min_access_size = 1,
+    .valid.max_access_size = 4,
+    .impl.min_access_size = 1,
+    .impl.max_access_size = 2,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static uint64_t thunderbolt_vga_bochs_read(void *ptr, hwaddr addr,
+                                           unsigned size)
+{
+    VGACommonState *s = ptr;
+    int index = addr >> 1;
+
+    vbe_ioport_write_index(s, 0, index);
+    return vbe_ioport_read_data(s, 0);
+}
+
+static void thunderbolt_vga_bochs_write(void *ptr, hwaddr addr,
+                                        uint64_t val, unsigned size)
+{
+    VGACommonState *s = ptr;
+    int index = addr >> 1;
+
+    vbe_ioport_write_index(s, 0, index);
+    vbe_ioport_write_data(s, 0, val);
+}
+
+static const MemoryRegionOps thunderbolt_vga_bochs_ops = {
+    .read = thunderbolt_vga_bochs_read,
+    .write = thunderbolt_vga_bochs_write,
+    .valid.min_access_size = 1,
+    .valid.max_access_size = 4,
+    .impl.min_access_size = 2,
+    .impl.max_access_size = 2,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static uint64_t thunderbolt_vga_qext_read(void *ptr, hwaddr addr,
+                                          unsigned size)
+{
+    VGACommonState *s = ptr;
+
+    switch (addr) {
+    case PCI_VGA_QEXT_REG_SIZE:
+        return PCI_VGA_QEXT_SIZE;
+    case PCI_VGA_QEXT_REG_BYTEORDER:
+        return s->big_endian_fb ?
+            PCI_VGA_QEXT_BIG_ENDIAN : PCI_VGA_QEXT_LITTLE_ENDIAN;
+    default:
+        return 0;
+    }
+}
+
+static void thunderbolt_vga_qext_write(void *ptr, hwaddr addr,
+                                       uint64_t val, unsigned size)
+{
+    VGACommonState *s = ptr;
+
+    switch (addr) {
+    case PCI_VGA_QEXT_REG_BYTEORDER:
+        if (val == PCI_VGA_QEXT_BIG_ENDIAN) {
+            s->big_endian_fb = true;
+        }
+        if (val == PCI_VGA_QEXT_LITTLE_ENDIAN) {
+            s->big_endian_fb = false;
+        }
+        break;
+    }
+}
+
+static const MemoryRegionOps thunderbolt_vga_qext_ops = {
+    .read = thunderbolt_vga_qext_read,
+    .write = thunderbolt_vga_qext_write,
+    .valid.min_access_size = 4,
+    .valid.max_access_size = 4,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static bool thunderbolt_vga_get_big_endian_fb(Object *obj, Error **errp)
+{
+    ThunderboltVGAState *d = THUNDERBOLT_VGA(obj);
+
+    return d->vga.big_endian_fb;
+}
+
+static void thunderbolt_vga_set_big_endian_fb(Object *obj, bool value,
+                                              Error **errp)
+{
+    ThunderboltVGAState *d = THUNDERBOLT_VGA(obj);
+
+    d->vga.big_endian_fb = value;
+}
+
+static void thunderbolt_vga_mmio_region_init(ThunderboltVGAState *d,
+                                             Object *owner,
+                                             MemoryRegion *parent)
+{
+    VGACommonState *s = &d->vga;
+
+    memory_region_init_io(&d->mrs[0], owner, &thunderbolt_vga_ioport_ops, s,
+                          "vga ioports remapped", PCI_VGA_IOPORT_SIZE);
+    memory_region_add_subregion(parent, PCI_VGA_IOPORT_OFFSET, &d->mrs[0]);
+
+    memory_region_init_io(&d->mrs[1], owner, &thunderbolt_vga_bochs_ops, s,
+                          "bochs dispi interface", PCI_VGA_BOCHS_SIZE);
+    memory_region_add_subregion(parent, PCI_VGA_BOCHS_OFFSET, &d->mrs[1]);
+
+    if (d->flags & (1 << THUNDERBOLT_VGA_FLAG_ENABLE_QEXT)) {
+        memory_region_init_io(&d->mrs[2], owner, &thunderbolt_vga_qext_ops, s,
+                              "qemu extended regs", PCI_VGA_QEXT_SIZE);
+        memory_region_add_subregion(parent, PCI_VGA_QEXT_OFFSET, &d->mrs[2]);
+    }
+
+    if (d->flags & (1 << THUNDERBOLT_VGA_FLAG_ENABLE_EDID)) {
+        qemu_edid_generate(d->edid, sizeof(d->edid), &d->edid_info);
+        qemu_edid_region_io(&d->mrs[3], owner, d->edid, sizeof(d->edid));
+        memory_region_add_subregion(parent, 0, &d->mrs[3]);
+    }
+}
+
+static void thunderbolt_vga_realize(PCIDevice *dev, Error **errp)
+{
+    ThunderboltVGAState *d = THUNDERBOLT_VGA(dev);
+    VGACommonState *s = &d->vga;
+
+    if (!vga_common_init(s, OBJECT(dev), errp)) {
+        return;
+    }
+    vga_init(s, OBJECT(dev), pci_address_space(dev), pci_address_space_io(dev),
+             true);
+
+    s->con = qemu_graphic_console_create(DEVICE(dev), 0, s->hw_ops, s);
+
+    pci_register_bar(dev, 0, PCI_BASE_ADDRESS_MEM_PREFETCH, &s->vram);
+
+    if (d->flags & (1 << THUNDERBOLT_VGA_FLAG_ENABLE_MMIO)) {
+        memory_region_init_io(&d->mmio, OBJECT(dev), &unassigned_io_ops, NULL,
+                              "vga.mmio", PCI_VGA_MMIO_SIZE);
+
+        if (d->flags & (1 << THUNDERBOLT_VGA_FLAG_ENABLE_QEXT)) {
+            pci_set_byte(&dev->config[PCI_REVISION_ID], 2);
+        }
+
+        thunderbolt_vga_mmio_region_init(d, OBJECT(dev), &d->mmio);
+        pci_register_bar(dev, 2, PCI_BASE_ADDRESS_SPACE_MEMORY, &d->mmio);
+    }
+}
+
+static const Property thunderbolt_vga_properties[] = {
+    DEFINE_PROP_UINT32("vgamem_mb", ThunderboltVGAState, vga.vram_size_mb, 4),
+    DEFINE_PROP_BIT("mmio", ThunderboltVGAState, flags,
+                    THUNDERBOLT_VGA_FLAG_ENABLE_MMIO, true),
+    DEFINE_PROP_BIT("qemu-extended-regs", ThunderboltVGAState, flags,
+                    THUNDERBOLT_VGA_FLAG_ENABLE_QEXT, true),
+    DEFINE_PROP_BIT("edid", ThunderboltVGAState, flags,
+                    THUNDERBOLT_VGA_FLAG_ENABLE_EDID, true),
+    DEFINE_PROP_UINT32("xres", ThunderboltVGAState, edid_info.prefx, 1280),
+    DEFINE_PROP_UINT32("yres", ThunderboltVGAState, edid_info.prefy, 800),
+    DEFINE_PROP_UINT32("xmax", ThunderboltVGAState, edid_info.maxx, 1280),
+    DEFINE_PROP_UINT32("ymax", ThunderboltVGAState, edid_info.maxy, 800),
+    DEFINE_PROP_UINT32("refresh_rate", ThunderboltVGAState,
+                       edid_info.refresh_rate, 60000),
+    DEFINE_PROP_BOOL("global-vmstate", ThunderboltVGAState,
+                     vga.global_vmstate, false),
+};
+
+static void thunderbolt_vga_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+    k->realize = thunderbolt_vga_realize;
+    k->romfile = "vgabios-stdvga.bin";
+    k->class_id = PCI_CLASS_DISPLAY_VGA;
+    device_class_set_props(dc, thunderbolt_vga_properties);
+    dc->desc = "Thunderbolt hotpluggable VGA";
+    dc->hotpluggable = true;
+
+    object_class_property_add_bool(klass, "big-endian-framebuffer",
+                                   thunderbolt_vga_get_big_endian_fb,
+                                   thunderbolt_vga_set_big_endian_fb);
+}
+
+static const TypeInfo thunderbolt_vga_info = {
+    .name = TYPE_THUNDERBOLT_VGA,
+    .parent = TYPE_PCI_VGA,
+    .instance_size = sizeof(ThunderboltVGAState),
+    .class_init = thunderbolt_vga_class_init,
+};
+
+static void thunderbolt_vga_register_types(void)
+{
+    type_register_static(&thunderbolt_vga_info);
+}
+
+type_init(thunderbolt_vga_register_types)
-- 
2.47.3
[RFC PATCH v2 4/5] tests/qtest: cover Thunderbolt VGA hotplug
Posted by Dmitry R 14 hours ago
Add a qtest that starts a q35 machine with thunderbolt-root-port and
verifies that regular VGA remains non-hotpluggable while thunderbolt-vga
can be hotplugged behind the Thunderbolt root port.

The test also checks the constrained thunderbolt-vga display defaults used
by the macOS validation flow.

Signed-off-by: Dmitry R <rdmitry0911@gmail.com>
---
 tests/qtest/display-vga-test.c | 68 ++++++++++++++++++++++++++++++++++
 1 file changed, 68 insertions(+)

diff --git a/tests/qtest/display-vga-test.c b/tests/qtest/display-vga-test.c
index 75b341a9c6..c5f526a56e 100644
--- a/tests/qtest/display-vga-test.c
+++ b/tests/qtest/display-vga-test.c
@@ -9,6 +9,21 @@
 
 #include "qemu/osdep.h"
 #include "libqtest.h"
+#include "qobject/qdict.h"
+
+static void assert_qom_uint(QTestState *qts, const char *path,
+                            const char *property, uint64_t expected)
+{
+    QDict *rsp;
+
+    rsp = qtest_qmp(qts, "{ 'execute': 'qom-get', 'arguments': {"
+                          "'path': %s,"
+                          "'property': %s"
+                          "} }", path, property);
+    g_assert(qdict_haskey(rsp, "return"));
+    g_assert_cmpuint(qdict_get_uint(rsp, "return"), ==, expected);
+    qobject_unref(rsp);
+}
 
 static void pci_multihead(void)
 {
@@ -18,6 +33,51 @@ static void pci_multihead(void)
     qtest_quit(qts);
 }
 
+static void thunderbolt_vga_hotplug(void)
+{
+    QTestState *qts;
+    QDict *rsp;
+    QDict *err;
+
+    qts = qtest_init("-machine q35 -display none -nodefaults -vga none "
+                     "-device VGA,id=bootvga,bus=pcie.0,addr=0x02 "
+                     "-device thunderbolt-root-port,id=tbrp0,bus=pcie.0,"
+                     "chassis=1,slot=1,addr=0x05");
+
+    rsp = qtest_qmp(qts, "{ 'execute': 'device_add', "
+                          "'arguments': {"
+                          "'driver': 'VGA',"
+                          "'id': 'badstd',"
+                          "'bus': 'tbrp0',"
+                          "'addr': '0x01'"
+                          "} }");
+    g_assert(qdict_haskey(rsp, "error"));
+    err = qdict_get_qdict(rsp, "error");
+    g_assert_cmpstr(qdict_get_str(err, "desc"), ==,
+                    "Device 'VGA' does not support hotplugging");
+    qobject_unref(rsp);
+
+    rsp = qtest_qmp(qts, "{ 'execute': 'device_add', "
+                          "'arguments': {"
+                          "'driver': 'thunderbolt-vga',"
+                          "'id': 'tbvga0',"
+                          "'bus': 'tbrp0',"
+                          "'addr': '0x00'"
+                          "} }");
+    g_assert(qdict_haskey(rsp, "return"));
+    g_assert(!qdict_haskey(rsp, "error"));
+    qobject_unref(rsp);
+
+    assert_qom_uint(qts, "/machine/peripheral/tbvga0", "vgamem_mb", 4);
+    assert_qom_uint(qts, "/machine/peripheral/tbvga0", "xres", 1280);
+    assert_qom_uint(qts, "/machine/peripheral/tbvga0", "yres", 800);
+    assert_qom_uint(qts, "/machine/peripheral/tbvga0", "xmax", 1280);
+    assert_qom_uint(qts, "/machine/peripheral/tbvga0", "ymax", 800);
+    assert_qom_uint(qts, "/machine/peripheral/tbvga0", "refresh_rate", 60000);
+
+    qtest_quit(qts);
+}
+
 static void test_vga(gconstpointer data)
 {
     QTestState *qts;
@@ -31,6 +91,7 @@ int main(int argc, char **argv)
     static const char *devices[] = {
         "cirrus-vga",
         "VGA",
+        "thunderbolt-vga",
         "secondary-vga",
         "virtio-gpu-pci",
         "virtio-vga"
@@ -50,5 +111,12 @@ int main(int argc, char **argv)
         qtest_add_func("/display/pci/multihead", pci_multihead);
     }
 
+    if (qtest_has_machine("q35") &&
+        qtest_has_device("thunderbolt-root-port") &&
+        qtest_has_device("thunderbolt-vga")) {
+        qtest_add_func("/display/pci/thunderbolt-vga-hotplug",
+                       thunderbolt_vga_hotplug);
+    }
+
     return g_test_run();
 }
-- 
2.47.3
[RFC PATCH v2 5/5] docs: document Thunderbolt PCIe hotplug layer
Posted by Dmitry R 14 hours ago
Add documentation and usage examples for the Thunderbolt PCIe hotplug
layer, including VGA, USB storage, and NVMe hotplug flows.

Add a MAINTAINERS entry for the Thunderbolt PCIe files with Dmitry R as
reviewer, while keeping the files under the existing PCI area through
their paths.

Signed-off-by: Dmitry R <rdmitry0911@gmail.com>
---
 MAINTAINERS                             |  7 ++
 README.md                               | 91 +++++++++++++++++++++++
 docs/specs/index.rst                    |  1 +
 docs/specs/thunderbolt-pcie-hotplug.rst | 99 +++++++++++++++++++++++++
 4 files changed, 198 insertions(+)
 create mode 100644 README.md
 create mode 100644 docs/specs/thunderbolt-pcie-hotplug.rst

diff --git a/MAINTAINERS b/MAINTAINERS
index 6171cc7494..0be88e77fa 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2223,6 +2223,13 @@ F: docs/pci*
 F: docs/specs/*pci*
 F: docs/system/sriov.rst
 
+Thunderbolt PCIe
+R: Dmitry R <rdmitry0911@gmail.com>
+S: Maintained
+F: docs/specs/thunderbolt-pcie-hotplug.rst
+F: hw/display/thunderbolt-vga.c
+F: hw/pci-bridge/thunderbolt*.c
+
 ARM PCI Hotplug
 M: Gustavo Romero <gustavo.romero@linaro.org>
 L: qemu-arm@nongnu.org
diff --git a/README.md b/README.md
new file mode 100644
index 0000000000..1115c9db82
--- /dev/null
+++ b/README.md
@@ -0,0 +1,91 @@
+SPDX-License-Identifier: GPL-2.0-or-later
+
+# QEMU Thunderbolt PCIe hotplug layer
+
+This branch adds a Thunderbolt-flavoured PCIe hotplug layer for QEMU
+experiments.  The implementation keeps normal QEMU PCI devices intact and
+adds a new Thunderbolt bus/root-port layer around them.
+
+The primary goal is to provide hotplug and hot-unplug for real and virtual
+devices in guests that do not support those devices being added or removed
+directly.  macOS is the main validation target for this path: devices can be
+tunnelled through a guest-visible Thunderbolt layer even when direct PCIe
+hotplug for the same endpoint is not usable.
+
+The regular QEMU `VGA` device is not changed and is not made hotpluggable.
+Thunderbolt display experiments use a separate `thunderbolt-vga` device type.
+
+## Added device types
+
+- `thunderbolt-root-port`: a PCIe root port that creates a `thunderbolt-bus`.
+  Its `connected` QOM property controls guest-visible link presence.
+- `thunderbolt-pcie-pci-bridge`: a PCIe-to-PCI bridge for tunnelled PCI
+  endpoints with Thunderbolt-related ACPI properties.
+- `thunderbolt-vga`: a hotpluggable stdvga-compatible endpoint with constrained
+  default EDID settings for guest hotplug tests.
+
+## Basic boot example
+
+Start the guest with its normal boot display and an empty Thunderbolt root
+port:
+
+```sh
+qemu-system-x86_64 \
+  -machine q35 \
+  -device VGA,id=bootvga,bus=pcie.0,addr=0x06,vgamem_mb=4,xres=1280,yres=800,xmax=1280,ymax=800,refresh_rate=60000 \
+  -device thunderbolt-root-port,id=tbrp0,bus=pcie.0,chassis=1,slot=1,addr=0x05 \
+  ...
+```
+
+Hotplug and remove a Thunderbolt VGA endpoint through the QEMU monitor:
+
+```text
+(qemu) device_add thunderbolt-vga,id=tbvga0,bus=tbrp0,addr=0x00
+(qemu) device_del tbvga0
+```
+
+Disconnect or reconnect the whole Thunderbolt root port:
+
+```text
+(qemu) qom-set /machine/peripheral/tbrp0 connected false
+(qemu) qom-set /machine/peripheral/tbrp0 connected true
+```
+
+## USB storage example
+
+Attach a USB controller behind the Thunderbolt root port, then attach USB mass
+storage to that controller:
+
+```text
+(qemu) device_add qemu-xhci,id=tbxhci0,bus=tbrp0,addr=0x00
+(qemu) drive_add 0 if=none,id=TBUSB,file=TBUSB.raw,format=raw,cache=writeback,aio=threads
+(qemu) device_add usb-storage,id=tbusb0,bus=tbxhci0.0,drive=TBUSB,serial=tb-usb-stick0
+```
+
+After the guest ejects or unmounts the disk, remove the device and controller:
+
+```text
+(qemu) device_del tbusb0
+(qemu) device_del tbxhci0
+```
+
+## NVMe example
+
+```text
+(qemu) drive_add 0 if=none,id=TBNVME,file=TBNVME.qcow2,format=qcow2
+(qemu) device_add nvme,id=tbnvme0,bus=tbrp0,addr=0x00,serial=tb-nvme0,drive=TBNVME
+(qemu) device_del tbnvme0
+```
+
+## Validation status
+
+The development validation used macOS guests booted from overlay disk images so
+the base images stayed immutable.  The tested hotplug flows covered:
+
+- `thunderbolt-vga` detection and removal while the standard boot VGA remained.
+- NVMe controller hotplug and driver instance disappearance after unplug.
+- USB controller hotplug, USB storage mount/write/eject, controller removal,
+  replug, and data persistence verification.
+
+The qtest coverage checks that regular `VGA` hotplug still fails while
+`thunderbolt-vga` can be hotplugged behind `thunderbolt-root-port`.
diff --git a/docs/specs/index.rst b/docs/specs/index.rst
index b7909a108a..1f0d3e4b4a 100644
--- a/docs/specs/index.rst
+++ b/docs/specs/index.rst
@@ -31,6 +31,7 @@ guest hardware that is specific to QEMU.
    pvpanic
    spdm
    standard-vga
+   thunderbolt-pcie-hotplug
    virt-ctlr
    vmcoreinfo
    vmgenid
diff --git a/docs/specs/thunderbolt-pcie-hotplug.rst b/docs/specs/thunderbolt-pcie-hotplug.rst
new file mode 100644
index 0000000000..f14c64b181
--- /dev/null
+++ b/docs/specs/thunderbolt-pcie-hotplug.rst
@@ -0,0 +1,99 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+Thunderbolt PCIe hotplug layer
+==============================
+
+This series adds a Thunderbolt-flavoured PCIe layer for experiments with
+guest-visible PCIe tunnelling.  Devices behind the layer remain normal QEMU
+PCI devices; the Thunderbolt root port provides the guest-visible connection
+state, ACPI properties, and PCIe hotplug notifications.
+
+The main use case is hotplug and hot-unplug of real and virtual devices in
+guests that cannot use direct hotplug for those devices.  For example, a macOS
+guest can observe the endpoint through a guest-visible Thunderbolt layer when
+direct PCIe hotplug for the same endpoint is not supported or not usable.
+
+The implementation adds three user-visible device types:
+
+``thunderbolt-root-port``
+  PCIe root port that creates a ``thunderbolt-bus`` secondary bus.  The
+  ``connected`` QOM property controls whether devices behind the port are
+  reported as present to the guest.
+
+``thunderbolt-pcie-pci-bridge``
+  PCIe-to-PCI bridge for tunnelled PCI endpoints.  It adds Thunderbolt-related
+  ACPI properties while leaving downstream PCI devices otherwise normal.
+
+``thunderbolt-vga``
+  Hotpluggable stdvga-compatible device for Thunderbolt experiments.  The
+  regular ``VGA`` device is not made hotpluggable and its defaults are not
+  changed.
+
+Basic launch example
+--------------------
+
+Start the guest with its normal boot display and an empty Thunderbolt root
+port:
+
+::
+
+  qemu-system-x86_64 \
+      -machine q35 \
+      -device VGA,id=bootvga,bus=pcie.0,addr=0x06,vgamem_mb=4,xres=1280,yres=800,xmax=1280,ymax=800,refresh_rate=60000 \
+      -device thunderbolt-root-port,id=tbrp0,bus=pcie.0,chassis=1,slot=1,addr=0x05 \
+      ...
+
+Hotplug a Thunderbolt VGA endpoint after the guest has booted:
+
+::
+
+  (qemu) device_add thunderbolt-vga,id=tbvga0,bus=tbrp0,addr=0x00
+  (qemu) device_del tbvga0
+
+Temporarily disconnect and reconnect the whole Thunderbolt port:
+
+::
+
+  (qemu) qom-set /machine/peripheral/tbrp0 connected false
+  (qemu) qom-set /machine/peripheral/tbrp0 connected true
+
+USB storage example
+-------------------
+
+Attach a USB controller to the Thunderbolt root port, then attach USB mass
+storage to that controller:
+
+::
+
+  (qemu) device_add qemu-xhci,id=tbxhci0,bus=tbrp0,addr=0x00
+  (qemu) drive_add 0 if=none,id=TBUSB,file=TBUSB.raw,format=raw,cache=writeback,aio=threads
+  (qemu) device_add usb-storage,id=tbusb0,bus=tbxhci0.0,drive=TBUSB,serial=tb-usb-stick0
+
+After the guest ejects or unmounts the disk, remove the USB device and the
+controller:
+
+::
+
+  (qemu) device_del tbusb0
+  (qemu) device_del tbxhci0
+
+NVMe example
+------------
+
+Attach an NVMe controller behind the Thunderbolt root port:
+
+::
+
+  (qemu) drive_add 0 if=none,id=TBNVME,file=TBNVME.qcow2,format=qcow2
+  (qemu) device_add nvme,id=tbnvme0,bus=tbrp0,addr=0x00,serial=tb-nvme0,drive=TBNVME
+  (qemu) device_del tbnvme0
+
+Notes for review
+----------------
+
+The regular QEMU ``VGA`` type is intentionally left non-hotpluggable.  Tests
+check that hotplugging ``VGA`` behind a Thunderbolt root port still fails, and
+that ``thunderbolt-vga`` can be hotplugged with constrained display defaults.
+
+The macOS validation flow used QCOW2 overlays for all mutable disks so the base
+guest images remained unchanged.
-- 
2.47.3
Re: [RFC PATCH v1 0/1] Thunderbolt PCIe hotplug layer
Posted by Michael S. Tsirkin 14 hours ago
On Fri, Jul 10, 2026 at 04:34:37PM +0300, Dmitry R wrote:
> This RFC adds a Thunderbolt-flavoured PCIe hotplug layer for QEMU.
> 
> The main goal is hotplug and hot-unplug of real and virtual devices in
> guests where direct hotplug for those devices is not supported or not
> usable.  macOS is the main validation target for this path: the guest can
> observe endpoints through a guest-visible Thunderbolt layer even when direct
> PCIe hotplug for the same endpoint is not usable.


Very happy to see this.


> The series intentionally keeps regular QEMU PCI endpoint devices intact.
> Devices behind the new layer remain normal QEMU PCI devices; the
> Thunderbolt root port controls guest-visible link presence, publishes ACPI
> properties, and emits PCIe hotplug notifications.
> 
> The patch adds:
> 
> * thunderbolt-root-port, creating a thunderbolt-bus secondary bus.
> * thunderbolt-pcie-pci-bridge for tunnelled PCI endpoints.
> * thunderbolt-vga, a separate stdvga-compatible hotpluggable endpoint.
> * README/specs documentation with VGA, USB storage, and NVMe examples.
> * qtest coverage for the Thunderbolt VGA hotplug path.
> 
> The regular QEMU VGA device is not changed and remains non-hotpluggable.
> The Thunderbolt display path uses the separate thunderbolt-vga device type.
> 
> Runtime validation performed so far:
> 
> * macOS guest booted from QCOW2 overlays over immutable base disk images.
> * Thunderbolt VGA hotplug/removal while the standard boot VGA remains.
> * NVMe hotplug/removal with guest driver instance checks.
> * USB controller hotplug, USB storage mount/write/eject, controller removal,
>   replug, and data persistence verification.
> 
> Build/test validation:
> 
> * ninja -C build qemu-system-x86_64 tests/qtest/display-vga-test
> * meson test -C build qtest-x86_64/display-vga-test --print-errorlogs
> * scripts/checkpatch.pl --no-tree --no-signoff on the full diff:
>   0 errors, with expected warnings for MIT inheritance in thunderbolt-vga.c
>   and possible MAINTAINERS coverage.
> 
> Open questions:
> 
> * Whether this should be split into smaller review patches after the overall
>   Thunderbolt modelling direction is accepted.

I'd prefer a split up version, as it is, I'm not ever
sure which part of the  patch does modeling refer to.


> * Which MAINTAINERS entry should own the Thunderbolt PCIe layer.

Are you going to be available long term for review?
if yes you can add an entry with yourself as reviewer,
and it will inherit maintainer from pci-bridge

> * Whether the guest-visible properties are sufficient for non-macOS guests
>   that may also benefit from Thunderbolt-style tunnelling.
> 
> Dmitry R (1):
>   hw/pci-bridge: add Thunderbolt PCIe hotplug layer
> 
>  README.md                                   |  91 ++++
>  docs/specs/index.rst                        |   1 +
>  docs/specs/thunderbolt-pcie-hotplug.rst     |  99 ++++
>  hw/display/meson.build                      |   1 +
>  hw/display/thunderbolt-vga.c                | 275 ++++++++++
>  hw/pci-bridge/meson.build                   |   2 +
>  hw/pci-bridge/thunderbolt_pcie_pci_bridge.c | 156 ++++++
>  hw/pci-bridge/thunderbolt_root_port.c       | 565 ++++++++++++++++++++
>  tests/qtest/display-vga-test.c              |  68 +++
>  9 files changed, 1258 insertions(+)
>  create mode 100644 README.md
>  create mode 100644 docs/specs/thunderbolt-pcie-hotplug.rst
>  create mode 100644 hw/display/thunderbolt-vga.c
>  create mode 100644 hw/pci-bridge/thunderbolt_pcie_pci_bridge.c
>  create mode 100644 hw/pci-bridge/thunderbolt_root_port.c
> 
> -- 
> 2.47.3