#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;
* 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
#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 */
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 */
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 */
}
memcpy(dp, skb_pull(dlci->skb, len), len);
__gsm_data_queue(dlci, msg);
- if (last)
+ if (last) {
+ kfree_skb(dlci->skb);
dlci->skb = NULL;
+ }
return size;
}
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);
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);
}
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);
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 */
}
}
+/*
+ * 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 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;
+ kref_get(&mux_net->ref);
+
+ 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;
+ 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 = (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++;
+ kref_put(&mux_net->ref, net_free);
+ 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;
+ kref_put(&mux_net->ref, net_free);
+ 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);
+ kref_put(&mux_net->ref, net_free);
+}
+
+
+/* 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 = {
struct gsm_dlci *dlci = tty->driver_data;
if (dlci == NULL)
return;
+ mutex_lock(&dlci->mutex);
+ gsm_destroy_network(dlci);
+ mutex_unlock(&dlci->mutex);
if (tty_port_close_start(&dlci->port, tty, filp) == 0)
return;
gsm_dlci_begin_close(dlci);
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)