Gigaset: permit module unload

Fix the initialization and reference counting of the Gigaset driver modules
so that they can be unloaded when they are not actually in use.

Signed-off-by: Tilman Schmidt <tilman@imap.cc>
Cc: Hansjoerg Lipp <hjlipp@web.de>
Cc: Greg KH <gregkh@suse.de>
Cc: Karsten Keil <kkeil@suse.de>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
diff --git a/drivers/isdn/gigaset/bas-gigaset.c b/drivers/isdn/gigaset/bas-gigaset.c
index 7c90530..5255b5e 100644
--- a/drivers/isdn/gigaset/bas-gigaset.c
+++ b/drivers/isdn/gigaset/bas-gigaset.c
@@ -134,7 +134,6 @@
 
 
 static struct gigaset_driver *driver = NULL;
-static struct cardstate *cardstate = NULL;
 
 /* usb specific object needed to register this driver with the usb subsystem */
 static struct usb_driver gigaset_usb_driver = {
@@ -2247,11 +2246,11 @@
 		 __func__, le16_to_cpu(udev->descriptor.idVendor),
 		 le16_to_cpu(udev->descriptor.idProduct));
 
-	cs = gigaset_getunassignedcs(driver);
-	if (!cs) {
-		dev_err(&udev->dev, "no free cardstate\n");
+	/* allocate memory for our device state and intialize it */
+	cs = gigaset_initcs(driver, BAS_CHANNELS, 0, 0, cidmode,
+			    GIGASET_MODULENAME);
+	if (!cs)
 		return -ENODEV;
-	}
 	ucs = cs->hw.bas;
 
 	/* save off device structure ptrs for later use */
@@ -2320,7 +2319,7 @@
 error:
 	freeurbs(cs);
 	usb_set_intfdata(interface, NULL);
-	gigaset_unassign(cs);
+	gigaset_freecs(cs);
 	return -ENODEV;
 }
 
@@ -2362,7 +2361,7 @@
 	ucs->interface = NULL;
 	ucs->udev = NULL;
 	cs->dev = NULL;
