From: Ken Mills <[email protected]>

Subject: [PATCH] n_gsm: add net capability to GSM 27.010 mux

This patch adds the ability to open a network data connection over a mux
virtual tty channel. This is for modems that support data connections
with raw IP frames instead of PPP. On high speed data connections this
eliminates a significant amount of PPP overhead. To use this interface,
the application must first tell the modem to open a network connection on
a virtual tty. Once that has been accomplished, the app will issue an
IOCTL on that virtual tty to create the network interface. The IOCTL will
return the index of the interface created.

The two IOCTL commands are:

        ioctl( fd, GSMIOC_ENABLE_NET );

        ioctl( fd, GSMIOC_DISABLE_NET );

Signed-off-by: Ken Mills <[email protected]>
---
 drivers/char/n_gsm.c   |  225 ++++++++++++++++++++++++++++++++++++++++++++++--
 include/linux/gsmmux.h |    2 +
 2 files changed, 220 insertions(+), 7 deletions(-)
 mode change 100644 => 100755 drivers/char/n_gsm.c

diff --git a/drivers/char/n_gsm.c b/drivers/char/n_gsm.c
old mode 100644
new mode 100755
index 6762e0a..9398213
--- a/drivers/char/n_gsm.c
+++ b/drivers/char/n_gsm.c
@@ -60,6 +60,10 @@
 #include <linux/kfifo.h>
 #include <linux/skbuff.h>
 #include <linux/gsmmux.h>
+#include <net/arp.h>
+#include <linux/ip.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
 
 static int debug;
 module_param(debug, int, 0600);
@@ -76,8 +80,24 @@ module_param(debug, int, 0600);
 
 /* 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
+#define MAX_MRU 1509
+#define MAX_MTU 1509
+#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
@@ -133,6 +153,7 @@ struct gsm_dlci {
        struct sk_buff_head skb_list;   /* Queued frames */
        /* Data handling callback */
        void (*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 */
@@ -493,7 +514,6 @@ static void gsm_print_packet(const char *hdr, int addr, int 
cr,
        pr_debug("\n");
 }
 
-
 /*
  *     Link level transmission side
  */
@@ -886,7 +906,8 @@ 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)
                dlci->skb = NULL;
@@ -922,6 +943,7 @@ static void gsm_dlci_data_sweep(struct gsm_mux *gsm)
                        i++;
                        continue;
                }
+
                if (dlci->adaption < 3)
                        len = gsm_dlci_data_output(gsm, dlci);
                else
@@ -950,7 +972,10 @@ 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);
+               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);
@@ -1515,8 +1540,8 @@ static void gsm_dlci_data(struct gsm_dlci *dlci, u8 
*data, int clen)
 
        if (debug & 16)
                pr_debug("gsm_dlci_data: %d bytes for tty %p\n", len, tty);
