Skip to content

Commit

Permalink
Gigaset: permit module unload
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Tilman Schmidt authored and Linus Torvalds committed Feb 6, 2008
1 parent 9d4bee2 commit e468c04
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 142 deletions.
80 changes: 39 additions & 41 deletions drivers/isdn/gigaset/bas-gigaset.c
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,6 @@ struct bas_cardstate {


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 = {
Expand Down Expand Up @@ -2247,11 +2246,11 @@ static int gigaset_probe(struct usb_interface *interface,
__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 */
Expand Down Expand Up @@ -2320,7 +2319,7 @@ static int gigaset_probe(struct usb_interface *interface,
error:
freeurbs(cs);
usb_set_intfdata(interface, NULL);
gigaset_unassign(cs);
gigaset_freecs(cs);
return -ENODEV;
}

Expand Down Expand Up @@ -2362,7 +2361,7 @@ static void gigaset_disconnect(struct usb_interface *interface)
ucs->interface = NULL;
ucs->udev = NULL;
cs->dev = NULL;
gigaset_unassign(cs);
gigaset_freecs(cs);
}

/* gigaset_suspend
Expand Down Expand Up @@ -2501,12 +2500,6 @@ static int __init bas_gigaset_init(void)
&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) {
Expand All @@ -2518,9 +2511,7 @@ static int __init bas_gigaset_init(void)
info(DRIVER_DESC);
return 0;

error: if (cardstate)
gigaset_freecs(cardstate);
cardstate = NULL;
error:
if (driver)
gigaset_freedriver(driver);
driver = NULL;
Expand All @@ -2532,43 +2523,50 @@ error: if (cardstate)
*/
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 */

/* 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);
}
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);
/* 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 */
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;
}
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;
}
Expand Down
105 changes: 29 additions & 76 deletions drivers/isdn/gigaset/common.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ MODULE_PARM_DESC(debug, "debug level");
/* 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)
Expand Down Expand Up @@ -178,7 +177,7 @@ int gigaset_get_channel(struct bc_state *bcs)
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);
Expand All @@ -203,6 +202,7 @@ void gigaset_free_channel(struct bc_state *bcs)
}
--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);
}
Expand Down Expand Up @@ -356,39 +356,36 @@ static struct cardstate *alloc_cs(struct gigaset_driver *drv)
{
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)
{
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);
}

Expand All @@ -397,7 +394,7 @@ static void make_invalid(struct cardstate *cs, unsigned mask)
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);
}

Expand Down Expand Up @@ -893,10 +890,17 @@ int gigaset_start(struct cardstate *cs)
}
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)) {
Expand All @@ -913,6 +917,7 @@ void gigaset_shutdown(struct cardstate *cs)

exit:
mutex_unlock(&cs->mutex);
return 0;
}
EXPORT_SYMBOL_GPL(gigaset_shutdown);

Expand Down Expand Up @@ -954,13 +959,11 @@ struct cardstate *gigaset_get_cs_by_id(int id)
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)
Expand All @@ -983,10 +986,9 @@ void gigaset_debugdrivers(void)
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);
Expand All @@ -1010,7 +1012,7 @@ static struct cardstate *gigaset_get_cs_by_minor(unsigned minor)
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)
Expand Down Expand Up @@ -1038,7 +1040,6 @@ void gigaset_freedriver(struct gigaset_driver *drv)
gigaset_if_freedriver(drv);

kfree(drv->cs);
kfree(drv->flags);
kfree(drv);
}
EXPORT_SYMBOL_GPL(gigaset_freedriver);
Expand Down Expand Up @@ -1080,12 +1081,8 @@ struct gigaset_driver *gigaset_initdriver(unsigned minor, unsigned minors,
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;
Expand All @@ -1106,53 +1103,9 @@ struct gigaset_driver *gigaset_initdriver(unsigned minor, unsigned minors,
}
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);

Expand Down
Loading

0 comments on commit e468c04

Please sign in to comment.