-	gigaset_unassign(cs);
+	gigaset_freecs(cs);
 }
 
 /* gigaset_suspend
@@ -2501,12 +2500,6 @@
 				       &gigops, THIS_MODULE)) == NULL)
 		goto error;
 
-	/* allocate memory for our device state and intialize it */
-	cardstate = gigaset_initcs(driver, BAS_CHANNELS, 0, 0, cidmode,
-				   GIGASET_MODULENAME);
-	if (!cardstate)
-		goto error;
-
 	/* register this driver with the USB subsystem */
 	result = usb_register(&gigaset_usb_driver);
 	if (result < 0) {
@@ -2518,9 +2511,7 @@
 	info(DRIVER_DESC);
 	return 0;
 
-error:	if (cardstate)
-		gigaset_freecs(cardstate);
-	cardstate = NULL;
+error:
 	if (driver)
 		gigaset_freedriver(driver);
 	driver = NULL;
@@ -2532,43 +2523,50 @@
  */
 static void __exit bas_gigaset_exit(void)
 {
-	struct bas_cardstate *ucs = cardstate->hw.bas;
+	struct bas_cardstate *ucs;
+	int i;
 
 	gigaset_blockdriver(driver); /* => probe will fail
 				      * => no gigaset_start any more
 				      */
 
-	gigaset_shutdown(cardstate);
-	/* from now on, no isdn callback should be possible */
+	/* stop all connected devices */
+	for (i = 0; i < driver->minors; i++) {
+		if (gigaset_shutdown(driver->cs + i) < 0)
+			continue;		/* no device */
+		/* from now on, no isdn callback should be possible */
 
-	/* close all still open channels */
-	if (ucs->basstate & BS_B1OPEN) {
-		gig_dbg(DEBUG_INIT, "closing B1 channel");
-		usb_control_msg(ucs->udev, usb_sndctrlpipe(ucs->udev, 0),
-				HD_CLOSE_B1CHANNEL, OUT_VENDOR_REQ, 0, 0,
-				NULL, 0, BAS_TIMEOUT);
+		/* close all still open channels */
+		ucs = driver->cs[i].hw.bas;
+		if (ucs->basstate & BS_B1OPEN) {
+			gig_dbg(DEBUG_INIT, "closing B1 channel");
+			usb_control_msg(ucs->udev,
+					usb_sndctrlpipe(ucs->udev, 0),
+					HD_CLOSE_B1CHANNEL, OUT_VENDOR_REQ,
+					0, 0, NULL, 0, BAS_TIMEOUT);
+		}
+		if (ucs->basstate & BS_B2OPEN) {
+			gig_dbg(DEBUG_INIT, "closing B2 channel");
+			usb_control_msg(ucs->udev,
+					usb_sndctrlpipe(ucs->udev, 0),
+					HD_CLOSE_B2CHANNEL, OUT_VENDOR_REQ,
+					0, 0, NULL, 0, BAS_TIMEOUT);
+		}
+		if (ucs->basstate & BS_ATOPEN) {
+			gig_dbg(DEBUG_INIT, "closing AT channel");
+			usb_control_msg(ucs->udev,
+					usb_sndctrlpipe(ucs->udev, 0),
+					HD_CLOSE_ATCHANNEL, OUT_VENDOR_REQ,
+					0, 0, NULL, 0, BAS_TIMEOUT);
+		}
+		ucs->basstate = 0;
 	}
-	if (ucs->basstate & BS_B2OPEN) {
-		gig_dbg(DEBUG_INIT, "closing B2 channel");
-		usb_control_msg(ucs->udev, usb_sndctrlpipe(ucs->udev, 0),
-				HD_CLOSE_B2CHANNEL, OUT_VENDOR_REQ, 0, 0,
-				NULL, 0, BAS_TIMEOUT);
-	}
-	if (ucs->basstate & BS_ATOPEN) {
-		gig_dbg(DEBUG_INIT, "closing AT channel");
-		usb_control_msg(ucs->udev, usb_sndctrlpipe(ucs->udev, 0),
-				HD_CLOSE_ATCHANNEL, OUT_VENDOR_REQ, 0, 0,
-				NULL, 0, BAS_TIMEOUT);
-	}
-	ucs->basstate = 0;
 
 	/* deregister this driver with the USB subsystem */
 	usb_deregister(&gigaset_usb_driver);
 	/* this will call the disconnect-callback */
 	/* from now on, no disconnect/probe callback should be running */
 
-	gigaset_freecs(cardstate);
-	cardstate = NULL;
 	gigaset_freedriver(driver);
 	driver = NULL;
 }
diff --git a/drivers/isdn/gigaset/common.c b/drivers/isdn/gigaset/common.c
index f8f7d7e5..aacedec 100644
--- a/drivers/isdn/gigaset/common.c
+++ b/drivers/isdn/gigaset/common.c
@@ -31,7 +31,6 @@
 /* driver state flags */
 #define VALID_MINOR	0x01
 #define VALID_ID	0x02
-#define ASSIGNED	0x04
 
 void gigaset_dbg_buffer(enum debuglevel level, const unsigned char *msg,
 			size_t len, const unsigned char *buf)
@@ -178,7 +177,7 @@
 	unsigned long flags;
 
 	spin_lock_irqsave(&bcs->cs->lock, flags);
-	if (bcs->use_count) {
+	if (bcs->use_count || !try_module_get(bcs->cs->driver->owner)) {
 		gig_dbg(DEBUG_ANY, "could not allocate channel %d",
 			bcs->channel);
 		spin_unlock_irqrestore(&bcs->cs->lock, flags);
@@ -203,6 +202,7 @@
 	}
 	--bcs->use_count;
 	bcs->busy = 0;
+	module_put(bcs->cs->driver->owner);
 	gig_dbg(DEBUG_ANY, "freed channel %d", bcs->channel);
 	spin_unlock_irqrestore(&bcs->cs->lock, flags);
 }
@@ -356,31 +356,28 @@
 {
 	unsigned long flags;
 	unsigned i;
+	struct cardstate *cs;
 	struct cardstate *ret = NULL;
 
 	spin_lock_irqsave(&drv->lock, flags);
+	if (drv->blocked)
+		goto exit;
 	for (i = 0; i < drv->minors; ++i) {
-		if (!(drv->flags[i] & VALID_MINOR)) {
-			if (try_module_get(drv->owner)) {
-				drv->flags[i] = VALID_MINOR;
-				ret = drv->cs + i;
-			}
+		cs = drv->cs + i;
+		if (!(cs->flags & VALID_MINOR)) {
+			cs->flags = VALID_MINOR;
+			ret = cs;
 			break;
 		}
 	}
+exit:
 	spin_unlock_irqrestore(&drv->lock, flags);
 	return ret;
 }
 
 static void free_cs(struct cardstate *cs)
 {
-	unsigned long flags;
-	struct gigaset_driver *drv = cs->driver;
-	spin_lock_irqsave(&drv->lock, flags);
-	if (drv->flags[cs->minor_index] & VALID_MINOR)
-		module_put(drv->owner);
-	drv->flags[cs->minor_index] = 0;
-	spin_unlock_irqrestore(&drv->lock, flags);
+	cs->flags = 0;
 }
 
 static void make_valid(struct cardstate *cs, unsigned mask)
@@ -388,7 +385,7 @@
 	unsigned long flags;
 	struct gigaset_driver *drv = cs->driver;
 	spin_lock_irqsave(&drv->lock, flags);
-	drv->flags[cs->minor_index] |= mask;
+	cs->flags |= mask;
 	spin_unlock_irqrestore(&drv->lock, flags);
 }
 
@@ -397,7 +394,7 @@
 	unsigned long flags;
 	struct gigaset_driver *drv = cs->driver;
 	spin_lock_irqsave(&drv->lock, flags);
-	drv->flags[cs->minor_index] &= ~mask;
+	cs->flags &= ~mask;
 	spin_unlock_irqrestore(&drv->lock, flags);
 }
 
@@ -893,10 +890,17 @@
 }
 EXPORT_SYMBOL_GPL(gigaset_start);
 
