From nobody Sun Jul 12 00:33:39 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=gmail.com ARC-Seal: i=1; a=rsa-sha256; t=1783694048; cv=none; d=zohomail.com; s=zohoarc; b=nCOnwceYMNDHSkmLCp9Gjct4lx3/brJmyx9EEQi/+7ATVBBvV+jueRIqK6zlVETCZYFtrdguKKEsdsuJCeNPU2Ze4eFZRhp6zwfimfgEFQ366xEZHC2/f7+QfHnxm5jqPWsvEhihAlmbqpBnNlHsK/EyaRTJX2PZoM7Nh19HMVA= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1783694048; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=t1qzZjKhgO7dW7CG5cEqQbp4NZ8ciCe32QXZGVumo7M=; b=Xdk6YSOzNPEQst80FeHKQi5ZN1E60uaY5gXxfncyKTq18MorWvYLRhC69QRl8fcpnUA70TkbGlAHIG2MhuGYT6V+lZvT0i6CEE5XVb5qR1L9Jin8jJQO3erjdmw8pvGqQ2wV2Z/I8OViwrZRjFx2kFkfJbtDNBp4sMKV0EFwloE= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1783694048431293.7721084789998; Fri, 10 Jul 2026 07:34:08 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wiCIA-0006ot-4Y; Fri, 10 Jul 2026 10:33:26 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wiBO6-0003kI-Oy for qemu-devel@nongnu.org; Fri, 10 Jul 2026 09:35:32 -0400 Received: from mail-wr1-x42c.google.com ([2a00:1450:4864:20::42c]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1wiBO0-0004wT-9w for qemu-devel@nongnu.org; Fri, 10 Jul 2026 09:35:28 -0400 Received: by mail-wr1-x42c.google.com with SMTP id ffacd0b85a97d-474560436c3so883768f8f.0 for ; Fri, 10 Jul 2026 06:35:23 -0700 (PDT) Received: from debian-for-building.localdomain (static.244.71.243.136.clients.your-server.de. [136.243.71.244]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-47a9b4d850dsm61405779f8f.0.2026.07.10.06.35.21 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 10 Jul 2026 06:35:21 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1783690523; x=1784295323; darn=nongnu.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=t1qzZjKhgO7dW7CG5cEqQbp4NZ8ciCe32QXZGVumo7M=; b=YRwgl/BlewaQ/g3Nj9Gj09yapMV011x0pNc9T+jygLblvXB9sqyiF23NoCK23gglSz phDkQE0iEoZaltnerOyJFv540fPa0adhwcSzjlOwPwtEws0ADK9vMIHkuTqyiHYAWEHq Imew18bi/nDh3EBwZU3Qmf+fA9YMWxEd5DkWEiJWCnClXqmk2K4Dvxhdk16PGJ8JK+BW 95dNoNj0iF6P7jFL3Ou39N3E2/yBIqU9Yn7H2PQk/zp/Y/OURtU/q8NEBZT7Mhoa/FRt mKb0lfjcMM9+VZqH4H/sSsxySk6/g0mmE/KPFy7XgWNils7jWAXWPv9uy2NtQ+H4z4dC iMGQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1783690523; x=1784295323; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=t1qzZjKhgO7dW7CG5cEqQbp4NZ8ciCe32QXZGVumo7M=; b=Ug2+MX1n86Q7SwOgKTEoXnGcuZqsbIPgjYbzFgRWlyxg4dYb5jsQEXqJPROikFd/Dh Kbavd5t5yHPoMIYUB8wjz5MuwrhxMLoRmFMNOzkBPuIX8BQ+gFTUyoATZfVXQldReu0M paiNvUe0UE3BNCgD6+A0zY7Gvfowmj04ncaSLK8w2cvu3Vr6ux/+EZ6k2QmWFkhHfk70 yE9lC11uOulJB+jQkqakNGKCq+FUn4PcoZkkHigSVxX9d4TE4nJgS8e6dS1LfdqNdE3g DJgP0lFfh7YxVUcKn+dBZssWRaqn6mEryXNJv+B7AF5SP+0c0ILYeiI7gSD+lqXLAtzS 3I5w== X-Gm-Message-State: AOJu0YzbD40HE6TnXNBPm9Qykcl5w9GglU5G0PYEzvMRK+jV0hPKpUVY IEZoxSMEni//tp7jxkn3BNjVZVyEykWke5OkLctkXdSros610ehHsQzTCzJxTZgnnzA= X-Gm-Gg: AfdE7ckp/RiQKfFVZ9apCgA494xGQ6Ho9W5Ydn3K4xG9/QqA1BuODO6JF9I+Vb1BGcU 8coN6x/V2pmJYpxq9OIFx1HQTBeRORqXl1/iZMtN3jeYeUNhykJJL9PoVXWVDiA1FEKd5Oz9cD8 buXCWYxYiYxpo+REZqGpvg3KgfhFLw3Dkl+mp/XqAx4rww0WhDyf+VBIkgAqM9GaC2jRgbNmRiu 5RyjzPowBp6mzVwHkvcJ1VRV9Tk5Imj4EgzmqDc9pzcO2JnGrHvUmfYWxtyBIbfT0BhWVWwq4U2 8bGVhc+RciIBXu2ISXTfvVe6sjWyVEX4o/WrgdsUfdsgZrESn/703re973cUR9Ma8kW68oMfx7X bOIWUZNyxnGuUp8i92BcZ1lE2eRxwN0hgdSIHIq0//0p3YvfBlRNFZ8s4E3OhpR4OAOXWKQMELh IGMcGeG+1ePV6XE1f4BvkDs3yiiusMmV3SrTG57HRTtI+8mSd69NN9khWiWnngxQdGJ3lSRZ1Mo IFam7WcqHtRCzaxAo0= X-Received: by 2002:a05:6000:25c2:b0:47d:e121:1dd1 with SMTP id ffacd0b85a97d-47df07554bamr13639088f8f.17.1783690522172; Fri, 10 Jul 2026 06:35:22 -0700 (PDT) From: Dmitry R To: qemu-devel@nongnu.org Cc: Dmitry R , "Michael S . Tsirkin" , Pierrick Bouvier , Fabiano Rosas , Laurent Vivier , Paolo Bonzini Subject: [RFC PATCH v1 1/1] hw/pci-bridge: add Thunderbolt PCIe hotplug layer Date: Fri, 10 Jul 2026 16:34:38 +0300 Message-ID: <20260710133440.2845828-2-rdmitry0911@gmail.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260710133440.2845828-1-rdmitry0911@gmail.com> References: <20260710133440.2845828-1-rdmitry0911@gmail.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=2a00:1450:4864:20::42c; envelope-from=rdmitry0911@gmail.com; helo=mail-wr1-x42c.google.com X-Spam_score_int: -17 X-Spam_score: -1.8 X-Spam_bar: - X-Spam_report: (-1.8 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_ENVFROM_END_DIGIT=0.25, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-Mailman-Approved-At: Fri, 10 Jul 2026 10:33:24 -0400 X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @gmail.com) X-ZM-MESSAGEID: 1783694050229158500 Content-Type: text/plain; charset="utf-8" Add a Thunderbolt-flavoured PCIe root port and bus for guest-visible PCIe t= unnelling experiments. Devices behind the new bus remain normal QEMU PCI d= evices; the Thunderbolt root port controls link presence through a connecte= d QOM property and emits the usual PCIe hotplug notifications. Add a Thunderbolt PCIe-to-PCI bridge for tunnelled PCI endpoints and a sepa= rate thunderbolt-vga device type for stdvga-compatible display hotplug test= s. The regular VGA device implementation is not touched and remains non-ho= tpluggable. Add qtest coverage for the Thunderbolt VGA hotplug path and documentation w= ith HMP examples for VGA, USB storage, and NVMe hotplug. thunderbolt-vga.c keeps the MIT license because it is derived from the exis= ting stdvga implementation in hw/display/vga-pci.c, which carries the same = license. Signed-off-by: Dmitry R --- 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 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 ty= pe. + +## Added device types + +- `thunderbolt-root-port`: a PCIe root port that creates a `thunderbolt-bu= s`. + 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 constr= ained + 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=3Dbootvga,bus=3Dpcie.0,addr=3D0x06,vgamem_mb=3D4,xres=3D1= 280,yres=3D800,xmax=3D1280,ymax=3D800,refresh_rate=3D60000 \ + -device thunderbolt-root-port,id=3Dtbrp0,bus=3Dpcie.0,chassis=3D1,slot= =3D1,addr=3D0x05 \ + ... +``` + +Hotplug and remove a Thunderbolt VGA endpoint through the QEMU monitor: + +```text +(qemu) device_add thunderbolt-vga,id=3Dtbvga0,bus=3Dtbrp0,addr=3D0x00 +(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=3Dtbxhci0,bus=3Dtbrp0,addr=3D0x00 +(qemu) drive_add 0 if=3Dnone,id=3DTBUSB,file=3DTBUSB.raw,format=3Draw,cach= e=3Dwriteback,aio=3Dthreads +(qemu) device_add usb-storage,id=3Dtbusb0,bus=3Dtbxhci0.0,drive=3DTBUSB,se= rial=3Dtb-usb-stick0 +``` + +After the guest ejects or unmounts the disk, remove the device and control= ler: + +```text +(qemu) device_del tbusb0 +(qemu) device_del tbxhci0 +``` + +## NVMe example + +```text +(qemu) drive_add 0 if=3Dnone,id=3DTBNVME,file=3DTBNVME.qcow2,format=3Dqcow2 +(qemu) device_add nvme,id=3Dtbnvme0,bus=3Dtbrp0,addr=3D0x00,serial=3Dtb-nv= me0,drive=3DTBNVME +(qemu) device_del tbnvme0 +``` + +## Validation status + +The development validation used macOS guests booted from overlay disk imag= es so +the base images stayed immutable. The tested hotplug flows covered: + +- `thunderbolt-vga` detection and removal while the standard boot VGA rema= ined. +- NVMe controller hotplug and driver instance disappearance after unplug. +- USB controller hotplug, USB storage mount/write/eject, controller remova= l, + 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/thunderbo= lt-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 +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D + +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 connecti= on +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 m= acOS +guest can observe the endpoint through a guest-visible Thunderbolt layer w= hen +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-rel= ated + 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=3Dbootvga,bus=3Dpcie.0,addr=3D0x06,vgamem_mb=3D4,xres= =3D1280,yres=3D800,xmax=3D1280,ymax=3D800,refresh_rate=3D60000 \ + -device thunderbolt-root-port,id=3Dtbrp0,bus=3Dpcie.0,chassis=3D1,sl= ot=3D1,addr=3D0x05 \ + ... + +Hotplug a Thunderbolt VGA endpoint after the guest has booted: + +:: + + (qemu) device_add thunderbolt-vga,id=3Dtbvga0,bus=3Dtbrp0,addr=3D0x00 + (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=3Dtbxhci0,bus=3Dtbrp0,addr=3D0x00 + (qemu) drive_add 0 if=3Dnone,id=3DTBUSB,file=3DTBUSB.raw,format=3Draw,ca= che=3Dwriteback,aio=3Dthreads + (qemu) device_add usb-storage,id=3Dtbusb0,bus=3Dtbxhci0.0,drive=3DTBUSB,= serial=3Dtb-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=3Dnone,id=3DTBNVME,file=3DTBNVME.qcow2,format=3Dqc= ow2 + (qemu) device_add nvme,id=3Dtbnvme0,bus=3Dtbrp0,addr=3D0x00,serial=3Dtb-= nvme0,drive=3DTBNVME + (qemu) device_del tbnvme0 + +Notes for review +---------------- + +The regular QEMU ``VGA`` type is intentionally left non-hotpluggable. Tes= ts +check that hotplugging ``VGA`` behind a Thunderbolt root port still fails,= and +that ``thunderbolt-vga`` can be hotplugged with constrained display defaul= ts. + +The macOS validation flow used QCOW2 overlays for all mutable disks so the= base +guest images remained unchanged. 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('ssd= 0323.c')) system_ss.add(when: 'CONFIG_XEN_BUS', if_true: files('xenfb.c')) =20 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 unchan= ged. + * + * 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 =3D 1, + THUNDERBOLT_VGA_FLAG_ENABLE_QEXT =3D 2, + THUNDERBOLT_VGA_FLAG_ENABLE_EDID =3D 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 =3D ptr; + uint64_t ret =3D 0; + + switch (size) { + case 1: + ret =3D vga_ioport_read(s, addr + 0x3c0); + break; + case 2: + ret =3D vga_ioport_read(s, addr + 0x3c0); + ret |=3D 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 =3D 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 =3D { + .read =3D thunderbolt_vga_ioport_read, + .write =3D thunderbolt_vga_ioport_write, + .valid.min_access_size =3D 1, + .valid.max_access_size =3D 4, + .impl.min_access_size =3D 1, + .impl.max_access_size =3D 2, + .endianness =3D DEVICE_LITTLE_ENDIAN, +}; + +static uint64_t thunderbolt_vga_bochs_read(void *ptr, hwaddr addr, + unsigned size) +{ + VGACommonState *s =3D ptr; + int index =3D 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 =3D ptr; + int index =3D addr >> 1; + + vbe_ioport_write_index(s, 0, index); + vbe_ioport_write_data(s, 0, val); +} + +static const MemoryRegionOps thunderbolt_vga_bochs_ops =3D { + .read =3D thunderbolt_vga_bochs_read, + .write =3D thunderbolt_vga_bochs_write, + .valid.min_access_size =3D 1, + .valid.max_access_size =3D 4, + .impl.min_access_size =3D 2, + .impl.max_access_size =3D 2, + .endianness =3D DEVICE_LITTLE_ENDIAN, +}; + +static uint64_t thunderbolt_vga_qext_read(void *ptr, hwaddr addr, + unsigned size) +{ + VGACommonState *s =3D 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 =3D ptr; + + switch (addr) { + case PCI_VGA_QEXT_REG_BYTEORDER: + if (val =3D=3D PCI_VGA_QEXT_BIG_ENDIAN) { + s->big_endian_fb =3D true; + } + if (val =3D=3D PCI_VGA_QEXT_LITTLE_ENDIAN) { + s->big_endian_fb =3D false; + } + break; + } +} + +static const MemoryRegionOps thunderbolt_vga_qext_ops =3D { + .read =3D thunderbolt_vga_qext_read, + .write =3D thunderbolt_vga_qext_write, + .valid.min_access_size =3D 4, + .valid.max_access_size =3D 4, + .endianness =3D DEVICE_LITTLE_ENDIAN, +}; + +static bool thunderbolt_vga_get_big_endian_fb(Object *obj, Error **errp) +{ + ThunderboltVGAState *d =3D 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 =3D THUNDERBOLT_VGA(obj); + + d->vga.big_endian_fb =3D value; +} + +static void thunderbolt_vga_mmio_region_init(ThunderboltVGAState *d, + Object *owner, + MemoryRegion *parent) +{ + VGACommonState *s =3D &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 =3D THUNDERBOLT_VGA(dev); + VGACommonState *s =3D &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 =3D 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, N= ULL, + "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[] =3D { + 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 *dat= a) +{ + DeviceClass *dc =3D DEVICE_CLASS(klass); + PCIDeviceClass *k =3D PCI_DEVICE_CLASS(klass); + + k->realize =3D thunderbolt_vga_realize; + k->romfile =3D "vgabios-stdvga.bin"; + k->class_id =3D PCI_CLASS_DISPLAY_VGA; + device_class_set_props(dc, thunderbolt_vga_properties); + dc->desc =3D "Thunderbolt hotpluggable VGA"; + dc->hotpluggable =3D 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 =3D { + .name =3D TYPE_THUNDERBOLT_VGA, + .parent =3D TYPE_PCI_VGA, + .instance_size =3D sizeof(ThunderboltVGAState), + .class_init =3D thunderbolt_vga_class_init, +}; + +static void thunderbolt_vga_register_types(void) +{ + type_register_static(&thunderbolt_vga_info); +} + +type_init(thunderbolt_vga_register_types) diff --git a/hw/pci-bridge/meson.build b/hw/pci-bridge/meson.build index 2e0eb0d233..8bb58ec84c 100644 --- a/hw/pci-bridge/meson.build +++ b/hw/pci-bridge/meson.build @@ -3,7 +3,9 @@ pci_ss.add(when: 'CONFIG_PCI_BRIDGE', if_true: files('pci_b= ridge_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', 'g= en_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_pci= e_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', 'x= io3130_downstream.c')) diff --git a/hw/pci-bridge/thunderbolt_pcie_pci_bridge.c b/hw/pci-bridge/th= underbolt_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 PC= Ie + * tunnel endpoint. Downstream PCI devices are described normally so exist= ing + * 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 =3D 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 =3D 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 =3D 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 !=3D 0); + + method =3D aml_method("_DSM", 4, AML_SERIALIZED); + params =3D aml_local(0); + pkg =3D 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 =3D 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 =3D 0; devfn < ARRAY_SIZE(bus->devices); devfn++) { + PCIDevice *pdev =3D bus->devices[devfn]; + int adr =3D PCI_SLOT(devfn) << 16 | PCI_FUNC(devfn); + Aml *dev; + + if (!pdev || thunderbolt_bridge_devfn_ignored(devfn, bus)) { + continue; + } + + dev =3D 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 =3D PCI_BRIDGE(adev); + + build_tunnelled_device_acpi_properties(scope); + + if (!DEVICE(br)->hotplugged) { + PCIBus *sec_bus =3D 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 =3D DEVICE_CLASS(klass); + AcpiDevAmlIfClass *adevc =3D ACPI_DEV_AML_IF_CLASS(klass); + + dc->desc =3D "Thunderbolt PCI Express to PCI Bridge"; + adevc->build_dev_aml =3D build_thunderbolt_pcie_pci_bridge_aml; +} + +static const TypeInfo thunderbolt_pcie_pci_bridge_info =3D { + .name =3D TYPE_THUNDERBOLT_PCIE_PCI_BRIDGE, + .parent =3D "pcie-pci-bridge", + .class_init =3D 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) diff --git a/hw/pci-bridge/thunderbolt_root_port.c b/hw/pci-bridge/thunderb= olt_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 the= ir + * 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 presen= t, + bool notify); +static void thunderbolt_root_port_sync_connected(ThunderboltRootPort *trp, + bool notify); + +static Aml *build_device_properties_dsd(Aml *properties) +{ + Aml *dsd; + + dsd =3D 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 =3D 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 =3D 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 =3D THUNDERBOLT_ROOT_PORT(obj); + + if (trp->connected =3D=3D value) { + return; + } + + trp->connected =3D 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 =3D PCI_DEVICE(trp); + PCIBus *bus =3D pci_bridge_get_sec_bus(PCI_BRIDGE(rp)); + bool present =3D false; + int devfn; + + for (devfn =3D 0; devfn < PCI_DEVFN_MAX; devfn++) { + PCIDevice *child =3D bus->devices[devfn]; + + if (!child) { + continue; + } + + present =3D 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 =3D 0; devfn < PCI_DEVFN_MAX; devfn++) { + PCIDevice *child =3D bus->devices[devfn]; + + if (child) { + pci_set_enabled(child, false); + } + } + } +} + +static void thunderbolt_root_port_machine_done(Notifier *notifier, void *d= ata) +{ + ThunderboltRootPort *trp =3D container_of(notifier, ThunderboltRootPor= t, + machine_done); + + thunderbolt_root_port_sync_connected(trp, false); +} + +static void thunderbolt_root_port_device_realize(DeviceListener *listener, + DeviceState *dev) +{ + ThunderboltRootPort *trp =3D container_of(listener, ThunderboltRootPor= t, + 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 =3D PCI_BUS(dev->parent_bus); + if (bus->parent_dev !=3D PCI_DEVICE(trp)) { + return; + } + + thunderbolt_root_port_sync_connected(trp, false); +} + +static void thunderbolt_root_port_instance_init(Object *obj) +{ + ThunderboltRootPort *trp =3D THUNDERBOLT_ROOT_PORT(obj); + + trp->connected =3D 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 presen= t, + bool notify) +{ + uint8_t *exp_cap =3D dev->config + dev->exp.exp_cap; + uint32_t lnkcap =3D 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 =3D 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, fal= se); +} + +static void thunderbolt_root_port_unplug_request(HotplugHandler *hotplug_d= ev, + 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 =3D 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 **e= rrp) +{ + int rc; + + rc =3D msix_init_exclusive_bar(dev, THUNDERBOLT_ROOT_PORT_MSIX_NR_VECT= OR, + 0, errp); + if (rc < 0) { + assert(rc =3D=3D -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 =3D + 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 =3D THUNDERBOLT_ROOT_PORT(obj); + PCIDevice *dev =3D PCI_DEVICE(obj); + DeviceState *qdev =3D 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 =3D PCIE_PORT(dev); + PCIESlot *s =3D PCIE_SLOT(dev); + PCIDeviceClass *dc =3D PCI_DEVICE_GET_CLASS(dev); + PCIERootPortClass *rpc =3D 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 =3D 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 =3D rpc->interrupts_init(dev, errp); + if (rc < 0) { + goto err_bridge; + } + } + + rc =3D 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 =3D pcie_chassis_add_slot(s); + if (rc < 0) { + error_setg(errp, "Can't add chassis slot, error %d", rc); + goto err_pcie_cap; + } + + rc =3D 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 =3D + thunderbolt_root_port_device_realize; + device_listener_register(&THUNDERBOLT_ROOT_PORT(dev)->listener); + + THUNDERBOLT_ROOT_PORT(dev)->machine_done.notify =3D + 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 =3D PCIE_ROOT_PORT_GET_CLASS(dev); + PCIESlot *s =3D 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 =3D THUNDERBOLT_ROOT_PORT(dev); + PCIESlot *slot =3D PCIE_SLOT(dev); + PCIERootPortClass *rpc =3D PCIE_ROOT_PORT_GET_CLASS(dev); + Error *local_err =3D 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 =3D=3D (uint64_t)-1 && slot->hotplug) { + trp->res_reserve.io =3D THUNDERBOLT_ROOT_DEFAULT_IO_RANGE; + } + + rc =3D 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 =3D PCI_DEVICE(dev); + + pci_word_test_and_clear_mask(pdev->wmask + PCI_COMMAND, + PCI_COMMAND_IO); + pdev->wmask[PCI_IO_BASE] =3D 0; + pdev->wmask[PCI_IO_LIMIT] =3D 0; + } +} + +static bool thunderbolt_root_port_test_migrate_msix(void *opaque, + int version_id) +{ + ThunderboltRootPort *trp =3D opaque; + + return trp->migrate_msix; +} + +static const VMStateDescription vmstate_thunderbolt_root_port =3D { + .name =3D "thunderbolt-root-port", + .priority =3D MIG_PRI_PCI_BUS, + .version_id =3D 1, + .minimum_version_id =3D 1, + .post_load =3D pcie_cap_slot_post_load, + .fields =3D (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[] =3D { + 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 =3D DEVICE_CLASS(klass); + PCIDeviceClass *k =3D PCI_DEVICE_CLASS(klass); + PCIERootPortClass *rpc =3D PCIE_ROOT_PORT_CLASS(klass); + AcpiDevAmlIfClass *adevc =3D ACPI_DEV_AML_IF_CLASS(klass); + ResettableClass *rc =3D RESETTABLE_CLASS(klass); + HotplugHandlerClass *hc =3D HOTPLUG_HANDLER_CLASS(klass); + + k->vendor_id =3D PCI_VENDOR_ID_REDHAT; + k->device_id =3D PCI_DEVICE_ID_REDHAT_PCIE_RP; + k->config_write =3D thunderbolt_root_port_write_config; + k->realize =3D thunderbolt_root_port_realize_pci; + k->exit =3D thunderbolt_root_port_exit; + dc->desc =3D "Thunderbolt PCI Express Root Port"; + dc->vmsd =3D &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 =3D thunderbolt_root_port_reset_hold; + + hc->pre_plug =3D thunderbolt_root_port_pre_plug; + hc->plug =3D thunderbolt_root_port_plug; + hc->unplug =3D thunderbolt_root_port_unplug; + hc->unplug_request =3D thunderbolt_root_port_unplug_request; + + rpc->aer_vector =3D thunderbolt_root_port_aer_vector; + rpc->interrupts_init =3D thunderbolt_root_port_interrupts_init; + rpc->interrupts_uninit =3D thunderbolt_root_port_interrupts_uninit; + rpc->aer_offset =3D THUNDERBOLT_ROOT_PORT_AER_OFFSET; + rpc->acs_offset =3D THUNDERBOLT_ROOT_PORT_ACS_OFFSET; + adevc->build_dev_aml =3D build_thunderbolt_root_port_aml; +} + +static const TypeInfo thunderbolt_root_port_info =3D { + .name =3D TYPE_THUNDERBOLT_ROOT_PORT, + .parent =3D TYPE_PCIE_ROOT_PORT, + .instance_size =3D sizeof(ThunderboltRootPort), + .instance_init =3D thunderbolt_root_port_instance_init, + .class_init =3D thunderbolt_root_port_class_init, + .interfaces =3D (const InterfaceInfo[]) { + { TYPE_ACPI_DEV_AML_IF }, + { }, + }, +}; + +static const TypeInfo thunderbolt_bus_info =3D { + .name =3D TYPE_THUNDERBOLT_BUS, + .parent =3D 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) 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 @@ =20 #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 =3D 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"), =3D=3D, expected); + qobject_unref(rsp); +} =20 static void pci_multihead(void) { @@ -18,6 +33,51 @@ static void pci_multihead(void) qtest_quit(qts); } =20 +static void thunderbolt_vga_hotplug(void) +{ + QTestState *qts; + QDict *rsp; + QDict *err; + + qts =3D qtest_init("-machine q35 -display none -nodefaults -vga none " + "-device VGA,id=3Dbootvga,bus=3Dpcie.0,addr=3D0x02 " + "-device thunderbolt-root-port,id=3Dtbrp0,bus=3Dpcie.= 0," + "chassis=3D1,slot=3D1,addr=3D0x05"); + + rsp =3D qtest_qmp(qts, "{ 'execute': 'device_add', " + "'arguments': {" + "'driver': 'VGA'," + "'id': 'badstd'," + "'bus': 'tbrp0'," + "'addr': '0x01'" + "} }"); + g_assert(qdict_haskey(rsp, "error")); + err =3D qdict_get_qdict(rsp, "error"); + g_assert_cmpstr(qdict_get_str(err, "desc"), =3D=3D, + "Device 'VGA' does not support hotplugging"); + qobject_unref(rsp); + + rsp =3D 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", 600= 00); + + 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[] =3D { "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); } =20 + 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(); } --=20 2.47.3