diff options
Diffstat (limited to 'drivers/tty/n_gsm.c')
-rw-r--r-- | drivers/tty/n_gsm.c | 401 |
1 files changed, 372 insertions, 29 deletions
diff --git a/drivers/tty/n_gsm.c b/drivers/tty/n_gsm.c index 19b4ae052af..8a50e4eebf1 100644 --- a/drivers/tty/n_gsm.c +++ b/drivers/tty/n_gsm.c @@ -58,6 +58,10 @@ #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; @@ -77,8 +81,24 @@ module_param(debug, int, 0600); * Semi-arbitrary buffer size limits. 0710 is normally run with 32-64 byte * limits so this is plenty */ -#define MAX_MRU 512 -#define MAX_MTU 512 +#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 @@ -113,6 +133,8 @@ 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 kref ref; /* freed from port or mux close */ + struct mutex mutex; /* Link layer */ spinlock_t lock; /* Protects the internal state */ @@ -123,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 */ @@ -134,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 */ @@ -169,6 +194,8 @@ struct gsm_control { struct gsm_mux { struct tty_struct *tty; /* The tty our ldisc is bound to */ spinlock_t lock; + unsigned int num; + struct kref ref; /* Events on the GSM channel */ wait_queue_head_t event; @@ -250,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 @@ -878,8 +907,10 @@ static int gsm_dlci_data_output_framed(struct gsm_mux *gsm, memcpy(dp, dlci->skb->data, len); skb_pull(dlci->skb, len); __gsm_data_queue(dlci, msg); - if (last) + if (last) { + kfree_skb(dlci->skb); dlci->skb = NULL; + } return size; } @@ -912,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); @@ -939,9 +970,12 @@ static void gsm_dlci_data_kick(struct gsm_dlci *dlci) 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) + 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); + } else if (dlci->gsm->tx_bytes < TX_THRESH_LO) gsm_dlci_data_sweep(dlci->gsm); spin_unlock_irqrestore(&dlci->gsm->tx_lock, flags); } @@ -1588,6 +1622,8 @@ static struct gsm_dlci *gsm_dlci_alloc(struct gsm_mux *gsm, int addr) if (dlci == NULL) return NULL; spin_lock_init(&dlci->lock); + kref_init(&dlci->ref); + mutex_init(&dlci->mutex); dlci->fifo = &dlci->_fifo; if (kfifo_alloc(&dlci->_fifo, 4096, GFP_KERNEL) < 0) { kfree(dlci); @@ -1613,26 +1649,52 @@ 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 kref *ref) +{ + struct gsm_dlci *dlci = container_of(ref, struct gsm_dlci, ref); + + del_timer_sync(&dlci->t1); + dlci->gsm->dlci[dlci->addr] = NULL; + kfifo_free(dlci->fifo); + while ((dlci->skb = skb_dequeue(&dlci->skb_list))) + kfree_skb(dlci->skb); + kfree(dlci); +} + +static inline void dlci_get(struct gsm_dlci *dlci) +{ + kref_get(&dlci->ref); +} + +static inline void dlci_put(struct gsm_dlci *dlci) +{ + kref_put(&dlci->ref, gsm_dlci_free); +} + +/** + * 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) { tty_vhangup(tty); tty_kref_put(tty); } - del_timer_sync(&dlci->t1); - dlci->gsm->dlci[dlci->addr] = NULL; - kfifo_free(dlci->fifo); - kfree(dlci); + dlci_put(dlci); } /* @@ -1823,10 +1885,6 @@ static void gsm0_receive(struct gsm_mux *gsm, unsigned char c) break; case GSM_FCS: /* FCS follows the packet */ gsm->received_fcs = c; - if (c == GSM0_SOF) { - gsm->state = GSM_SEARCH; - break; - } gsm_queue(gsm); gsm->state = GSM_SSOF; break; @@ -1970,7 +2028,7 @@ void gsm_cleanup_mux(struct gsm_mux *gsm) /* Free up any link layer users */ for (i = 0; i < NUM_DLCI; i++) if (gsm->dlci[i]) - gsm_dlci_free(gsm->dlci[i]); + gsm_dlci_release(gsm->dlci[i]); /* Now wipe the queues */ for (txq = gsm->tx_head; txq != NULL; txq = gsm->tx_head) { gsm->tx_head = txq->next; @@ -2010,6 +2068,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; } @@ -2030,8 +2089,7 @@ 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 truly dead. + * Dispose of allocated resources for a dead mux */ void gsm_free_mux(struct gsm_mux *gsm) { @@ -2042,6 +2100,28 @@ void gsm_free_mux(struct gsm_mux *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 * * Creates a new mux ready for activation. @@ -2064,12 +2144,12 @@ struct gsm_mux *gsm_alloc_mux(void) return NULL; } spin_lock_init(&gsm->lock); + kref_init(&gsm->ref); 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 */ @@ -2115,13 +2195,20 @@ 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; } @@ -2136,7 +2223,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; @@ -2224,7 +2316,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); } /** @@ -2476,6 +2568,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; +} + +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 = { @@ -2579,6 +2885,9 @@ static int gsmtty_open(struct tty_struct *tty, struct file *filp) port = &dlci->port; port->count++; tty->driver_data = dlci; + dlci_get(dlci); + dlci_get(dlci->gsm->dlci[0]); + mux_get(dlci->gsm); tty_port_tty_set(port, tty); dlci->modem_rx = 0; @@ -2594,13 +2903,23 @@ 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; + 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; + goto out; gsm_dlci_begin_close(dlci); tty_port_close_end(&dlci->port, tty); tty_port_tty_set(&dlci->port, NULL); +out: + dlci_put(dlci); + dlci_put(gsm->dlci[0]); + mux_put(gsm); } static void gsmtty_hangup(struct tty_struct *tty) @@ -2677,7 +2996,32 @@ static int gsmtty_tiocmset(struct tty_struct *tty, 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; + + 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) @@ -2726,7 +3070,6 @@ static int gsmtty_break_ctl(struct tty_struct *tty, int state) return gsmtty_modem_update(dlci, encode); } -static struct tty_driver *gsm_tty_driver; /* Virtual ttys for the demux */ static const struct tty_operations gsmtty_ops = { |