]> xenbits.xen.org Git - xenclient/kernel.git/commitdiff
Enhanced the secondary bus reset logic to handle non PCIe
authorRoss Philipson <ross.philipson@citrix.com>
Tue, 31 Mar 2009 13:53:10 +0000 (09:53 -0400)
committerRoss Philipson <ross.philipson@citrix.com>
Tue, 31 Mar 2009 13:53:10 +0000 (09:53 -0400)
devices. Fixed a bug in the classify function.

 Changes to be committed:
modified:   drivers/xen/pciback/pciback_ops.c

drivers/xen/pciback/pciback_ops.c

index 879975345ac650d4b9a0a96f8ac6899cad7a6b1d..feb3b081a9d0538b534bcf28e580aadb6295eb87 100644 (file)
@@ -26,6 +26,19 @@ module_param(verbose_request, int, 0644);
 /* 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.
  */
@@ -54,6 +67,122 @@ void pciback_reload_config_space(struct pci_dev *dev)
        }
 }
 
+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)
@@ -268,81 +397,44 @@ static int pciback_do_dstate_transition_reset(struct pci_dev *dev)
 }
 
 /* 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 
@@ -392,9 +484,9 @@ void pciback_flr_device(struct pci_dev *dev)
                        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");
@@ -526,7 +618,7 @@ void pciback_classify_device(struct pci_dev *dev)
        }
 
        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: