The patch titled bas_gigaset: suspend support has been added to the -mm tree. Its filename is bas_gigaset-suspend-support.patch *** Remember to use Documentation/SubmitChecklist when testing your code *** See http://www.zip.com.au/~akpm/linux/patches/stuff/added-to-mm.txt to find out what to do about this ------------------------------------------------------ Subject: bas_gigaset: suspend support From: Tilman Schmidt <tilman@xxxxxxx> Add basic suspend/resume support to the bas_gigaset driver. Signed-off-by: Tilman Schmidt <tilman@xxxxxxx> Signed-off-by: Andrew Morton <akpm@xxxxxxxxxxxxxxxxxxxx> --- drivers/isdn/gigaset/bas-gigaset.c | 230 ++++++++++++++++++++++++--- 1 file changed, 207 insertions(+), 23 deletions(-) diff -puN drivers/isdn/gigaset/bas-gigaset.c~bas_gigaset-suspend-support drivers/isdn/gigaset/bas-gigaset.c --- a/drivers/isdn/gigaset/bas-gigaset.c~bas_gigaset-suspend-support +++ a/drivers/isdn/gigaset/bas-gigaset.c @@ -43,6 +43,9 @@ MODULE_PARM_DESC(cidmode, "Call-ID mode" #define GIGASET_MODULENAME "bas_gigaset" #define GIGASET_DEVNAME "ttyGB" +/* number of B channels */ +#define BAS_CHANNELS 2 + /* length limit according to Siemens 3070usb-protokoll.doc ch. 2.1 */ #define IF_WRITEBUF 264 @@ -73,6 +76,14 @@ static int gigaset_probe(struct usb_inte /* Function will be called if the device is unplugged */ static void gigaset_disconnect(struct usb_interface *interface); +/* functions called before/after suspend */ +static int gigaset_suspend(struct usb_interface *intf, pm_message_t message); +static int gigaset_resume(struct usb_interface *intf); + +/* functions called before/after device reset */ +static int gigaset_pre_reset(struct usb_interface *intf); +static int gigaset_post_reset(struct usb_interface *intf); + static int atread_submit(struct cardstate *, int); static void stopurbs(struct bas_bc_state *); static int req_submit(struct bc_state *, int, int, int); @@ -107,6 +118,7 @@ struct bas_cardstate { spinlock_t lock; /* locks all following */ atomic_t basstate; /* bitmap (BS_*) */ int pending; /* uncompleted base request */ + wait_queue_head_t waitqueue; int rcvbuf_size; /* size of AT receive buffer */ /* 0: no receive in progress */ int retry_cmd_in; /* receive req retry count */ @@ -121,6 +133,7 @@ struct bas_cardstate { #define BS_ATTIMER 0x020 /* waiting for HD_READY_SEND_ATDATA */ #define BS_ATRDPEND 0x040 /* urb_cmd_in in use */ #define BS_ATWRPEND 0x080 /* urb_cmd_out in use */ +#define BS_SUSPEND 0x100 /* USB port suspended */ static struct gigaset_driver *driver = NULL; @@ -132,6 +145,11 @@ static struct usb_driver gigaset_usb_dri .probe = gigaset_probe, .disconnect = gigaset_disconnect, .id_table = gigaset_table, + .suspend = gigaset_suspend, + .resume = gigaset_resume, + .reset_resume = gigaset_post_reset, + .pre_reset = gigaset_pre_reset, + .post_reset = gigaset_post_reset, }; /* get message text for usb_submit_urb return code @@ -464,6 +482,7 @@ static void read_ctrl_callback(struct ur int rc; update_basstate(ucs, 0, BS_ATRDPEND); + wake_up(&ucs->waitqueue); if (!ucs->rcvbuf_size) { dev_warn(cs->dev, "%s: no receive in progress\n", __func__); @@ -550,17 +569,27 @@ static void read_ctrl_callback(struct ur static int atread_submit(struct cardstate *cs, int timeout) { struct bas_cardstate *ucs = cs->hw.bas; + int basstate; int ret; gig_dbg(DEBUG_USBREQ, "-------> HD_READ_ATMESSAGE (%d)", ucs->rcvbuf_size); - if (update_basstate(ucs, BS_ATRDPEND, 0) & BS_ATRDPEND) { + if ((basstate = update_basstate(ucs, BS_ATRDPEND, 0)) & BS_ATRDPEND) { dev_err(cs->dev, "could not submit HD_READ_ATMESSAGE: URB busy\n"); return -EBUSY; } + if (basstate & BS_SUSPEND) { + dev_notice(cs->dev, + "HD_READ_ATMESSAGE not submitted, " + "suspend in progress\n"); + update_basstate(ucs, 0, BS_ATRDPEND); + /* treat like disconnect */ + return -ENODEV; + } + ucs->dr_cmd_in.bRequestType = IN_VENDOR_REQ; ucs->dr_cmd_in.bRequest = HD_READ_ATMESSAGE; ucs->dr_cmd_in.wValue = 0; @@ -745,6 +774,7 @@ static void read_int_callback(struct urb } check_pending(ucs); + wake_up(&ucs->waitqueue); resubmit: rc = usb_submit_urb(urb, GFP_ATOMIC); @@ -932,15 +962,15 @@ static int starturbs(struct bc_state *bc ubc->isoouturbs[k].limit = -1; } - /* submit two URBs, keep third one */ - for (k = 0; k < 2; ++k) { + /* keep one URB free, submit the others */ + for (k = 0; k < BAS_OUTURBS-1; ++k) { dump_urb(DEBUG_ISO, "Initial isoc write", urb); rc = usb_submit_urb(ubc->isoouturbs[k].urb, GFP_ATOMIC); if (rc != 0) goto error; } dump_urb(DEBUG_ISO, "Initial isoc write (free)", urb); - ubc->isooutfree = &ubc->isoouturbs[2]; + ubc->isooutfree = &ubc->isoouturbs[BAS_OUTURBS-1]; ubc->isooutdone = ubc->isooutovfl = NULL; return 0; error: @@ -1406,6 +1436,8 @@ static void req_timeout(unsigned long da dev_warn(bcs->cs->dev, "request 0x%02x timed out, clearing\n", pending); } + + wake_up(&ucs->waitqueue); } /* write_ctrl_callback @@ -1445,7 +1477,9 @@ static void write_ctrl_callback(struct u break; default: /* any failure */ - if (++ucs->retry_ctrl > BAS_RETRY) { + /* don't retry if suspend requested */ + if (++ucs->retry_ctrl > BAS_RETRY || + (atomic_read(&ucs->basstate) & BS_SUSPEND)) { dev_err(&ucs->interface->dev, "control request 0x%02x failed: %s\n", ucs->dr_ctrl.bRequest, @@ -1474,6 +1508,7 @@ static void write_ctrl_callback(struct u del_timer(&ucs->timer_ctrl); ucs->pending = 0; spin_unlock_irqrestore(&ucs->lock, flags); + wake_up(&ucs->waitqueue); } /* req_submit @@ -1548,37 +1583,46 @@ static int req_submit(struct bc_state *b */ static int gigaset_init_bchannel(struct bc_state *bcs) { + struct cardstate *cs = bcs->cs; int req, ret; unsigned long flags; - spin_lock_irqsave(&bcs->cs->lock, flags); - if (unlikely(!bcs->cs->connected)) { + spin_lock_irqsave(&cs->lock, flags); + if (unlikely(!cs->connected)) { gig_dbg(DEBUG_USBREQ, "%s: not connected", __func__); - spin_unlock_irqrestore(&bcs->cs->lock, flags); + spin_unlock_irqrestore(&cs->lock, flags); return -ENODEV; } + if (atomic_read(&cs->hw.bas->basstate) & BS_SUSPEND) { + dev_notice(cs->dev, + "not starting isochronous I/O, " + "suspend in progress\n"); + spin_unlock_irqrestore(&cs->lock, flags); + return -EHOSTUNREACH; + } + if ((ret = starturbs(bcs)) < 0) { - dev_err(bcs->cs->dev, + dev_err(cs->dev, "could not start isochronous I/O for channel B%d: %s\n", bcs->channel + 1, ret == -EFAULT ? "null URB" : get_usb_rcmsg(ret)); if (ret != -ENODEV) error_hangup(bcs); - spin_unlock_irqrestore(&bcs->cs->lock, flags); + spin_unlock_irqrestore(&cs->lock, flags); return ret; } req = bcs->channel ? HD_OPEN_B2CHANNEL : HD_OPEN_B1CHANNEL; if ((ret = req_submit(bcs, req, 0, BAS_TIMEOUT)) < 0) { - dev_err(bcs->cs->dev, "could not open channel B%d\n", + dev_err(cs->dev, "could not open channel B%d\n", bcs->channel + 1); stopurbs(bcs->hw.bas); if (ret != -ENODEV) error_hangup(bcs); } - spin_unlock_irqrestore(&bcs->cs->lock, flags); + spin_unlock_irqrestore(&cs->lock, flags); return ret; } @@ -1594,20 +1638,21 @@ static int gigaset_init_bchannel(struct */ static int gigaset_close_bchannel(struct bc_state *bcs) { + struct cardstate *cs = bcs->cs; int req, ret; unsigned long flags; - spin_lock_irqsave(&bcs->cs->lock, flags); - if (unlikely(!bcs->cs->connected)) { - spin_unlock_irqrestore(&bcs->cs->lock, flags); + spin_lock_irqsave(&cs->lock, flags); + if (unlikely(!cs->connected)) { + spin_unlock_irqrestore(&cs->lock, flags); gig_dbg(DEBUG_USBREQ, "%s: not connected", __func__); return -ENODEV; } - if (!(atomic_read(&bcs->cs->hw.bas->basstate) & + if (!(atomic_read(&cs->hw.bas->basstate) & (bcs->channel ? BS_B2OPEN : BS_B1OPEN))) { /* channel not running: just signal common.c */ - spin_unlock_irqrestore(&bcs->cs->lock, flags); + spin_unlock_irqrestore(&cs->lock, flags); gigaset_bchannel_down(bcs); return 0; } @@ -1615,10 +1660,10 @@ static int gigaset_close_bchannel(struct /* channel running: tell device to close it */ req = bcs->channel ? HD_CLOSE_B2CHANNEL : HD_CLOSE_B1CHANNEL; if ((ret = req_submit(bcs, req, 0, BAS_TIMEOUT)) < 0) - dev_err(bcs->cs->dev, "closing channel B%d failed\n", + dev_err(cs->dev, "closing channel B%d failed\n", bcs->channel + 1); - spin_unlock_irqrestore(&bcs->cs->lock, flags); + spin_unlock_irqrestore(&cs->lock, flags); return ret; } @@ -1668,6 +1713,7 @@ static void write_command_callback(struc unsigned long flags; update_basstate(ucs, 0, BS_ATWRPEND); + wake_up(&ucs->waitqueue); /* check status */ switch (urb->status) { @@ -1691,6 +1737,13 @@ static void write_command_callback(struc ucs->retry_cmd_out); break; } + if (atomic_read(&ucs->basstate) & BS_SUSPEND) { + dev_warn(cs->dev, + "command write: %s, " + "won't retry - suspend requested\n", + get_usb_statmsg(status)); + break; + } if (cs->cmdbuf == NULL) { dev_warn(cs->dev, "command write: %s, " @@ -1799,6 +1852,12 @@ static int start_cbsend(struct cardstate int rc; int retval = 0; + /* check if suspend requested */ + if (atomic_read(&ucs->basstate) & BS_SUSPEND) { + gig_dbg(DEBUG_TRANSCMD|DEBUG_LOCKCMD, "suspending"); + return -EHOSTUNREACH; + } + /* check if AT channel is open */ if (!(atomic_read(&ucs->basstate) & BS_ATOPEN)) { gig_dbg(DEBUG_TRANSCMD|DEBUG_LOCKCMD, "AT channel not open"); @@ -2102,7 +2161,7 @@ static void freeurbs(struct cardstate *c int i, j; gig_dbg(DEBUG_INIT, "%s: killing URBs", __func__); - for (j = 0; j < 2; ++j) { + for (j = 0; j < BAS_CHANNELS; ++j) { ubc = cs->bcs[j].hw.bas; for (i = 0; i < BAS_OUTURBS; ++i) { usb_kill_urb(ubc->isoouturbs[i].urb); @@ -2203,7 +2262,7 @@ static int gigaset_probe(struct usb_inte !(ucs->urb_ctrl = usb_alloc_urb(0, GFP_KERNEL))) goto allocerr; - for (j = 0; j < 2; ++j) { + for (j = 0; j < BAS_CHANNELS; ++j) { ubc = cs->bcs[j].hw.bas; for (i = 0; i < BAS_OUTURBS; ++i) if (!(ubc->isoouturbs[i].urb = @@ -2276,7 +2335,7 @@ static void gigaset_disconnect(struct us /* tell LL all channels are down */ //FIXME shouldn't gigaset_stop() do this? - for (j = 0; j < 2; ++j) + for (j = 0; j < BAS_CHANNELS; ++j) gigaset_bchannel_down(cs->bcs + j); /* stop driver (common part) */ @@ -2298,6 +2357,131 @@ static void gigaset_disconnect(struct us gigaset_unassign(cs); } +/* gigaset_suspend + * This function is called before the USB connection is suspended. + */ +static int gigaset_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct cardstate *cs; + struct bas_cardstate *ucs; + int basstate; + int rc; + + if ((cs = usb_get_intfdata(intf)) == NULL || + (ucs = cs->hw.bas) == NULL) { + err("%s: no cardstate", __func__); + return -EFAULT; + } + + /* set suspend flag; this stops AT command/response traffic */ + if (update_basstate(ucs, BS_SUSPEND, 0) & BS_SUSPEND) { + gig_dbg(DEBUG_INIT, "already suspended"); + return 0; + } + + //FIXME propagate to common module? ISDN_STAT_STOP? block open()? + + /* wait a bit for blocking conditions to go away */ + //FIXME actively terminate them? + rc = wait_event_timeout(ucs->waitqueue, + !(atomic_read(&ucs->basstate) & + (BS_B1OPEN|BS_B2OPEN|BS_ATRDPEND|BS_ATWRPEND)), + BAS_TIMEOUT*HZ/10); + gig_dbg(DEBUG_INIT, "wait_event_timeout() -> %d", rc); + + /* check for conditions preventing suspend */ + basstate = atomic_read(&ucs->basstate); + if (basstate & (BS_B1OPEN|BS_B2OPEN|BS_ATRDPEND|BS_ATWRPEND)) { + dev_warn(cs->dev, "cannot suspend:\n"); + if (basstate & BS_B1OPEN) + dev_warn(cs->dev, " B channel 1 open\n"); + if (basstate & BS_B2OPEN) + dev_warn(cs->dev, " B channel 2 open\n"); + if (basstate & BS_ATRDPEND) + dev_warn(cs->dev, " receiving AT reply\n"); + if (basstate & BS_ATWRPEND) + dev_warn(cs->dev, " sending AT command\n"); + update_basstate(ucs, 0, BS_SUSPEND); + return -EBUSY; + } + + /* close AT channel if open */ + if (basstate & BS_ATOPEN) { + gig_dbg(DEBUG_INIT, "closing AT channel"); + if ((rc = req_submit(cs->bcs, HD_CLOSE_ATCHANNEL, 0, 0)) != 0) { + update_basstate(ucs, 0, BS_SUSPEND); + return rc; + } + wait_event_timeout(ucs->waitqueue, !ucs->pending, + BAS_TIMEOUT*HZ/10); + /* in case of timeout, proceed anyway */ + } + + /* kill all URBs and timers that might still be pending */ + usb_kill_urb(ucs->urb_ctrl); + usb_kill_urb(ucs->urb_int_in); + del_timer_sync(&ucs->timer_ctrl); + + gig_dbg(DEBUG_INIT, "suspend complete"); + return 0; +} + +/* gigaset_resume + * This function is called after the USB connection has been resumed. + */ +static int gigaset_resume(struct usb_interface *intf) +{ + struct cardstate *cs; + struct bas_cardstate *ucs; + int rc; + + if ((cs = usb_get_intfdata(intf)) == NULL || + (ucs = cs->hw.bas) == NULL) { + err("%s: no cardstate", __func__); + return -EFAULT; + } + + /* resubmit interrupt URB for messages from base */ + if ((rc = usb_submit_urb(ucs->urb_int_in, GFP_KERNEL)) != 0) { + dev_err(cs->dev, "could not resubmit interrupt URB: %s\n", + get_usb_rcmsg(rc)); + return rc; + } + + /* clear suspend flag to reallow activity */ + update_basstate(ucs, 0, BS_SUSPEND); + + //FIXME propagate to common module? ISDN_STAT_STAR? unblock open()? + + gig_dbg(DEBUG_INIT, "resume complete"); + return 0; +} + +/* gigaset_pre_reset + * This function is called before the USB connection is reset. + */ +static int gigaset_pre_reset(struct usb_interface *intf) +{ + /* handle just like suspend but always succeed */ + gigaset_suspend(intf, PMSG_ON); + return 0; +} + +/* gigaset_post_reset + * This function is called after the USB connection has been reset. + */ +static int gigaset_post_reset(struct usb_interface *intf) +{ + //struct cardstate *cs = usb_get_intfdata(intf); + + /* reinitialize device */ + //FIXME anything to do? eg send HD_DEVICE_INIT_ACK? + + /* resume operations */ + return gigaset_resume(intf); +} + + static const struct gigaset_ops gigops = { gigaset_write_cmd, gigaset_write_room, @@ -2331,7 +2515,7 @@ static int __init bas_gigaset_init(void) goto error; /* allocate memory for our device state and intialize it */ - cardstate = gigaset_initcs(driver, 2, 0, 0, cidmode, + cardstate = gigaset_initcs(driver, BAS_CHANNELS, 0, 0, cidmode, GIGASET_MODULENAME); if (!cardstate) goto error; _ Patches currently in -mm which might be from tilman@xxxxxxx are usb_gigaset-suspend-support.patch bas_gigaset-suspend-support.patch - To unsubscribe from this list: send the line "unsubscribe mm-commits" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html