-
        if (tty) {
+
                switch (dlci->adaption)  {
                /* Unsupported types */
                /* Packetised interruptible data */
@@ -1615,6 +1640,7 @@ static struct gsm_dlci *gsm_dlci_alloc(struct gsm_mux 
*gsm, int addr)
        dlci->addr = addr;
        dlci->adaption = gsm->adaption;
        dlci->state = DLCI_CLOSED;
+       dlci->net = NULL;       /* network not initially created */
        if (addr)
                dlci->data = gsm_dlci_data;
        else
@@ -2481,6 +2507,175 @@ 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 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);
+               free_netdev(dlci->net);
+               dlci->net = 0;
+               dlci->adaption = 1;
+               dlci->data = gsm_dlci_data;
+       }
+}
+static int gsm_mux_net_start_xmit(struct sk_buff *skb,
+                                     struct net_device *net)
+{
+       struct gsm_dlci *dlci = ((struct gsm_mux_net *)netdev_priv(net))->dlci;
+       struct gsm_mux_net *mux_net;
+
+       mux_net = (struct gsm_mux_net *)netdev_priv(net);
+       kref_get(&mux_net->ref);
+
+       dlci->skb = skb;
+       gsm_dlci_data_kick(dlci);
+       STATS(net).tx_packets++;
+       STATS(net).tx_bytes += skb->len;
+       /* And tell the kernel when the last transmit started. */
+       net->trans_start = jiffies;
+       dev_kfree_skb(skb);
+       kref_put(&mux_net->ref, net_free);
+       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;
+
+       mux_net = (struct gsm_mux_net *)netdev_priv(net);
+       kref_get(&mux_net->ref);
+
+       /* Allocate an sk_buff */
+       skb = dev_alloc_skb(size + NET_IP_ALIGN);
+       if (!skb) {
+               /* We got no receive buffer. */
+               STATS(net).rx_dropped++;
+               return;
+       }
+       skb_reserve(skb, NET_IP_ALIGN);
+       memcpy(skb->data, in_buf, size);
+       skb_put(skb, 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;
+       kref_put(&mux_net->ref, net_free);
+       return;
+}
+
+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,
+       };
+       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->mtu = MAX_MTU;
+       net->tx_queue_len = 10;
+}
+
+
+static void gsm_destroy_network(struct gsm_dlci *dlci)
+{
+       struct gsm_mux_net *mux_net;
+
+       pr_debug("destroy network interface");
+       mux_net = (struct gsm_mux_net *)netdev_priv(dlci->net);
+       kref_put(&mux_net->ref, net_free);
+}
+
+
+static int gsm_create_network(struct gsm_dlci *dlci)
+{
+       char netname[6];
+       int retval = 0;
+       int channel = dlci->addr;
+       struct net_device *net;
+       struct gsm_mux_net *mux_net;
+
+       pr_debug("create network interface");
+       netname[5] = 0;
+       snprintf(netname, 6, "gsm%02d", channel);
+       net = alloc_netdev(sizeof(struct gsm_mux_net),
+                       netname,
+                       gsm_mux_net_init);
+       if (!net) {
+               pr_err("alloc_netdev failed");
+               retval = -ENOMEM;
+               goto error_ret;
+       }
+
+       dlci->net = net;
+       dlci->adaption = 3;
+       dlci->data = gsm_mux_rx_netchar;
+       mux_net = (struct gsm_mux_net *)netdev_priv(net);
+       memset(mux_net, 0, sizeof(struct gsm_mux_net));
+       mux_net->dlci = dlci;
+       kref_init(&mux_net->ref);
+       pr_debug("register netdev");
+       retval = register_netdev(net);
+       if (retval) {
+               pr_err("network register fail %d\n", retval);
+               goto error_ret;
+       }
+       return net->ifindex;    /* return network index */
+
+error_ret:
+       return retval;
+}
 
 /* Line discipline for real tty */
 struct tty_ldisc_ops tty_ldisc_packet = {
@@ -2682,7 +2877,23 @@ static int gsmtty_tiocmset(struct tty_struct *tty, 
struct file *filp,
 static int gsmtty_ioctl(struct tty_struct *tty, struct file *filp,
                        unsigned int cmd, unsigned long arg)
 {
-       return -ENOIOCTLCMD;
+       struct gsm_dlci *dlci = tty->driver_data;
+       int retval = 0;
+
+       switch (cmd) {
+       case GSMIOC_ENABLE_NET:
+               if (!capable(CAP_NET_ADMIN))
+                       return -EPERM;
+               retval = gsm_create_network(dlci);
+               return retval;  /* return net interface index or error code */
+       case GSMIOC_DISABLE_NET:
+               if (!capable(CAP_NET_ADMIN))
+                       return -EPERM;
+               gsm_destroy_network(dlci);
+               return 0;
+       default:
+               return n_tty_ioctl_helper(tty, filp, cmd, arg);
+       }
 }
 
 static void gsmtty_set_termios(struct tty_struct *tty, struct ktermios *old)
diff --git a/include/linux/gsmmux.h b/include/linux/gsmmux.h
index 378de41..314c681 100644
--- a/include/linux/gsmmux.h
+++ b/include/linux/gsmmux.h
@@ -20,6 +20,8 @@ struct gsm_config
 
 #define GSMIOC_GETCONF         _IOR('G', 0, struct gsm_config)
 #define GSMIOC_SETCONF         _IOW('G', 1, struct gsm_config)
+#define GSMIOC_ENABLE_NET      _IOW('G', 2, struct gsm_config)
+#define GSMIOC_DISABLE_NET     _IOW('G', 3, struct gsm_config)
 
 
 #endif
-- 
1.7.0.4
_______________________________________________
MeeGo-kernel mailing list
[email protected]
http://lists.meego.com/listinfo/meego-kernel

Reply via email to