Re: [PATCH V2 08/11] PCI: hv: Build device id for a VMBus device, export PCI devid function

Easwar Hariharan
2026-05-01T18:38:52Z

On 4/30/2026 5:41 PM, Mukesh R wrote:
> On Hyper-V, most hypercalls related to PCI passthru to map/unmap regions,
> interrupts, etc need a device ID as a parameter. This device ID refers
> to that specific device during the lifetime of passthru.
>
> An L1VH VM only contains VMBus based devices. A device ID for a VMBus
> device is slightly different in that it uses the hv_pcibus_device info
> for building it to make sure it matches exactly what the hypervisor
> expects. This VMBus based device ID is needed when attaching devices in
> an L1VH based guest VM. Before building it, a check is done to make sure
> the device is a valid VMBus device.
>
> In remaining cases, PCI device ID is used. So, also make PCI device ID
> build function hv_build_devid_type_pci() public.
>
> Signed-off-by: Mukesh R <mrathor@linux.microsoft.com>
> ---
> arch/x86/hyperv/irqdomain.c | 9 +++++----
> arch/x86/include/asm/mshyperv.h | 6 ++++++
> drivers/pci/controller/pci-hyperv.c | 24 ++++++++++++++++++++++++
> include/asm-generic/mshyperv.h | 8 ++++++++
> 4 files changed, 43 insertions(+), 4 deletions(-)
>
Please see the discussion on a similar patch for the guest IOMMU driver here:
https://lore.kernel.org/all/2dabc1b8-0cf0-4fc8-9cd4-cce60adfc05e@linux.microsoft.com/

My implementation of that approach is below:

--------------------x8-------------------------------------------------
commit 233e90466cb79b3a952806206d89164ca2a1428a
Author: Easwar Hariharan <easwar.hariharan@linux.microsoft.com>
Date: Thu Apr 30 20:46:05 2026 +0000

PCI: hv: Inform IOMMU when new devices are offered

Hyper-V uses a logical device ID to identify a PCI endpoint device for
child partitions. This ID is built from parts of the virtual PCI bus
GUID and the function number of the PCI BDF. This ID is required to
identify devices to the hypervisor for IOMMU management hypercalls used
by the Hyper-V IOMMU driver.

Inform the IOMMU driver of the Hyper-V vPCI bus GUID so it can build the
logical device ID for vPCI devices, and factor the logic for building
this ID into a standalone helper function for clarity and easier
maintenance in tandem with the IOMMU driver's version.

Signed-off-by: Easwar Hariharan <easwar.hariharan@linux.microsoft.com>

diff --git a/arch/x86/include/asm/mshyperv.h b/arch/x86/include/asm/mshyperv.h
index 20d947c2c758..d1070a4a24eb 100644
--- a/arch/x86/include/asm/mshyperv.h
+++ b/arch/x86/include/asm/mshyperv.h
@@ -315,6 +315,9 @@ static inline void __mshv_vtl_return_call(struct mshv_vtl_cpu_context *vtl0) {}

#ifdef CONFIG_HYPERV_PVIOMMU
int __init hv_iommu_init(void);
+int hv_iommu_inform(int dom, guid_t bus_instance_guid);
+#else
+int hv_iommu_inform(int dom, guid_t bus_instance_guid) {}
#endif

#include <asm-generic/mshyperv.h>
diff --git a/drivers/iommu/hyperv/iommu.c b/drivers/iommu/hyperv/iommu.c
index ad7000f4566e..34a39c3b89b4 100644
--- a/drivers/iommu/hyperv/iommu.c
+++ b/drivers/iommu/hyperv/iommu.c
@@ -8,6 +8,7 @@

#include <linux/iommu.h>
#include <linux/pci.h>
+#include <linux/uuid.h>
#include <linux/dma-map-ops.h>
#include <linux/generic_pt/iommu.h>
#include <linux/syscore_ops.h>
@@ -26,12 +27,86 @@ static struct hv_iommu_domain hv_blocking_domain;
static const struct iommu_domain_ops hv_iommu_identity_domain_ops;
static const struct iommu_domain_ops hv_iommu_blocking_domain_ops;
static struct iommu_ops hv_iommu_ops;
+static struct list_head hv_pci_bus_list;

