public inbox for linux-kernel@vger.kernel.org
 help / color / mirror / Atom feed
From: Alex Chiang <achiang@hp.com>
To: linux-pci@vger.kernel.org, linux-kernel@vger.kernel.org
Cc: jbarnes@virtuousgeek.org, kristen.c.accardi@intel.com,
	matthew@wil.cx, kaneshige.kenji@jp.fujitsu.com,
	Alex Chiang <achiang@hp.com>
Subject: [PATCH v6-resend 05/17] PCI: prevent duplicate slot names
Date: Tue, 14 Oct 2008 01:25:27 -0600	[thread overview]
Message-ID: <20081014072527.24319.15143.stgit@bob.kio> (raw)
In-Reply-To: <20081014072311.24319.27334.stgit@bob.kio>

Prevent callers of pci_create_slot() from registering slots with
duplicate names. This condition occurs most often when PCI hotplug
drivers are loaded on platforms with broken firmware that assigns
identical names to multiple slots.

We now rename these duplicate slots on behalf of the user.

If firmware assigns the name N to multiple slots, then:

	The first registered slot is assigned N
	The second registered slot is assigned N-1
	The third registered slot is assigned N-2
	etc.

This is the permanent fix mentioned in earlier commits d6a9e9b4 and
167e782e (shpchp/pciehp: Rename duplicate slot name...).

We take advantage of the new 'hotplug' parameter in pci_create_slot()
to prevent a slot create/rename race between hotplug drivers and
detection drivers.

	Scenario A:
	hotplug driver                  detection driver
	--------------                  ----------------
	pci_create_slot(hotplug=set)
					pci_create_slot(hotplug=NULL)

The hotplug driver creates the slot with its desired name, and then
releases the semaphore. Now, the detection driver tries to create
the same slot, but it already exists. We don't care about renaming,
so return the existing slot.

	Scenario B:
	hotplug driver                  detection driver
	--------------                  ----------------
					pci_create_slot(hotplug=NULL)
	pci_create_slot(hotplug=set)

The detection driver creates the slot with name "X". Then the hotplug
driver tries to create the same slot, but wants the name "Y" instead.
We detect that we're trying to create the same slot and that we also
want a rename, so rename the slot to "Y" and return.

	Scenario C:
	hotplug driver                  hotplug driver
	--------------                  ----------------
	pci_create_slot(hotplug=set)
					pci_create_slot(hotplug=set)

Two separate hotplug drivers are attempting to claim the slot and
are passing valid hotplug_slot args to pci_create_slot(). We detect
that the slot already has a ->hotplug callback, prevent a rename,
and return -EBUSY.

Cc: jbarnes@virtuousgeek.org
Cc: kristen.c.accardi@intel.com
Cc: matthew@wil.cx
Cc: kaneshige.kenji@jp.fujitsu.com
Signed-off-by: Alex Chiang <achiang@hp.com>
---

 drivers/pci/hotplug/pci_hotplug_core.c |   26 ------
 drivers/pci/hotplug/pciehp_core.c      |   15 ---
 drivers/pci/hotplug/shpchp_core.c      |   15 ---
 drivers/pci/slot.c                     |  141 ++++++++++++++++++++++++++------
 4 files changed, 115 insertions(+), 82 deletions(-)

