Blob Blame History Raw
From: Pierre Morel <pmorel@linux.ibm.com>
Date: Wed, 22 Apr 2020 15:15:23 +0200
Subject: s390/pci: Handling multifunctions
Git-commit: 44510d6fa0c00aa90b80075caa6b313b25927475
Patch-mainline: v5.8-rc1
References: jsc#SLE-13818 bsc#1177117 LTC#186574

We allow multiple functions on a single bus.
We suppress the ZPCI_DEVFN definition and replace its
occurences with zpci->devfn.

We verify the number of device during the registration.

There can never be more domains in use than existing
devices, so we do not need to verify the count of domain
after having verified the count of devices.

Signed-off-by: Pierre Morel <pmorel@linux.ibm.com>
Reviewed-by: Niklas Schnelle <schnelle@linux.ibm.com>
Signed-off-by: Vasily Gorbik <gor@linux.ibm.com>
Acked-by: Petr Tesarik <ptesarik@suse.com>
---
 arch/s390/include/asm/pci.h        |    8 +-
 arch/s390/pci/pci.c                |   39 ++++-----
 arch/s390/pci/pci_bus.c            |  148 ++++++++++++++++++++++++++++++++-----
 arch/s390/pci/pci_bus.h            |    5 -
 arch/s390/pci/pci_event.c          |    4 -
 drivers/pci/hotplug/s390_pci_hpc.c |    6 -
 6 files changed, 159 insertions(+), 51 deletions(-)

--- 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;
 
@@ -128,7 +129,8 @@ struct zpci_dev {
 	u8		pfgid;		/* function group ID */
 	u8		pft;		/* pci function type */
 	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;
@@ -252,7 +254,7 @@ static inline struct zpci_dev *to_zpci(s
 {
 	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)
--- a/arch/s390/pci/pci.c
+++ b/arch/s390/pci/pci.c
@@ -370,29 +370,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;
-
-	if (!zdev || devfn != ZPCI_DEVFN)
-		ret = -ENODEV;
-	else
-		ret = zpci_cfg_store(zdev, where, val, size);
+	struct zpci_dev *zdev = get_zdev_by_bus(bus, devfn);
 
-	return ret;
+	return (zdev) ? zpci_cfg_store(zdev, where, val, size) : -ENODEV;
 }
 
 static struct pci_ops pci_root_ops = {
@@ -750,12 +738,12 @@ int zpci_create_device(struct zpci_dev *
 	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:
@@ -769,18 +757,25 @@ void zpci_release_device(struct kref *kr
 {
 	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;
--- 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
 {
 	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
 	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(i
 	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
 	}
 	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 z
 	struct zpci_bus *zbus = zdev->zbus;
 
 	zpci_nb_devices--;
-	zbus->function[ZPCI_DEVFN] = NULL;
+	zbus->function[zdev->devfn] = NULL;
 	zpci_bus_put(zbus);
 }
--- 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];
 }
--- a/arch/s390/pci/pci_event.c
+++ b/arch/s390/pci/pci_event.c
@@ -55,7 +55,7 @@ static void __zpci_event_error(struct zp
 	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(st
 	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));
--- 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_sl
 	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_s
 	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)