-void gigaset_shutdown(struct cardstate *cs)
+/* gigaset_shutdown
+ * check if a device is associated to the cardstate structure and stop it
+ * return value: 0 if ok, -1 if no device was associated
+ */
+int gigaset_shutdown(struct cardstate *cs)
 {
 	mutex_lock(&cs->mutex);
 
+	if (!(cs->flags & VALID_MINOR))
+		return -1;
+
 	cs->waiting = 1;
 
 	if (!gigaset_add_event(cs, &cs->at_state, EV_SHUTDOWN, NULL, 0, NULL)) {
@@ -913,6 +917,7 @@
 
 exit:
 	mutex_unlock(&cs->mutex);
+	return 0;
 }
 EXPORT_SYMBOL_GPL(gigaset_shutdown);
 
@@ -954,13 +959,11 @@
 	list_for_each_entry(drv, &drivers, list) {
 		spin_lock(&drv->lock);
 		for (i = 0; i < drv->minors; ++i) {
-			if (drv->flags[i] & VALID_ID) {
-				cs = drv->cs + i;
-				if (cs->myid == id)
-					ret = cs;
-			}
-			if (ret)
+			cs = drv->cs + i;
+			if ((cs->flags & VALID_ID) && cs->myid == id) {
+				ret = cs;
 				break;
+			}
 		}
 		spin_unlock(&drv->lock);
 		if (ret)
@@ -983,10 +986,9 @@
 		spin_lock(&drv->lock);
 		for (i = 0; i < drv->minors; ++i) {
 			gig_dbg(DEBUG_DRIVER, "  index %u", i);
-			gig_dbg(DEBUG_DRIVER, "    flags 0x%02x",
-				drv->flags[i]);
 			cs = drv->cs + i;
 			gig_dbg(DEBUG_DRIVER, "    cardstate %p", cs);
+			gig_dbg(DEBUG_DRIVER, "    flags 0x%02x", cs->flags);
 			gig_dbg(DEBUG_DRIVER, "    minor_index %u",
 				cs->minor_index);
 			gig_dbg(DEBUG_DRIVER, "    driver %p", cs->driver);
@@ -1010,7 +1012,7 @@
 			continue;
 		index = minor - drv->minor;
 		spin_lock(&drv->lock);
-		if (drv->flags[index] & VALID_MINOR)
+		if (drv->cs[index].flags & VALID_MINOR)
 			ret = drv->cs + index;
 		spin_unlock(&drv->lock);
 		if (ret)
@@ -1038,7 +1040,6 @@
 	gigaset_if_freedriver(drv);
 
 	kfree(drv->cs);
-	kfree(drv->flags);
 	kfree(drv);
 }
 EXPORT_SYMBOL_GPL(gigaset_freedriver);
@@ -1080,12 +1081,8 @@
 	if (!drv->cs)
 		goto error;
 
-	drv->flags = kmalloc(minors * sizeof *drv->flags, GFP_KERNEL);
-	if (!drv->flags)
-		goto error;
-
 	for (i = 0; i < minors; ++i) {
-		drv->flags[i] = 0;
+		drv->cs[i].flags = 0;
 		drv->cs[i].driver = drv;
 		drv->cs[i].ops = drv->ops;
 		drv->cs[i].minor_index = i;
@@ -1106,53 +1103,9 @@
 }
 EXPORT_SYMBOL_GPL(gigaset_initdriver);
 
-/* For drivers without fixed assignment device<->cardstate (usb) */
-struct cardstate *gigaset_getunassignedcs(struct gigaset_driver *drv)
-{
-	unsigned long flags;
-	struct cardstate *cs = NULL;
-	unsigned i;
-
-	spin_lock_irqsave(&drv->lock, flags);
-	if (drv->blocked)
-		goto exit;
-	for (i = 0; i < drv->minors; ++i) {
-		if ((drv->flags[i] & VALID_MINOR) &&
-		    !(drv->flags[i] & ASSIGNED)) {
-			drv->flags[i] |= ASSIGNED;
-			cs = drv->cs + i;
-			break;
-		}
-	}
-exit:
-	spin_unlock_irqrestore(&drv->lock, flags);
-	return cs;
-}
-EXPORT_SYMBOL_GPL(gigaset_getunassignedcs);
-
-void gigaset_unassign(struct cardstate *cs)
-{
-	unsigned long flags;
-	unsigned *minor_flags;
-	struct gigaset_driver *drv;
-
-	if (!cs)
-		return;
-	drv = cs->driver;
-	spin_lock_irqsave(&drv->lock, flags);
-	minor_flags = drv->flags + cs->minor_index;
-	if (*minor_flags & VALID_MINOR)
-		*minor_flags &= ~ASSIGNED;
-	spin_unlock_irqrestore(&drv->lock, flags);
-}
-EXPORT_SYMBOL_GPL(gigaset_unassign);
-
 void gigaset_blockdriver(struct gigaset_driver *drv)
 {
-	unsigned long flags;
-	spin_lock_irqsave(&drv->lock, flags);
 	drv->blocked = 1;
-	spin_unlock_irqrestore(&drv->lock, flags);
 }
 EXPORT_SYMBOL_GPL(gigaset_blockdriver);
 
diff --git a/drivers/isdn/gigaset/gigaset.h b/drivers/isdn/gigaset/gigaset.h
index 92986a5..f365993 100644
--- a/drivers/isdn/gigaset/gigaset.h
+++ b/drivers/isdn/gigaset/gigaset.h
@@ -435,6 +435,7 @@
 	unsigned minor_index;
 	struct device *dev;
 	struct device *tty_dev;
+	unsigned flags;
 
 	const struct gigaset_ops *ops;
 
@@ -539,7 +540,6 @@
 	unsigned minor;
 	unsigned minors;
 	struct cardstate *cs;
-	unsigned *flags;
 	int blocked;
 
 	const struct gigaset_ops *ops;
@@ -767,10 +767,6 @@
 void gigaset_debugdrivers(void);
 struct cardstate *gigaset_get_cs_by_tty(struct tty_struct *tty);
 struct cardstate *gigaset_get_cs_by_id(int id);
-
-/* For drivers without fixed assignment device<->cardstate (usb) */
-struct cardstate *gigaset_getunassignedcs(struct gigaset_driver *drv);
-void gigaset_unassign(struct cardstate *cs);
 void gigaset_blockdriver(struct gigaset_driver *drv);
 
 /* Allocate and initialize card state. Calls hardware dependent
@@ -789,7 +785,7 @@
 void gigaset_stop(struct cardstate *cs);
 
 /* Tell common.c that the driver is being unloaded. */
-void gigaset_shutdown(struct cardstate *cs);
+int gigaset_shutdown(struct cardstate *cs);
 
 /* Tell common.c that an skb has been sent. */
 void gigaset_skb_sent(struct bc_state *bcs, struct sk_buff *skb);
diff --git a/drivers/isdn/gigaset/interface.c b/drivers/isdn/gigaset/interface.c
index f4731c9..af195b0 100644
--- a/drivers/isdn/gigaset/interface.c
+++ b/drivers/isdn/gigaset/interface.c
@@ -161,7 +161,7 @@
 	tty->driver_data = NULL;
 
 	cs = gigaset_get_cs_by_tty(tty);
-	if (!cs)
+	if (!cs || !try_module_get(cs->driver->owner))
 		return -ENODEV;
 
 	if (mutex_lock_interruptible(&cs->mutex))
@@ -207,6 +207,8 @@
 	}
 
 	mutex_unlock(&cs->mutex);
+
+	module_put(cs->driver->owner);
 }
 
 static int if_ioctl(struct tty_struct *tty, struct file *file,
diff --git a/drivers/isdn/gigaset/usb-gigaset.c b/drivers/isdn/gigaset/usb-gigaset.c
index c58ddee..77d20ab 100644
--- a/drivers/isdn/gigaset/usb-gigaset.c
+++ b/drivers/isdn/gigaset/usb-gigaset.c
@@ -115,7 +115,6 @@
 static int gigaset_pre_reset(struct usb_interface *intf);
 
 static struct gigaset_driver *driver = NULL;
-static struct cardstate *cardstate = NULL;
 
 /* usb specific object needed to register this driver with the usb subsystem */
 static struct usb_driver gigaset_usb_driver = {
@@ -727,11 +726,10 @@
 
 	dev_info(&udev->dev, "%s: Device matched ... !\n", __func__);
 
-	cs = gigaset_getunassignedcs(driver);
-	if (!cs) {
-		dev_warn(&udev->dev, "no free cardstate\n");
+	/* allocate memory for our device state and intialize it */
+	cs = gigaset_initcs(driver, 1, 1, 0, cidmode, GIGASET_MODULENAME);
+	if (!cs)
 		return -ENODEV;
-	}
 	ucs = cs->hw.usb;
 
 	/* save off device structure ptrs for later use */
@@ -818,7 +816,7 @@
 	usb_put_dev(ucs->udev);
 	ucs->udev = NULL;
 	ucs->interface = NULL;
-	gigaset_unassign(cs);
+	gigaset_freecs(cs);
 	return retval;
 }
 
@@ -852,7 +850,7 @@
 	ucs->interface = NULL;
 	ucs->udev = NULL;
 	cs->dev = NULL;
-	gigaset_unassign(cs);
+	gigaset_freecs(cs);
 }
 
 /* gigaset_suspend
@@ -934,11 +932,6 @@
 				       &ops, THIS_MODULE)) == NULL)
 		goto error;
 
-	/* allocate memory for our device state and intialize it */
-	cardstate = gigaset_initcs(driver, 1, 1, 0, cidmode, GIGASET_MODULENAME);
-	if (!cardstate)
-		goto error;
-
 	/* register this driver with the USB subsystem */
 	result = usb_register(&gigaset_usb_driver);
 	if (result < 0) {
@@ -951,9 +944,7 @@
 	info(DRIVER_DESC);
 	return 0;
 
-error:	if (cardstate)
-		gigaset_freecs(cardstate);
-	cardstate = NULL;
+error:
 	if (driver)
 		gigaset_freedriver(driver);
 	driver = NULL;
@@ -967,11 +958,16 @@
  */
 static void __exit usb_gigaset_exit(void)
 {
+	int i;
+
 	gigaset_blockdriver(driver); /* => probe will fail
 				      * => no gigaset_start any more
 				      */
 
-	gigaset_shutdown(cardstate);
+	/* stop all connected devices */
+	for (i = 0; i < driver->minors; i++)
+		gigaset_shutdown(driver->cs + i);
+
 	/* from now on, no isdn callback should be possible */
 
 	/* deregister this driver with the USB subsystem */
@@ -979,8 +975,6 @@
 	/* this will call the disconnect-callback */
 	/* from now on, no disconnect/probe callback should be running */
 
-	gigaset_freecs(cardstate);
-	cardstate = NULL;
 	gigaset_freedriver(driver);
 	driver = NULL;
 }