Merge branch 'tty-next' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/tty-2.6
[pandora-kernel.git] / drivers / tty / n_gsm.c
index 19b4ae0..8a50e4e 100644 (file)
 #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)
 {
@@ -2041,6 +2099,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
  *
@@ -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 = {