From: Ross Philipson Date: Tue, 31 Mar 2009 13:53:10 +0000 (-0400) Subject: Enhanced the secondary bus reset logic to handle non PCIe X-Git-Url: http://xenbits.xensource.com/gitweb?a=commitdiff_plain;h=5176853698bd2ddc15a7ac054ab5afaeaeb954e8;p=xenclient%2Fkernel.git Enhanced the secondary bus reset logic to handle non PCIe devices. Fixed a bug in the classify function. Changes to be committed: modified: drivers/xen/pciback/pciback_ops.c --- diff --git a/drivers/xen/pciback/pciback_ops.c b/drivers/xen/pciback/pciback_ops.c index 87997534..feb3b081 100644 --- a/drivers/xen/pciback/pciback_ops.c +++ b/drivers/xen/pciback/pciback_ops.c @@ -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: