Commit be700103 authored by Haiyang Zhang's avatar Haiyang Zhang Committed by Lorenzo Pieralisi

PCI: hv: Detect and fix Hyper-V PCI domain number collision

Currently in Azure cloud, for passthrough devices, the host sets the
device instance ID's bytes 8 - 15 to a value derived from the host HWID,
which is the same on all devices in a VM. So, the device instance ID's
bytes 8 and 9 provided by the host are no longer unique. This affects
all Azure hosts since July 2018, and can cause device passthrough to VMs
to fail because the bytes 8 and 9 are used as PCI domain number.
Collision of domain numbers will cause the second device with the same
domain number fail to load.

In the cases of collision, we will detect and find another number that is
not in use.
Suggested-by: default avatarMichael Kelley <mikelley@microsoft.com>
Signed-off-by: default avatarHaiyang Zhang <haiyangz@microsoft.com>
Signed-off-by: default avatarLorenzo Pieralisi <lorenzo.pieralisi@arm.com>
Acked-by: default avatarSasha Levin <sashal@kernel.org>
parent f58ba5e3
...@@ -2510,6 +2510,48 @@ static void put_hvpcibus(struct hv_pcibus_device *hbus) ...@@ -2510,6 +2510,48 @@ static void put_hvpcibus(struct hv_pcibus_device *hbus)
complete(&hbus->remove_event); complete(&hbus->remove_event);
} }
#define HVPCI_DOM_MAP_SIZE (64 * 1024)
static DECLARE_BITMAP(hvpci_dom_map, HVPCI_DOM_MAP_SIZE);
/*
* PCI domain number 0 is used by emulated devices on Gen1 VMs, so define 0
* as invalid for passthrough PCI devices of this driver.
*/
#define HVPCI_DOM_INVALID 0
/**
* hv_get_dom_num() - Get a valid PCI domain number
* Check if the PCI domain number is in use, and return another number if
* it is in use.
*
* @dom: Requested domain number
*
* return: domain number on success, HVPCI_DOM_INVALID on failure
*/
static u16 hv_get_dom_num(u16 dom)
{
unsigned int i;
if (test_and_set_bit(dom, hvpci_dom_map) == 0)
return dom;
for_each_clear_bit(i, hvpci_dom_map, HVPCI_DOM_MAP_SIZE) {
if (test_and_set_bit(i, hvpci_dom_map) == 0)
return i;
}
return HVPCI_DOM_INVALID;
}
/**
* hv_put_dom_num() - Mark the PCI domain number as free
* @dom: Domain number to be freed
*/
static void hv_put_dom_num(u16 dom)
{
clear_bit(dom, hvpci_dom_map);
}
/** /**
* hv_pci_probe() - New VMBus channel probe, for a root PCI bus * hv_pci_probe() - New VMBus channel probe, for a root PCI bus
* @hdev: VMBus's tracking struct for this root PCI bus * @hdev: VMBus's tracking struct for this root PCI bus
...@@ -2521,6 +2563,7 @@ static int hv_pci_probe(struct hv_device *hdev, ...@@ -2521,6 +2563,7 @@ static int hv_pci_probe(struct hv_device *hdev,
const struct hv_vmbus_device_id *dev_id) const struct hv_vmbus_device_id *dev_id)
{ {
struct hv_pcibus_device *hbus; struct hv_pcibus_device *hbus;
u16 dom_req, dom;
int ret; int ret;
/* /*
...@@ -2535,19 +2578,34 @@ static int hv_pci_probe(struct hv_device *hdev, ...@@ -2535,19 +2578,34 @@ static int hv_pci_probe(struct hv_device *hdev,
hbus->state = hv_pcibus_init; hbus->state = hv_pcibus_init;
/* /*
* The PCI bus "domain" is what is called "segment" in ACPI and * The PCI bus "domain" is what is called "segment" in ACPI and other
* other specs. Pull it from the instance ID, to get something * specs. Pull it from the instance ID, to get something usually
* unique. Bytes 8 and 9 are what is used in Windows guests, so * unique. In rare cases of collision, we will find out another number
* do the same thing for consistency. Note that, since this code * not in use.
* only runs in a Hyper-V VM, Hyper-V can (and does) guarantee *
* that (1) the only domain in use for something that looks like * Note that, since this code only runs in a Hyper-V VM, Hyper-V
* a physical PCI bus (which is actually emulated by the * together with this guest driver can guarantee that (1) The only
* hypervisor) is domain 0 and (2) there will be no overlap * domain used by Gen1 VMs for something that looks like a physical
* between domains derived from these instance IDs in the same * PCI bus (which is actually emulated by the hypervisor) is domain 0.
* VM. * (2) There will be no overlap between domains (after fixing possible
* collisions) in the same VM.
*/ */
hbus->sysdata.domain = hdev->dev_instance.b[9] | dom_req = hdev->dev_instance.b[8] << 8 | hdev->dev_instance.b[9];
hdev->dev_instance.b[8] << 8; dom = hv_get_dom_num(dom_req);
if (dom == HVPCI_DOM_INVALID) {
dev_err(&hdev->device,
"Unable to use dom# 0x%hx or other numbers", dom_req);
ret = -EINVAL;
goto free_bus;
}
if (dom != dom_req)
dev_info(&hdev->device,
"PCI dom# 0x%hx has collision, using 0x%hx",
dom_req, dom);
hbus->sysdata.domain = dom;
hbus->hdev = hdev; hbus->hdev = hdev;
refcount_set(&hbus->remove_lock, 1); refcount_set(&hbus->remove_lock, 1);
...@@ -2562,7 +2620,7 @@ static int hv_pci_probe(struct hv_device *hdev, ...@@ -2562,7 +2620,7 @@ static int hv_pci_probe(struct hv_device *hdev,
hbus->sysdata.domain); hbus->sysdata.domain);
if (!hbus->wq) { if (!hbus->wq) {
ret = -ENOMEM; ret = -ENOMEM;
goto free_bus; goto free_dom;
} }
ret = vmbus_open(hdev->channel, pci_ring_size, pci_ring_size, NULL, 0, ret = vmbus_open(hdev->channel, pci_ring_size, pci_ring_size, NULL, 0,
...@@ -2639,6 +2697,8 @@ static int hv_pci_probe(struct hv_device *hdev, ...@@ -2639,6 +2697,8 @@ static int hv_pci_probe(struct hv_device *hdev,
vmbus_close(hdev->channel); vmbus_close(hdev->channel);
destroy_wq: destroy_wq:
destroy_workqueue(hbus->wq); destroy_workqueue(hbus->wq);
free_dom:
hv_put_dom_num(hbus->sysdata.domain);
free_bus: free_bus:
free_page((unsigned long)hbus); free_page((unsigned long)hbus);
return ret; return ret;
...@@ -2720,6 +2780,9 @@ static int hv_pci_remove(struct hv_device *hdev) ...@@ -2720,6 +2780,9 @@ static int hv_pci_remove(struct hv_device *hdev)
put_hvpcibus(hbus); put_hvpcibus(hbus);
wait_for_completion(&hbus->remove_event); wait_for_completion(&hbus->remove_event);
destroy_workqueue(hbus->wq); destroy_workqueue(hbus->wq);
hv_put_dom_num(hbus->sysdata.domain);
free_page((unsigned long)hbus); free_page((unsigned long)hbus);
return 0; return 0;
} }
...@@ -2747,6 +2810,9 @@ static void __exit exit_hv_pci_drv(void) ...@@ -2747,6 +2810,9 @@ static void __exit exit_hv_pci_drv(void)
static int __init init_hv_pci_drv(void) static int __init init_hv_pci_drv(void)
{ {
/* Set the invalid domain number's bit, so it will not be used */
set_bit(HVPCI_DOM_INVALID, hvpci_dom_map);
return vmbus_driver_register(&hv_pci_drv); return vmbus_driver_register(&hv_pci_drv);
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment