summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--arch/s390/include/asm/pci.h8
-rw-r--r--arch/s390/pci/pci.c39
-rw-r--r--arch/s390/pci/pci_bus.c148
-rw-r--r--arch/s390/pci/pci_bus.h5
-rw-r--r--arch/s390/pci/pci_event.c4
-rw-r--r--drivers/pci/hotplug/s390_pci_hpc.c6
6 files changed, 159 insertions, 51 deletions
diff --git a/arch/s390/include/asm/pci.h b/arch/s390/include/asm/pci.h
index 7d99ab35833c..c1558cf071b8 100644
--- a/arch/s390/include/asm/pci.h
+++ b/arch/s390/include/asm/pci.h
@@ -22,7 +22,6 @@ int pci_domain_nr(struct pci_bus *);
int pci_proc_domain(struct pci_bus *);
#define ZPCI_BUS_NR 0 /* default bus number */
-#define ZPCI_DEVFN 0 /* default device number */
#define ZPCI_NR_DMA_SPACES 1
#define ZPCI_NR_DEVICES CONFIG_PCI_NR_FUNCTIONS
@@ -110,6 +109,7 @@ struct zpci_bus {
struct resource bus_resource;
int pchid;
int domain_nr;
+ bool multifunction;
enum pci_bus_speed max_bus_speed;
};
@@ -117,6 +117,7 @@ struct zpci_bus {
struct zpci_dev {
struct zpci_bus *zbus;
struct list_head entry; /* list of all zpci_devices, needed for hotplug, etc. */
+ struct list_head bus_next;
struct kref kref;
struct hotplug_slot hotplug_slot;
@@ -129,7 +130,8 @@ struct zpci_dev {
u8 pft; /* pci function type */
u8 port;
u8 rid_available : 1;
- u8 reserved : 7;
+ u8 has_hp_slot : 1;
+ u8 reserved : 6;
unsigned int devfn; /* DEVFN part of the RID*/
struct mutex lock;
@@ -253,7 +255,7 @@ static inline struct zpci_dev *to_zpci(struct pci_dev *pdev)
{
struct zpci_bus *zbus = pdev->sysdata;
- return zbus->function[ZPCI_DEVFN];
+ return zbus->function[pdev->devfn];
}
static inline struct zpci_dev *to_zpci_dev(struct device *dev)
diff --git a/arch/s390/pci/pci.c b/arch/s390/pci/pci.c
index 41423dad881c..3f6670613c57 100644
--- a/arch/s390/pci/pci.c
+++ b/arch/s390/pci/pci.c
@@ -371,29 +371,17 @@ EXPORT_SYMBOL(pci_iounmap);
static int pci_read(struct pci_bus *bus, unsigned int devfn, int where,
int size, u32 *val)
{
- struct zpci_dev *zdev = get_zdev_by_bus(bus);
- int ret;
+ struct zpci_dev *zdev = get_zdev_by_bus(bus, devfn);
- if (!zdev || devfn != ZPCI_DEVFN)
- ret = -ENODEV;
- else
- ret = zpci_cfg_load(zdev, where, val, size);
-
- return ret;
+ return (zdev) ? zpci_cfg_load(zdev, where, val, size) : -ENODEV;
}
static int pci_write(struct pci_bus *bus, unsigned int devfn, int where,
int size, u32 val)
{
- struct zpci_dev *zdev = get_zdev_by_bus(bus);
- int ret;
+ struct zpci_dev *zdev = get_zdev_by_bus(bus, devfn);
- if (!zdev || devfn != ZPCI_DEVFN)
- ret = -ENODEV;
- else
- ret = zpci_cfg_store(zdev, where, val, size);
-
- return ret;
+ return (zdev) ? zpci_cfg_store(zdev, where, val, size) : -ENODEV;
}
static struct pci_ops pci_root_ops = {
@@ -708,12 +696,12 @@ int zpci_create_device(struct zpci_dev *zdev)
if (rc)
goto out_disable;
- zpci_init_slot(zdev);
return 0;
out_disable:
if (zdev->state == ZPCI_FN_STATE_ONLINE)
zpci_disable_device(zdev);
+
out_destroy_iommu:
zpci_destroy_iommu(zdev);
out:
@@ -727,18 +715,25 @@ void zpci_release_device(struct kref *kref)
{
struct zpci_dev *zdev = container_of(kref, struct zpci_dev, kref);
+ if (zdev->zbus->bus) {
+ struct pci_dev *pdev;
+
+ pdev = pci_get_slot(zdev->zbus->bus, zdev->devfn);
+ if (pdev)
+ pci_stop_and_remove_bus_device_locked(pdev);
+ }
+
switch (zdev->state) {
case ZPCI_FN_STATE_ONLINE:
case ZPCI_FN_STATE_CONFIGURED:
zpci_disable_device(zdev);
fallthrough;
case ZPCI_FN_STATE_STANDBY:
- if (zdev->zbus) {
+ if (zdev->has_hp_slot)
zpci_exit_slot(zdev);
- zpci_cleanup_bus_resources(zdev);
- zpci_bus_device_unregister(zdev);
- zpci_destroy_iommu(zdev);
- }
+ zpci_cleanup_bus_resources(zdev);
+ zpci_bus_device_unregister(zdev);
+ zpci_destroy_iommu(zdev);
fallthrough;
default:
break;
diff --git a/arch/s390/pci/pci_bus.c b/arch/s390/pci/pci_bus.c
index b4fefc69c461..542c6b8f56df 100644
--- a/arch/s390/pci/pci_bus.c
+++ b/arch/s390/pci/pci_bus.c
@@ -62,14 +62,16 @@ static void zpci_bus_release(struct kref *kref)
{
struct zpci_bus *zbus = container_of(kref, struct zpci_bus, kref);
- pci_lock_rescan_remove();
- pci_stop_root_bus(zbus->bus);
+ if (zbus->bus) {
+ pci_lock_rescan_remove();
+ pci_stop_root_bus(zbus->bus);
- zpci_free_domain(zbus->domain_nr);
- pci_free_resource_list(&zbus->resources);
+ zpci_free_domain(zbus->domain_nr);
+ pci_free_resource_list(&zbus->resources);
- pci_remove_root_bus(zbus->bus);
- pci_unlock_rescan_remove();
+ pci_remove_root_bus(zbus->bus);
+ pci_unlock_rescan_remove();
+ }
spin_lock(&zbus_list_lock);
list_del(&zbus->bus_next);
@@ -82,6 +84,23 @@ static void zpci_bus_put(struct zpci_bus *zbus)
kref_put(&zbus->kref, zpci_bus_release);
}
+static struct zpci_bus *zpci_bus_get(int pchid)
+{
+ struct zpci_bus *zbus;
+
+ spin_lock(&zbus_list_lock);
+ list_for_each_entry(zbus, &zbus_list, bus_next) {
+ if (pchid == zbus->pchid) {
+ kref_get(&zbus->kref);
+ goto out_unlock;
+ }
+ }
+ zbus = NULL;
+out_unlock:
+ spin_unlock(&zbus_list_lock);
+ return zbus;
+}
+
static struct zpci_bus *zpci_bus_alloc(int pchid)
{
struct zpci_bus *zbus;
@@ -107,11 +126,62 @@ static struct zpci_bus *zpci_bus_alloc(int pchid)
return zbus;
}
-int zpci_bus_device_register(struct zpci_dev *zdev, struct pci_ops *ops)
+static int zpci_bus_add_device(struct zpci_bus *zbus, struct zpci_dev *zdev)
{
- struct zpci_bus *zbus;
+ struct pci_bus *bus;
+ struct resource_entry *window, *n;
+ struct resource *res;
+ struct pci_dev *pdev;
int rc;
+ bus = zbus->bus;
+ if (!bus)
+ return -EINVAL;
+
+ pdev = pci_get_slot(bus, zdev->devfn);
+ if (pdev) {
+ /* Device is already known. */
+ pci_dev_put(pdev);
+ return 0;
+ }
+
+ rc = zpci_init_slot(zdev);
+ if (rc)
+ return rc;
+ zdev->has_hp_slot = 1;
+
+ resource_list_for_each_entry_safe(window, n, &zbus->resources) {
+ res = window->res;
+ pci_bus_add_resource(bus, res, 0);
+ }
+
+ pdev = pci_scan_single_device(bus, zdev->devfn);
+ if (pdev) {
+ pdev->multifunction = 1;
+ pci_bus_add_device(pdev);
+ }
+
+ return 0;
+}
+
+static void zpci_bus_add_devices(struct zpci_bus *zbus)
+{
+ int i;
+
+ for (i = 1; i < ZPCI_FUNCTIONS_PER_BUS; i++)
+ if (zbus->function[i])
+ zpci_bus_add_device(zbus, zbus->function[i]);
+
+ pci_lock_rescan_remove();
+ pci_bus_add_devices(zbus->bus);
+ pci_unlock_rescan_remove();
+}
+
+int zpci_bus_device_register(struct zpci_dev *zdev, struct pci_ops *ops)
+{
+ struct zpci_bus *zbus = NULL;
+ int rc = -EBADF;
+
if (zpci_nb_devices == ZPCI_NR_DEVICES) {
pr_warn("Adding PCI function %08x failed because the configured limit of %d is reached\n",
zdev->fid, ZPCI_NR_DEVICES);
@@ -119,25 +189,65 @@ int zpci_bus_device_register(struct zpci_dev *zdev, struct pci_ops *ops)
}
zpci_nb_devices++;
- if (zdev->devfn != ZPCI_DEVFN)
+ if (zdev->devfn >= ZPCI_FUNCTIONS_PER_BUS)
return -EINVAL;
- zbus = zpci_bus_alloc(zdev->pchid);
- if (!zbus)
- return -ENOMEM;
+ if (!s390_pci_no_rid && zdev->rid_available)
+ zbus = zpci_bus_get(zdev->pchid);
+
+ if (!zbus) {
+ zbus = zpci_bus_alloc(zdev->pchid);
+ if (!zbus)
+ return -ENOMEM;
+ }
zdev->zbus = zbus;
- zbus->function[ZPCI_DEVFN] = zdev;
+ if (zbus->function[zdev->devfn]) {
+ pr_err("devfn %04x is already assigned\n", zdev->devfn);
+ goto error; /* rc already set */
+ }
+ zbus->function[zdev->devfn] = zdev;
zpci_setup_bus_resources(zdev, &zbus->resources);
- zbus->max_bus_speed = zdev->max_bus_speed;
- rc = zpci_bus_scan(zbus, (u16)zdev->uid, ops);
- if (!rc)
- return 0;
+ if (zbus->bus) {
+ if (!zbus->multifunction) {
+ WARN_ONCE(1, "zbus is not multifunction\n");
+ goto error_bus;
+ }
+ if (!zdev->rid_available) {
+ WARN_ONCE(1, "rid_available not set for multifunction\n");
+ goto error_bus;
+ }
+ rc = zpci_bus_add_device(zbus, zdev);
+ if (rc)
+ goto error_bus;
+ } else if (zdev->devfn == 0) {
+ if (zbus->multifunction && !zdev->rid_available) {
+ WARN_ONCE(1, "rid_available not set on function 0 for multifunction\n");
+ goto error_bus;
+ }
+ rc = zpci_bus_scan(zbus, (u16)zdev->uid, ops);
+ if (rc)
+ goto error_bus;
+ zpci_bus_add_devices(zbus);
+ rc = zpci_init_slot(zdev);
+ if (rc)
+ goto error_bus;
+ zdev->has_hp_slot = 1;
+ zbus->multifunction = zdev->rid_available;
+ zbus->max_bus_speed = zdev->max_bus_speed;
+ } else {
+ zbus->multifunction = 1;
+ }
+ return 0;
+
+error_bus:
+ zpci_nb_devices--;
+ zbus->function[zdev->devfn] = NULL;
+error:
pr_err("Adding PCI function %08x failed\n", zdev->fid);
- zdev->zbus = NULL;
zpci_bus_put(zbus);
return rc;
}
@@ -147,6 +257,6 @@ void zpci_bus_device_unregister(struct zpci_dev *zdev)
struct zpci_bus *zbus = zdev->zbus;
zpci_nb_devices--;
- zbus->function[ZPCI_DEVFN] = NULL;
+ zbus->function[zdev->devfn] = NULL;
zpci_bus_put(zbus);
}
diff --git a/arch/s390/pci/pci_bus.h b/arch/s390/pci/pci_bus.h
index c6aff42cc2cf..89be3c354b7b 100644
--- a/arch/s390/pci/pci_bus.h
+++ b/arch/s390/pci/pci_bus.h
@@ -22,9 +22,10 @@ void zpci_free_domain(int domain);
int zpci_setup_bus_resources(struct zpci_dev *zdev,
struct list_head *resources);
-static inline struct zpci_dev *get_zdev_by_bus(struct pci_bus *bus)
+static inline struct zpci_dev *get_zdev_by_bus(struct pci_bus *bus,
+ unsigned int devfn)
{
struct zpci_bus *zbus = bus->sysdata;
- return zbus->function[ZPCI_DEVFN];
+ return (devfn >= ZPCI_FUNCTIONS_PER_BUS) ? NULL : zbus->function[devfn];
}
diff --git a/arch/s390/pci/pci_event.c b/arch/s390/pci/pci_event.c
index c296214f0a19..08e1d619398e 100644
--- a/arch/s390/pci/pci_event.c
+++ b/arch/s390/pci/pci_event.c
@@ -55,7 +55,7 @@ static void __zpci_event_error(struct zpci_ccdf_err *ccdf)
zpci_err_hex(ccdf, sizeof(*ccdf));
if (zdev)
- pdev = pci_get_slot(zdev->zbus->bus, ZPCI_DEVFN);
+ pdev = pci_get_slot(zdev->zbus->bus, zdev->devfn);
pr_err("%s: Event 0x%x reports an error for PCI function 0x%x\n",
pdev ? pci_name(pdev) : "n/a", ccdf->pec, ccdf->fid);
@@ -81,7 +81,7 @@ static void __zpci_event_availability(struct zpci_ccdf_avail *ccdf)
int ret;
if (zdev && zdev->zbus && zdev->zbus->bus)
- pdev = pci_get_slot(zdev->zbus->bus, ZPCI_DEVFN);
+ pdev = pci_get_slot(zdev->zbus->bus, zdev->devfn);
zpci_err("avail CCDF:\n");
zpci_err_hex(ccdf, sizeof(*ccdf));
diff --git a/drivers/pci/hotplug/s390_pci_hpc.c b/drivers/pci/hotplug/s390_pci_hpc.c
index a9c9f05fe54b..1579ba895edf 100644
--- a/drivers/pci/hotplug/s390_pci_hpc.c
+++ b/drivers/pci/hotplug/s390_pci_hpc.c
@@ -66,7 +66,7 @@ static int enable_slot(struct hotplug_slot *hotplug_slot)
if (rc)
goto out_deconfigure;
- pci_scan_slot(zbus->bus, ZPCI_DEVFN);
+ pci_scan_slot(zbus->bus, zdev->devfn);
pci_lock_rescan_remove();
pci_bus_add_devices(zbus->bus);
pci_unlock_rescan_remove();
@@ -89,7 +89,7 @@ static int disable_slot(struct hotplug_slot *hotplug_slot)
if (!zpci_fn_configured(zdev->state))
return -EIO;
- pdev = pci_get_slot(zbus->bus, ZPCI_DEVFN);
+ pdev = pci_get_slot(zbus->bus, zdev->devfn);
if (pdev) {
pci_stop_and_remove_bus_device_locked(pdev);
pci_dev_put(pdev);
@@ -141,7 +141,7 @@ int zpci_init_slot(struct zpci_dev *zdev)
snprintf(name, SLOT_NAME_SIZE, "%08x", zdev->fid);
return pci_hp_register(&zdev->hotplug_slot, zbus->bus,
- ZPCI_DEVFN, name);
+ zdev->devfn, name);
}
void zpci_exit_slot(struct zpci_dev *zdev)