diff --git a/drivers/pci/hotplug/pci_hotplug_core.c b/drivers/pci/hotplug/pci_hotplug_core.c
index e715248..a6f1f28 100644
--- a/drivers/pci/hotplug/pci_hotplug_core.c
+++ b/drivers/pci/hotplug/pci_hotplug_core.c
@@ -569,12 +569,6 @@ int pci_hp_register(struct hotplug_slot *slot, struct pci_bus *bus, int slot_nr,
 
 	mutex_lock(&pci_hp_mutex);
 
-	/* Check if we have already registered a slot with the same name. */
-	if (get_slot_from_name(name)) {
-		result = -EEXIST;
-		goto out;
-	}
-
 	/*
 	 * No problems if we call this interface from both ACPI_PCI_SLOT
 	 * driver and call it here again. If we've already created the
@@ -583,27 +577,12 @@ int pci_hp_register(struct hotplug_slot *slot, struct pci_bus *bus, int slot_nr,
 	pci_slot = pci_create_slot(bus, slot_nr, name, slot);
 	if (IS_ERR(pci_slot)) {
 		result = PTR_ERR(pci_slot);
-		goto cleanup;
-	}
-
-	if (pci_slot->hotplug) {
-		dbg("%s: already claimed\n", __func__);
-		result = -EBUSY;
-		goto cleanup;
+		goto out;
 	}
 
 	slot->pci_slot = pci_slot;
 	pci_slot->hotplug = slot;
 
-	/*
-	 * Allow pcihp drivers to override the ACPI_PCI_SLOT name.
-	 */
-	if (strcmp(kobject_name(&pci_slot->kobj), name)) {
-		result = kobject_rename(&pci_slot->kobj, name);
-		if (result)
-			goto cleanup;
-	}
-
 	list_add(&slot->slot_list, &pci_hotplug_slot_list);
 
 	result = fs_add_slot(pci_slot);
@@ -612,9 +591,6 @@ int pci_hp_register(struct hotplug_slot *slot, struct pci_bus *bus, int slot_nr,
 out:
 	mutex_unlock(&pci_hp_mutex);
 	return result;
-cleanup:
-	pci_destroy_slot(pci_slot);
-	goto out;
 }
 
 /**
diff --git a/drivers/pci/hotplug/pciehp_core.c b/drivers/pci/hotplug/pciehp_core.c
index 3ace5e0..af89d7b 100644
--- a/drivers/pci/hotplug/pciehp_core.c
+++ b/drivers/pci/hotplug/pciehp_core.c
@@ -196,7 +196,6 @@ static int init_slots(struct controller *ctrl)
 	struct slot *slot;
 	struct hotplug_slot *hotplug_slot;
 	struct hotplug_slot_info *info;
-	int len, dup = 1;
 	int retval = -ENOMEM;
 
 	list_for_each_entry(slot, &ctrl->slot_list, slot_list) {
@@ -223,25 +222,11 @@ static int init_slots(struct controller *ctrl)
 		ctrl_dbg(ctrl, "Registering bus=%x dev=%x hp_slot=%x sun=%x "
 			 "slot_device_offset=%x\n", slot->bus, slot->device,
 			 slot->hp_slot, slot->number, ctrl->slot_device_offset);
-duplicate_name:
 		retval = pci_hp_register(hotplug_slot,
 					 ctrl->pci_dev->subordinate,
 					 slot->device,
 					 slot->name);
 		if (retval) {
-			/*
-			 * If slot N already exists, we'll try to create
-			 * slot N-1, N-2 ... N-M, until we overflow.
-			 */
-			if (retval == -EEXIST) {
-				len = snprintf(slot->name, SLOT_NAME_SIZE,
-					       "%d-%d", slot->number, dup++);
-				if (len < SLOT_NAME_SIZE)
-					goto duplicate_name;
-				else
-					ctrl_err(ctrl, "duplicate slot name "
-						 "overflow\n");
-			}
 			ctrl_err(ctrl, "pci_hp_register failed with error %d\n",
 				 retval);
 			goto error_info;
diff --git a/drivers/pci/hotplug/shpchp_core.c b/drivers/pci/hotplug/shpchp_core.c
index bf50966..cfdd079 100644
--- a/drivers/pci/hotplug/shpchp_core.c
+++ b/drivers/pci/hotplug/shpchp_core.c
@@ -102,7 +102,7 @@ static int init_slots(struct controller *ctrl)
 	struct hotplug_slot *hotplug_slot;
 	struct hotplug_slot_info *info;
 	int retval = -ENOMEM;
-	int i, len, dup = 1;
+	int i;
 
 	for (i = 0; i < ctrl->num_slots; i++) {
 		slot = kzalloc(sizeof(*slot), GFP_KERNEL);
@@ -144,23 +144,10 @@ static int init_slots(struct controller *ctrl)
 		dbg("Registering bus=%x dev=%x hp_slot=%x sun=%x "
 		    "slot_device_offset=%x\n", slot->bus, slot->device,
 		    slot->hp_slot, slot->number, ctrl->slot_device_offset);
-duplicate_name:
 		retval = pci_hp_register(slot->hotplug_slot,
 				ctrl->pci_dev->subordinate, slot->device,
 				hotplug_slot->name);
 		if (retval) {
-			/*
-			 * If slot N already exists, we'll try to create
-			 * slot N-1, N-2 ... N-M, until we overflow.
-			 */
-			if (retval == -EEXIST) {
-				len = snprintf(slot->name, SLOT_NAME_SIZE,
-					       "%d-%d", slot->number, dup++);
-				if (len < SLOT_NAME_SIZE)
-					goto duplicate_name;
-				else
-					err("duplicate slot name overflow\n");
-			}
 			err("pci_hp_register failed with error %d\n", retval);
 			goto error_info;
 		}
diff --git a/drivers/pci/slot.c b/drivers/pci/slot.c
index 0e009c3..b6ee352 100644
--- a/drivers/pci/slot.c
+++ b/drivers/pci/slot.c
@@ -78,6 +78,77 @@ static struct kobj_type pci_slot_ktype = {
 	.default_attrs = pci_slot_default_attrs,
 };
 
+static char *make_slot_name(const char *name)
+{
+	char *new_name;
+	int len, max, dup;
+
+	new_name = kstrdup(name, GFP_KERNEL);
+	if (!new_name)
+		return NULL;
+
+	/*
+	 * Make sure we hit the realloc case the first time through the
+	 * loop.  'len' will be strlen(name) + 3 at that point which is
+	 * enough space for "name-X" and the trailing NUL.
+	 */
+	len = strlen(name) + 2;
+	max = 1;
+	dup = 1;
+
+	for (;;) {
+		struct kobject *dup_slot;
+		dup_slot = kset_find_obj(pci_slots_kset, new_name);
+		if (!dup_slot)
+			break;
+		kobject_put(dup_slot);
+		if (dup == max) {
+			len++;
+			max *= 10;
+			kfree(new_name);
+			new_name = kmalloc(len, GFP_KERNEL);
+			if (!new_name)
+				break;
+		}
+		sprintf(new_name, "%s-%d", name, dup++);
+	}
+
+	return new_name;
+}
+
+static int rename_slot(struct pci_slot *slot, const char *name)
+{
+	int result = 0;
+	char *slot_name;
+
+	if (strcmp(kobject_name(&slot->kobj), name) == 0)
+		return result;
+
+	slot_name = make_slot_name(name);
+	if (!slot_name)
+		return -ENOMEM;
+
+	result = kobject_rename(&slot->kobj, slot_name);
+	kfree(slot_name);
+
+	return result;
+}
+
+static struct pci_slot *get_slot(struct pci_bus *parent, int slot_nr)
+{
+	struct pci_slot *slot;
+	/*
+	 * We already hold pci_bus_sem so don't worry
+	 */
+	list_for_each_entry(slot, &parent->slots, list)
+		if (slot->number == slot_nr) {
+			kobject_get(&slot->kobj);
+			return slot;
+		}
+
+	return NULL;
+}
+
 /**
  * pci_create_slot - create or increment refcount for physical PCI slot
  * @parent: struct pci_bus of parent bridge
@@ -90,7 +161,17 @@ static struct kobj_type pci_slot_ktype = {
  * either return a new &struct pci_slot to the caller, or if the pci_slot
  * already exists, its refcount will be incremented.
  *
- * Slots are uniquely identified by a @pci_bus, @slot_nr, @name tuple.
+ * Slots are uniquely identified by a @pci_bus, @slot_nr tuple.
+ *
+ * There are known platforms with broken firmware that assign the same
+ * name to multiple slots. Workaround these broken platforms by renaming
+ * the slots on behalf of the caller. If firmware assigns name N to
+ * multiple slots:
+ *
+ * The first slot is assigned N
+ * The second slot is assigned N-1
+ * The third slot is assigned N-2
+ * etc.
  *
  * Placeholder slots:
  * In most cases, @pci_bus, @slot_nr will be sufficient to uniquely identify
@@ -99,62 +180,67 @@ static struct kobj_type pci_slot_ktype = {
  * the slot. In this scenario, the caller may pass -1 for @slot_nr.
  *
  * The following semantics are imposed when the caller passes @slot_nr ==
- * -1. First, the check for existing %struct pci_slot is skipped, as the
- * caller may know about several unpopulated slots on a given %struct
- * pci_bus, and each slot would have a @slot_nr of -1.  Uniqueness for
- * these slots is then determined by the @name parameter. We expect
- * kobject_init_and_add() to warn us if the caller attempts to create
- * multiple slots with the same name. The other change in semantics is
+ * -1. First, we no longer check for an existing %struct pci_slot, as there
+ * may be many slots with @slot_nr of -1.  The other change in semantics is
  * user-visible, which is the 'address' parameter presented in sysfs will
  * consist solely of a dddd:bb tuple, where dddd is the PCI domain of the
  * %struct pci_bus and bb is the bus number. In other words, the devfn of
  * the 'placeholder' slot will not be displayed.
  */
-
 struct pci_slot *pci_create_slot(struct pci_bus *parent, int slot_nr,
 				 const char *name,
 				 struct hotplug_slot *hotplug)
 {
 	struct pci_dev *dev;
 	struct pci_slot *slot;
-	int err;
+	int err = 0;
+	char *slot_name = NULL;
 
 	down_write(&pci_bus_sem);
 
 	if (slot_nr == -1)
 		goto placeholder;
 
-	/* If we've already created this slot, bump refcount and return. */
-	list_for_each_entry(slot, &parent->slots, list) {
-		if (slot->number == slot_nr) {
-			kobject_get(&slot->kobj);
-			pr_debug("%s: inc refcount to %d on %04x:%02x:%02x\n",
-				 __func__,
-				 atomic_read(&slot->kobj.kref.refcount),
-				 pci_domain_nr(parent), parent->number,
-				 slot_nr);
-			goto out;
+	/*
+	 * Hotplug drivers are allowed to rename an existing slot,
+	 * but only if not already claimed.
+	 */
+	slot = get_slot(parent, slot_nr);
+	if (slot) {
+		if (hotplug) {
+			if ((err = slot->hotplug ? -EBUSY : 0)
+			     || (err = rename_slot(slot, name))) {
+				kobject_put(&slot->kobj);
+				slot = NULL;
+				goto err;
+			}
 		}
+		goto out;
 	}
 
 placeholder:
 	slot = kzalloc(sizeof(*slot), GFP_KERNEL);
 	if (!slot) {
-		slot = ERR_PTR(-ENOMEM);
-		goto out;
+		err = -ENOMEM;
+		goto err;
 	}
 
 	slot->bus = parent;
 	slot->number = slot_nr;
 
 	slot->kobj.kset = pci_slots_kset;
-	err = kobject_init_and_add(&slot->kobj, &pci_slot_ktype, NULL,
-				   "%s", name);
-	if (err) {
-		printk(KERN_ERR "Unable to register kobject %s\n", name);
+
+	slot_name = make_slot_name(name);
+	if (!slot_name) {
+		err = -ENOMEM;
 		goto err;
 	}
 
+	err = kobject_init_and_add(&slot->kobj, &pci_slot_ktype, NULL,
+				   "%s", slot_name);
+	if (err)
+		goto err;
+
 	INIT_LIST_HEAD(&slot->list);
 	list_add(&slot->list, &parent->slots);
 
@@ -166,10 +252,10 @@ placeholder:
 	pr_debug("%s: created pci_slot on %04x:%02x:%02x\n",
 		 __func__, pci_domain_nr(parent), parent->number, slot_nr);
 
- out:
+out:
 	up_write(&pci_bus_sem);
 	return slot;
- err:
+err:
 	kfree(slot);
 	slot = ERR_PTR(err);
 	goto out;
@@ -210,7 +296,6 @@ EXPORT_SYMBOL_GPL(pci_renumber_slot);
  * just call kobject_put on its kobj and let our release methods do the
  * rest.
  */
-
 void pci_destroy_slot(struct pci_slot *slot)
 {
 	pr_debug("%s: dec refcount to %d on %04x:%02x:%02x\n", __func__,


  parent reply	other threads:[~2008-10-14  7:27 UTC|newest]

Thread overview: 19+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2008-10-14  7:25 [PATCH v6-resend 00/17] PCI: let the core manage slot names Alex Chiang
2008-10-14  7:25 ` [PATCH v6-resend 01/17] PCI Hotplug core: add 'name' param pci_hp_register interface Alex Chiang
2008-10-14  7:25 ` [PATCH v6-resend 02/17] PCI: rename pci_update_slot_number to pci_renumber_slot Alex Chiang
2008-10-14  7:25 ` [PATCH v6-resend 03/17] PCI: update pci_create_slot() to take a 'hotplug' param Alex Chiang
2008-10-14  7:25 ` [PATCH v6-resend 04/17] PCI Hotplug: serialize pci_hp_register and pci_hp_deregister Alex Chiang
2008-10-14  7:25 ` Alex Chiang [this message]
2008-10-14  7:25 ` [PATCH v6-resend 06/17] PCI, PCI Hotplug: introduce slot_name helpers Alex Chiang
2008-10-14  7:25 ` [PATCH v6-resend 07/17] PCI: acpiphp: remove 'name' parameter Alex Chiang
2008-10-14  7:25 ` [PATCH v6-resend 08/17] PCI: cpci_hotplug: stop managing hotplug_slot->name Alex Chiang
2008-10-14  7:25 ` [PATCH v6-resend 09/17] PCI: cpqphp: " Alex Chiang
2008-10-14  7:25 ` [PATCH v6-resend 10/17] PCI: fakephp: remove 'name' parameter Alex Chiang
2008-10-14  7:25 ` [PATCH v6-resend 11/17] PCI: ibmphp: stop managing hotplug_slot->name Alex Chiang
2008-10-14  7:26 ` [PATCH v6-resend 12/17] PCI: pciehp: remove 'name' parameter Alex Chiang
2008-10-14  7:26 ` [PATCH v6-resend 13/17] PCI: rpaphp: kmalloc/kfree slot->name directly Alex Chiang
2008-10-14  7:26 ` [PATCH v6-resend 14/17] PCI: SGI Hotplug: stop managing bss_hotplug_slot->name Alex Chiang
2008-10-14  7:26 ` [PATCH v6-resend 15/17] PCI: shcphp: remove 'name' parameter Alex Chiang
2008-10-14  7:26 ` [PATCH v6-resend 16/17] PCI: Hotplug core: remove 'name' Alex Chiang
2008-10-14  7:26 ` [PATCH v6-resend 17/17] PCI Hotplug: fakephp: add duplicate slot name debugging Alex Chiang
2008-10-15 16:50 ` [PATCH v6-resend 00/17] PCI: let the core manage slot names Kenji Kaneshige

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20081014072527.24319.15143.stgit@bob.kio \
    --to=achiang@hp.com \
    --cc=jbarnes@virtuousgeek.org \
    --cc=kaneshige.kenji@jp.fujitsu.com \
    --cc=kristen.c.accardi@intel.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-pci@vger.kernel.org \
    --cc=matthew@wil.cx \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox