diff --git a/drivers/hv/hv_common.c b/drivers/hv/hv_common.c index 8896fb4809b5..4f3bb9b4e87c 100644 --- a/drivers/hv/hv_common.c +++ b/drivers/hv/hv_common.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -78,6 +79,22 @@ static struct ctl_table_header *hv_ctl_table_hdr; u8 * __percpu *hv_synic_eventring_tail; EXPORT_SYMBOL_GPL(hv_synic_eventring_tail); +struct hv_pci_busdata { + int pci_domain_nr; + u32 logical_dev_id_prefix; + struct rhash_head node; + struct rcu_head rcu; +}; + +static struct rhashtable hv_pci_bus_ht; +static bool hv_pci_bus_ht_initialized; + +static const struct rhashtable_params hv_pci_bus_ht_params = { + .key_len = sizeof_field(struct hv_pci_busdata, pci_domain_nr), + .key_offset = offsetof(struct hv_pci_busdata, pci_domain_nr), + .head_offset = offsetof(struct hv_pci_busdata, node), +}; + /* * Hyper-V specific initialization and shutdown code that is * common across all architectures. Called from architecture @@ -86,6 +103,11 @@ EXPORT_SYMBOL_GPL(hv_synic_eventring_tail); void __init hv_common_free(void) { + if (hv_pci_bus_ht_initialized) { + rhashtable_destroy(&hv_pci_bus_ht); + hv_pci_bus_ht_initialized = false; + } + unregister_sysctl_table(hv_ctl_table_hdr); hv_ctl_table_hdr = NULL; @@ -315,6 +337,7 @@ u8 __init get_vtl(void) int __init hv_common_init(void) { int i; + int ret; union hv_hypervisor_version_info version; /* Get information about the Microsoft Hypervisor version */ @@ -394,6 +417,13 @@ int __init hv_common_init(void) for (i = 0; i < nr_cpu_ids; i++) hv_vp_index[i] = VP_INVAL; + ret = rhashtable_init(&hv_pci_bus_ht, &hv_pci_bus_ht_params); + if (ret) { + hv_common_free(); + return ret; + } + hv_pci_bus_ht_initialized = true; + return 0; } @@ -862,3 +892,87 @@ const char *hv_result_to_string(u64 status) return "Unknown"; } EXPORT_SYMBOL_GPL(hv_result_to_string); + +/* + * Logical device ID registry for Hyper-V PCI buses. The pci-hyperv + * driver registers each bus's logical device ID prefix before scanning + * its devices. Consumers look up the prefix by PCI domain number when + * building logical device IDs for Hyper-V interfaces. + */ +int hv_pci_register_dev_id(int pci_domain_nr, u32 logical_dev_id_prefix) +{ + struct hv_pci_busdata *bus, *new; + int ret; + + new = kzalloc_obj(*new, GFP_KERNEL); + if (!new) + return -ENOMEM; + + new->pci_domain_nr = pci_domain_nr; + new->logical_dev_id_prefix = logical_dev_id_prefix; + + bus = rhashtable_lookup_get_insert_fast(&hv_pci_bus_ht, &new->node, + hv_pci_bus_ht_params); + if (IS_ERR(bus)) { + ret = PTR_ERR(bus); + goto free_new; + } + + if (WARN_ONCE(bus != NULL, + "Hyper-V PCI domain %04x is already registered\n", + pci_domain_nr)) { + ret = -EEXIST; + goto free_new; + } + + return 0; + +free_new: + kfree(new); + return ret; +} +EXPORT_SYMBOL_FOR_MODULES(hv_pci_register_dev_id, "pci-hyperv"); + +void hv_pci_unregister_dev_id(int pci_domain_nr) +{ + struct hv_pci_busdata *bus; + int ret = -ENOENT; + + rcu_read_lock(); + bus = rhashtable_lookup(&hv_pci_bus_ht, &pci_domain_nr, + hv_pci_bus_ht_params); + if (bus) + ret = rhashtable_remove_fast(&hv_pci_bus_ht, &bus->node, + hv_pci_bus_ht_params); + rcu_read_unlock(); + + if (WARN_ONCE(ret, + "Failed to unregister Hyper-V PCI domain %04x\n", + pci_domain_nr)) + return; + + kfree_rcu(bus, rcu); +} +EXPORT_SYMBOL_FOR_MODULES(hv_pci_unregister_dev_id, "pci-hyperv"); + +/* + * Look up the logical device ID prefix registered for @pci_domain_nr. + * Returns 0 on success with *prefix filled in; -ENODEV if no entry is + * registered for that PCI domain. + */ +int hv_pci_lookup_dev_id(int pci_domain_nr, u32 *prefix) +{ + struct hv_pci_busdata *bus; + int ret = -ENODEV; + + rcu_read_lock(); + bus = rhashtable_lookup(&hv_pci_bus_ht, &pci_domain_nr, + hv_pci_bus_ht_params); + if (bus) { + *prefix = bus->logical_dev_id_prefix; + ret = 0; + } + rcu_read_unlock(); + + return ret; +} diff --git a/drivers/pci/controller/pci-hyperv.c b/drivers/pci/controller/pci-hyperv.c index 89816a2bd7cd..db70be47af6e 100644 --- a/drivers/pci/controller/pci-hyperv.c +++ b/drivers/pci/controller/pci-hyperv.c @@ -641,10 +641,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 = (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) | + params->device_id = hv_build_logical_dev_id_prefix(hbus->hdev) | PCI_FUNC(pdev->devfn); params->int_target.vector = hv_msi_get_int_vector(data); @@ -3716,6 +3713,7 @@ static int hv_pci_probe(struct hv_device *hdev, struct hv_pcibus_device *hbus; int ret, dom; u16 dom_req; + u32 prefix; char *name; bridge = devm_pci_alloc_host_bridge(&hdev->device, 0); @@ -3858,13 +3856,22 @@ static int hv_pci_probe(struct hv_device *hdev, hbus->state = hv_pcibus_probed; - ret = create_root_hv_pci_bus(hbus); + /* Register the bus before scanning any devices on it. */ + prefix = hv_build_logical_dev_id_prefix(hdev); + + ret = hv_pci_register_dev_id(dom, prefix); if (ret) goto free_windows; + ret = create_root_hv_pci_bus(hbus); + if (ret) + goto unregister_pci_bus; + mutex_unlock(&hbus->state_lock); return 0; +unregister_pci_bus: + hv_pci_unregister_dev_id(dom); free_windows: hv_pci_free_bridge_windows(hbus); exit_d0: @@ -3978,6 +3985,8 @@ static void hv_pci_remove(struct hv_device *hdev) hbus = hv_get_drvdata(hdev); if (hbus->state == hv_pcibus_installed) { + int dom = hbus->bridge->domain_nr; + tasklet_disable(&hdev->channel->callback_event); hbus->state = hv_pcibus_removing; tasklet_enable(&hdev->channel->callback_event); @@ -3995,6 +4004,8 @@ static void hv_pci_remove(struct hv_device *hdev) hv_pci_remove_slots(hbus); pci_remove_root_bus(hbus->bridge->bus); pci_unlock_rescan_remove(); + + hv_pci_unregister_dev_id(dom); } hv_pci_bus_exit(hdev, false); diff --git a/include/asm-generic/mshyperv.h b/include/asm-generic/mshyperv.h index bf601d67cecb..34d4c332449c 100644 --- a/include/asm-generic/mshyperv.h +++ b/include/asm-generic/mshyperv.h @@ -73,6 +73,10 @@ extern enum hv_partition_type hv_curr_partition_type; extern void * __percpu *hyperv_pcpu_input_arg; extern void * __percpu *hyperv_pcpu_output_arg; +int hv_pci_register_dev_id(int pci_domain_nr, u32 logical_dev_id_prefix); +void hv_pci_unregister_dev_id(int pci_domain_nr); +int hv_pci_lookup_dev_id(int pci_domain_nr, u32 *prefix); + u64 hv_do_hypercall(u64 control, void *inputaddr, void *outputaddr); u64 hv_do_fast_hypercall8(u16 control, u64 input8); u64 hv_do_fast_hypercall16(u16 control, u64 input1, u64 input2); diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index 9e109d91aa14..e61f9a4cb7c3 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -1299,6 +1299,14 @@ struct hv_device { #define device_to_hv_device(d) container_of_const(d, struct hv_device, device) #define drv_to_hv_drv(d) container_of_const(d, struct hv_driver, driver) +static inline u32 hv_build_logical_dev_id_prefix(struct hv_device *hdev) +{ + return ((u32)hdev->dev_instance.b[5] << 24) | + ((u32)hdev->dev_instance.b[4] << 16) | + ((u32)hdev->dev_instance.b[7] << 8) | + (hdev->dev_instance.b[6] & 0xf8u); +} + static inline void hv_set_drvdata(struct hv_device *dev, void *data) { dev_set_drvdata(&dev->device, data);