Re-structure the adding of PCMCIA devices and the binding of devices
and drivers by cardmgr in bind_device: pcmcia_add_device() adds a new 
PCMCIA device for a socket and a device function, if it hasn't been done 
before.

Signed-off-by: Dominik Brodowski <linux@brodo.de>
---
 drivers/pcmcia/ds.c |  198 +++++++++++++++++++++++++++++++++++-----------------
 include/pcmcia/ds.h |    3
 2 files changed, 140 insertions(+), 61 deletions(-)

Index: 2.6.10-rc3/drivers/pcmcia/ds.c
===================================================================
--- 2.6.10-rc3.orig/drivers/pcmcia/ds.c	2004-12-06 14:14:53.888565208 +0100
+++ 2.6.10-rc3/drivers/pcmcia/ds.c	2004-12-06 14:17:15.583024376 +0100
@@ -355,7 +355,8 @@
 
 static void pcmcia_put_dev(struct pcmcia_device *p_dev)
 {
-	put_device(&p_dev->dev);
+	if (p_dev)
+		put_device(&p_dev->dev);
 }
 
 static void pcmcia_release_dev(struct device *dev)
@@ -425,6 +426,122 @@
 }
 
 
+/*
+ * pcmcia_find_dev -- obtain a reference to a struct pcmcia_device by the socket and the function
+ */
+static struct pcmcia_device * pcmcia_find_dev(struct pcmcia_bus_socket *s, unsigned int function)
+{
+	struct pcmcia_device *tmp_dev, *p_dev = NULL;
+	unsigned long flags;
+
+	s = pcmcia_get_bus_socket(s);
+	if (!s)
+		return NULL;
+
+	spin_lock_irqsave(&pcmcia_dev_list_lock, flags);
+	list_for_each_entry(tmp_dev, &s->devices_list, socket_device_list) {
+		if (tmp_dev->func == function) {
+			p_dev = pcmcia_get_dev(tmp_dev);
+			break;
+		}
+	}
+	spin_unlock_irqrestore(&pcmcia_dev_list_lock, flags);
+
+	pcmcia_put_bus_socket(s);
+
+	return (p_dev);
+}
+
+/* device_add_lock is needed to avoid double registration by cardmgr and kernel.
+ * Serializes pcmcia_device_add; will most likely be removed in future.
+ *
+ * While it has the caveat that adding new PCMCIA devices inside(!) device_register()
+ * won't work, this doesn't matter much at the moment: the driver core doesn't
+ * support it either.
+ */
+static DECLARE_MUTEX(device_add_lock);
+
+static int pcmcia_device_add(struct pcmcia_bus_socket *s, unsigned int function, struct pcmcia_driver *p_drv)
+{
+	struct pcmcia_device *p_dev, *tmp_dev;
+	unsigned long flags;
+	int ret = 0;
+
+	s = pcmcia_get_bus_socket(s);
+	if (!s)
+		return -ENODEV;
+
+	down(&device_add_lock);
+
+	p_dev = pcmcia_find_dev(s, function);
+	if (p_dev) {
+		ret = -EBUSY;
+		goto err_put_dev;
+	}
+
+	/*
+	 * Allocate a struct pcmcia_device and fill it with life
+	 */
+	p_dev = kmalloc(sizeof(struct pcmcia_device), GFP_KERNEL);
+	if (!p_dev) {
+		ret = -ENOMEM;
+		goto err_put;
+	}
+	memset(p_dev, 0, sizeof(struct pcmcia_device));
+
+	p_dev->socket = s->parent;
+	p_dev->func   = function;
+
+	p_dev->dev.bus = &pcmcia_bus_type;
+	p_dev->dev.parent = s->parent->dev.dev;
+	p_dev->dev.release = pcmcia_release_dev;
+	sprintf (p_dev->dev.bus_id, "pcmcia%d.%d", p_dev->socket->sock, p_dev->func);
+
+	/* temporary workaround */
+	p_dev->dev.driver = &p_drv->drv;
+
+	/* compat */
+	p_dev->client.client_magic = CLIENT_MAGIC;
+	p_dev->client.Socket = s->parent;
+	p_dev->client.Function = function;
+	p_dev->client.state = CLIENT_UNBOUND;
+
+	/*
+	 * Add to the list in pcmcia_bus_socket. Also, assert that no
+	 * such device exists at the moment.
+	 */
+	spin_lock_irqsave(&pcmcia_dev_list_lock, flags);
+	list_for_each_entry(tmp_dev, &s->devices_list, socket_device_list) {
+		WARN_ON(tmp_dev->func == function);
+	}
+	list_add_tail(&p_dev->socket_device_list, &s->devices_list);
+	spin_unlock_irqrestore(&pcmcia_dev_list_lock, flags);
+
+	ret = device_register(&p_dev->dev);
+	if (ret) {
+		ret = -EIO;
+
+		spin_lock_irqsave(&pcmcia_dev_list_lock, flags);
+		list_del(&p_dev->socket_device_list);
+		spin_unlock_irqrestore(&pcmcia_dev_list_lock, flags);
+
+		kfree(p_dev);
+
+		goto err_put;
+       }
+
+	up(&device_add_lock);
+
+	return 0;
+
+ err_put_dev:
+	pcmcia_put_dev(p_dev);
+ err_put:
+	up(&device_add_lock);
+	pcmcia_put_bus_socket(s);
+	return (ret);
+}
+
 /*======================================================================
 
     These manage a ring buffer of events pending for one user process
@@ -594,8 +711,7 @@
 static int bind_request(struct pcmcia_bus_socket *s, bind_info_t *bind_info)
 {
 	struct pcmcia_driver *p_drv;
-	struct pcmcia_device *p_dev, *tmp_dev;
-	unsigned long flags;
+	struct pcmcia_device *p_dev;
 	int ret = 0;
 
 	s = pcmcia_get_bus_socket(s);
@@ -616,72 +732,31 @@
 		goto err_put_driver;
 	}
 
-	/* Currently, the userspace pcmcia cardmgr detects pcmcia devices.
-	 * Here this information is translated into a kernel
-	 * struct pcmcia_device.
-	 */
-
-	p_dev = kmalloc(sizeof(struct pcmcia_device), GFP_KERNEL);
-	if (!p_dev) {
-		ret = -ENOMEM;
+	ret = pcmcia_device_add(s, bind_info->function, p_drv);
+	if ((ret) && (ret != -EBUSY))
 		goto err_put_module;
-	}
-	memset(p_dev, 0, sizeof(struct pcmcia_device));
-
-	p_dev->socket = s->parent;
-	p_dev->func   = bind_info->function;
-
-	p_dev->dev.bus = &pcmcia_bus_type;
-	p_dev->dev.parent = s->parent->dev.dev;
-	p_dev->dev.release = pcmcia_release_dev;
-	sprintf (p_dev->dev.bus_id, "pcmcia%d.%d", p_dev->socket->sock, p_dev->func);
-	p_dev->dev.driver = &p_drv->drv;
 
