Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a40c4b37 authored by Bjorn Helgaas's avatar Bjorn Helgaas
Browse files

Merge branch 'remotes/lorenzo/pci/hv'

  - Fix Hyper-V use-after-free in pci_dev removal (Dexuan Cui)

  - Fix Hyper-V build error in non-sysfs config (Randy Dunlap)

  - Reallocate to avoid Hyper-V domain number collisions (Haiyang Zhang)

  - Use Hyper-V instance ID bytes 4-5 to reduce domain collisions (Haiyang
    Zhang)

* remotes/lorenzo/pci/hv:
  PCI: hv: Use bytes 4 and 5 from instance ID as the PCI domain numbers
  PCI: hv: Detect and fix Hyper-V PCI domain number collision
  PCI: pci-hyperv: Fix build errors on non-SYSFS config
  PCI: hv: Avoid use of hv_pci_dev->pci_slot after freeing it
parents b83e445d f73f8a50
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -181,7 +181,7 @@ config PCI_LABEL

config PCI_HYPERV
        tristate "Hyper-V PCI Frontend"
        depends on X86 && HYPERV && PCI_MSI && PCI_MSI_IRQ_DOMAIN && X86_64
        depends on X86_64 && HYPERV && PCI_MSI && PCI_MSI_IRQ_DOMAIN && SYSFS
        help
          The PCI device frontend driver allows the kernel to import arbitrary
          PCI devices from a PCI backend to support PCI driver domains.
+80 −14
Original line number Diff line number Diff line
@@ -2510,6 +2510,48 @@ static void put_hvpcibus(struct hv_pcibus_device *hbus)
		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
 * @hdev:	VMBus's tracking struct for this root PCI bus
@@ -2521,6 +2563,7 @@ static int hv_pci_probe(struct hv_device *hdev,
			const struct hv_vmbus_device_id *dev_id)
{
	struct hv_pcibus_device *hbus;
	u16 dom_req, dom;
	int ret;

	/*
@@ -2535,19 +2578,34 @@ static int hv_pci_probe(struct hv_device *hdev,
	hbus->state = hv_pcibus_init;

	/*
	 * The PCI bus "domain" is what is called "segment" in ACPI and
	 * other specs.  Pull it from the instance ID, to get something
	 * unique.  Bytes 8 and 9 are what is used in Windows guests, so
	 * do the same thing for consistency.  Note that, since this code
	 * 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
	 * a physical PCI bus (which is actually emulated by the
	 * hypervisor) is domain 0 and (2) there will be no overlap
	 * between domains derived from these instance IDs in the same
	 * VM.
	 */
	hbus->sysdata.domain = hdev->dev_instance.b[9] |
			       hdev->dev_instance.b[8] << 8;
	 * The PCI bus "domain" is what is called "segment" in ACPI and other
	 * specs. Pull it from the instance ID, to get something usually
	 * unique. In rare cases of collision, we will find out another number
	 * not in use.
	 *
	 * Note that, since this code only runs in a Hyper-V VM, Hyper-V
	 * together with this guest driver can guarantee that (1) The only
	 * domain used by Gen1 VMs for something that looks like a physical
	 * PCI bus (which is actually emulated by the hypervisor) is domain 0.
	 * (2) There will be no overlap between domains (after fixing possible
	 * collisions) in the same VM.
	 */
	dom_req = hdev->dev_instance.b[5] << 8 | hdev->dev_instance.b[4];
	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;
	refcount_set(&hbus->remove_lock, 1);
@@ -2562,7 +2620,7 @@ static int hv_pci_probe(struct hv_device *hdev,
					   hbus->sysdata.domain);
	if (!hbus->wq) {
		ret = -ENOMEM;
		goto free_bus;
		goto free_dom;
	}

	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,
	vmbus_close(hdev->channel);
destroy_wq:
	destroy_workqueue(hbus->wq);
free_dom:
	hv_put_dom_num(hbus->sysdata.domain);
free_bus:
	free_page((unsigned long)hbus);
	return ret;
@@ -2701,8 +2761,8 @@ static int hv_pci_remove(struct hv_device *hdev)
		/* Remove the bus from PCI's point of view. */
		pci_lock_rescan_remove();
		pci_stop_root_bus(hbus->pci_bus);
		pci_remove_root_bus(hbus->pci_bus);
		hv_pci_remove_slots(hbus);
		pci_remove_root_bus(hbus->pci_bus);
		pci_unlock_rescan_remove();
		hbus->state = hv_pcibus_removed;
	}
@@ -2720,6 +2780,9 @@ static int hv_pci_remove(struct hv_device *hdev)
	put_hvpcibus(hbus);
	wait_for_completion(&hbus->remove_event);
	destroy_workqueue(hbus->wq);

	hv_put_dom_num(hbus->sysdata.domain);

	free_page((unsigned long)hbus);
	return 0;
}
@@ -2747,6 +2810,9 @@ static void __exit exit_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);
}