#define hv_iommu_present(iommu_cap) (iommu_cap & HV_IOMMU_CAP_PRESENT)
#define hv_iommu_s1_domain_supported(iommu_cap) (iommu_cap & HV_IOMMU_CAP_S1)
#define hv_iommu_5lvl_supported(iommu_cap) (iommu_cap & HV_IOMMU_CAP_S1_5LVL)
#define hv_iommu_ats_supported(iommu_cap) (iommu_cap & HV_IOMMU_CAP_ATS)

+/*
+ * Build a "Device Logical ID" out of this PCI bus's instance GUID and the
+ * function number of the device.
+ * This is identical and should be maintained in sync with
+ * hv_pci_build_logical_dev_id() in pci-hyperv.c. Only repeated here to avoid
+ * dependency of a built-in driver (iommu) on a module (pci-hyperv) and to
+ * maintain the correct direction of dependency of the PCI driver on the IOMMU
+ * instead of vice versa
+ */
+static u64 hv_iommu_build_logical_dev_id(struct pci_dev *pdev,
+ guid_t bus_instance_guid)
+{
+ return (u64)((bus_instance_guid.b[5] << 24) |
+ (bus_instance_guid.b[4] << 16) |
+ (bus_instance_guid.b[7] << 8) |
+ (bus_instance_guid.b[6] & 0xf8) |
+ PCI_FUNC(pdev->devfn));
+}
+
+static struct hv_pci_busdata *find_hv_pci_bus(int dom)
+{
+ struct hv_pci_busdata *curr = NULL;
+
+ list_for_each_entry(curr, &hv_pci_bus_list, list) {
+ if (curr->dom == dom) {
+ return curr;
+ }
+ }
+ return NULL;
+}
+
+#define INVALID_LOGICAL_DEV_ID 0
+static u64 hv_iommu_get_logical_dev_id(struct pci_dev *pdev)
+{
+ struct hv_pci_busdata *bus = find_hv_pci_bus(pci_domain_nr(pdev->bus));
+
+ if (!bus) {
+ dev_WARN(&pdev->dev,
+ "received device ID request for missing hv_pci bus\n");
+ return INVALID_LOGICAL_DEV_ID;
+ }
+
+ return hv_iommu_build_logical_dev_id(pdev, bus->bus_instance_guid);
+}
+
+int hv_iommu_inform(int dom, guid_t bus_instance_guid)
+{
+ struct hv_pci_busdata *found, *new;
+
+ /* Don't spend memory if there is no consumer */
+ if (no_iommu || !iommu_detected)
+ return 0;
+
+ found = find_hv_pci_bus(dom);
+ if (found && !guid_equal(&found->bus_instance_guid, &bus_instance_guid)) {
+ found->bus_instance_guid = bus_instance_guid;
+ return 0;
+ }
+
+ new = kzalloc_obj(*new);
+ if (!new) {
+ pr_info("No memory to allocate hv_pci bus data\n");
+ return -ENOMEM;
+ }
+
+ new->dom = dom;
+ new->bus_instance_guid = bus_instance_guid;
+ list_add(&new->list, &hv_pci_bus_list);
+
+ return 0;
+}
+EXPORT_SYMBOL_FOR_MODULES(hv_iommu_inform, "pci-hyperv");
+
static int hv_create_device_domain(struct hv_iommu_domain *hv_domain, u32 domain_stage)
{
int ret;
@@ -143,7 +218,7 @@ static void hv_iommu_detach_dev(struct iommu_domain *domain, struct device *dev)
input = *this_cpu_ptr(hyperv_pcpu_input_arg);
memset(input, 0, sizeof(*input));
input->partition_id = HV_PARTITION_ID_SELF;
- input->device_id.as_uint64 = hv_build_logical_dev_id(pdev);
+ input->device_id.as_uint64 = hv_iommu_get_logical_dev_id(pdev);
status = hv_do_hypercall(HVCALL_DETACH_DEVICE_DOMAIN, input, NULL);

local_irq_restore(flags);
@@ -185,7 +260,7 @@ static int hv_iommu_attach_dev(struct iommu_domain *domain, struct device *dev,
input = *this_cpu_ptr(hyperv_pcpu_input_arg);
memset(input, 0, sizeof(*input));
input->device_domain = hv_domain->device_domain;
- input->device_id.as_uint64 = hv_build_logical_dev_id(pdev);
+ input->device_id.as_uint64 = hv_iommu_get_logical_dev_id(pdev);
status = hv_do_hypercall(HVCALL_ATTACH_DEVICE_DOMAIN, input, NULL);

local_irq_restore(flags);
@@ -213,7 +288,7 @@ static int hv_iommu_get_logical_device_property(struct device *dev,
output = *this_cpu_ptr(hyperv_pcpu_input_arg) + sizeof(*input);
memset(input, 0, sizeof(*input));
input->partition_id = HV_PARTITION_ID_SELF;
- input->logical_device_id = hv_build_logical_dev_id(to_pci_dev(dev));
+ input->logical_device_id = hv_iommu_get_logical_dev_id(to_pci_dev(dev));
input->code = code;
status = hv_do_hypercall(HVCALL_GET_LOGICAL_DEVICE_PROPERTY, input, output);
*property = *output;
@@ -665,6 +740,7 @@ int __init hv_iommu_init(void)

iommu_detected = 1;
pci_request_acs();
+ INIT_LIST_HEAD(&hv_pci_bus_list);

hv_iommu = kzalloc(sizeof(*hv_iommu), GFP_KERNEL);
if (!hv_iommu)
diff --git a/drivers/iommu/hyperv/iommu.h b/drivers/iommu/hyperv/iommu.h
index 8829176ddb51..5b2d1c41c101 100644
--- a/drivers/iommu/hyperv/iommu.h
+++ b/drivers/iommu/hyperv/iommu.h
@@ -36,6 +36,12 @@ struct hv_iommu_domain {
u64 pgsize_bitmap;
};

+struct hv_pci_busdata {
+ int dom;
+ guid_t bus_instance_guid;
+ struct list_head list;
+};
+
struct hv_iommu_endpoint {
struct device *dev;
struct hv_iommu_dev *hv_iommu;
diff --git a/drivers/pci/controller/pci-hyperv.c b/drivers/pci/controller/pci-hyperv.c
index 3b7adf28ee09..5d4fb2c2f60a 100644
--- a/drivers/pci/controller/pci-hyperv.c
+++ b/drivers/pci/controller/pci-hyperv.c
@@ -597,23 +597,19 @@ static unsigned int hv_msi_get_int_vector(struct irq_data *data)

#define hv_msi_prepare pci_msi_prepare

-/**
+#define INVALID_LOGICAL_DEV_ID 0
+/*
* Build a "Device Logical ID" out of this PCI bus's instance GUID and the
* function number of the device.
*/
-u64 hv_build_logical_dev_id(struct pci_dev *pdev)
+static u64 hv_pci_build_logical_dev_id(struct pci_dev *pdev, guid_t bus_instance_guid)
{
- struct pci_bus *pbus = pdev->bus;
- struct hv_pcibus_device *hbus = container_of(pbus->sysdata,
- struct hv_pcibus_device, sysdata);
-
- return (u64)((hbus->hdev->dev_instance.b[5] << 24) |
- (hbus->hdev->dev_instance.b[4] << 16) |
- (hbus->hdev->dev_instance.b[7] << 8) |
- (hbus->hdev->dev_instance.b[6] & 0xf8) |
+ return (u64)((bus_instance_guid.b[5] << 24) |
+ (bus_instance_guid.b[4] << 16) |
+ (bus_instance_guid.b[7] << 8) |
+ (bus_instance_guid.b[6] & 0xf8) |
PCI_FUNC(pdev->devfn));
}
-EXPORT_SYMBOL_GPL(hv_build_logical_dev_id);

/**
* hv_irq_retarget_interrupt() - "Unmask" the IRQ by setting its current
@@ -657,7 +653,7 @@ static void hv_irq_retarget_interrupt(struct irq_data *data)
params->int_entry.source = HV_INTERRUPT_SOURCE_MSI;
params->int_entry.msi_entry.address.as_uint32 = int_desc->address & 0xffffffff;
params->int_entry.msi_entry.data.as_uint32 = int_desc->data;
- params->device_id = hv_build_logical_dev_id(pdev);
+ params->device_id = hv_pci_build_logical_dev_id(pdev, hbus->hdev->dev_instance);
params->int_target.vector = hv_msi_get_int_vector(data);

if (hbus->protocol_version >= PCI_PROTOCOL_VERSION_1_2) {
@@ -3869,6 +3865,9 @@ static int hv_pci_probe(struct hv_device *hdev,

hbus->state = hv_pcibus_probed;

+ /* Inform the IOMMU of the bus GUID of devices headed their way */
+ hv_iommu_inform(dom, hdev->dev_instance);
+
ret = create_root_hv_pci_bus(hbus);
if (ret)
goto free_windows;

--------------------8x-------------------------------------------------

Thanks,
Easwar (he/him)