-	/* compat */
-	p_dev->client.client_magic = CLIENT_MAGIC;
-	p_dev->client.Socket = s->parent;
-	p_dev->client.Function = bind_info->function;
-	p_dev->client.state = CLIENT_UNBOUND;
-
-	ret = device_register(&p_dev->dev);
-	if (ret) {
-		kfree(p_dev);
+	p_dev = pcmcia_find_dev(s, bind_info->function);
+	if (!p_dev) {
+		ret = -EBUSY;
 		goto err_put_module;
 	}
 
-	/*
-	 * Add to the list in pcmcia_bus_socket. Also, assert that no
-	 * such device exists at the moment.
-	 */
-
-	spin_lock_irqsave(&pcmcia_dev_list_lock, flags);
-	list_for_each_entry(tmp_dev, &s->devices_list, socket_device_list) {
-		if (tmp_dev->func == bind_info->function) {
-			spin_unlock_irqrestore(&pcmcia_dev_list_lock, flags);
-			bind_info->instance = tmp_dev->instance;
-			ret = -EBUSY;
-			goto err_unregister;
-		}
+ 	/* if there's already a device registered, and it was registered
+	 * by userspace before, we need to return the "instance". Therefore,
+	 * we need to set the cardmgr flag */
+	if (p_dev->cardmgr) {
+		bind_info->instance = p_dev->instance;
+		ret = -EBUSY;
+		goto err_put_device;
 	}
-	list_add_tail(&p_dev->socket_device_list, &s->devices_list);
-	spin_unlock_irqrestore(&pcmcia_dev_list_lock, flags);
 
-	ret = pcmcia_device_probe(&p_dev->dev);
-	if (ret)
-		goto err_unregister;
-
-	module_put(p_drv->owner);
-
-	put_driver(&p_drv->drv);
-
-	return 0;
-
- err_unregister:
-	device_unregister(&p_dev->dev);
-	module_put(p_drv->owner);
-	put_driver(&p_drv->drv);
-	return (ret);
+ 	ret = pcmcia_device_probe(&p_dev->dev);
+	if (!ret)
+		p_dev->cardmgr++;
 
+ err_put_device:
+	pcmcia_put_dev(p_dev);
  err_put_module:
 	module_put(p_drv->owner);
  err_put_driver:
@@ -691,6 +766,7 @@
 	return (ret);
 } /* bind_request */
 
+
 int pcmcia_register_client(client_handle_t *handle, client_reg_t *req)
 {
 	client_t *client = NULL;
Index: 2.6.10-rc3/include/pcmcia/ds.h
===================================================================
--- 2.6.10-rc3.orig/include/pcmcia/ds.h	2004-12-06 14:14:18.258981728 +0100
+++ 2.6.10-rc3/include/pcmcia/ds.h	2004-12-06 14:16:00.892379080 +0100
@@ -164,6 +164,9 @@
 		event_callback_args_t 	event_callback_args;
 	}			client;
 
+	/* registration by cardmgr done? */
+	unsigned int		cardmgr;
+
 	struct device		dev;
 };