Hi

On Thu, Jun 18, 2026 at 4:49 PM Xiangfeng Cai
<[email protected]> wrote:
>
> The xHCI PCI wrapper embeds an xhci-core child via object_initialize_child()
> and, in usb_xhci_pci_realize(), points the child's "host" link back at the PCI
> device:
>
>     object_property_set_link(OBJECT(&s->xhci), "host", OBJECT(s), NULL);
>
> "host" is a DEFINE_PROP_LINK property, which qdev registers as an
> OBJ_PROP_LINK_STRONG link. A strong link takes a reference on its target, so
> this creates a refcount cycle: the PCI device owns the child, and the child's
> strong link pins the PCI device.
>
> On unplug (guest ACPI eject or QMP device_del), pci_qdev_unrealize() calls
> pc->exit() but never unrealizes the no-bus child. object_unparent() then drops
> only the parent/bus references, leaving the link reference in place. The PCI
> device stays at refcount 1 forever, so object_finalize()/device_finalize() is
> never reached. Symptom observed under gdb after eject:
>
>     p *((Object *)dev)  =>  ref = 1, parent = 0x0, realized = false
>     p ((XHCIPciState *)dev)->xhci.hostOpaque  =>  points back at dev
>
> Fix usb_xhci_pci_exit() to tear down the embedded child explicitly: unrealize
> it first (so the set-link-before-realize check passes), then clear the "host"
> link. This releases the strong reference, lets the PCI device refcount reach 
> 0,
> and allows device_finalize() to run.
>
> Fixes: 8ddab8dd3d81 ("usb/hcd-xhci: Split pci wrapper for xhci base model")
> Signed-off-by: Xiangfeng Cai <[email protected]>
> ---
>  hw/usb/hcd-xhci-pci.c | 12 ++++++++++++
>  1 file changed, 12 insertions(+)
>
> diff --git a/hw/usb/hcd-xhci-pci.c b/hw/usb/hcd-xhci-pci.c
> index c5446a4a5e..b124251ae3 100644
> --- a/hw/usb/hcd-xhci-pci.c
> +++ b/hw/usb/hcd-xhci-pci.c
> @@ -196,6 +196,18 @@ static void usb_xhci_pci_exit(PCIDevice *dev)
>          && dev->msix_entry_used) {
>          msix_uninit(dev, &s->xhci.mem, &s->xhci.mem);
>      }
> +    /*
> +     * The embedded xhci-core child holds a strong "host" link back to this
> +     * PCI device (set in usb_xhci_pci_realize()), forming a refcount cycle:
> +     * the PCI device owns the child, and the child's strong link pins the 
> PCI
> +     * device. On unplug, object_unparent() only drops the parent/bus refs, 
> so
> +     * the link ref keeps this device at refcount 1 forever and
> +     * device_finalize() never runs. Unrealize the child first (so the
> +     * realized-check in set_link passes), then clear the link to break the
> +     * cycle.
> +     */
> +    qdev_unrealize(DEVICE(&s->xhci));
> +    object_property_set_link(OBJECT(&s->xhci), "host", NULL, &error_abort);

This reverts the set_link/realize from pci_realize, it looks correct to me.
Could somebody else check if this is how we should handle the embedded objects?

Acked-by: Marc-André Lureau <[email protected]>

-- 
Marc-André Lureau

Reply via email to