|
@@ -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)
|
|
|
{
|
|
@@ -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 = {
|