| /* |
| * Stuff used by all variants of the driver |
| * |
| * Copyright (c) 2001 by Stefan Eilers, |
| * Hansjoerg Lipp <hjlipp@web.de>, |
| * Tilman Schmidt <tilman@imap.cc>. |
| * |
| * ===================================================================== |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License as |
| * published by the Free Software Foundation; either version 2 of |
| * the License, or (at your option) any later version. |
| * ===================================================================== |
| */ |
| |
| #include "gigaset.h" |
| #include <linux/ctype.h> |
| #include <linux/module.h> |
| #include <linux/moduleparam.h> |
| |
| /* Version Information */ |
| #define DRIVER_AUTHOR "Hansjoerg Lipp <hjlipp@web.de>, Tilman Schmidt <tilman@imap.cc>, Stefan Eilers" |
| #define DRIVER_DESC "Driver for Gigaset 307x" |
| |
| /* Module parameters */ |
| int gigaset_debuglevel = DEBUG_DEFAULT; |
| EXPORT_SYMBOL_GPL(gigaset_debuglevel); |
| module_param_named(debug, gigaset_debuglevel, int, S_IRUGO|S_IWUSR); |
| MODULE_PARM_DESC(debug, "debug level"); |
| |
| /* driver state flags */ |
| #define VALID_MINOR 0x01 |
| #define VALID_ID 0x02 |
| |
| void gigaset_dbg_buffer(enum debuglevel level, const unsigned char *msg, |
| size_t len, const unsigned char *buf) |
| { |
| unsigned char outbuf[80]; |
| unsigned char c; |
| size_t space = sizeof outbuf - 1; |
| unsigned char *out = outbuf; |
| size_t numin = len; |
| |
| while (numin--) { |
| c = *buf++; |
| if (c == '~' || c == '^' || c == '\\') { |
| if (!space--) |
| break; |
| *out++ = '\\'; |
| } |
| if (c & 0x80) { |
| if (!space--) |
| break; |
| *out++ = '~'; |
| c ^= 0x80; |
| } |
| if (c < 0x20 || c == 0x7f) { |
| if (!space--) |
| break; |
| *out++ = '^'; |
| c ^= 0x40; |
| } |
| if (!space--) |
| break; |
| *out++ = c; |
| } |
| *out = 0; |
| |
| gig_dbg(level, "%s (%u bytes): %s", msg, (unsigned) len, outbuf); |
| } |
| EXPORT_SYMBOL_GPL(gigaset_dbg_buffer); |
| |
| static int setflags(struct cardstate *cs, unsigned flags, unsigned delay) |
| { |
| int r; |
| |
| r = cs->ops->set_modem_ctrl(cs, cs->control_state, flags); |
| cs->control_state = flags; |
| if (r < 0) |
| return r; |
| |
| if (delay) { |
| set_current_state(TASK_INTERRUPTIBLE); |
| schedule_timeout(delay * HZ / 1000); |
| } |
| |
| return 0; |
| } |
| |
| int gigaset_enterconfigmode(struct cardstate *cs) |
| { |
| int i, r; |
| |
| cs->control_state = TIOCM_RTS; //FIXME |
| |
| r = setflags(cs, TIOCM_DTR, 200); |
| if (r < 0) |
| goto error; |
| r = setflags(cs, 0, 200); |
| if (r < 0) |
| goto error; |
| for (i = 0; i < 5; ++i) { |
| r = setflags(cs, TIOCM_RTS, 100); |
| if (r < 0) |
| goto error; |
| r = setflags(cs, 0, 100); |
| if (r < 0) |
| goto error; |
| } |
| r = setflags(cs, TIOCM_RTS|TIOCM_DTR, 800); |
| if (r < 0) |
| goto error; |
| |
| return 0; |
| |
| error: |
| dev_err(cs->dev, "error %d on setuartbits\n", -r); |
| cs->control_state = TIOCM_RTS|TIOCM_DTR; // FIXME is this a good value? |
| cs->ops->set_modem_ctrl(cs, 0, TIOCM_RTS|TIOCM_DTR); |
| |
| return -1; //r |
| } |
| |
| static int test_timeout(struct at_state_t *at_state) |
| { |
| if (!at_state->timer_expires) |
| return 0; |
| |
| if (--at_state->timer_expires) { |
| gig_dbg(DEBUG_MCMD, "decreased timer of %p to %lu", |
| at_state, at_state->timer_expires); |
| return 0; |
| } |
| |
| if (!gigaset_add_event(at_state->cs, at_state, EV_TIMEOUT, NULL, |
| at_state->timer_index, NULL)) { |
| //FIXME what should we do? |
| } |
| |
| return 1; |
| } |
| |
| static void timer_tick(unsigned long data) |
| { |
| struct cardstate *cs = (struct cardstate *) data; |
| unsigned long flags; |
| unsigned channel; |
| struct at_state_t *at_state; |
| int timeout = 0; |
| |
| spin_lock_irqsave(&cs->lock, flags); |
| |
| for (channel = 0; channel < cs->channels; ++channel) |
| if (test_timeout(&cs->bcs[channel].at_state)) |
| timeout = 1; |
| |
| if (test_timeout(&cs->at_state)) |
| timeout = 1; |
| |
| list_for_each_entry(at_state, &cs->temp_at_states, list) |
| if (test_timeout(at_state)) |
| timeout = 1; |
| |
| if (cs->running) { |
| mod_timer(&cs->timer, jiffies + msecs_to_jiffies(GIG_TICK)); |
| if (timeout) { |
| gig_dbg(DEBUG_CMD, "scheduling timeout"); |
| tasklet_schedule(&cs->event_tasklet); |
| } |
| } |
| |
| spin_unlock_irqrestore(&cs->lock, flags); |
| } |
| |
| int gigaset_get_channel(struct bc_state *bcs) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&bcs->cs->lock, flags); |
| 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); |
| return 0; |
| } |
| ++bcs->use_count; |
| bcs->busy = 1; |
| gig_dbg(DEBUG_ANY, "allocated channel %d", bcs->channel); |
| spin_unlock_irqrestore(&bcs->cs->lock, flags); |
| return 1; |
| } |
| |
| void gigaset_free_channel(struct bc_state *bcs) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&bcs->cs->lock, flags); |
| if (!bcs->busy) { |
| gig_dbg(DEBUG_ANY, "could not free channel %d", bcs->channel); |
| spin_unlock_irqrestore(&bcs->cs->lock, flags); |
| return; |
| } |
| --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); |
| } |
| |
| int gigaset_get_channels(struct cardstate *cs) |
| { |
| unsigned long flags; |
| int i; |
| |
| spin_lock_irqsave(&cs->lock, flags); |
| for (i = 0; i < cs->channels; ++i) |
| if (cs->bcs[i].use_count) { |
| spin_unlock_irqrestore(&cs->lock, flags); |
| gig_dbg(DEBUG_ANY, "could not allocate all channels"); |
| return 0; |
| } |
| for (i = 0; i < cs->channels; ++i) |
| ++cs->bcs[i].use_count; |
| spin_unlock_irqrestore(&cs->lock, flags); |
| |
| gig_dbg(DEBUG_ANY, "allocated all channels"); |
| |
| return 1; |
| } |
| |
| void gigaset_free_channels(struct cardstate *cs) |
| { |
| unsigned long flags; |
| int i; |
| |
| gig_dbg(DEBUG_ANY, "unblocking all channels"); |
| spin_lock_irqsave(&cs->lock, flags); |
| for (i = 0; i < cs->channels; ++i) |
| --cs->bcs[i].use_count; |
| spin_unlock_irqrestore(&cs->lock, flags); |
| } |
| |
| void gigaset_block_channels(struct cardstate *cs) |
| { |
| unsigned long flags; |
| int i; |
| |
| gig_dbg(DEBUG_ANY, "blocking all channels"); |
| spin_lock_irqsave(&cs->lock, flags); |
| for (i = 0; i < cs->channels; ++i) |
| ++cs->bcs[i].use_count; |
| spin_unlock_irqrestore(&cs->lock, flags); |
| } |
| |
| static void clear_events(struct cardstate *cs) |
| { |
| struct event_t *ev; |
| unsigned head, tail; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&cs->ev_lock, flags); |
| |
| head = cs->ev_head; |
| tail = cs->ev_tail; |
| |
| while (tail != head) { |
| ev = cs->events + head; |
| kfree(ev->ptr); |
| head = (head + 1) % MAX_EVENTS; |
| } |
| |
| cs->ev_head = tail; |
| |
| spin_unlock_irqrestore(&cs->ev_lock, flags); |
| } |
| |
| struct event_t *gigaset_add_event(struct cardstate *cs, |
| struct at_state_t *at_state, int type, |
| void *ptr, int parameter, void *arg) |
| { |
| unsigned long flags; |
| unsigned next, tail; |
| struct event_t *event = NULL; |
| |
| spin_lock_irqsave(&cs->ev_lock, flags); |
| |
| tail = cs->ev_tail; |
| next = (tail + 1) % MAX_EVENTS; |
| if (unlikely(next == cs->ev_head)) |
| dev_err(cs->dev, "event queue full\n"); |
| else { |
| event = cs->events + tail; |
| event->type = type; |
| event->at_state = at_state; |
| event->cid = -1; |
| event->ptr = ptr; |
| event->arg = arg; |
| event->parameter = parameter; |
| cs->ev_tail = next; |
| } |
| |
| spin_unlock_irqrestore(&cs->ev_lock, flags); |
| |
| return event; |
| } |
| EXPORT_SYMBOL_GPL(gigaset_add_event); |
| |
| static void free_strings(struct at_state_t *at_state) |
| { |
| int i; |
| |
| for (i = 0; i < STR_NUM; ++i) { |
| kfree(at_state->str_var[i]); |
| at_state->str_var[i] = NULL; |
| } |
| } |
| |
| static void clear_at_state(struct at_state_t *at_state) |
| { |
| free_strings(at_state); |
| } |
| |
| static void dealloc_at_states(struct cardstate *cs) |
| { |
| struct at_state_t *cur, *next; |
| |
| list_for_each_entry_safe(cur, next, &cs->temp_at_states, list) { |
| list_del(&cur->list); |
| free_strings(cur); |
| kfree(cur); |
| } |
| } |
| |
| static void gigaset_freebcs(struct bc_state *bcs) |
| { |
| int i; |
| |
| gig_dbg(DEBUG_INIT, "freeing bcs[%d]->hw", bcs->channel); |
| if (!bcs->cs->ops->freebcshw(bcs)) { |
| gig_dbg(DEBUG_INIT, "failed"); |
| } |
| |
| gig_dbg(DEBUG_INIT, "clearing bcs[%d]->at_state", bcs->channel); |
| clear_at_state(&bcs->at_state); |
| gig_dbg(DEBUG_INIT, "freeing bcs[%d]->skb", bcs->channel); |
| |
| if (bcs->skb) |
| dev_kfree_skb(bcs->skb); |
| for (i = 0; i < AT_NUM; ++i) { |
| kfree(bcs->commands[i]); |
| bcs->commands[i] = NULL; |
| } |
| } |
| |
| 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) { |
| 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) |
| { |
| 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); |
| cs->flags |= mask; |
| spin_unlock_irqrestore(&drv->lock, flags); |
| } |
| |
| static void make_invalid(struct cardstate *cs, unsigned mask) |
| { |
| unsigned long flags; |
| struct gigaset_driver *drv = cs->driver; |
| spin_lock_irqsave(&drv->lock, flags); |
| cs->flags &= ~mask; |
| spin_unlock_irqrestore(&drv->lock, flags); |
| } |
| |
| void gigaset_freecs(struct cardstate *cs) |
| { |
| int i; |
| unsigned long flags; |
| |
| if (!cs) |
| return; |
| |
| mutex_lock(&cs->mutex); |
| |
| if (!cs->bcs) |
| goto f_cs; |
| if (!cs->inbuf) |
| goto f_bcs; |
| |
| spin_lock_irqsave(&cs->lock, flags); |
| cs->running = 0; |
| spin_unlock_irqrestore(&cs->lock, flags); /* event handler and timer are |
| not rescheduled below */ |
| |
| tasklet_kill(&cs->event_tasklet); |
| del_timer_sync(&cs->timer); |
| |
| switch (cs->cs_init) { |
| default: |
| /* clear device sysfs */ |
| gigaset_free_dev_sysfs(cs); |
| |
| gigaset_if_free(cs); |
| |
| gig_dbg(DEBUG_INIT, "clearing hw"); |
| cs->ops->freecshw(cs); |
| |
| //FIXME cmdbuf |
| |
| /* fall through */ |
| case 2: /* error in initcshw */ |
| /* Deregister from LL */ |
| make_invalid(cs, VALID_ID); |
| gig_dbg(DEBUG_INIT, "clearing iif"); |
| gigaset_i4l_cmd(cs, ISDN_STAT_UNLOAD); |
| |
| /* fall through */ |
| case 1: /* error when regestering to LL */ |
| gig_dbg(DEBUG_INIT, "clearing at_state"); |
| clear_at_state(&cs->at_state); |
| dealloc_at_states(cs); |
| |
| /* fall through */ |
| case 0: /* error in one call to initbcs */ |
| for (i = 0; i < cs->channels; ++i) { |
| gig_dbg(DEBUG_INIT, "clearing bcs[%d]", i); |
| gigaset_freebcs(cs->bcs + i); |
| } |
| |
| clear_events(cs); |
| gig_dbg(DEBUG_INIT, "freeing inbuf"); |
| kfree(cs->inbuf); |
| } |
| f_bcs: gig_dbg(DEBUG_INIT, "freeing bcs[]"); |
| kfree(cs->bcs); |
| f_cs: gig_dbg(DEBUG_INIT, "freeing cs"); |
| mutex_unlock(&cs->mutex); |
| free_cs(cs); |
| } |
| EXPORT_SYMBOL_GPL(gigaset_freecs); |
| |
| void gigaset_at_init(struct at_state_t *at_state, struct bc_state *bcs, |
| struct cardstate *cs, int cid) |
| { |
| int i; |
| |
| INIT_LIST_HEAD(&at_state->list); |
| at_state->waiting = 0; |
| at_state->getstring = 0; |
| at_state->pending_commands = 0; |
| at_state->timer_expires = 0; |
| at_state->timer_active = 0; |
| at_state->timer_index = 0; |
| at_state->seq_index = 0; |
| at_state->ConState = 0; |
| for (i = 0; i < STR_NUM; ++i) |
| at_state->str_var[i] = NULL; |
| at_state->int_var[VAR_ZDLE] = 0; |
| at_state->int_var[VAR_ZCTP] = -1; |
| at_state->int_var[VAR_ZSAU] = ZSAU_NULL; |
| at_state->cs = cs; |
| at_state->bcs = bcs; |
| at_state->cid = cid; |
| if (!cid) |
| at_state->replystruct = cs->tabnocid; |
| else |
| at_state->replystruct = cs->tabcid; |
| } |
| |
| |
| static void gigaset_inbuf_init(struct inbuf_t *inbuf, struct bc_state *bcs, |
| struct cardstate *cs, int inputstate) |
| /* inbuf->read must be allocated before! */ |
| { |
| inbuf->head = 0; |
| inbuf->tail = 0; |
| inbuf->cs = cs; |
| inbuf->bcs = bcs; /*base driver: NULL*/ |
| inbuf->rcvbuf = NULL; |
| inbuf->inputstate = inputstate; |
| } |
| |
| /* append received bytes to inbuf */ |
| int gigaset_fill_inbuf(struct inbuf_t *inbuf, const unsigned char *src, |
| unsigned numbytes) |
| { |
| unsigned n, head, tail, bytesleft; |
| |
| gig_dbg(DEBUG_INTR, "received %u bytes", numbytes); |
| |
| if (!numbytes) |
| return 0; |
| |
| bytesleft = numbytes; |
| tail = inbuf->tail; |
| head = inbuf->head; |
| gig_dbg(DEBUG_INTR, "buffer state: %u -> %u", head, tail); |
| |
| while (bytesleft) { |
| if (head > tail) |
| n = head - 1 - tail; |
| else if (head == 0) |
| n = (RBUFSIZE-1) - tail; |
| else |
| n = RBUFSIZE - tail; |
| if (!n) { |
| dev_err(inbuf->cs->dev, |
| "buffer overflow (%u bytes lost)\n", |
| bytesleft); |
| break; |
| } |
| if (n > bytesleft) |
| n = bytesleft; |
| memcpy(inbuf->data + tail, src, n); |
| bytesleft -= n; |
| tail = (tail + n) % RBUFSIZE; |
| src += n; |
| } |
| gig_dbg(DEBUG_INTR, "setting tail to %u", tail); |
| inbuf->tail = tail; |
| return numbytes != bytesleft; |
| } |
| EXPORT_SYMBOL_GPL(gigaset_fill_inbuf); |
| |
| /* Initialize the b-channel structure */ |
| static struct bc_state *gigaset_initbcs(struct bc_state *bcs, |
| struct cardstate *cs, int channel) |
| { |
| int i; |
| |
| bcs->tx_skb = NULL; //FIXME -> hw part |
| |
| skb_queue_head_init(&bcs->squeue); |
| |
| bcs->corrupted = 0; |
| bcs->trans_down = 0; |
| bcs->trans_up = 0; |
| |
| gig_dbg(DEBUG_INIT, "setting up bcs[%d]->at_state", channel); |
| gigaset_at_init(&bcs->at_state, bcs, cs, -1); |
| |
| #ifdef CONFIG_GIGASET_DEBUG |
| bcs->emptycount = 0; |
| #endif |
| |
| gig_dbg(DEBUG_INIT, "allocating bcs[%d]->skb", channel); |
| bcs->fcs = PPP_INITFCS; |
| bcs->inputstate = 0; |
| if (cs->ignoreframes) { |
| bcs->inputstate |= INS_skip_frame; |
| bcs->skb = NULL; |
| } else if ((bcs->skb = dev_alloc_skb(SBUFSIZE + HW_HDR_LEN)) != NULL) |
| skb_reserve(bcs->skb, HW_HDR_LEN); |
| else { |
| pr_err("out of memory\n"); |
| bcs->inputstate |= INS_skip_frame; |
| } |
| |
| bcs->channel = channel; |
| bcs->cs = cs; |
| |
| bcs->chstate = 0; |
| bcs->use_count = 1; |
| bcs->busy = 0; |
| bcs->ignore = cs->ignoreframes; |
| |
| for (i = 0; i < AT_NUM; ++i) |
| bcs->commands[i] = NULL; |
| |
| gig_dbg(DEBUG_INIT, " setting up bcs[%d]->hw", channel); |
| if (cs->ops->initbcshw(bcs)) |
| return bcs; |
| |
| gig_dbg(DEBUG_INIT, " failed"); |
| |
| gig_dbg(DEBUG_INIT, " freeing bcs[%d]->skb", channel); |
| if (bcs->skb) |
| dev_kfree_skb(bcs->skb); |
| |
| return NULL; |
| } |
| |
| /* gigaset_initcs |
| * Allocate and initialize cardstate structure for Gigaset driver |
| * Calls hardware dependent gigaset_initcshw() function |
| * Calls B channel initialization function gigaset_initbcs() for each B channel |
| * parameters: |
| * drv hardware driver the device belongs to |
| * channels number of B channels supported by device |
| * onechannel !=0: B channel data and AT commands share one |
| * communication channel |
| * ==0: B channels have separate communication channels |
| * ignoreframes number of frames to ignore after setting up B channel |
| * cidmode !=0: start in CallID mode |
| * modulename name of driver module (used for I4L registration) |
| * return value: |
| * pointer to cardstate structure |
| */ |
| struct cardstate *gigaset_initcs(struct gigaset_driver *drv, int channels, |
| int onechannel, int ignoreframes, |
| int cidmode, const char *modulename) |
| { |
| struct cardstate *cs = NULL; |
| unsigned long flags; |
| int i; |
| |
| gig_dbg(DEBUG_INIT, "allocating cs"); |
| if (!(cs = alloc_cs(drv))) { |
| pr_err("maximum number of devices exceeded\n"); |
| return NULL; |
| } |
| |
| gig_dbg(DEBUG_INIT, "allocating bcs[0..%d]", channels - 1); |
| cs->bcs = kmalloc(channels * sizeof(struct bc_state), GFP_KERNEL); |
| if (!cs->bcs) { |
| pr_err("out of memory\n"); |
| goto error; |
| } |
| gig_dbg(DEBUG_INIT, "allocating inbuf"); |
| cs->inbuf = kmalloc(sizeof(struct inbuf_t), GFP_KERNEL); |
| if (!cs->inbuf) { |
| pr_err("out of memory\n"); |
| goto error; |
| } |
| |
| cs->cs_init = 0; |
| cs->channels = channels; |
| cs->onechannel = onechannel; |
| cs->ignoreframes = ignoreframes; |
| INIT_LIST_HEAD(&cs->temp_at_states); |
| cs->running = 0; |
| init_timer(&cs->timer); /* clear next & prev */ |
| spin_lock_init(&cs->ev_lock); |
| cs->ev_tail = 0; |
| cs->ev_head = 0; |
| |
| tasklet_init(&cs->event_tasklet, &gigaset_handle_event, |
| (unsigned long) cs); |
| cs->commands_pending = 0; |
| cs->cur_at_seq = 0; |
| cs->gotfwver = -1; |
| cs->open_count = 0; |
| cs->dev = NULL; |
| cs->tty = NULL; |
| cs->tty_dev = NULL; |
| cs->cidmode = cidmode != 0; |
| cs->tabnocid = gigaset_tab_nocid; |
| cs->tabcid = gigaset_tab_cid; |
| |
| init_waitqueue_head(&cs->waitqueue); |
| cs->waiting = 0; |
| |
| cs->mode = M_UNKNOWN; |
| cs->mstate = MS_UNINITIALIZED; |
| |
| for (i = 0; i < channels; ++i) { |
| gig_dbg(DEBUG_INIT, "setting up bcs[%d].read", i); |
| if (!gigaset_initbcs(cs->bcs + i, cs, i)) { |
| pr_err("could not allocate channel %d data\n", i); |
| goto error; |
| } |
| } |
| |
| ++cs->cs_init; |
| |
| gig_dbg(DEBUG_INIT, "setting up at_state"); |
| spin_lock_init(&cs->lock); |
| gigaset_at_init(&cs->at_state, NULL, cs, 0); |
| cs->dle = 0; |
| cs->cbytes = 0; |
| |
| gig_dbg(DEBUG_INIT, "setting up inbuf"); |
| if (onechannel) { //FIXME distinction necessary? |
| gigaset_inbuf_init(cs->inbuf, cs->bcs, cs, INS_command); |
| } else |
| gigaset_inbuf_init(cs->inbuf, NULL, cs, INS_command); |
| |
| cs->connected = 0; |
| cs->isdn_up = 0; |
| |
| gig_dbg(DEBUG_INIT, "setting up cmdbuf"); |
| cs->cmdbuf = cs->lastcmdbuf = NULL; |
| spin_lock_init(&cs->cmdlock); |
| cs->curlen = 0; |
| cs->cmdbytes = 0; |
| |
| gig_dbg(DEBUG_INIT, "setting up iif"); |
| if (!gigaset_register_to_LL(cs, modulename)) { |
| pr_err("error registering ISDN device\n"); |
| goto error; |
| } |
| |
| make_valid(cs, VALID_ID); |
| ++cs->cs_init; |
| gig_dbg(DEBUG_INIT, "setting up hw"); |
| if (!cs->ops->initcshw(cs)) |
| goto error; |
| |
| ++cs->cs_init; |
| |
| /* set up character device */ |
| gigaset_if_init(cs); |
| |
| /* set up device sysfs */ |
| gigaset_init_dev_sysfs(cs); |
| |
| spin_lock_irqsave(&cs->lock, flags); |
| cs->running = 1; |
| spin_unlock_irqrestore(&cs->lock, flags); |
| setup_timer(&cs->timer, timer_tick, (unsigned long) cs); |
| cs->timer.expires = jiffies + msecs_to_jiffies(GIG_TICK); |
| /* FIXME: can jiffies increase too much until the timer is added? |
| * Same problem(?) with mod_timer() in timer_tick(). */ |
| add_timer(&cs->timer); |
| |
| gig_dbg(DEBUG_INIT, "cs initialized"); |
| return cs; |
| |
| error: |
| gig_dbg(DEBUG_INIT, "failed"); |
| gigaset_freecs(cs); |
| return NULL; |
| } |
| EXPORT_SYMBOL_GPL(gigaset_initcs); |
| |
| /* ReInitialize the b-channel structure on hangup */ |
| void gigaset_bcs_reinit(struct bc_state *bcs) |
| { |
| struct sk_buff *skb; |
| struct cardstate *cs = bcs->cs; |
| unsigned long flags; |
| |
| while ((skb = skb_dequeue(&bcs->squeue)) != NULL) |
| dev_kfree_skb(skb); |
| |
| spin_lock_irqsave(&cs->lock, flags); |
| clear_at_state(&bcs->at_state); |
| bcs->at_state.ConState = 0; |
| bcs->at_state.timer_active = 0; |
| bcs->at_state.timer_expires = 0; |
| bcs->at_state.cid = -1; /* No CID defined */ |
| spin_unlock_irqrestore(&cs->lock, flags); |
| |
| bcs->inputstate = 0; |
| |
| #ifdef CONFIG_GIGASET_DEBUG |
| bcs->emptycount = 0; |
| #endif |
| |
| bcs->fcs = PPP_INITFCS; |
| bcs->chstate = 0; |
| |
| bcs->ignore = cs->ignoreframes; |
| if (bcs->ignore) |
| bcs->inputstate |= INS_skip_frame; |
| |
| |
| cs->ops->reinitbcshw(bcs); |
| } |
| |
| static void cleanup_cs(struct cardstate *cs) |
| { |
| struct cmdbuf_t *cb, *tcb; |
| int i; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&cs->lock, flags); |
| |
| cs->mode = M_UNKNOWN; |
| cs->mstate = MS_UNINITIALIZED; |
| |
| clear_at_state(&cs->at_state); |
| dealloc_at_states(cs); |
| free_strings(&cs->at_state); |
| gigaset_at_init(&cs->at_state, NULL, cs, 0); |
| |
| kfree(cs->inbuf->rcvbuf); |
| cs->inbuf->rcvbuf = NULL; |
| cs->inbuf->inputstate = INS_command; |
| cs->inbuf->head = 0; |
| cs->inbuf->tail = 0; |
| |
| cb = cs->cmdbuf; |
| while (cb) { |
| tcb = cb; |
| cb = cb->next; |
| kfree(tcb); |
| } |
| cs->cmdbuf = cs->lastcmdbuf = NULL; |
| cs->curlen = 0; |
| cs->cmdbytes = 0; |
| cs->gotfwver = -1; |
| cs->dle = 0; |
| cs->cur_at_seq = 0; |
| cs->commands_pending = 0; |
| cs->cbytes = 0; |
| |
| spin_unlock_irqrestore(&cs->lock, flags); |
| |
| for (i = 0; i < cs->channels; ++i) { |
| gigaset_freebcs(cs->bcs + i); |
| if (!gigaset_initbcs(cs->bcs + i, cs, i)) |
| pr_err("could not allocate channel %d data\n", i); |
| } |
| |
| if (cs->waiting) { |
| cs->cmd_result = -ENODEV; |
| cs->waiting = 0; |
| wake_up_interruptible(&cs->waitqueue); |
| } |
| } |
| |
| |
| int gigaset_start(struct cardstate *cs) |
| { |
| unsigned long flags; |
| |
| if (mutex_lock_interruptible(&cs->mutex)) |
| return 0; |
| |
| spin_lock_irqsave(&cs->lock, flags); |
| cs->connected = 1; |
| spin_unlock_irqrestore(&cs->lock, flags); |
| |
| if (cs->mstate != MS_LOCKED) { |
| cs->ops->set_modem_ctrl(cs, 0, TIOCM_DTR|TIOCM_RTS); |
| cs->ops->baud_rate(cs, B115200); |
| cs->ops->set_line_ctrl(cs, CS8); |
| cs->control_state = TIOCM_DTR|TIOCM_RTS; |
| } else { |
| //FIXME use some saved values? |
| } |
| |
| cs->waiting = 1; |
| |
| if (!gigaset_add_event(cs, &cs->at_state, EV_START, NULL, 0, NULL)) { |
| cs->waiting = 0; |
| //FIXME what should we do? |
| goto error; |
| } |
| |
| gig_dbg(DEBUG_CMD, "scheduling START"); |
| gigaset_schedule_event(cs); |
| |
| wait_event(cs->waitqueue, !cs->waiting); |
| |
| mutex_unlock(&cs->mutex); |
| return 1; |
| |
| error: |
| mutex_unlock(&cs->mutex); |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(gigaset_start); |
| |
| /* 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)) { |
| mutex_unlock(&cs->mutex); |
| return -1; |
| } |
| |
| cs->waiting = 1; |
| |
| if (!gigaset_add_event(cs, &cs->at_state, EV_SHUTDOWN, NULL, 0, NULL)) { |
| //FIXME what should we do? |
| goto exit; |
| } |
| |
| gig_dbg(DEBUG_CMD, "scheduling SHUTDOWN"); |
| gigaset_schedule_event(cs); |
| |
| wait_event(cs->waitqueue, !cs->waiting); |
| |
| cleanup_cs(cs); |
| |
| exit: |
| mutex_unlock(&cs->mutex); |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(gigaset_shutdown); |
| |
| void gigaset_stop(struct cardstate *cs) |
| { |
| mutex_lock(&cs->mutex); |
| |
| cs->waiting = 1; |
| |
| if (!gigaset_add_event(cs, &cs->at_state, EV_STOP, NULL, 0, NULL)) { |
| //FIXME what should we do? |
| goto exit; |
| } |
| |
| gig_dbg(DEBUG_CMD, "scheduling STOP"); |
| gigaset_schedule_event(cs); |
| |
| wait_event(cs->waitqueue, !cs->waiting); |
| |
| cleanup_cs(cs); |
| |
| exit: |
| mutex_unlock(&cs->mutex); |
| } |
| EXPORT_SYMBOL_GPL(gigaset_stop); |
| |
| static LIST_HEAD(drivers); |
| static DEFINE_SPINLOCK(driver_lock); |
| |
| struct cardstate *gigaset_get_cs_by_id(int id) |
| { |
| unsigned long flags; |
| struct cardstate *ret = NULL; |
| struct cardstate *cs; |
| struct gigaset_driver *drv; |
| unsigned i; |
| |
| spin_lock_irqsave(&driver_lock, flags); |
| list_for_each_entry(drv, &drivers, list) { |
| spin_lock(&drv->lock); |
| for (i = 0; i < drv->minors; ++i) { |
| cs = drv->cs + i; |
| if ((cs->flags & VALID_ID) && cs->myid == id) { |
| ret = cs; |
| break; |
| } |
| } |
| spin_unlock(&drv->lock); |
| if (ret) |
| break; |
| } |
| spin_unlock_irqrestore(&driver_lock, flags); |
| return ret; |
| } |
| |
| void gigaset_debugdrivers(void) |
| { |
| unsigned long flags; |
| static struct cardstate *cs; |
| struct gigaset_driver *drv; |
| unsigned i; |
| |
| spin_lock_irqsave(&driver_lock, flags); |
| list_for_each_entry(drv, &drivers, list) { |
| gig_dbg(DEBUG_DRIVER, "driver %p", drv); |
| spin_lock(&drv->lock); |
| for (i = 0; i < drv->minors; ++i) { |
| gig_dbg(DEBUG_DRIVER, " index %u", 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); |
| gig_dbg(DEBUG_DRIVER, " i4l id %d", cs->myid); |
| } |
| spin_unlock(&drv->lock); |
| } |
| spin_unlock_irqrestore(&driver_lock, flags); |
| } |
| |
| static struct cardstate *gigaset_get_cs_by_minor(unsigned minor) |
| { |
| unsigned long flags; |
| struct cardstate *ret = NULL; |
| struct gigaset_driver *drv; |
| unsigned index; |
| |
| spin_lock_irqsave(&driver_lock, flags); |
| list_for_each_entry(drv, &drivers, list) { |
| if (minor < drv->minor || minor >= drv->minor + drv->minors) |
| continue; |
| index = minor - drv->minor; |
| spin_lock(&drv->lock); |
| if (drv->cs[index].flags & VALID_MINOR) |
| ret = drv->cs + index; |
| spin_unlock(&drv->lock); |
| if (ret) |
| break; |
| } |
| spin_unlock_irqrestore(&driver_lock, flags); |
| return ret; |
| } |
| |
| struct cardstate *gigaset_get_cs_by_tty(struct tty_struct *tty) |
| { |
| if (tty->index < 0 || tty->index >= tty->driver->num) |
| return NULL; |
| return gigaset_get_cs_by_minor(tty->index + tty->driver->minor_start); |
| } |
| |
| void gigaset_freedriver(struct gigaset_driver *drv) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&driver_lock, flags); |
| list_del(&drv->list); |
| spin_unlock_irqrestore(&driver_lock, flags); |
| |
| gigaset_if_freedriver(drv); |
| |
| kfree(drv->cs); |
| kfree(drv); |
| } |
| EXPORT_SYMBOL_GPL(gigaset_freedriver); |
| |
| /* gigaset_initdriver |
| * Allocate and initialize gigaset_driver structure. Initialize interface. |
| * parameters: |
| * minor First minor number |
| * minors Number of minors this driver can handle |
| * procname Name of the driver |
| * devname Name of the device files (prefix without minor number) |
| * return value: |
| * Pointer to the gigaset_driver structure on success, NULL on failure. |
| */ |
| struct gigaset_driver *gigaset_initdriver(unsigned minor, unsigned minors, |
| const char *procname, |
| const char *devname, |
| const struct gigaset_ops *ops, |
| struct module *owner) |
| { |
| struct gigaset_driver *drv; |
| unsigned long flags; |
| unsigned i; |
| |
| drv = kmalloc(sizeof *drv, GFP_KERNEL); |
| if (!drv) |
| return NULL; |
| |
| drv->have_tty = 0; |
| drv->minor = minor; |
| drv->minors = minors; |
| spin_lock_init(&drv->lock); |
| drv->blocked = 0; |
| drv->ops = ops; |
| drv->owner = owner; |
| INIT_LIST_HEAD(&drv->list); |
| |
| drv->cs = kmalloc(minors * sizeof *drv->cs, GFP_KERNEL); |
| if (!drv->cs) |
| goto error; |
| |
| for (i = 0; i < minors; ++i) { |
| drv->cs[i].flags = 0; |
| drv->cs[i].driver = drv; |
| drv->cs[i].ops = drv->ops; |
| drv->cs[i].minor_index = i; |
| mutex_init(&drv->cs[i].mutex); |
| } |
| |
| gigaset_if_initdriver(drv, procname, devname); |
| |
| spin_lock_irqsave(&driver_lock, flags); |
| list_add(&drv->list, &drivers); |
| spin_unlock_irqrestore(&driver_lock, flags); |
| |
| return drv; |
| |
| error: |
| kfree(drv->cs); |
| kfree(drv); |
| return NULL; |
| } |
| EXPORT_SYMBOL_GPL(gigaset_initdriver); |
| |
| void gigaset_blockdriver(struct gigaset_driver *drv) |
| { |
| drv->blocked = 1; |
| } |
| EXPORT_SYMBOL_GPL(gigaset_blockdriver); |
| |
| static int __init gigaset_init_module(void) |
| { |
| /* in accordance with the principle of least astonishment, |
| * setting the 'debug' parameter to 1 activates a sensible |
| * set of default debug levels |
| */ |
| if (gigaset_debuglevel == 1) |
| gigaset_debuglevel = DEBUG_DEFAULT; |
| |
| pr_info(DRIVER_DESC "\n"); |
| return 0; |
| } |
| |
| static void __exit gigaset_exit_module(void) |
| { |
| } |
| |
| module_init(gigaset_init_module); |
| module_exit(gigaset_exit_module); |
| |
| MODULE_AUTHOR(DRIVER_AUTHOR); |
| MODULE_DESCRIPTION(DRIVER_DESC); |
| |
| MODULE_LICENSE("GPL"); |