/* TODO remove once this is fixed */
static int disable_gm45_igfx_flr = 1;
+struct pcistub_sbr_entry {
+ struct list_head dev_list;
+ struct pci_dev *dev;
+};
+
+struct pcistub_sbr_list {
+ struct list_head dev_list;
+ struct pci_dev *bridge;
+ struct pci_dev *dev;
+ int find_all;
+ int err;
+};
+
/* Used to store the config state so it can be restored after
* resets.
*/
}
}
+static void pciback_walk_bus_cb(struct pci_dev *dev, void *userdata)
+{
+ struct pcistub_sbr_list *list = (struct pcistub_sbr_list*)userdata;
+ struct pcistub_sbr_entry *entry;
+ struct pci_dev *dev_tmp;
+
+ if (list->err != 0)
+ return;
+
+ /* For PCIe endpoints we are only looking for co-assigned functions */
+ if (!list->find_all &&
+ (dev->bus->number != list->dev->bus->number ||
+ PCI_SLOT(dev->devfn) != PCI_SLOT(list->dev->devfn)))
+ return;
+
+ dev_tmp = pcistub_ref_pci_dev(dev_tmp);
+ if (dev_tmp == NULL) {
+ /* not controlled by pciback, fail */
+ list->err = ENXIO;
+ return;
+ }
+
+ entry = kzalloc(sizeof(*entry), GFP_ATOMIC);
+ if (entry == NULL) {
+ pcistub_unref_pci_dev(dev_tmp);
+ list->err = ENOMEM;
+ return;
+ }
+
+ entry->dev = dev_tmp;
+ list_add_tail(&entry->dev_list, &list->dev_list);
+}
+
+static int pciback_cleanup_sbr_list(struct pcistub_sbr_list *list)
+{
+ struct pcistub_sbr_entry *entry;
+
+ list_for_each_entry(entry, &list->dev_list, dev_list) {
+ pcistub_unref_pci_dev(entry->dev);
+ kfree(entry);
+ }
+}
+
+/* Routine to find all devices and bridges that need to be reset
+ * during a secondary bus reset. For PCIe this is simply all the
+ * functions on the particular device. For PCI this is all devices
+ * and bridges below the topmost PCI/PCI-X bridge.
+ */
+static int pciback_get_sbr_list(struct pci_dev *dev,
+ struct pcistub_sbr_list *list, int pcie_endpoint)
+{
+ struct pci_dev *bridge = dev->bus->self;
+ struct pci_dev *last = NULL;
+ int exp_pos;
+ u16 exp_caps = 0;
+
+ list->err = 0;
+ list->dev = dev;
+ INIT_LIST_HEAD(&list->dev_list);
+
+ if (!pcie_endpoint) {
+ while (bridge) {
+ /* Looking for the uppermost PCI/PCI-X bridge. If it is not PCIe then
+ * this is a PCI/PCI-X bridge. If it is PCIe then except the PCIe to
+ * PCI/PCI-X type 7, the reset of the bridge types are PCIe so the last
+ * bridge encountered was the topmost PCI/PCI-X bridge which may be NULL.
+ */
+ exp_pos = pci_find_capability(bridge, PCI_CAP_ID_EXP);
+ if (exp_pos != 0) {
+ pci_read_config_word(bridge, exp_pos + PCI_EXP_FLAGS, &exp_caps);
+ if (((exp_caps & PCI_EXP_FLAGS_TYPE) >> 4) != PCI_EXP_TYPE_PCI_BRIDGE) {
+ last = bridge;
+ break;
+ }
+ }
+ last = bridge;
+ bridge = last->bus->self;
+ }
+ list->bridge = last;
+ list->find_all = 1; /* find all devices/bridges below the topmost */
+ }
+ else {
+ if (bridge) {
+ /* For PCIe, SBR logic is limited to PCIe endpoints behind a root/switch
+ * port.
+ */
+ exp_pos = pci_find_capability(bridge, PCI_CAP_ID_EXP);
+ if (likely(exp_pos != 0)) {
+ pci_read_config_word(bridge, exp_pos + PCI_EXP_FLAGS, &exp_caps);
+ exp_caps = ((exp_caps & PCI_EXP_FLAGS_TYPE) >> 4);
+ if (exp_caps == PCI_EXP_TYPE_ROOT_PORT ||
+ exp_caps == PCI_EXP_TYPE_UPSTREAM ||
+ exp_caps == PCI_EXP_TYPE_DOWNSTREAM)
+ last = bridge;
+ }
+ }
+ list->bridge = last;
+ list->find_all = 0; /* find just functions on this slot */
+ }
+
+ /* Sanity check, there may not be any appropriate bridge to reset */
+ if (!list->bridge) {
+ dev_dbg(&dev->dev, "No appropriate bridge to reset\n");
+ return ENXIO;
+ }
+
+ pci_walk_bus(list->bridge->subordinate, pciback_walk_bus_cb, list);
+
+ if (list->err) {
+ pciback_cleanup_sbr_list(list);
+ return list->err;
+ }
+
+ return 0;
+}
+
/* Ensure a device is "turned off" and ready to be exported.
* (Also see pciback_config_reset to ensure virtual configuration space is
* ready to be re-exported)
}
/* Do a secondary bus reset on a bridge. This is only done if all
- * co-assignment rules are satisfied or if it was explicitly
- * requested via pciback parameters. Currently this is used only
- * for PCIe devices where all the functions are co-assigned.
+ * co-assignment rules are satisfied and if it was explicitly
+ * requested via pciback parameters.
*/
-static int pciback_do_secondary_bus_reset(struct pci_dev *dev)
+static int pciback_do_secondary_bus_reset(struct pci_dev *dev, u32 dev_type)
{
- struct pci_dev *bridge = dev->bus->self;
+ struct pcistub_sbr_list sbr_list;
+ struct pcistub_sbr_entry *entry;
u16 pci_bctl = 0;
- struct pci_dev *dev_tmp;
- struct pci_dev *dev_arr[8];
- int i = 0, err = 0;
+ int err = 0;
- if (!bridge) {
- dev_dbg(&dev->dev, "No parent bridge to reset\n");
- return ENXIO;
+ /* Call helper to get the device list needed for the device type. */
+ err = pciback_get_sbr_list(dev, &sbr_list,
+ (dev_type == PCIBACK_TYPE_PCIe_ENDPOINT ? 1 : 0));
+ if (err) {
+ dev_warn(&dev->dev,
+ "secondary bus reset failed for device - all functions need to be co-assigned - err: %d\n", err);
+ return err;
}
- dev_dbg(&dev->dev, "doing PCIe secondary bus reset\n");
-
- /* Enumerate all devices that share the same slot for the
- * multifunction device case.
- */
- memset(dev_arr, 0, 8*sizeof(struct pci_dev*));
- dev_tmp = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, NULL);
- while (dev_tmp != NULL) {
- if (dev->bus->number == dev_tmp->bus->number &&
- PCI_SLOT(dev->devfn) == PCI_SLOT(dev_tmp->devfn)) {
-
- /* Check that the pciback driver owns the devices and get
- * a reference so they can be reset.
- */
- dev_arr[i] = pcistub_ref_pci_dev(dev_tmp);
- if (dev_arr[i] == NULL)
- goto failed;
-
- i++;
- if (i == 8) {
- pci_dev_put(dev_tmp);
- break;
- }
- }
-
- dev_tmp = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, dev_tmp);
- }
pci_block_user_cfg_access(dev);
- /* Reset the bus and restore the PCI space for all the devfn found above.
+ /* Reset the secondary bus and restore the PCI space for all the devfn found above.
*/
- pci_read_config_word(bridge, PCI_BRIDGE_CONTROL, &pci_bctl);
- pci_write_config_word(bridge, PCI_BRIDGE_CONTROL, pci_bctl | PCI_BRIDGE_CTL_BUS_RESET);
+ pci_read_config_word(sbr_list.bridge, PCI_BRIDGE_CONTROL, &pci_bctl);
+ pci_write_config_word(sbr_list.bridge, PCI_BRIDGE_CONTROL, pci_bctl | PCI_BRIDGE_CTL_BUS_RESET);
msleep(200);
- pci_write_config_word(bridge, PCI_BRIDGE_CONTROL, pci_bctl);
+ pci_write_config_word(sbr_list.bridge, PCI_BRIDGE_CONTROL, pci_bctl);
msleep(200);
-
- for (i = 0; i < 8; i++) {
- if (dev_arr[i] != NULL)
- pciback_reload_config_space(dev_arr[i]);
+
+ list_for_each_entry(entry, &sbr_list.dev_list, dev_list) {
+ pciback_reload_config_space(entry->dev);
}
- pciback_reload_config_space(dev);
-
+
pci_unblock_user_cfg_access(dev);
- goto cleanup;
+ pciback_cleanup_sbr_list(&sbr_list);
-failed:
- err = -ENXIO;
- dev_warn(&dev->dev,
- "secondary bus reset failed for device - all functions need to be co-assigned!\n");
-
-cleanup:
- for (i = 0; i < 8; i++) {
- if (dev_arr[i] != NULL)
- pcistub_unref_pci_dev(dev_arr[i]);
- }
- return err;
+ return 0;
}
/* This function is used to do a function level reset on a singe
break;
}
- /* Else for PCIe devices behind root port, attempt a secondary bus reset */
- if (dev_data->dev_type == PCIBACK_TYPE_PCIe_ENDPOINT && dev_data->use_sbr) {
- err = pciback_do_secondary_bus_reset(dev);
+ /* Else attempt a secondary bus reset if all conditions are met */
+ if (dev_data->use_sbr) {
+ err = pciback_do_secondary_bus_reset(dev, dev_data->dev_type);
if (err)
dev_warn(&dev->dev, "FLR functionality not supported; "
"attempts to use secondary bus reset unsuccessful;\n");
}
pci_read_config_word(dev, exp_pos + PCI_EXP_FLAGS, &exp_caps);
- dev_data->dev_type = ((PCI_EXP_FLAGS_TYPE >> 4) == PCI_EXP_TYPE_PCI_BRIDGE) ? PCIBACK_TYPE_PCI_BRIDGE : PCIBACK_TYPE_PCIe_BRIDGE;
+ dev_data->dev_type = (((exp_caps & PCI_EXP_FLAGS_TYPE) >> 4) == PCI_EXP_TYPE_PCI_BRIDGE) ? PCIBACK_TYPE_PCI_BRIDGE : PCIBACK_TYPE_PCIe_BRIDGE;
classify_done: