diff options
Diffstat (limited to 'drivers/tty/n_gsm.c')
| -rw-r--r-- | drivers/tty/n_gsm.c | 1026 |
1 files changed, 761 insertions, 265 deletions
diff --git a/drivers/tty/n_gsm.c b/drivers/tty/n_gsm.c index 81b46585edf..2ebe47b78a3 100644 --- a/drivers/tty/n_gsm.c +++ b/drivers/tty/n_gsm.c @@ -19,9 +19,8 @@ * * TO DO: * Mostly done: ioctls for setting modes/timing - * Partly done: hooks so you can pull off frames to non tty devs + * Partly done: hooks so you can pull off frames to non tty devs * Restart DLCI 0 when it closes ? - * Test basic encoding * Improve the tx engine * Resolve tx side locking by adding a queue_head and routing * all control traffic via it @@ -58,34 +57,58 @@ #include <linux/serial.h> #include <linux/kfifo.h> #include <linux/skbuff.h> +#include <net/arp.h> +#include <linux/ip.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> #include <linux/gsmmux.h> static int debug; module_param(debug, int, 0600); -#define T1 (HZ/10) -#define T2 (HZ/3) -#define N2 3 +/* Defaults: these are from the specification */ + +#define T1 10 /* 100mS */ +#define T2 34 /* 333mS */ +#define N2 3 /* Retry 3 times */ /* Use long timers for testing at low speed with debug on */ #ifdef DEBUG_TIMING -#define T1 HZ -#define T2 (2 * HZ) +#define T1 100 +#define T2 200 #endif -/* Semi-arbitary buffer size limits. 0710 is normally run with 32-64 byte - limits so this is plenty */ -#define MAX_MRU 512 -#define MAX_MTU 512 +/* + * Semi-arbitrary buffer size limits. 0710 is normally run with 32-64 byte + * limits so this is plenty + */ +#define MAX_MRU 1500 +#define MAX_MTU 1500 +#define GSM_NET_TX_TIMEOUT (HZ*10) + +/** + * struct gsm_mux_net - network interface + * @struct gsm_dlci* dlci + * @struct net_device_stats stats; + * + * Created when net interface is initialized. + **/ +struct gsm_mux_net { + struct kref ref; + struct gsm_dlci *dlci; + struct net_device_stats stats; +}; + +#define STATS(net) (((struct gsm_mux_net *)netdev_priv(net))->stats) /* * Each block of data we have queued to go out is in the form of - * a gsm_msg which holds everything we need in a link layer independant + * a gsm_msg which holds everything we need in a link layer independent * format */ struct gsm_msg { - struct gsm_msg *next; + struct list_head list; u8 addr; /* DLCI address + flags */ u8 ctrl; /* Control byte + flags */ unsigned int len; /* Length of data block (can be zero) */ @@ -111,6 +134,7 @@ struct gsm_dlci { #define DLCI_OPENING 1 /* Sending SABM not seen UA */ #define DLCI_OPEN 2 /* SABM/UA complete */ #define DLCI_CLOSING 3 /* Sending DISC not seen UA/DM */ + struct mutex mutex; /* Link layer */ spinlock_t lock; /* Protects the internal state */ @@ -121,6 +145,7 @@ struct gsm_dlci { struct kfifo *fifo; /* Queue fifo for the DLCI */ struct kfifo _fifo; /* For new fifo API porting only */ int adaption; /* Adaption layer in use */ + int prev_adaption; u32 modem_rx; /* Our incoming virtual modem lines */ u32 modem_tx; /* Our outgoing modem lines */ int dead; /* Refuse re-open */ @@ -132,6 +157,8 @@ struct gsm_dlci { struct sk_buff_head skb_list; /* Queued frames */ /* Data handling callback */ void (*data)(struct gsm_dlci *dlci, u8 *data, int len); + void (*prev_data)(struct gsm_dlci *dlci, u8 *data, int len); + struct net_device *net; /* network interface, if created */ }; /* DLCI 0, 62/63 are special or reseved see gsmtty_open */ @@ -167,6 +194,9 @@ struct gsm_control { struct gsm_mux { struct tty_struct *tty; /* The tty our ldisc is bound to */ spinlock_t lock; + struct mutex mutex; + unsigned int num; + struct kref ref; /* Events on the GSM channel */ wait_queue_head_t event; @@ -184,6 +214,9 @@ struct gsm_mux { #define GSM_DATA 5 #define GSM_FCS 6 #define GSM_OVERRUN 7 +#define GSM_LEN0 8 +#define GSM_LEN1 9 +#define GSM_SSOF 10 unsigned int len; unsigned int address; unsigned int count; @@ -191,6 +224,7 @@ struct gsm_mux { int encoding; u8 control; u8 fcs; + u8 received_fcs; u8 *txframe; /* TX framing buffer */ /* Methods for the receiver side */ @@ -211,8 +245,7 @@ struct gsm_mux { unsigned int tx_bytes; /* TX data outstanding */ #define TX_THRESH_HI 8192 #define TX_THRESH_LO 2048 - struct gsm_msg *tx_head; /* Pending data packets */ - struct gsm_msg *tx_tail; + struct list_head tx_list; /* Pending data packets */ /* Control messages */ struct timer_list t2_timer; /* Retransmit timer for commands */ @@ -244,6 +277,8 @@ struct gsm_mux { static struct gsm_mux *gsm_mux[MAX_MUX]; /* GSM muxes */ static spinlock_t gsm_mux_lock; +static struct tty_driver *gsm_tty_driver; + /* * This section of the driver logic implements the GSM encodings * both the basic and the 'advanced'. Reliable transport is not @@ -286,7 +321,7 @@ static spinlock_t gsm_mux_lock; #define MDM_DV 0x40 #define GSM0_SOF 0xF9 -#define GSM1_SOF 0x7E +#define GSM1_SOF 0x7E #define GSM1_ESCAPE 0x7D #define GSM1_ESCAPE_BITS 0x20 #define XON 0x11 @@ -429,61 +464,63 @@ static void gsm_print_packet(const char *hdr, int addr, int cr, if (!(debug & 1)) return; - printk(KERN_INFO "%s %d) %c: ", hdr, addr, "RC"[cr]); + pr_info("%s %d) %c: ", hdr, addr, "RC"[cr]); switch (control & ~PF) { case SABM: - printk(KERN_CONT "SABM"); + pr_cont("SABM"); break; case UA: - printk(KERN_CONT "UA"); + pr_cont("UA"); break; case DISC: - printk(KERN_CONT "DISC"); + pr_cont("DISC"); break; case DM: - printk(KERN_CONT "DM"); + pr_cont("DM"); break; case UI: - printk(KERN_CONT "UI"); + pr_cont("UI"); break; case UIH: - printk(KERN_CONT "UIH"); + pr_cont("UIH"); break; default: if (!(control & 0x01)) { - printk(KERN_CONT "I N(S)%d N(R)%d", - (control & 0x0E) >> 1, (control & 0xE)>> 5); + pr_cont("I N(S)%d N(R)%d", + (control & 0x0E) >> 1, (control & 0xE0) >> 5); } else switch (control & 0x0F) { - case RR: - printk("RR(%d)", (control & 0xE0) >> 5); - break; - case RNR: - printk("RNR(%d)", (control & 0xE0) >> 5); - break; - case REJ: - printk("REJ(%d)", (control & 0xE0) >> 5); - break; - default: - printk(KERN_CONT "[%02X]", control); + case RR: + pr_cont("RR(%d)", (control & 0xE0) >> 5); + break; + case RNR: + pr_cont("RNR(%d)", (control & 0xE0) >> 5); + break; + case REJ: + pr_cont("REJ(%d)", (control & 0xE0) >> 5); + break; + default: + pr_cont("[%02X]", control); } } if (control & PF) - printk(KERN_CONT "(P)"); + pr_cont("(P)"); else - printk(KERN_CONT "(F)"); + pr_cont("(F)"); if (dlen) { int ct = 0; while (dlen--) { - if (ct % 8 == 0) - printk(KERN_CONT "\n "); - printk(KERN_CONT "%02X ", *data++); + if (ct % 8 == 0) { + pr_cont("\n"); + pr_debug(" "); + } + pr_cont("%02X ", *data++); ct++; } } - printk(KERN_CONT "\n"); + pr_cont("\n"); } @@ -518,17 +555,6 @@ static int gsm_stuff_frame(const u8 *input, u8 *output, int len) return olen; } -static void hex_packet(const unsigned char *p, int len) -{ - int i; - for (i = 0; i < len; i++) { - if (i && (i % 16) == 0) - printk("\n"); - printk("%02X ", *p++); - } - printk("\n"); -} - /** * gsm_send - send a control frame * @gsm: our GSM mux @@ -636,7 +662,7 @@ static struct gsm_msg *gsm_data_alloc(struct gsm_mux *gsm, u8 addr, int len, m->len = len; m->addr = addr; m->ctrl = ctrl; - m->next = NULL; + INIT_LIST_HEAD(&m->list); return m; } @@ -646,22 +672,21 @@ static struct gsm_msg *gsm_data_alloc(struct gsm_mux *gsm, u8 addr, int len, * * The tty device has called us to indicate that room has appeared in * the transmit queue. Ram more data into the pipe if we have any + * If we have been flow-stopped by a CMD_FCOFF, then we can only + * send messages on DLCI0 until CMD_FCON * * FIXME: lock against link layer control transmissions */ static void gsm_data_kick(struct gsm_mux *gsm) { - struct gsm_msg *msg = gsm->tx_head; + struct gsm_msg *msg, *nmsg; int len; int skip_sof = 0; - /* FIXME: We need to apply this solely to data messages */ - if (gsm->constipated) - return; - - while (gsm->tx_head != NULL) { - msg = gsm->tx_head; + list_for_each_entry_safe(msg, nmsg, &gsm->tx_list, list) { + if (gsm->constipated && msg->addr) + continue; if (gsm->encoding != 0) { gsm->txframe[0] = GSM1_SOF; len = gsm_stuff_frame(msg->data, @@ -675,23 +700,22 @@ static void gsm_data_kick(struct gsm_mux *gsm) len = msg->len + 2; } - if (debug & 4) { - printk("gsm_data_kick: \n"); - hex_packet(gsm->txframe, len); - } + if (debug & 4) + print_hex_dump_bytes("gsm_data_kick: ", + DUMP_PREFIX_OFFSET, + gsm->txframe, len); if (gsm->output(gsm, gsm->txframe + skip_sof, len - skip_sof) < 0) break; /* FIXME: Can eliminate one SOF in many more cases */ - gsm->tx_head = msg->next; - if (gsm->tx_head == NULL) - gsm->tx_tail = NULL; gsm->tx_bytes -= msg->len; - kfree(msg); /* For a burst of frames skip the extra SOF within the burst */ skip_sof = 1; + + list_del(&msg->list); + kfree(msg); } } @@ -716,8 +740,8 @@ static void __gsm_data_queue(struct gsm_dlci *dlci, struct gsm_msg *msg) if (msg->len < 128) *--dp = (msg->len << 1) | EA; else { - *--dp = ((msg->len & 127) << 1) | EA; - *--dp = (msg->len >> 6) & 0xfe; + *--dp = (msg->len >> 7); /* bits 7 - 15 */ + *--dp = (msg->len & 127) << 1; /* bits 0 - 6 */ } } @@ -741,11 +765,7 @@ static void __gsm_data_queue(struct gsm_dlci *dlci, struct gsm_msg *msg) msg->data = dp; /* Add to the actual output queue */ - if (gsm->tx_tail) - gsm->tx_tail->next = msg; - else - gsm->tx_head = msg; - gsm->tx_tail = msg; + list_add_tail(&msg->list, &gsm->tx_list); gsm->tx_bytes += msg->len; gsm_data_kick(gsm); } @@ -784,38 +804,41 @@ static int gsm_dlci_data_output(struct gsm_mux *gsm, struct gsm_dlci *dlci) { struct gsm_msg *msg; u8 *dp; - int len, size; + int len, total_size, size; int h = dlci->adaption - 1; - len = kfifo_len(dlci->fifo); - if (len == 0) - return 0; - - /* MTU/MRU count only the data bits */ - if (len > gsm->mtu) - len = gsm->mtu; - - size = len + h; - - msg = gsm_data_alloc(gsm, dlci->addr, size, gsm->ftype); - /* FIXME: need a timer or something to kick this so it can't - get stuck with no work outstanding and no buffer free */ - if (msg == NULL) - return -ENOMEM; - dp = msg->data; - switch (dlci->adaption) { - case 1: /* Unstructured */ - break; - case 2: /* Unstructed with modem bits. Always one byte as we never - send inline break data */ - *dp += gsm_encode_modem(dlci); - len--; - break; + total_size = 0; + while (1) { + len = kfifo_len(dlci->fifo); + if (len == 0) + return total_size; + + /* MTU/MRU count only the data bits */ + if (len > gsm->mtu) + len = gsm->mtu; + + size = len + h; + + msg = gsm_data_alloc(gsm, dlci->addr, size, gsm->ftype); + /* FIXME: need a timer or something to kick this so it can't + get stuck with no work outstanding and no buffer free */ + if (msg == NULL) + return -ENOMEM; + dp = msg->data; + switch (dlci->adaption) { + case 1: /* Unstructured */ + break; + case 2: /* Unstructed with modem bits. + Always one byte as we never send inline break data */ + *dp++ = gsm_encode_modem(dlci); + break; + } + WARN_ON(kfifo_out_locked(dlci->fifo, dp , len, &dlci->lock) != len); + __gsm_data_queue(dlci, msg); + total_size += size; } - WARN_ON(kfifo_out_locked(dlci->fifo, dp , len, &dlci->lock) != len); - __gsm_data_queue(dlci, msg); /* Bytes of data we used up */ - return size; + return total_size; } /** @@ -845,7 +868,7 @@ static int gsm_dlci_data_output_framed(struct gsm_mux *gsm, /* dlci->skb is locked by tx_lock */ if (dlci->skb == NULL) { - dlci->skb = skb_dequeue(&dlci->skb_list); + dlci->skb = skb_dequeue_tail(&dlci->skb_list); if (dlci->skb == NULL) return 0; first = 1; @@ -856,7 +879,7 @@ static int gsm_dlci_data_output_framed(struct gsm_mux *gsm, if (len > gsm->mtu) { if (dlci->adaption == 3) { /* Over long frame, bin it */ - kfree_skb(dlci->skb); + dev_kfree_skb_any(dlci->skb); dlci->skb = NULL; return 0; } @@ -869,8 +892,11 @@ static int gsm_dlci_data_output_framed(struct gsm_mux *gsm, /* FIXME: need a timer or something to kick this so it can't get stuck with no work outstanding and no buffer free */ - if (msg == NULL) + if (msg == NULL) { + skb_queue_tail(&dlci->skb_list, dlci->skb); + dlci->skb = NULL; return -ENOMEM; + } dp = msg->data; if (dlci->adaption == 4) { /* Interruptible framed (Packetised Data) */ @@ -878,10 +904,13 @@ static int gsm_dlci_data_output_framed(struct gsm_mux *gsm, *dp++ = last << 7 | first << 6 | 1; /* EA */ len--; } - memcpy(dp, skb_pull(dlci->skb, len), len); + memcpy(dp, dlci->skb->data, len); + skb_pull(dlci->skb, len); __gsm_data_queue(dlci, msg); - if (last) + if (last) { + dev_kfree_skb_any(dlci->skb); dlci->skb = NULL; + } return size; } @@ -914,7 +943,7 @@ static void gsm_dlci_data_sweep(struct gsm_mux *gsm) i++; continue; } - if (dlci->adaption < 3) + if (dlci->adaption < 3 && !dlci->net) len = gsm_dlci_data_output(gsm, dlci); else len = gsm_dlci_data_output_framed(gsm, dlci); @@ -938,12 +967,21 @@ static void gsm_dlci_data_sweep(struct gsm_mux *gsm) static void gsm_dlci_data_kick(struct gsm_dlci *dlci) { unsigned long flags; + int sweep; + + if (dlci->constipated) + return; spin_lock_irqsave(&dlci->gsm->tx_lock, flags); /* If we have nothing running then we need to fire up */ - if (dlci->gsm->tx_bytes == 0) - gsm_dlci_data_output(dlci->gsm, dlci); - else if (dlci->gsm->tx_bytes < TX_THRESH_LO) + sweep = (dlci->gsm->tx_bytes < TX_THRESH_LO); + if (dlci->gsm->tx_bytes == 0) { + if (dlci->net) + gsm_dlci_data_output_framed(dlci->gsm, dlci); + else + gsm_dlci_data_output(dlci->gsm, dlci); + } + if (sweep) gsm_dlci_data_sweep(dlci->gsm); spin_unlock_irqrestore(&dlci->gsm->tx_lock, flags); } @@ -968,6 +1006,8 @@ static void gsm_control_reply(struct gsm_mux *gsm, int cmd, u8 *data, { struct gsm_msg *msg; msg = gsm_data_alloc(gsm, 0, dlen + 2, gsm->ftype); + if (msg == NULL) + return; msg->data[0] = (cmd & 0xFE) << 1 | EA; /* Clear C/R */ msg->data[1] = (dlen << 1) | EA; memcpy(msg->data + 2, data, dlen); @@ -985,22 +1025,37 @@ static void gsm_control_reply(struct gsm_mux *gsm, int cmd, u8 *data, */ static void gsm_process_modem(struct tty_struct *tty, struct gsm_dlci *dlci, - u32 modem) + u32 modem, int clen) { int mlines = 0; - u8 brk = modem >> 6; + u8 brk = 0; + int fc; + + /* The modem status command can either contain one octet (v.24 signals) + or two octets (v.24 signals + break signals). The length field will + either be 2 or 3 respectively. This is specified in section + 5.4.6.3.7 of the 27.010 mux spec. */ + + if (clen == 2) + modem = modem & 0x7f; + else { + brk = modem & 0x7f; + modem = (modem >> 7) & 0x7f; + } /* Flow control/ready to communicate */ - if (modem & MDM_FC) { + fc = (modem & MDM_FC) || !(modem & MDM_RTR); + if (fc && !dlci->constipated) { /* Need to throttle our output on this device */ dlci->constipated = 1; - } - if (modem & MDM_RTC) { - mlines |= TIOCM_DSR | TIOCM_DTR; + } else if (!fc && dlci->constipated) { dlci->constipated = 0; gsm_dlci_data_kick(dlci); } + /* Map modem bits */ + if (modem & MDM_RTC) + mlines |= TIOCM_DSR | TIOCM_DTR; if (modem & MDM_RTR) mlines |= TIOCM_RTS | TIOCM_CTS; if (modem & MDM_IC) @@ -1011,11 +1066,11 @@ static void gsm_process_modem(struct tty_struct *tty, struct gsm_dlci *dlci, /* Carrier drop -> hangup */ if (tty) { if ((mlines & TIOCM_CD) == 0 && (dlci->modem_rx & TIOCM_CD)) - if (!(tty->termios->c_cflag & CLOCAL)) + if (!(tty->termios.c_cflag & CLOCAL)) tty_hangup(tty); - if (brk & 0x01) - tty_insert_flip_char(tty, 0, TTY_BREAK); } + if (brk & 0x01) + tty_insert_flip_char(&dlci->port, 0, TTY_BREAK); dlci->modem_rx = mlines; } @@ -1035,6 +1090,7 @@ static void gsm_control_modem(struct gsm_mux *gsm, u8 *data, int clen) { unsigned int addr = 0; unsigned int modem = 0; + unsigned int brk = 0; struct gsm_dlci *dlci; int len = clen; u8 *dp = data; @@ -1061,8 +1117,18 @@ static void gsm_control_modem(struct gsm_mux *gsm, u8 *data, int clen) if (len == 0) return; } + len--; + if (len > 0) { + while (gsm_read_ea(&brk, *dp++) == 0) { + len--; + if (len == 0) + return; + } + modem <<= 7; + modem |= (brk & 0x7f); + } tty = tty_port_tty_get(&dlci->port); - gsm_process_modem(tty, dlci, modem); + gsm_process_modem(tty, dlci, modem, clen); if (tty) { tty_wakeup(tty); tty_kref_put(tty); @@ -1083,8 +1149,8 @@ static void gsm_control_modem(struct gsm_mux *gsm, u8 *data, int clen) static void gsm_control_rls(struct gsm_mux *gsm, u8 *data, int clen) { - struct tty_struct *tty; - unsigned int addr = 0 ; + struct tty_port *port; + unsigned int addr = 0; u8 bits; int len = clen; u8 *dp = data; @@ -1106,19 +1172,18 @@ static void gsm_control_rls(struct gsm_mux *gsm, u8 *data, int clen) bits = *dp; if ((bits & 1) == 0) return; - /* See if we have an uplink tty */ - tty = tty_port_tty_get(&gsm->dlci[addr]->port); - if (tty) { - if (bits & 2) - tty_insert_flip_char(tty, 0, TTY_OVERRUN); - if (bits & 4) - tty_insert_flip_char(tty, 0, TTY_PARITY); - if (bits & 8) - tty_insert_flip_char(tty, 0, TTY_FRAME); - tty_flip_buffer_push(tty); - tty_kref_put(tty); - } + port = &gsm->dlci[addr]->port; + + if (bits & 2) + tty_insert_flip_char(port, 0, TTY_OVERRUN); + if (bits & 4) + tty_insert_flip_char(port, 0, TTY_PARITY); + if (bits & 8) + tty_insert_flip_char(port, 0, TTY_FRAME); + + tty_flip_buffer_push(port); + gsm_control_reply(gsm, CMD_RLS, data, clen); } @@ -1140,6 +1205,8 @@ static void gsm_control_message(struct gsm_mux *gsm, unsigned int command, u8 *data, int clen) { u8 buf[1]; + unsigned long flags; + switch (command) { case CMD_CLD: { struct gsm_dlci *dlci = gsm->dlci[0]; @@ -1156,16 +1223,18 @@ static void gsm_control_message(struct gsm_mux *gsm, unsigned int command, gsm_control_reply(gsm, CMD_TEST, data, clen); break; case CMD_FCON: - /* Modem wants us to STFU */ - gsm->constipated = 1; - gsm_control_reply(gsm, CMD_FCON, NULL, 0); - break; - case CMD_FCOFF: /* Modem can accept data again */ gsm->constipated = 0; - gsm_control_reply(gsm, CMD_FCOFF, NULL, 0); + gsm_control_reply(gsm, CMD_FCON, NULL, 0); /* Kick the link in case it is idling */ + spin_lock_irqsave(&gsm->tx_lock, flags); gsm_data_kick(gsm); + spin_unlock_irqrestore(&gsm->tx_lock, flags); + break; + case CMD_FCOFF: + /* Modem wants us to STFU */ + gsm->constipated = 1; + gsm_control_reply(gsm, CMD_FCOFF, NULL, 0); break; case CMD_MSC: /* Out of band modem line change indicator for a DLCI */ @@ -1181,8 +1250,8 @@ static void gsm_control_message(struct gsm_mux *gsm, unsigned int command, break; /* Optional unsupported commands */ case CMD_PN: /* Parameter negotiation */ - case CMD_RPN: /* Remote port negotation */ - case CMD_SNC: /* Service negotation command */ + case CMD_RPN: /* Remote port negotiation */ + case CMD_SNC: /* Service negotiation command */ default: /* Reply to bad commands with an NSC */ buf[0] = command; @@ -1229,7 +1298,7 @@ static void gsm_control_response(struct gsm_mux *gsm, unsigned int command, } /** - * gsm_control_transmit - send control packet + * gsm_control_transmit - send control packet * @gsm: gsm mux * @ctrl: frame to send * @@ -1238,8 +1307,7 @@ static void gsm_control_response(struct gsm_mux *gsm, unsigned int command, static void gsm_control_transmit(struct gsm_mux *gsm, struct gsm_control *ctrl) { - struct gsm_msg *msg = gsm_data_alloc(gsm, 0, ctrl->len + 1, - gsm->ftype|PF); + struct gsm_msg *msg = gsm_data_alloc(gsm, 0, ctrl->len + 1, gsm->ftype); if (msg == NULL) return; msg->data[0] = (ctrl->cmd << 1) | 2 | EA; /* command */ @@ -1359,14 +1427,10 @@ static void gsm_dlci_close(struct gsm_dlci *dlci) { del_timer(&dlci->t1); if (debug & 8) - printk("DLCI %d goes closed.\n", dlci->addr); + pr_debug("DLCI %d goes closed.\n", dlci->addr); dlci->state = DLCI_CLOSED; if (dlci->addr != 0) { - struct tty_struct *tty = tty_port_tty_get(&dlci->port); - if (tty) { - tty_hangup(tty); - tty_kref_put(tty); - } + tty_port_tty_hangup(&dlci->port, false); kfifo_reset(dlci->fifo); } else dlci->gsm->dead = 1; @@ -1390,7 +1454,7 @@ static void gsm_dlci_open(struct gsm_dlci *dlci) /* This will let a tty open continue */ dlci->state = DLCI_OPEN; if (debug & 8) - printk("DLCI %d goes open.\n", dlci->addr); + pr_debug("DLCI %d goes open.\n", dlci->addr); wake_up(&dlci->gsm->event); } @@ -1484,39 +1548,41 @@ static void gsm_dlci_begin_close(struct gsm_dlci *dlci) * open we shovel the bits down it, if not we drop them. */ -static void gsm_dlci_data(struct gsm_dlci *dlci, u8 *data, int len) +static void gsm_dlci_data(struct gsm_dlci *dlci, u8 *data, int clen) { /* krefs .. */ struct tty_port *port = &dlci->port; - struct tty_struct *tty = tty_port_tty_get(port); + struct tty_struct *tty; unsigned int modem = 0; + int len = clen; if (debug & 16) - printk("%d bytes for tty %p\n", len, tty); - if (tty) { - switch (dlci->adaption) { - /* Unsupported types */ - /* Packetised interruptible data */ - case 4: - break; - /* Packetised uininterruptible voice/data */ - case 3: - break; - /* Asynchronous serial with line state in each frame */ - case 2: - while (gsm_read_ea(&modem, *data++) == 0) { - len--; - if (len == 0) - return; - } - gsm_process_modem(tty, dlci, modem); - /* Line state will go via DLCI 0 controls only */ - case 1: - default: - tty_insert_flip_string(tty, data, len); - tty_flip_buffer_push(tty); + pr_debug("%d bytes for tty\n", len); + switch (dlci->adaption) { + /* Unsupported types */ + /* Packetised interruptible data */ + case 4: + break; + /* Packetised uininterruptible voice/data */ + case 3: + break; + /* Asynchronous serial with line state in each frame */ + case 2: + while (gsm_read_ea(&modem, *data++) == 0) { + len--; + if (len == 0) + return; } - tty_kref_put(tty); + tty = tty_port_tty_get(port); + if (tty) { + gsm_process_modem(tty, dlci, modem, clen); + tty_kref_put(tty); + } + /* Line state will go via DLCI 0 controls only */ + case 1: + default: + tty_insert_flip_string(port, data, len); + tty_flip_buffer_push(port); } } @@ -1576,6 +1642,7 @@ static struct gsm_dlci *gsm_dlci_alloc(struct gsm_mux *gsm, int addr) if (dlci == NULL) return NULL; spin_lock_init(&dlci->lock); + mutex_init(&dlci->mutex); dlci->fifo = &dlci->_fifo; if (kfifo_alloc(&dlci->_fifo, 4096, GFP_KERNEL) < 0) { kfree(dlci); @@ -1601,29 +1668,63 @@ static struct gsm_dlci *gsm_dlci_alloc(struct gsm_mux *gsm, int addr) } /** - * gsm_dlci_free - release DLCI + * gsm_dlci_free - free DLCI + * @dlci: DLCI to free + * + * Free up a DLCI. + * + * Can sleep. + */ +static void gsm_dlci_free(struct tty_port *port) +{ + struct gsm_dlci *dlci = container_of(port, struct gsm_dlci, port); + + del_timer_sync(&dlci->t1); + dlci->gsm->dlci[dlci->addr] = NULL; + kfifo_free(dlci->fifo); + while ((dlci->skb = skb_dequeue(&dlci->skb_list))) + dev_kfree_skb(dlci->skb); + kfree(dlci); +} + +static inline void dlci_get(struct gsm_dlci *dlci) +{ + tty_port_get(&dlci->port); +} + +static inline void dlci_put(struct gsm_dlci *dlci) +{ + tty_port_put(&dlci->port); +} + +static void gsm_destroy_network(struct gsm_dlci *dlci); + +/** + * gsm_dlci_release - release DLCI * @dlci: DLCI to destroy * - * Free up a DLCI. Currently to keep the lifetime rules sane we only - * clean up DLCI objects when the MUX closes rather than as the port - * is closed down on both the tty and mux levels. + * Release a DLCI. Actual free is deferred until either + * mux is closed or tty is closed - whichever is last. * * Can sleep. */ -static void gsm_dlci_free(struct gsm_dlci *dlci) +static void gsm_dlci_release(struct gsm_dlci *dlci) { struct tty_struct *tty = tty_port_tty_get(&dlci->port); if (tty) { + mutex_lock(&dlci->mutex); + gsm_destroy_network(dlci); + mutex_unlock(&dlci->mutex); + tty_vhangup(tty); + + tty_port_tty_set(&dlci->port, NULL); tty_kref_put(tty); } - del_timer_sync(&dlci->t1); - dlci->gsm->dlci[dlci->addr] = NULL; - kfifo_free(dlci->fifo); - kfree(dlci); + dlci->state = DLCI_CLOSED; + dlci_put(dlci); } - /* * LAPBish link layer logic */ @@ -1648,10 +1749,17 @@ static void gsm_queue(struct gsm_mux *gsm) if ((gsm->control & ~PF) == UI) gsm->fcs = gsm_fcs_add_block(gsm->fcs, gsm->buf, gsm->len); + if (gsm->encoding == 0) { + /* WARNING: gsm->received_fcs is used for + gsm->encoding = 0 only. + In this case it contain the last piece of data + required to generate final CRC */ + gsm->fcs = gsm_fcs_add(gsm->fcs, gsm->received_fcs); + } if (gsm->fcs != GOOD_FCS) { gsm->bad_fcs++; if (debug & 4) - printk("BAD FCS %02x\n", gsm->fcs); + pr_debug("BAD FCS %02x\n", gsm->fcs); return; } address = gsm->address >> 1; @@ -1746,6 +1854,8 @@ invalid: static void gsm0_receive(struct gsm_mux *gsm, unsigned char c) { + unsigned int len; + switch (gsm->state) { case GSM_SEARCH: /* SOF marker */ if (c == GSM0_SOF) { @@ -1754,8 +1864,8 @@ static void gsm0_receive(struct gsm_mux *gsm, unsigned char c) gsm->len = 0; gsm->fcs = INIT_FCS; } - break; /* Address EA */ - case GSM_ADDRESS: + break; + case GSM_ADDRESS: /* Address EA */ gsm->fcs = gsm_fcs_add(gsm->fcs, c); if (gsm_read_ea(&gsm->address, c)) gsm->state = GSM_CONTROL; @@ -1763,9 +1873,9 @@ static void gsm0_receive(struct gsm_mux *gsm, unsigned char c) case GSM_CONTROL: /* Control Byte */ gsm->fcs = gsm_fcs_add(gsm->fcs, c); gsm->control = c; - gsm->state = GSM_LEN; + gsm->state = GSM_LEN0; break; - case GSM_LEN: /* Length EA */ + case GSM_LEN0: /* Length EA */ gsm->fcs = gsm_fcs_add(gsm->fcs, c); if (gsm_read_ea(&gsm->len, c)) { if (gsm->len > gsm->mru) { @@ -1774,8 +1884,28 @@ static void gsm0_receive(struct gsm_mux *gsm, unsigned char c) break; } gsm->count = 0; - gsm->state = GSM_DATA; + if (!gsm->len) + gsm->state = GSM_FCS; + else + gsm->state = GSM_DATA; + break; } + gsm->state = GSM_LEN1; + break; + case GSM_LEN1: + gsm->fcs = gsm_fcs_add(gsm->fcs, c); + len = c; + gsm->len |= len << 7; + if (gsm->len > gsm->mru) { + gsm->bad_size++; + gsm->state = GSM_SEARCH; + break; + } + gsm->count = 0; + if (!gsm->len) + gsm->state = GSM_FCS; + else + gsm->state = GSM_DATA; break; case GSM_DATA: /* Data */ gsm->buf[gsm->count++] = c; @@ -1783,16 +1913,21 @@ static void gsm0_receive(struct gsm_mux *gsm, unsigned char c) gsm->state = GSM_FCS; break; case GSM_FCS: /* FCS follows the packet */ - gsm->fcs = c; + gsm->received_fcs = c; gsm_queue(gsm); - /* And then back for the next frame */ - gsm->state = GSM_SEARCH; + gsm->state = GSM_SSOF; + break; + case GSM_SSOF: + if (c == GSM0_SOF) { + gsm->state = GSM_SEARCH; + break; + } break; } } /** - * gsm0_receive - perform processing for non-transparency + * gsm1_receive - perform processing for non-transparency * @gsm: gsm data for this ldisc instance * @c: character * @@ -1854,7 +1989,7 @@ static void gsm1_receive(struct gsm_mux *gsm, unsigned char c) gsm->state = GSM_DATA; break; case GSM_DATA: /* Data */ - if (gsm->count > gsm->mru ) { /* Allow one for the FCS */ + if (gsm->count > gsm->mru) { /* Allow one for the FCS */ gsm->state = GSM_OVERRUN; gsm->bad_size++; } else @@ -1893,11 +2028,12 @@ static void gsm_error(struct gsm_mux *gsm, * and then shut down each device hanging up the channels as we go. */ -void gsm_cleanup_mux(struct gsm_mux *gsm) +static void gsm_cleanup_mux(struct gsm_mux *gsm) { int i; struct gsm_dlci *dlci = gsm->dlci[0]; - struct gsm_msg *txq; + struct gsm_msg *txq, *ntxq; + struct gsm_control *gc; gsm->dead = 1; @@ -1911,6 +2047,13 @@ void gsm_cleanup_mux(struct gsm_mux *gsm) spin_unlock(&gsm_mux_lock); WARN_ON(i == MAX_MUX); + /* In theory disconnecting DLCI 0 is sufficient but for some + modems this is apparently not the case. */ + if (dlci) { + gc = gsm_control_send(gsm, CMD_CLD, NULL, 0); + if (gc) + gsm_control_wait(gsm, gc); + } del_timer_sync(&gsm->t2_timer); /* Now we are sure T2 has stopped */ if (dlci) { @@ -1920,17 +2063,16 @@ void gsm_cleanup_mux(struct gsm_mux *gsm) dlci->state == DLCI_CLOSED); } /* Free up any link layer users */ + mutex_lock(&gsm->mutex); for (i = 0; i < NUM_DLCI; i++) if (gsm->dlci[i]) - gsm_dlci_free(gsm->dlci[i]); + gsm_dlci_release(gsm->dlci[i]); + mutex_unlock(&gsm->mutex); /* Now wipe the queues */ - for (txq = gsm->tx_head; txq != NULL; txq = gsm->tx_head) { - gsm->tx_head = txq->next; + list_for_each_entry_safe(txq, ntxq, &gsm->tx_list, list) kfree(txq); - } - gsm->tx_tail = NULL; + INIT_LIST_HEAD(&gsm->tx_list); } -EXPORT_SYMBOL_GPL(gsm_cleanup_mux); /** * gsm_activate_mux - generic GSM setup @@ -1941,7 +2083,7 @@ EXPORT_SYMBOL_GPL(gsm_cleanup_mux); * finally kick off connecting to DLCI 0 on the modem. */ -int gsm_activate_mux(struct gsm_mux *gsm) +static int gsm_activate_mux(struct gsm_mux *gsm) { struct gsm_dlci *dlci; int i = 0; @@ -1962,6 +2104,7 @@ int gsm_activate_mux(struct gsm_mux *gsm) spin_lock(&gsm_mux_lock); for (i = 0; i < MAX_MUX; i++) { if (gsm_mux[i] == NULL) { + gsm->num = i; gsm_mux[i] = gsm; break; } @@ -1976,22 +2119,41 @@ int gsm_activate_mux(struct gsm_mux *gsm) gsm->dead = 0; /* Tty opens are now permissible */ return 0; } -EXPORT_SYMBOL_GPL(gsm_activate_mux); /** * gsm_free_mux - free up a mux * @mux: mux to free * - * Dispose of allocated resources for a dead mux. No refcounting - * at present so the mux must be truely dead. + * Dispose of allocated resources for a dead mux */ -void gsm_free_mux(struct gsm_mux *gsm) +static void gsm_free_mux(struct gsm_mux *gsm) { kfree(gsm->txframe); kfree(gsm->buf); kfree(gsm); } -EXPORT_SYMBOL_GPL(gsm_free_mux); + +/** + * gsm_free_muxr - free up a mux + * @mux: mux to free + * + * Dispose of allocated resources for a dead mux + */ +static void gsm_free_muxr(struct kref *ref) +{ + struct gsm_mux *gsm = container_of(ref, struct gsm_mux, ref); + gsm_free_mux(gsm); +} + +static inline void mux_get(struct gsm_mux *gsm) +{ + kref_get(&gsm->ref); +} + +static inline void mux_put(struct gsm_mux *gsm) +{ + kref_put(&gsm->ref, gsm_free_muxr); +} /** * gsm_alloc_mux - allocate a mux @@ -1999,7 +2161,7 @@ EXPORT_SYMBOL_GPL(gsm_free_mux); * Creates a new mux ready for activation. */ -struct gsm_mux *gsm_alloc_mux(void) +static struct gsm_mux *gsm_alloc_mux(void) { struct gsm_mux *gsm = kzalloc(sizeof(struct gsm_mux), GFP_KERNEL); if (gsm == NULL) @@ -2016,12 +2178,14 @@ struct gsm_mux *gsm_alloc_mux(void) return NULL; } spin_lock_init(&gsm->lock); + mutex_init(&gsm->mutex); + kref_init(&gsm->ref); + INIT_LIST_HEAD(&gsm->tx_list); gsm->t1 = T1; gsm->t2 = T2; gsm->n2 = N2; gsm->ftype = UIH; - gsm->initiator = 0; gsm->adaption = 1; gsm->encoding = 1; gsm->mru = 64; /* Default to encoding 1 so these should be 64 */ @@ -2030,10 +2194,6 @@ struct gsm_mux *gsm_alloc_mux(void) return gsm; } -EXPORT_SYMBOL_GPL(gsm_alloc_mux); - - - /** * gsmld_output - write to link @@ -2051,10 +2211,9 @@ static int gsmld_output(struct gsm_mux *gsm, u8 *data, int len) set_bit(TTY_DO_WRITE_WAKEUP, &gsm->tty->flags); return -ENOSPC; } - if (debug & 4) { - printk("-->%d bytes out\n", len); - hex_packet(data, len); - } + if (debug & 4) + print_hex_dump_bytes("gsmld_output: ", DUMP_PREFIX_OFFSET, + data, len); gsm->tty->ops->write(gsm->tty, data, len); return len; } @@ -2071,20 +2230,27 @@ static int gsmld_output(struct gsm_mux *gsm, u8 *data, int len) static int gsmld_attach_gsm(struct tty_struct *tty, struct gsm_mux *gsm) { - int ret; + int ret, i; + int base = gsm->num << 6; /* Base for this MUX */ gsm->tty = tty_kref_get(tty); gsm->output = gsmld_output; ret = gsm_activate_mux(gsm); if (ret != 0) tty_kref_put(gsm->tty); + else { + /* Don't register device 0 - this is the control channel and not + a usable tty interface */ + for (i = 1; i < NUM_DLCI; i++) + tty_register_device(gsm_tty_driver, base + i, NULL); + } return ret; } /** * gsmld_detach_gsm - stop doing 0710 mux - * @tty: tty atttached to the mux + * @tty: tty attached to the mux * @gsm: mux * * Shutdown and then clean up the resources used by the line discipline @@ -2092,7 +2258,12 @@ static int gsmld_attach_gsm(struct tty_struct *tty, struct gsm_mux *gsm) static void gsmld_detach_gsm(struct tty_struct *tty, struct gsm_mux *gsm) { + int i; + int base = gsm->num << 6; /* Base for this MUX */ + WARN_ON(tty != gsm->tty); + for (i = 1; i < NUM_DLCI; i++) + tty_unregister_device(gsm_tty_driver, base + i); gsm_cleanup_mux(gsm); tty_kref_put(gsm->tty); gsm->tty = NULL; @@ -2106,15 +2277,15 @@ static void gsmld_receive_buf(struct tty_struct *tty, const unsigned char *cp, char *f; int i; char buf[64]; - char flags; + char flags = TTY_NORMAL; - if (debug & 4) { - printk("Inbytes %dd\n", count); - hex_packet(cp, count); - } + if (debug & 4) + print_hex_dump_bytes("gsmld_receive: ", DUMP_PREFIX_OFFSET, + cp, count); for (i = count, dp = cp, f = fp; i; i--, dp++) { - flags = *f++; + if (f) + flags = *f++; switch (flags) { case TTY_NORMAL: gsm->receive(gsm, *dp); @@ -2126,7 +2297,7 @@ static void gsmld_receive_buf(struct tty_struct *tty, const unsigned char *cp, gsm->error(gsm, *dp, flags); break; default: - printk(KERN_ERR "%s: unknown flag %d\n", + WARN_ONCE(1, "%s: unknown flag %d\n", tty_name(tty, buf), flags); break; } @@ -2181,7 +2352,7 @@ static void gsmld_close(struct tty_struct *tty) gsmld_flush_buffer(tty); /* Do other clean up here */ - gsm_free_mux(gsm); + mux_put(gsm); } /** @@ -2230,12 +2401,12 @@ static void gsmld_write_wakeup(struct tty_struct *tty) /* Queue poll */ clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); + spin_lock_irqsave(&gsm->tx_lock, flags); gsm_data_kick(gsm); if (gsm->tx_bytes < TX_THRESH_LO) { - spin_lock_irqsave(&gsm->tx_lock, flags); gsm_dlci_data_sweep(gsm); - spin_unlock_irqrestore(&gsm->tx_lock, flags); } + spin_unlock_irqrestore(&gsm->tx_lock, flags); } /** @@ -2321,7 +2492,7 @@ static int gsmld_config(struct tty_struct *tty, struct gsm_mux *gsm, int need_restart = 0; /* Stuff we don't support yet - UI or I frame transport, windowing */ - if ((c->adaption !=1 && c->adaption != 2) || c->k) + if ((c->adaption != 1 && c->adaption != 2) || c->k) return -EOPNOTSUPP; /* Check the MRU/MTU range looks sane */ if (c->mru > MAX_MRU || c->mtu > MAX_MTU || c->mru < 8 || c->mtu < 8) @@ -2373,6 +2544,7 @@ static int gsmld_config(struct tty_struct *tty, struct gsm_mux *gsm, gsm->initiator = c->initiator; gsm->mru = c->mru; + gsm->mtu = c->mtu; gsm->encoding = c->encapsulation; gsm->adaption = c->adaption; gsm->n2 = c->n2; @@ -2416,7 +2588,7 @@ static int gsmld_ioctl(struct tty_struct *tty, struct file *file, c.i = 1; else c.i = 2; - printk("Ftype %d i %d\n", gsm->ftype, c.i); + pr_debug("Ftype %d i %d\n", gsm->ftype, c.i); c.mru = gsm->mru; c.mtu = gsm->mtu; c.k = 0; @@ -2432,6 +2604,220 @@ static int gsmld_ioctl(struct tty_struct *tty, struct file *file, } } +/* + * Network interface + * + */ + +static int gsm_mux_net_open(struct net_device *net) +{ + pr_debug("%s called\n", __func__); + netif_start_queue(net); + return 0; +} + +static int gsm_mux_net_close(struct net_device *net) +{ + netif_stop_queue(net); + return 0; +} + +static struct net_device_stats *gsm_mux_net_get_stats(struct net_device *net) +{ + return &((struct gsm_mux_net *)netdev_priv(net))->stats; +} +static void dlci_net_free(struct gsm_dlci *dlci) +{ + if (!dlci->net) { + WARN_ON(1); + return; + } + dlci->adaption = dlci->prev_adaption; + dlci->data = dlci->prev_data; + free_netdev(dlci->net); + dlci->net = NULL; +} +static void net_free(struct kref *ref) +{ + struct gsm_mux_net *mux_net; + struct gsm_dlci *dlci; + + mux_net = container_of(ref, struct gsm_mux_net, ref); + dlci = mux_net->dlci; + + if (dlci->net) { + unregister_netdev(dlci->net); + dlci_net_free(dlci); + } +} + +static inline void muxnet_get(struct gsm_mux_net *mux_net) +{ + kref_get(&mux_net->ref); +} + +static inline void muxnet_put(struct gsm_mux_net *mux_net) +{ + kref_put(&mux_net->ref, net_free); +} + +static int gsm_mux_net_start_xmit(struct sk_buff *skb, + struct net_device *net) +{ + struct gsm_mux_net *mux_net = (struct gsm_mux_net *)netdev_priv(net); + struct gsm_dlci *dlci = mux_net->dlci; + muxnet_get(mux_net); + + skb_queue_head(&dlci->skb_list, skb); + STATS(net).tx_packets++; + STATS(net).tx_bytes += skb->len; + gsm_dlci_data_kick(dlci); + /* And tell the kernel when the last transmit started. */ + net->trans_start = jiffies; + muxnet_put(mux_net); + return NETDEV_TX_OK; +} + +/* called when a packet did not ack after watchdogtimeout */ +static void gsm_mux_net_tx_timeout(struct net_device *net) +{ + /* Tell syslog we are hosed. */ + dev_dbg(&net->dev, "Tx timed out.\n"); + + /* Update statistics */ + STATS(net).tx_errors++; +} + +static void gsm_mux_rx_netchar(struct gsm_dlci *dlci, + unsigned char *in_buf, int size) +{ + struct net_device *net = dlci->net; + struct sk_buff *skb; + struct gsm_mux_net *mux_net = (struct gsm_mux_net *)netdev_priv(net); + muxnet_get(mux_net); + + /* Allocate an sk_buff */ + skb = dev_alloc_skb(size + NET_IP_ALIGN); + if (!skb) { + /* We got no receive buffer. */ + STATS(net).rx_dropped++; + muxnet_put(mux_net); + return; + } + skb_reserve(skb, NET_IP_ALIGN); + memcpy(skb_put(skb, size), in_buf, size); + + skb->dev = net; + skb->protocol = __constant_htons(ETH_P_IP); + + /* Ship it off to the kernel */ + netif_rx(skb); + + /* update out statistics */ + STATS(net).rx_packets++; + STATS(net).rx_bytes += size; + muxnet_put(mux_net); + return; +} + +static int gsm_change_mtu(struct net_device *net, int new_mtu) +{ + struct gsm_mux_net *mux_net = (struct gsm_mux_net *)netdev_priv(net); + if ((new_mtu < 8) || (new_mtu > mux_net->dlci->gsm->mtu)) + return -EINVAL; + net->mtu = new_mtu; + return 0; +} + +static void gsm_mux_net_init(struct net_device *net) +{ + static const struct net_device_ops gsm_netdev_ops = { + .ndo_open = gsm_mux_net_open, + .ndo_stop = gsm_mux_net_close, + .ndo_start_xmit = gsm_mux_net_start_xmit, + .ndo_tx_timeout = gsm_mux_net_tx_timeout, + .ndo_get_stats = gsm_mux_net_get_stats, + .ndo_change_mtu = gsm_change_mtu, + }; + + net->netdev_ops = &gsm_netdev_ops; + + /* fill in the other fields */ + net->watchdog_timeo = GSM_NET_TX_TIMEOUT; + net->flags = IFF_POINTOPOINT | IFF_NOARP | IFF_MULTICAST; + net->type = ARPHRD_NONE; + net->tx_queue_len = 10; +} + + +/* caller holds the dlci mutex */ +static void gsm_destroy_network(struct gsm_dlci *dlci) +{ + struct gsm_mux_net *mux_net; + + pr_debug("destroy network interface"); + if (!dlci->net) + return; + mux_net = (struct gsm_mux_net *)netdev_priv(dlci->net); + muxnet_put(mux_net); +} + + +/* caller holds the dlci mutex */ +static int gsm_create_network(struct gsm_dlci *dlci, struct gsm_netconfig *nc) +{ + char *netname; + int retval = 0; + struct net_device *net; + struct gsm_mux_net *mux_net; + + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + + /* Already in a non tty mode */ + if (dlci->adaption > 2) + return -EBUSY; + + if (nc->protocol != htons(ETH_P_IP)) + return -EPROTONOSUPPORT; + + if (nc->adaption != 3 && nc->adaption != 4) + return -EPROTONOSUPPORT; + + pr_debug("create network interface"); + + netname = "gsm%d"; + if (nc->if_name[0] != '\0') + netname = nc->if_name; + net = alloc_netdev(sizeof(struct gsm_mux_net), + netname, + gsm_mux_net_init); + if (!net) { + pr_err("alloc_netdev failed"); + return -ENOMEM; + } + net->mtu = dlci->gsm->mtu; + mux_net = (struct gsm_mux_net *)netdev_priv(net); + mux_net->dlci = dlci; + kref_init(&mux_net->ref); + strncpy(nc->if_name, net->name, IFNAMSIZ); /* return net name */ + + /* reconfigure dlci for network */ + dlci->prev_adaption = dlci->adaption; + dlci->prev_data = dlci->data; + dlci->adaption = nc->adaption; + dlci->data = gsm_mux_rx_netchar; + dlci->net = net; + + pr_debug("register netdev"); + retval = register_netdev(net); + if (retval) { + pr_err("network register fail %d\n", retval); + dlci_net_free(dlci); + return retval; + } + return net->ifindex; /* return network index */ +} /* Line discipline for real tty */ struct tty_ldisc_ops tty_ldisc_packet = { @@ -2504,16 +2890,17 @@ static void gsm_dtr_rts(struct tty_port *port, int onoff) static const struct tty_port_operations gsm_port_ops = { .carrier_raised = gsm_carrier_raised, .dtr_rts = gsm_dtr_rts, + .destruct = gsm_dlci_free, }; - -static int gsmtty_open(struct tty_struct *tty, struct file *filp) +static int gsmtty_install(struct tty_driver *driver, struct tty_struct *tty) { struct gsm_mux *gsm; struct gsm_dlci *dlci; - struct tty_port *port; unsigned int line = tty->index; unsigned int mux = line >> 6; + bool alloc = false; + int ret; line = line & 0x3F; @@ -2527,14 +2914,47 @@ static int gsmtty_open(struct tty_struct *tty, struct file *filp) gsm = gsm_mux[mux]; if (gsm->dead) return -EL2HLT; + /* If DLCI 0 is not yet fully open return an error. + This is ok from a locking + perspective as we don't have to worry about this + if DLCI0 is lost */ + mutex_lock(&gsm->mutex); + if (gsm->dlci[0] && gsm->dlci[0]->state != DLCI_OPEN) { + mutex_unlock(&gsm->mutex); + return -EL2NSYNC; + } dlci = gsm->dlci[line]; - if (dlci == NULL) + if (dlci == NULL) { + alloc = true; dlci = gsm_dlci_alloc(gsm, line); - if (dlci == NULL) + } + if (dlci == NULL) { + mutex_unlock(&gsm->mutex); return -ENOMEM; - port = &dlci->port; - port->count++; + } + ret = tty_port_install(&dlci->port, driver, tty); + if (ret) { + if (alloc) + dlci_put(dlci); + mutex_unlock(&gsm->mutex); + return ret; + } + + dlci_get(dlci); + dlci_get(gsm->dlci[0]); + mux_get(gsm); tty->driver_data = dlci; + mutex_unlock(&gsm->mutex); + + return 0; +} + +static int gsmtty_open(struct tty_struct *tty, struct file *filp) +{ + struct gsm_dlci *dlci = tty->driver_data; + struct tty_port *port = &dlci->port; + + port->count++; tty_port_tty_set(port, tty); dlci->modem_rx = 0; @@ -2550,18 +2970,33 @@ static int gsmtty_open(struct tty_struct *tty, struct file *filp) static void gsmtty_close(struct tty_struct *tty, struct file *filp) { struct gsm_dlci *dlci = tty->driver_data; + struct gsm_mux *gsm; + if (dlci == NULL) return; + if (dlci->state == DLCI_CLOSED) + return; + mutex_lock(&dlci->mutex); + gsm_destroy_network(dlci); + mutex_unlock(&dlci->mutex); + gsm = dlci->gsm; if (tty_port_close_start(&dlci->port, tty, filp) == 0) return; gsm_dlci_begin_close(dlci); + if (test_bit(ASYNCB_INITIALIZED, &dlci->port.flags)) { + if (C_HUPCL(tty)) + tty_port_lower_dtr_rts(&dlci->port); + } tty_port_close_end(&dlci->port, tty); tty_port_tty_set(&dlci->port, NULL); + return; } static void gsmtty_hangup(struct tty_struct *tty) { struct gsm_dlci *dlci = tty->driver_data; + if (dlci->state == DLCI_CLOSED) + return; tty_port_hangup(&dlci->port); gsm_dlci_begin_close(dlci); } @@ -2569,9 +3004,12 @@ static void gsmtty_hangup(struct tty_struct *tty) static int gsmtty_write(struct tty_struct *tty, const unsigned char *buf, int len) { + int sent; struct gsm_dlci *dlci = tty->driver_data; + if (dlci->state == DLCI_CLOSED) + return -EINVAL; /* Stuff the bytes into the fifo queue */ - int sent = kfifo_in_locked(dlci->fifo, buf, len, &dlci->lock); + sent = kfifo_in_locked(dlci->fifo, buf, len, &dlci->lock); /* Need to kick the channel */ gsm_dlci_data_kick(dlci); return sent; @@ -2580,18 +3018,24 @@ static int gsmtty_write(struct tty_struct *tty, const unsigned char *buf, static int gsmtty_write_room(struct tty_struct *tty) { struct gsm_dlci *dlci = tty->driver_data; + if (dlci->state == DLCI_CLOSED) + return -EINVAL; return TX_SIZE - kfifo_len(dlci->fifo); } static int gsmtty_chars_in_buffer(struct tty_struct *tty) { struct gsm_dlci *dlci = tty->driver_data; + if (dlci->state == DLCI_CLOSED) + return -EINVAL; return kfifo_len(dlci->fifo); } static void gsmtty_flush_buffer(struct tty_struct *tty) { struct gsm_dlci *dlci = tty->driver_data; + if (dlci->state == DLCI_CLOSED) + return; /* Caution needed: If we implement reliable transport classes then the data being transmitted can't simply be junked once it has first hit the stack. Until then we can just blow it @@ -2607,19 +3051,23 @@ static void gsmtty_wait_until_sent(struct tty_struct *tty, int timeout) to do here */ } -static int gsmtty_tiocmget(struct tty_struct *tty, struct file *filp) +static int gsmtty_tiocmget(struct tty_struct *tty) { struct gsm_dlci *dlci = tty->driver_data; + if (dlci->state == DLCI_CLOSED) + return -EINVAL; return dlci->modem_rx; } -static int gsmtty_tiocmset(struct tty_struct *tty, struct file *filp, +static int gsmtty_tiocmset(struct tty_struct *tty, unsigned int set, unsigned int clear) { struct gsm_dlci *dlci = tty->driver_data; unsigned int modem_tx = dlci->modem_tx; - modem_tx &= clear; + if (dlci->state == DLCI_CLOSED) + return -EINVAL; + modem_tx &= ~clear; modem_tx |= set; if (modem_tx != dlci->modem_tx) { @@ -2630,26 +3078,58 @@ static int gsmtty_tiocmset(struct tty_struct *tty, struct file *filp, } -static int gsmtty_ioctl(struct tty_struct *tty, struct file *filp, +static int gsmtty_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg) { - return -ENOIOCTLCMD; + struct gsm_dlci *dlci = tty->driver_data; + struct gsm_netconfig nc; + int index; + + if (dlci->state == DLCI_CLOSED) + return -EINVAL; + switch (cmd) { + case GSMIOC_ENABLE_NET: + if (copy_from_user(&nc, (void __user *)arg, sizeof(nc))) + return -EFAULT; + nc.if_name[IFNAMSIZ-1] = '\0'; + /* return net interface index or error code */ + mutex_lock(&dlci->mutex); + index = gsm_create_network(dlci, &nc); + mutex_unlock(&dlci->mutex); + if (copy_to_user((void __user *)arg, &nc, sizeof(nc))) + return -EFAULT; + return index; + case GSMIOC_DISABLE_NET: + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + mutex_lock(&dlci->mutex); + gsm_destroy_network(dlci); + mutex_unlock(&dlci->mutex); + return 0; + default: + return -ENOIOCTLCMD; + } } static void gsmtty_set_termios(struct tty_struct *tty, struct ktermios *old) { + struct gsm_dlci *dlci = tty->driver_data; + if (dlci->state == DLCI_CLOSED) + return; /* For the moment its fixed. In actual fact the speed information for the virtual channel can be propogated in both directions by the RPN control message. This however rapidly gets nasty as we then have to remap modem signals each way according to whether our virtual cable is null modem etc .. */ - tty_termios_copy_hw(tty->termios, old); + tty_termios_copy_hw(&tty->termios, old); } static void gsmtty_throttle(struct tty_struct *tty) { struct gsm_dlci *dlci = tty->driver_data; - if (tty->termios->c_cflag & CRTSCTS) + if (dlci->state == DLCI_CLOSED) + return; + if (tty->termios.c_cflag & CRTSCTS) dlci->modem_tx &= ~TIOCM_DTR; dlci->throttled = 1; /* Send an MSC with DTR cleared */ @@ -2659,7 +3139,9 @@ static void gsmtty_throttle(struct tty_struct *tty) static void gsmtty_unthrottle(struct tty_struct *tty) { struct gsm_dlci *dlci = tty->driver_data; - if (tty->termios->c_cflag & CRTSCTS) + if (dlci->state == DLCI_CLOSED) + return; + if (tty->termios.c_cflag & CRTSCTS) dlci->modem_tx |= TIOCM_DTR; dlci->throttled = 0; /* Send an MSC with DTR set */ @@ -2670,6 +3152,8 @@ static int gsmtty_break_ctl(struct tty_struct *tty, int state) { struct gsm_dlci *dlci = tty->driver_data; int encode = 0; /* Off */ + if (dlci->state == DLCI_CLOSED) + return -EINVAL; if (state == -1) /* "On indefinitely" - we can't encode this properly */ @@ -2682,10 +3166,20 @@ static int gsmtty_break_ctl(struct tty_struct *tty, int state) return gsmtty_modem_update(dlci, encode); } -static struct tty_driver *gsm_tty_driver; +static void gsmtty_remove(struct tty_driver *driver, struct tty_struct *tty) +{ + struct gsm_dlci *dlci = tty->driver_data; + struct gsm_mux *gsm = dlci->gsm; + + dlci_put(dlci); + dlci_put(gsm->dlci[0]); + mux_put(gsm); + driver->ttys[tty->index] = NULL; +} /* Virtual ttys for the demux */ static const struct tty_operations gsmtty_ops = { + .install = gsmtty_install, .open = gsmtty_open, .close = gsmtty_close, .write = gsmtty_write, @@ -2701,6 +3195,7 @@ static const struct tty_operations gsmtty_ops = { .tiocmget = gsmtty_tiocmget, .tiocmset = gsmtty_tiocmset, .break_ctl = gsmtty_break_ctl, + .remove = gsmtty_remove, }; @@ -2710,17 +3205,17 @@ static int __init gsm_init(void) /* Fill in our line protocol discipline, and register it */ int status = tty_register_ldisc(N_GSM0710, &tty_ldisc_packet); if (status != 0) { - printk(KERN_ERR "n_gsm: can't register line discipline (err = %d)\n", status); + pr_err("n_gsm: can't register line discipline (err = %d)\n", + status); return status; } gsm_tty_driver = alloc_tty_driver(256); if (!gsm_tty_driver) { tty_unregister_ldisc(N_GSM0710); - printk(KERN_ERR "gsm_init: tty allocation failed.\n"); + pr_err("gsm_init: tty allocation failed.\n"); return -EINVAL; } - gsm_tty_driver->owner = THIS_MODULE; gsm_tty_driver->driver_name = "gsmtty"; gsm_tty_driver->name = "gsmtty"; gsm_tty_driver->major = 0; /* Dynamic */ @@ -2728,7 +3223,7 @@ static int __init gsm_init(void) gsm_tty_driver->type = TTY_DRIVER_TYPE_SERIAL; gsm_tty_driver->subtype = SERIAL_TYPE_NORMAL; gsm_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV - | TTY_DRIVER_HARDWARE_BREAK; + | TTY_DRIVER_HARDWARE_BREAK; gsm_tty_driver->init_termios = tty_std_termios; /* Fixme */ gsm_tty_driver->init_termios.c_lflag &= ~ECHO; @@ -2739,10 +3234,11 @@ static int __init gsm_init(void) if (tty_register_driver(gsm_tty_driver)) { put_tty_driver(gsm_tty_driver); tty_unregister_ldisc(N_GSM0710); - printk(KERN_ERR "gsm_init: tty registration failed.\n"); + pr_err("gsm_init: tty registration failed.\n"); return -EBUSY; } - printk(KERN_INFO "gsm_init: loaded as %d,%d.\n", gsm_tty_driver->major, gsm_tty_driver->minor_start); + pr_debug("gsm_init: loaded as %d,%d.\n", + gsm_tty_driver->major, gsm_tty_driver->minor_start); return 0; } @@ -2750,10 +3246,10 @@ static void __exit gsm_exit(void) { int status = tty_unregister_ldisc(N_GSM0710); if (status != 0) - printk(KERN_ERR "n_gsm: can't unregister line discipline (err = %d)\n", status); + pr_err("n_gsm: can't unregister line discipline (err = %d)\n", + status); tty_unregister_driver(gsm_tty_driver); put_tty_driver(gsm_tty_driver); - printk(KERN_INFO "gsm_init: unloaded.\n"); } module_init(gsm_init); |
