Commit 6ab8fba7 authored by Russ Gorby's avatar Russ Gorby Committed by Greg Kroah-Hartman

tty: n_gsm: Added refcount usage to gsm_mux and gsm_dlci structs

The gsm_mux is created/destroyed when ldisc is
opened/closed but clients of the MUX channel devices (gsmttyN)
may access this structure as long as the TTYs are open.
For the open, the ldisc open is guaranteed to preceed the TTY open,
but the close has no such guaranteed ordering. As a result,
the gsm_mux can be freed in the ldisc close before being accessed
by one of the TTY clients. This can happen if the ldisc is removed
while there are open, active MUX channels.
A similar situation exists for DLCI-0, it is basically a resource
shared by MUX and DLCI  , and should not be freed while they can
be accessed

To avoid this, gsm_mux and dlcis now have a reference counter
ldisc open takes a reference on the mux and all the dlcis
gsmtty_open takes a reference on the mux, dlci0 and its specific
dlci. Dropping the last reference initiates the actual free.
Signed-off-by: default avatarRuss Gorby <russ.gorby@intel.com>
Acked-by: default avatarAlan Cox <alan@linux.intel.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent bcd5abe2
...@@ -133,6 +133,7 @@ struct gsm_dlci { ...@@ -133,6 +133,7 @@ struct gsm_dlci {
#define DLCI_OPENING 1 /* Sending SABM not seen UA */ #define DLCI_OPENING 1 /* Sending SABM not seen UA */
#define DLCI_OPEN 2 /* SABM/UA complete */ #define DLCI_OPEN 2 /* SABM/UA complete */
#define DLCI_CLOSING 3 /* Sending DISC not seen UA/DM */ #define DLCI_CLOSING 3 /* Sending DISC not seen UA/DM */
struct kref ref; /* freed from port or mux close */
struct mutex mutex; struct mutex mutex;
/* Link layer */ /* Link layer */
...@@ -194,6 +195,7 @@ struct gsm_mux { ...@@ -194,6 +195,7 @@ struct gsm_mux {
struct tty_struct *tty; /* The tty our ldisc is bound to */ struct tty_struct *tty; /* The tty our ldisc is bound to */
spinlock_t lock; spinlock_t lock;
unsigned int num; unsigned int num;
struct kref ref;
/* Events on the GSM channel */ /* Events on the GSM channel */
wait_queue_head_t event; wait_queue_head_t event;
...@@ -1606,6 +1608,7 @@ static struct gsm_dlci *gsm_dlci_alloc(struct gsm_mux *gsm, int addr) ...@@ -1606,6 +1608,7 @@ static struct gsm_dlci *gsm_dlci_alloc(struct gsm_mux *gsm, int addr)
if (dlci == NULL) if (dlci == NULL)
return NULL; return NULL;
spin_lock_init(&dlci->lock); spin_lock_init(&dlci->lock);
kref_init(&dlci->ref);
mutex_init(&dlci->mutex); mutex_init(&dlci->mutex);
dlci->fifo = &dlci->_fifo; dlci->fifo = &dlci->_fifo;
if (kfifo_alloc(&dlci->_fifo, 4096, GFP_KERNEL) < 0) { if (kfifo_alloc(&dlci->_fifo, 4096, GFP_KERNEL) < 0) {
...@@ -1632,26 +1635,52 @@ static struct gsm_dlci *gsm_dlci_alloc(struct gsm_mux *gsm, int addr) ...@@ -1632,26 +1635,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 * @dlci: DLCI to destroy
* *
* Free up a DLCI. Currently to keep the lifetime rules sane we only * Release a DLCI. Actual free is deferred until either
* clean up DLCI objects when the MUX closes rather than as the port * mux is closed or tty is closed - whichever is last.
* is closed down on both the tty and mux levels.
* *
* Can sleep. * 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); struct tty_struct *tty = tty_port_tty_get(&dlci->port);
if (tty) { if (tty) {
tty_vhangup(tty); tty_vhangup(tty);
tty_kref_put(tty); tty_kref_put(tty);
} }
del_timer_sync(&dlci->t1); dlci_put(dlci);
dlci->gsm->dlci[dlci->addr] = NULL;
kfifo_free(dlci->fifo);
kfree(dlci);
} }
/* /*
...@@ -1989,7 +2018,7 @@ void gsm_cleanup_mux(struct gsm_mux *gsm) ...@@ -1989,7 +2018,7 @@ void gsm_cleanup_mux(struct gsm_mux *gsm)
/* Free up any link layer users */ /* Free up any link layer users */
for (i = 0; i < NUM_DLCI; i++) for (i = 0; i < NUM_DLCI; i++)
if (gsm->dlci[i]) if (gsm->dlci[i])
gsm_dlci_free(gsm->dlci[i]); gsm_dlci_release(gsm->dlci[i]);
/* Now wipe the queues */ /* Now wipe the queues */
for (txq = gsm->tx_head; txq != NULL; txq = gsm->tx_head) { for (txq = gsm->tx_head; txq != NULL; txq = gsm->tx_head) {
gsm->tx_head = txq->next; gsm->tx_head = txq->next;
...@@ -2050,8 +2079,7 @@ EXPORT_SYMBOL_GPL(gsm_activate_mux); ...@@ -2050,8 +2079,7 @@ EXPORT_SYMBOL_GPL(gsm_activate_mux);
* gsm_free_mux - free up a mux * gsm_free_mux - free up a mux
* @mux: mux to free * @mux: mux to free
* *
* Dispose of allocated resources for a dead mux. No refcounting * Dispose of allocated resources for a dead mux
* at present so the mux must be truly dead.
*/ */
void gsm_free_mux(struct gsm_mux *gsm) void gsm_free_mux(struct gsm_mux *gsm)
{ {
...@@ -2061,6 +2089,28 @@ void gsm_free_mux(struct gsm_mux *gsm) ...@@ -2061,6 +2089,28 @@ void gsm_free_mux(struct gsm_mux *gsm)
} }
EXPORT_SYMBOL_GPL(gsm_free_mux); 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 * gsm_alloc_mux - allocate a mux
* *
...@@ -2084,6 +2134,7 @@ struct gsm_mux *gsm_alloc_mux(void) ...@@ -2084,6 +2134,7 @@ struct gsm_mux *gsm_alloc_mux(void)
return NULL; return NULL;
} }
spin_lock_init(&gsm->lock); spin_lock_init(&gsm->lock);
kref_init(&gsm->ref);
gsm->t1 = T1; gsm->t1 = T1;
gsm->t2 = T2; gsm->t2 = T2;
...@@ -2255,7 +2306,7 @@ static void gsmld_close(struct tty_struct *tty) ...@@ -2255,7 +2306,7 @@ static void gsmld_close(struct tty_struct *tty)
gsmld_flush_buffer(tty); gsmld_flush_buffer(tty);
/* Do other clean up here */ /* Do other clean up here */
gsm_free_mux(gsm); mux_put(gsm);
} }
/** /**
...@@ -2554,12 +2605,22 @@ static void net_free(struct kref *ref) ...@@ -2554,12 +2605,22 @@ static void net_free(struct kref *ref)
} }
} }
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, static int gsm_mux_net_start_xmit(struct sk_buff *skb,
struct net_device *net) struct net_device *net)
{ {
struct gsm_mux_net *mux_net = (struct gsm_mux_net *)netdev_priv(net); struct gsm_mux_net *mux_net = (struct gsm_mux_net *)netdev_priv(net);
struct gsm_dlci *dlci = mux_net->dlci; struct gsm_dlci *dlci = mux_net->dlci;
kref_get(&mux_net->ref); muxnet_get(mux_net);
skb_queue_head(&dlci->skb_list, skb); skb_queue_head(&dlci->skb_list, skb);
STATS(net).tx_packets++; STATS(net).tx_packets++;
...@@ -2567,7 +2628,7 @@ static int gsm_mux_net_start_xmit(struct sk_buff *skb, ...@@ -2567,7 +2628,7 @@ static int gsm_mux_net_start_xmit(struct sk_buff *skb,
gsm_dlci_data_kick(dlci); gsm_dlci_data_kick(dlci);
/* And tell the kernel when the last transmit started. */ /* And tell the kernel when the last transmit started. */
net->trans_start = jiffies; net->trans_start = jiffies;
kref_put(&mux_net->ref, net_free); muxnet_put(mux_net);
return NETDEV_TX_OK; return NETDEV_TX_OK;
} }
...@@ -2587,14 +2648,14 @@ static void gsm_mux_rx_netchar(struct gsm_dlci *dlci, ...@@ -2587,14 +2648,14 @@ static void gsm_mux_rx_netchar(struct gsm_dlci *dlci,
struct net_device *net = dlci->net; struct net_device *net = dlci->net;
struct sk_buff *skb; struct sk_buff *skb;
struct gsm_mux_net *mux_net = (struct gsm_mux_net *)netdev_priv(net); struct gsm_mux_net *mux_net = (struct gsm_mux_net *)netdev_priv(net);
kref_get(&mux_net->ref); muxnet_get(mux_net);
/* Allocate an sk_buff */ /* Allocate an sk_buff */
skb = dev_alloc_skb(size + NET_IP_ALIGN); skb = dev_alloc_skb(size + NET_IP_ALIGN);
if (!skb) { if (!skb) {
/* We got no receive buffer. */ /* We got no receive buffer. */
STATS(net).rx_dropped++; STATS(net).rx_dropped++;
kref_put(&mux_net->ref, net_free); muxnet_put(mux_net);
return; return;
} }
skb_reserve(skb, NET_IP_ALIGN); skb_reserve(skb, NET_IP_ALIGN);
...@@ -2609,7 +2670,7 @@ static void gsm_mux_rx_netchar(struct gsm_dlci *dlci, ...@@ -2609,7 +2670,7 @@ static void gsm_mux_rx_netchar(struct gsm_dlci *dlci,
/* update out statistics */ /* update out statistics */
STATS(net).rx_packets++; STATS(net).rx_packets++;
STATS(net).rx_bytes += size; STATS(net).rx_bytes += size;
kref_put(&mux_net->ref, net_free); muxnet_put(mux_net);
return; return;
} }
...@@ -2652,7 +2713,7 @@ static void gsm_destroy_network(struct gsm_dlci *dlci) ...@@ -2652,7 +2713,7 @@ static void gsm_destroy_network(struct gsm_dlci *dlci)
if (!dlci->net) if (!dlci->net)
return; return;
mux_net = (struct gsm_mux_net *)netdev_priv(dlci->net); mux_net = (struct gsm_mux_net *)netdev_priv(dlci->net);
kref_put(&mux_net->ref, net_free); muxnet_put(mux_net);
} }
...@@ -2814,6 +2875,9 @@ static int gsmtty_open(struct tty_struct *tty, struct file *filp) ...@@ -2814,6 +2875,9 @@ static int gsmtty_open(struct tty_struct *tty, struct file *filp)
port = &dlci->port; port = &dlci->port;
port->count++; port->count++;
tty->driver_data = dlci; tty->driver_data = dlci;
dlci_get(dlci);
dlci_get(dlci->gsm->dlci[0]);
mux_get(dlci->gsm);
tty_port_tty_set(port, tty); tty_port_tty_set(port, tty);
dlci->modem_rx = 0; dlci->modem_rx = 0;
...@@ -2829,16 +2893,23 @@ static int gsmtty_open(struct tty_struct *tty, struct file *filp) ...@@ -2829,16 +2893,23 @@ static int gsmtty_open(struct tty_struct *tty, struct file *filp)
static void gsmtty_close(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_dlci *dlci = tty->driver_data;
struct gsm_mux *gsm;
if (dlci == NULL) if (dlci == NULL)
return; return;
mutex_lock(&dlci->mutex); mutex_lock(&dlci->mutex);
gsm_destroy_network(dlci); gsm_destroy_network(dlci);
mutex_unlock(&dlci->mutex); mutex_unlock(&dlci->mutex);
gsm = dlci->gsm;
if (tty_port_close_start(&dlci->port, tty, filp) == 0) if (tty_port_close_start(&dlci->port, tty, filp) == 0)
return; goto out;
gsm_dlci_begin_close(dlci); gsm_dlci_begin_close(dlci);
tty_port_close_end(&dlci->port, tty); tty_port_close_end(&dlci->port, tty);
tty_port_tty_set(&dlci->port, NULL); 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) static void gsmtty_hangup(struct tty_struct *tty)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment