summaryrefslogtreecommitdiff
path: root/drivers/tty/n_gsm.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/tty/n_gsm.c')
-rw-r--r--drivers/tty/n_gsm.c473
1 files changed, 416 insertions, 57 deletions
diff --git a/drivers/tty/n_gsm.c b/drivers/tty/n_gsm.c
index 5e516f5cac5a..daf12132deb1 100644
--- a/drivers/tty/n_gsm.c
+++ b/drivers/tty/n_gsm.c
@@ -38,8 +38,10 @@
#include <linux/sched/signal.h>
#include <linux/interrupt.h>
#include <linux/tty.h>
+#include <linux/bitfield.h>
#include <linux/ctype.h>
#include <linux/mm.h>
+#include <linux/math.h>
#include <linux/string.h>
#include <linux/slab.h>
#include <linux/poll.h>
@@ -75,7 +77,12 @@ module_param(debug, int, 0600);
#define T1 10 /* 100mS */
#define T2 34 /* 333mS */
+#define T3 10 /* 10s */
#define N2 3 /* Retry 3 times */
+#define K 2 /* outstanding I frames */
+
+#define MAX_T3 255 /* In seconds. */
+#define MAX_WINDOW_SIZE 7 /* Limit of K in error recovery mode. */
/* Use long timers for testing at low speed with debug on */
#ifdef DEBUG_TIMING
@@ -89,6 +96,7 @@ module_param(debug, int, 0600);
*/
#define MAX_MRU 1500
#define MAX_MTU 1500
+#define MIN_MTU (PROT_OVERHEAD + 1)
/* SOF, ADDR, CTRL, LEN1, LEN2, ..., FCS, EOF */
#define PROT_OVERHEAD 7
#define GSM_NET_TX_TIMEOUT (HZ*10)
@@ -120,6 +128,7 @@ struct gsm_msg {
enum gsm_dlci_state {
DLCI_CLOSED,
+ DLCI_CONFIGURE, /* Sending PN (for adaption > 1) */
DLCI_OPENING, /* Sending SABM not seen UA */
DLCI_OPEN, /* SABM/UA complete */
DLCI_CLOSING, /* Sending DISC not seen UA/DM */
@@ -159,7 +168,12 @@ struct gsm_dlci {
int prev_adaption;
u32 modem_rx; /* Our incoming virtual modem lines */
u32 modem_tx; /* Our outgoing modem lines */
+ unsigned int mtu;
bool dead; /* Refuse re-open */
+ /* Configuration */
+ u8 prio; /* Priority */
+ u8 ftype; /* Frame type */
+ u8 k; /* Window size */
/* Flow control */
bool throttled; /* Private copy of throttle state */
bool constipated; /* Throttle status for outgoing */
@@ -172,6 +186,32 @@ struct gsm_dlci {
struct net_device *net; /* network interface, if created */
};
+/*
+ * Parameter bits used for parameter negotiation according to 3GPP 27.010
+ * chapter 5.4.6.3.1.
+ */
+
+struct gsm_dlci_param_bits {
+ u8 d_bits;
+ u8 i_cl_bits;
+ u8 p_bits;
+ u8 t_bits;
+ __le16 n_bits;
+ u8 na_bits;
+ u8 k_bits;
+};
+
+static_assert(sizeof(struct gsm_dlci_param_bits) == 8);
+
+#define PN_D_FIELD_DLCI GENMASK(5, 0)
+#define PN_I_CL_FIELD_FTYPE GENMASK(3, 0)
+#define PN_I_CL_FIELD_ADAPTION GENMASK(7, 4)
+#define PN_P_FIELD_PRIO GENMASK(5, 0)
+#define PN_T_FIELD_T1 GENMASK(7, 0)
+#define PN_N_FIELD_N1 GENMASK(15, 0)
+#define PN_NA_FIELD_N2 GENMASK(7, 0)
+#define PN_K_FIELD_K GENMASK(2, 0)
+
/* Total number of supported devices */
#define GSM_TTY_MINORS 256
@@ -264,7 +304,7 @@ struct gsm_mux {
bool constipated; /* Asked by remote to shut up */
bool has_devices; /* Devices were registered */
- struct mutex tx_mutex;
+ spinlock_t tx_lock;
unsigned int tx_bytes; /* TX data outstanding */
#define TX_THRESH_HI 8192
#define TX_THRESH_LO 2048
@@ -272,7 +312,7 @@ struct gsm_mux {
struct list_head tx_data_list; /* Pending data packets */
/* Control messages */
- struct delayed_work kick_timeout; /* Kick TX queuing on timeout */
+ struct timer_list kick_timer; /* Kick TX queuing on timeout */
struct timer_list t2_timer; /* Retransmit timer for commands */
int cretries; /* Command retry counter */
struct gsm_control *pending_cmd;/* Our current pending command */
@@ -282,7 +322,9 @@ struct gsm_mux {
int adaption; /* 1 or 2 supported */
u8 ftype; /* UI or UIH */
int t1, t2; /* Timers in 1/100th of a sec */
+ unsigned int t3; /* Power wake-up timer in seconds. */
int n2; /* Retry count */
+ u8 k; /* Window size */
/* Statistics (not currently exposed) */
unsigned long bad_fcs;
@@ -397,6 +439,7 @@ static const u8 gsm_fcs8[256] = {
#define INIT_FCS 0xFF
#define GOOD_FCS 0xCF
+static void gsm_dlci_close(struct gsm_dlci *dlci);
static int gsmld_output(struct gsm_mux *gsm, u8 *data, int len);
static int gsm_modem_update(struct gsm_dlci *dlci, u8 brk);
static struct gsm_msg *gsm_data_alloc(struct gsm_mux *gsm, u8 addr, int len,
@@ -520,6 +563,57 @@ static void gsm_hex_dump_bytes(const char *fname, const u8 *data,
}
/**
+ * gsm_encode_params - encode DLCI parameters
+ * @dlci: DLCI to encode from
+ * @params: buffer to fill with the encoded parameters
+ *
+ * Encodes the parameters according to GSM 07.10 section 5.4.6.3.1
+ * table 3.
+ */
+static int gsm_encode_params(const struct gsm_dlci *dlci,
+ struct gsm_dlci_param_bits *params)
+{
+ const struct gsm_mux *gsm = dlci->gsm;
+ unsigned int i, cl;
+
+ switch (dlci->ftype) {
+ case UIH:
+ i = 0; /* UIH */
+ break;
+ case UI:
+ i = 1; /* UI */
+ break;
+ default:
+ pr_debug("unsupported frame type %d\n", dlci->ftype);
+ return -EINVAL;
+ }
+
+ switch (dlci->adaption) {
+ case 1: /* Unstructured */
+ cl = 0; /* convergence layer type 1 */
+ break;
+ case 2: /* Unstructured with modem bits. */
+ cl = 1; /* convergence layer type 2 */
+ break;
+ default:
+ pr_debug("unsupported adaption %d\n", dlci->adaption);
+ return -EINVAL;
+ }
+
+ params->d_bits = FIELD_PREP(PN_D_FIELD_DLCI, dlci->addr);
+ /* UIH, convergence layer type 1 */
+ params->i_cl_bits = FIELD_PREP(PN_I_CL_FIELD_FTYPE, i) |
+ FIELD_PREP(PN_I_CL_FIELD_ADAPTION, cl);
+ params->p_bits = FIELD_PREP(PN_P_FIELD_PRIO, dlci->prio);
+ params->t_bits = FIELD_PREP(PN_T_FIELD_T1, gsm->t1);
+ params->n_bits = cpu_to_le16(FIELD_PREP(PN_N_FIELD_N1, dlci->mtu));
+ params->na_bits = FIELD_PREP(PN_NA_FIELD_N2, gsm->n2);
+ params->k_bits = FIELD_PREP(PN_K_FIELD_K, dlci->k);
+
+ return 0;
+}
+
+/**
* gsm_register_devices - register all tty devices for a given mux index
*
* @driver: the tty driver that describes the tty devices
@@ -700,6 +794,7 @@ static int gsm_send(struct gsm_mux *gsm, int addr, int cr, int control)
struct gsm_msg *msg;
u8 *dp;
int ocr;
+ unsigned long flags;
msg = gsm_data_alloc(gsm, addr, 0, control);
if (!msg)
@@ -721,10 +816,10 @@ static int gsm_send(struct gsm_mux *gsm, int addr, int cr, int control)
gsm_print_packet("Q->", addr, cr, control, NULL, 0);
- mutex_lock(&gsm->tx_mutex);
+ spin_lock_irqsave(&gsm->tx_lock, flags);
list_add_tail(&msg->list, &gsm->tx_ctrl_list);
gsm->tx_bytes += msg->len;
- mutex_unlock(&gsm->tx_mutex);
+ spin_unlock_irqrestore(&gsm->tx_lock, flags);
gsmld_write_trigger(gsm);
return 0;
@@ -749,7 +844,7 @@ static void gsm_dlci_clear_queues(struct gsm_mux *gsm, struct gsm_dlci *dlci)
spin_unlock_irqrestore(&dlci->lock, flags);
/* Clear data packets in MUX write queue */
- mutex_lock(&gsm->tx_mutex);
+ spin_lock_irqsave(&gsm->tx_lock, flags);
list_for_each_entry_safe(msg, nmsg, &gsm->tx_data_list, list) {
if (msg->addr != addr)
continue;
@@ -757,7 +852,7 @@ static void gsm_dlci_clear_queues(struct gsm_mux *gsm, struct gsm_dlci *dlci)
list_del(&msg->list);
kfree(msg);
}
- mutex_unlock(&gsm->tx_mutex);
+ spin_unlock_irqrestore(&gsm->tx_lock, flags);
}
/**
@@ -1028,7 +1123,7 @@ static void __gsm_data_queue(struct gsm_dlci *dlci, struct gsm_msg *msg)
gsm->tx_bytes += msg->len;
gsmld_write_trigger(gsm);
- schedule_delayed_work(&gsm->kick_timeout, 10 * gsm->t1 * HZ / 100);
+ mod_timer(&gsm->kick_timer, jiffies + 10 * gsm->t1 * HZ / 100);
}
/**
@@ -1043,9 +1138,10 @@ static void __gsm_data_queue(struct gsm_dlci *dlci, struct gsm_msg *msg)
static void gsm_data_queue(struct gsm_dlci *dlci, struct gsm_msg *msg)
{
- mutex_lock(&dlci->gsm->tx_mutex);
+ unsigned long flags;
+ spin_lock_irqsave(&dlci->gsm->tx_lock, flags);
__gsm_data_queue(dlci, msg);
- mutex_unlock(&dlci->gsm->tx_mutex);
+ spin_unlock_irqrestore(&dlci->gsm->tx_lock, flags);
}
/**
@@ -1057,7 +1153,7 @@ static void gsm_data_queue(struct gsm_dlci *dlci, struct gsm_msg *msg)
* is data. Keep to the MRU of the mux. This path handles the usual tty
* interface which is a byte stream with optional modem data.
*
- * Caller must hold the tx_mutex of the mux.
+ * Caller must hold the tx_lock of the mux.
*/
static int gsm_dlci_data_output(struct gsm_mux *gsm, struct gsm_dlci *dlci)
@@ -1074,12 +1170,12 @@ static int gsm_dlci_data_output(struct gsm_mux *gsm, struct gsm_dlci *dlci)
return 0;
/* MTU/MRU count only the data bits but watch adaption mode */
- if ((len + h) > gsm->mtu)
- len = gsm->mtu - h;
+ if ((len + h) > dlci->mtu)
+ len = dlci->mtu - h;
size = len + h;
- msg = gsm_data_alloc(gsm, dlci->addr, size, gsm->ftype);
+ msg = gsm_data_alloc(gsm, dlci->addr, size, dlci->ftype);
if (!msg)
return -ENOMEM;
dp = msg->data;
@@ -1117,7 +1213,7 @@ static int gsm_dlci_data_output(struct gsm_mux *gsm, struct gsm_dlci *dlci)
* is data. Keep to the MRU of the mux. This path handles framed data
* queued as skbuffs to the DLCI.
*
- * Caller must hold the tx_mutex of the mux.
+ * Caller must hold the tx_lock of the mux.
*/
static int gsm_dlci_data_output_framed(struct gsm_mux *gsm,
@@ -1133,7 +1229,7 @@ static int gsm_dlci_data_output_framed(struct gsm_mux *gsm,
if (dlci->adaption == 4)
overhead = 1;
- /* dlci->skb is locked by tx_mutex */
+ /* dlci->skb is locked by tx_lock */
if (dlci->skb == NULL) {
dlci->skb = skb_dequeue_tail(&dlci->skb_list);
if (dlci->skb == NULL)
@@ -1143,19 +1239,19 @@ static int gsm_dlci_data_output_framed(struct gsm_mux *gsm,
len = dlci->skb->len + overhead;
/* MTU/MRU count only the data bits */
- if (len > gsm->mtu) {
+ if (len > dlci->mtu) {
if (dlci->adaption == 3) {
/* Over long frame, bin it */
dev_kfree_skb_any(dlci->skb);
dlci->skb = NULL;
return 0;
}
- len = gsm->mtu;
+ len = dlci->mtu;
} else
last = 1;
size = len + overhead;
- msg = gsm_data_alloc(gsm, dlci->addr, size, gsm->ftype);
+ msg = gsm_data_alloc(gsm, dlci->addr, size, dlci->ftype);
if (msg == NULL) {
skb_queue_tail(&dlci->skb_list, dlci->skb);
dlci->skb = NULL;
@@ -1187,7 +1283,7 @@ static int gsm_dlci_data_output_framed(struct gsm_mux *gsm,
* Push an empty frame in to the transmit queue to update the modem status
* bits and to transmit an optional break.
*
- * Caller must hold the tx_mutex of the mux.
+ * Caller must hold the tx_lock of the mux.
*/
static int gsm_dlci_modem_output(struct gsm_mux *gsm, struct gsm_dlci *dlci,
@@ -1212,7 +1308,7 @@ static int gsm_dlci_modem_output(struct gsm_mux *gsm, struct gsm_dlci *dlci,
return -EINVAL;
}
- msg = gsm_data_alloc(gsm, dlci->addr, size, gsm->ftype);
+ msg = gsm_data_alloc(gsm, dlci->addr, size, dlci->ftype);
if (!msg) {
pr_err("%s: gsm_data_alloc error", __func__);
return -ENOMEM;
@@ -1285,7 +1381,7 @@ static int gsm_dlci_data_sweep(struct gsm_mux *gsm)
}
if (!sent)
break;
- };
+ }
return ret;
}
@@ -1301,12 +1397,13 @@ static int gsm_dlci_data_sweep(struct gsm_mux *gsm)
static void gsm_dlci_data_kick(struct gsm_dlci *dlci)
{
+ unsigned long flags;
int sweep;
if (dlci->constipated)
return;
- mutex_lock(&dlci->gsm->tx_mutex);
+ spin_lock_irqsave(&dlci->gsm->tx_lock, flags);
/* If we have nothing running then we need to fire up */
sweep = (dlci->gsm->tx_bytes < TX_THRESH_LO);
if (dlci->gsm->tx_bytes == 0) {
@@ -1317,7 +1414,7 @@ static void gsm_dlci_data_kick(struct gsm_dlci *dlci)
}
if (sweep)
gsm_dlci_data_sweep(dlci->gsm);
- mutex_unlock(&dlci->gsm->tx_mutex);
+ spin_unlock_irqrestore(&dlci->gsm->tx_lock, flags);
}
/*
@@ -1337,8 +1434,9 @@ static void gsm_dlci_data_kick(struct gsm_dlci *dlci)
static int gsm_control_command(struct gsm_mux *gsm, int cmd, const u8 *data,
int dlen)
{
- struct gsm_msg *msg = gsm_data_alloc(gsm, 0, dlen + 2, gsm->ftype);
+ struct gsm_msg *msg;
+ msg = gsm_data_alloc(gsm, 0, dlen + 2, gsm->dlci[0]->ftype);
if (msg == NULL)
return -ENOMEM;
@@ -1364,7 +1462,8 @@ static void gsm_control_reply(struct gsm_mux *gsm, int cmd, const u8 *data,
int dlen)
{
struct gsm_msg *msg;
- msg = gsm_data_alloc(gsm, 0, dlen + 2, gsm->ftype);
+
+ msg = gsm_data_alloc(gsm, 0, dlen + 2, gsm->dlci[0]->ftype);
if (msg == NULL)
return;
msg->data[0] = (cmd & 0xFE) << 1 | EA; /* Clear C/R */
@@ -1435,6 +1534,116 @@ static void gsm_process_modem(struct tty_struct *tty, struct gsm_dlci *dlci,
}
/**
+ * gsm_process_negotiation - process received parameters
+ * @gsm: GSM channel
+ * @addr: DLCI address
+ * @cr: command/response
+ * @params: encoded parameters from the parameter negotiation message
+ *
+ * Used when the response for our parameter negotiation command was
+ * received.
+ */
+static int gsm_process_negotiation(struct gsm_mux *gsm, unsigned int addr,
+ unsigned int cr,
+ const struct gsm_dlci_param_bits *params)
+{
+ struct gsm_dlci *dlci = gsm->dlci[addr];
+ unsigned int ftype, i, adaption, prio, n1, k;
+
+ i = FIELD_GET(PN_I_CL_FIELD_FTYPE, params->i_cl_bits);
+ adaption = FIELD_GET(PN_I_CL_FIELD_ADAPTION, params->i_cl_bits) + 1;
+ prio = FIELD_GET(PN_P_FIELD_PRIO, params->p_bits);
+ n1 = FIELD_GET(PN_N_FIELD_N1, get_unaligned_le16(&params->n_bits));
+ k = FIELD_GET(PN_K_FIELD_K, params->k_bits);
+
+ if (n1 < MIN_MTU) {
+ if (debug & DBG_ERRORS)
+ pr_info("%s N1 out of range in PN\n", __func__);
+ return -EINVAL;
+ }
+
+ switch (i) {
+ case 0x00:
+ ftype = UIH;
+ break;
+ case 0x01:
+ ftype = UI;
+ break;
+ case 0x02: /* I frames are not supported */
+ if (debug & DBG_ERRORS)
+ pr_info("%s unsupported I frame request in PN\n",
+ __func__);
+ return -EINVAL;
+ default:
+ if (debug & DBG_ERRORS)
+ pr_info("%s i out of range in PN\n", __func__);
+ return -EINVAL;
+ }
+
+ if (!cr && gsm->initiator) {
+ if (adaption != dlci->adaption) {
+ if (debug & DBG_ERRORS)
+ pr_info("%s invalid adaption %d in PN\n",
+ __func__, adaption);
+ return -EINVAL;
+ }
+ if (prio != dlci->prio) {
+ if (debug & DBG_ERRORS)
+ pr_info("%s invalid priority %d in PN",
+ __func__, prio);
+ return -EINVAL;
+ }
+ if (n1 > gsm->mru || n1 > dlci->mtu) {
+ /* We requested a frame size but the other party wants
+ * to send larger frames. The standard allows only a
+ * smaller response value than requested (5.4.6.3.1).
+ */
+ if (debug & DBG_ERRORS)
+ pr_info("%s invalid N1 %d in PN\n", __func__,
+ n1);
+ return -EINVAL;
+ }
+ dlci->mtu = n1;
+ if (ftype != dlci->ftype) {
+ if (debug & DBG_ERRORS)
+ pr_info("%s invalid i %d in PN\n", __func__, i);
+ return -EINVAL;
+ }
+ if (ftype != UI && ftype != UIH && k > dlci->k) {
+ if (debug & DBG_ERRORS)
+ pr_info("%s invalid k %d in PN\n", __func__, k);
+ return -EINVAL;
+ }
+ dlci->k = k;
+ } else if (cr && !gsm->initiator) {
+ /* Only convergence layer type 1 and 2 are supported. */
+ if (adaption != 1 && adaption != 2) {
+ if (debug & DBG_ERRORS)
+ pr_info("%s invalid adaption %d in PN\n",
+ __func__, adaption);
+ return -EINVAL;
+ }
+ dlci->adaption = adaption;
+ if (n1 > gsm->mru) {
+ /* Propose a smaller value */
+ dlci->mtu = gsm->mru;
+ } else if (n1 > MAX_MTU) {
+ /* Propose a smaller value */
+ dlci->mtu = MAX_MTU;
+ } else {
+ dlci->mtu = n1;
+ }
+ dlci->prio = prio;
+ dlci->ftype = ftype;
+ dlci->k = k;
+ } else {
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/**
* gsm_control_modem - modem status received
* @gsm: GSM channel
* @data: data following command
@@ -1488,6 +1697,65 @@ static void gsm_control_modem(struct gsm_mux *gsm, const u8 *data, int clen)
}
/**
+ * gsm_control_negotiation - parameter negotiation received
+ * @gsm: GSM channel
+ * @cr: command/response flag
+ * @data: data following command
+ * @dlen: data length
+ *
+ * We have received a parameter negotiation message. This is used by
+ * the GSM mux protocol to configure protocol parameters for a new DLCI.
+ */
+static void gsm_control_negotiation(struct gsm_mux *gsm, unsigned int cr,
+ const u8 *data, unsigned int dlen)
+{
+ unsigned int addr;
+ struct gsm_dlci_param_bits pn_reply;
+ struct gsm_dlci *dlci;
+ struct gsm_dlci_param_bits *params;
+
+ if (dlen < sizeof(struct gsm_dlci_param_bits))
+ return;
+
+ /* Invalid DLCI? */
+ params = (struct gsm_dlci_param_bits *)data;
+ addr = FIELD_GET(PN_D_FIELD_DLCI, params->d_bits);
+ if (addr == 0 || addr >= NUM_DLCI || !gsm->dlci[addr])
+ return;
+ dlci = gsm->dlci[addr];
+
+ /* Too late for parameter negotiation? */
+ if ((!cr && dlci->state == DLCI_OPENING) || dlci->state == DLCI_OPEN)
+ return;
+
+ /* Process the received parameters */
+ if (gsm_process_negotiation(gsm, addr, cr, params) != 0) {
+ /* Negotiation failed. Close the link. */
+ if (debug & DBG_ERRORS)
+ pr_info("%s PN failed\n", __func__);
+ gsm_dlci_close(dlci);
+ return;
+ }
+
+ if (cr) {
+ /* Reply command with accepted parameters. */
+ if (gsm_encode_params(dlci, &pn_reply) == 0)
+ gsm_control_reply(gsm, CMD_PN, (const u8 *)&pn_reply,
+ sizeof(pn_reply));
+ else if (debug & DBG_ERRORS)
+ pr_info("%s PN invalid\n", __func__);
+ } else if (dlci->state == DLCI_CONFIGURE) {
+ /* Proceed with link setup by sending SABM before UA */
+ dlci->state = DLCI_OPENING;
+ gsm_command(gsm, dlci->addr, SABM|PF);
+ mod_timer(&dlci->t1, jiffies + gsm->t1 * HZ / 100);
+ } else {
+ if (debug & DBG_ERRORS)
+ pr_info("%s PN in invalid state\n", __func__);
+ }
+}
+
+/**
* gsm_control_rls - remote line status
* @gsm: GSM channel
* @data: data bytes
@@ -1596,8 +1864,12 @@ static void gsm_control_message(struct gsm_mux *gsm, unsigned int command,
/* Modem wishes to enter power saving state */
gsm_control_reply(gsm, CMD_PSC, NULL, 0);
break;
+ /* Optional commands */
+ case CMD_PN:
+ /* Modem sends a parameter negotiation command */
+ gsm_control_negotiation(gsm, 1, data, clen);
+ break;
/* Optional unsupported commands */
- case CMD_PN: /* Parameter negotiation */
case CMD_RPN: /* Remote port negotiation */
case CMD_SNC: /* Service negotiation command */
default:
@@ -1630,8 +1902,8 @@ static void gsm_control_response(struct gsm_mux *gsm, unsigned int command,
spin_lock_irqsave(&gsm->control_lock, flags);
ctrl = gsm->pending_cmd;
- /* Does the reply match our command */
command |= 1;
+ /* Does the reply match our command */
if (ctrl != NULL && (command == ctrl->cmd || command == CMD_NSC)) {
/* Our command was replied to, kill the retry timer */
del_timer(&gsm->t2_timer);
@@ -1641,6 +1913,9 @@ static void gsm_control_response(struct gsm_mux *gsm, unsigned int command,
ctrl->error = -EOPNOTSUPP;
ctrl->done = 1;
wake_up(&gsm->event);
+ /* Or did we receive the PN response to our PN command */
+ } else if (command == CMD_PN) {
+ gsm_control_negotiation(gsm, 0, data, clen);
}
spin_unlock_irqrestore(&gsm->control_lock, flags);
}
@@ -1708,7 +1983,7 @@ static struct gsm_control *gsm_control_send(struct gsm_mux *gsm,
unsigned int command, u8 *data, int clen)
{
struct gsm_control *ctrl = kzalloc(sizeof(struct gsm_control),
- GFP_KERNEL);
+ GFP_ATOMIC);
unsigned long flags;
if (ctrl == NULL)
return NULL;
@@ -1819,6 +2094,32 @@ static void gsm_dlci_open(struct gsm_dlci *dlci)
}
/**
+ * gsm_dlci_negotiate - start parameter negotiation
+ * @dlci: DLCI to open
+ *
+ * Starts the parameter negotiation for the new DLCI. This needs to be done
+ * before the DLCI initialized the channel via SABM.
+ */
+static int gsm_dlci_negotiate(struct gsm_dlci *dlci)
+{
+ struct gsm_mux *gsm = dlci->gsm;
+ struct gsm_dlci_param_bits params;
+ int ret;
+
+ ret = gsm_encode_params(dlci, &params);
+ if (ret != 0)
+ return ret;
+
+ /* We cannot asynchronous wait for the command response with
+ * gsm_command() and gsm_control_wait() at this point.
+ */
+ ret = gsm_control_command(gsm, CMD_PN, (const u8 *)&params,
+ sizeof(params));
+
+ return ret;
+}
+
+/**
* gsm_dlci_t1 - T1 timer expiry
* @t: timer contained in the DLCI that opened
*
@@ -1839,6 +2140,14 @@ static void gsm_dlci_t1(struct timer_list *t)
struct gsm_mux *gsm = dlci->gsm;
switch (dlci->state) {
+ case DLCI_CONFIGURE:
+ if (dlci->retries && gsm_dlci_negotiate(dlci) == 0) {
+ dlci->retries--;
+ mod_timer(&dlci->t1, jiffies + gsm->t1 * HZ / 100);
+ } else {
+ gsm_dlci_begin_close(dlci); /* prevent half open link */
+ }
+ break;
case DLCI_OPENING:
if (dlci->retries) {
dlci->retries--;
@@ -1877,17 +2186,46 @@ static void gsm_dlci_t1(struct timer_list *t)
* to the modem which should then reply with a UA or ADM, at which point
* we will move into open state. Opening is done asynchronously with retry
* running off timers and the responses.
+ * Parameter negotiation is performed before SABM if required.
*/
static void gsm_dlci_begin_open(struct gsm_dlci *dlci)
{
- struct gsm_mux *gsm = dlci->gsm;
- if (dlci->state == DLCI_OPEN || dlci->state == DLCI_OPENING)
+ struct gsm_mux *gsm = dlci ? dlci->gsm : NULL;
+ bool need_pn = false;
+
+ if (!gsm)
return;
- dlci->retries = gsm->n2;
- dlci->state = DLCI_OPENING;
- gsm_command(dlci->gsm, dlci->addr, SABM|PF);
- mod_timer(&dlci->t1, jiffies + gsm->t1 * HZ / 100);
+
+ if (dlci->addr != 0) {
+ if (gsm->adaption != 1 || gsm->adaption != dlci->adaption)
+ need_pn = true;
+ if (dlci->prio != (roundup(dlci->addr + 1, 8) - 1))
+ need_pn = true;
+ if (gsm->ftype != dlci->ftype)
+ need_pn = true;
+ }
+
+ switch (dlci->state) {
+ case DLCI_CLOSED:
+ case DLCI_CLOSING:
+ dlci->retries = gsm->n2;
+ if (!need_pn) {
+ dlci->state = DLCI_OPENING;
+ gsm_command(gsm, dlci->addr, SABM|PF);
+ } else {
+ /* Configure DLCI before setup */
+ dlci->state = DLCI_CONFIGURE;
+ if (gsm_dlci_negotiate(dlci) != 0) {
+ gsm_dlci_close(dlci);
+ return;
+ }
+ }
+ mod_timer(&dlci->t1, jiffies + gsm->t1 * HZ / 100);
+ break;
+ default:
+ break;
+ }
}
/**
@@ -2019,23 +2357,24 @@ static void gsm_dlci_command(struct gsm_dlci *dlci, const u8 *data, int len)
}
/**
- * gsm_kick_timeout - transmit if possible
- * @work: work contained in our gsm object
+ * gsm_kick_timer - transmit if possible
+ * @t: timer contained in our gsm object
*
* Transmit data from DLCIs if the queue is empty. We can't rely on
* a tty wakeup except when we filled the pipe so we need to fire off
* new data ourselves in other cases.
*/
-static void gsm_kick_timeout(struct work_struct *work)
+static void gsm_kick_timer(struct timer_list *t)
{
- struct gsm_mux *gsm = container_of(work, struct gsm_mux, kick_timeout.work);
+ struct gsm_mux *gsm = from_timer(gsm, t, kick_timer);
+ unsigned long flags;
int sent = 0;
- mutex_lock(&gsm->tx_mutex);
+ spin_lock_irqsave(&gsm->tx_lock, flags);
/* If we have nothing running then we need to fire up */
if (gsm->tx_bytes < TX_THRESH_LO)
sent = gsm_dlci_data_sweep(gsm);
- mutex_unlock(&gsm->tx_mutex);
+ spin_unlock_irqrestore(&gsm->tx_lock, flags);
if (sent && debug & DBG_DATA)
pr_info("%s TX queue stalled\n", __func__);
@@ -2074,6 +2413,13 @@ static struct gsm_dlci *gsm_dlci_alloc(struct gsm_mux *gsm, int addr)
dlci->gsm = gsm;
dlci->addr = addr;
dlci->adaption = gsm->adaption;
+ dlci->mtu = gsm->mtu;
+ if (addr == 0)
+ dlci->prio = 0;
+ else
+ dlci->prio = roundup(addr + 1, 8) - 1;
+ dlci->ftype = gsm->ftype;
+ dlci->k = gsm->k;
dlci->state = DLCI_CLOSED;
if (addr) {
dlci->data = gsm_dlci_data;
@@ -2492,7 +2838,7 @@ static void gsm_cleanup_mux(struct gsm_mux *gsm, bool disc)
}
/* Finish outstanding timers, making sure they are done */
- cancel_delayed_work_sync(&gsm->kick_timeout);
+ del_timer_sync(&gsm->kick_timer);
del_timer_sync(&gsm->t2_timer);
/* Finish writing to ldisc */
@@ -2565,7 +2911,6 @@ static void gsm_free_mux(struct gsm_mux *gsm)
break;
}
}
- mutex_destroy(&gsm->tx_mutex);
mutex_destroy(&gsm->mutex);
kfree(gsm->txframe);
kfree(gsm->buf);
@@ -2637,19 +2982,21 @@ static struct gsm_mux *gsm_alloc_mux(void)
}
spin_lock_init(&gsm->lock);
mutex_init(&gsm->mutex);
- mutex_init(&gsm->tx_mutex);
kref_init(&gsm->ref);
INIT_LIST_HEAD(&gsm->tx_ctrl_list);
INIT_LIST_HEAD(&gsm->tx_data_list);
- INIT_DELAYED_WORK(&gsm->kick_timeout, gsm_kick_timeout);
+ timer_setup(&gsm->kick_timer, gsm_kick_timer, 0);
timer_setup(&gsm->t2_timer, gsm_control_retransmit, 0);
INIT_WORK(&gsm->tx_work, gsmld_write_task);
init_waitqueue_head(&gsm->event);
spin_lock_init(&gsm->control_lock);
+ spin_lock_init(&gsm->tx_lock);
gsm->t1 = T1;
gsm->t2 = T2;
+ gsm->t3 = T3;
gsm->n2 = N2;
+ gsm->k = K;
gsm->ftype = UIH;
gsm->adaption = 1;
gsm->encoding = GSM_ADV_OPT;
@@ -2670,7 +3017,6 @@ static struct gsm_mux *gsm_alloc_mux(void)
}
spin_unlock(&gsm_mux_lock);
if (i == MAX_MUX) {
- mutex_destroy(&gsm->tx_mutex);
mutex_destroy(&gsm->mutex);
kfree(gsm->txframe);
kfree(gsm->buf);
@@ -2690,7 +3036,7 @@ static void gsm_copy_config_values(struct gsm_mux *gsm,
c->initiator = gsm->initiator;
c->t1 = gsm->t1;
c->t2 = gsm->t2;
- c->t3 = 0; /* Not supported */
+ c->t3 = gsm->t3;
c->n2 = gsm->n2;
if (gsm->ftype == UIH)
c->i = 1;
@@ -2699,7 +3045,7 @@ static void gsm_copy_config_values(struct gsm_mux *gsm,
pr_debug("Ftype %d i %d\n", gsm->ftype, c->i);
c->mru = gsm->mru;
c->mtu = gsm->mtu;
- c->k = 0;
+ c->k = gsm->k;
}
static int gsm_config(struct gsm_mux *gsm, struct gsm_config *c)
@@ -2712,7 +3058,11 @@ static int gsm_config(struct gsm_mux *gsm, struct gsm_config *c)
if ((c->adaption != 1 && c->adaption != 2) || c->k)
return -EOPNOTSUPP;
/* Check the MRU/MTU range looks sane */
- if (c->mru > MAX_MRU || c->mtu > MAX_MTU || c->mru < 8 || c->mtu < 8)
+ if (c->mru < MIN_MTU || c->mtu < MIN_MTU)
+ return -EINVAL;
+ if (c->mru > MAX_MRU || c->mtu > MAX_MTU)
+ return -EINVAL;
+ if (c->t3 > MAX_T3)
return -EINVAL;
if (c->n2 > 255)
return -EINVAL;
@@ -2720,6 +3070,8 @@ static int gsm_config(struct gsm_mux *gsm, struct gsm_config *c)
return -EINVAL;
if (c->initiator > 1)
return -EINVAL;
+ if (c->k > MAX_WINDOW_SIZE)
+ return -EINVAL;
if (c->i == 0 || c->i > 2) /* UIH and UI only */
return -EINVAL;
/*
@@ -2767,6 +3119,10 @@ static int gsm_config(struct gsm_mux *gsm, struct gsm_config *c)
gsm->t1 = c->t1;
if (c->t2)
gsm->t2 = c->t2;
+ if (c->t3)
+ gsm->t3 = c->t3;
+ if (c->k)
+ gsm->k = c->k;
/*
* FIXME: We need to separate activation/deactivation from adding
@@ -2826,16 +3182,17 @@ static void gsmld_write_trigger(struct gsm_mux *gsm)
static void gsmld_write_task(struct work_struct *work)
{
struct gsm_mux *gsm = container_of(work, struct gsm_mux, tx_work);
+ unsigned long flags;
int i, ret;
/* All outstanding control channel and control messages and one data
* frame is sent.
*/
ret = -ENODEV;
- mutex_lock(&gsm->tx_mutex);
+ spin_lock_irqsave(&gsm->tx_lock, flags);
if (gsm->tty)
ret = gsm_data_kick(gsm);
- mutex_unlock(&gsm->tx_mutex);
+ spin_unlock_irqrestore(&gsm->tx_lock, flags);
if (ret >= 0)
for (i = 0; i < NUM_DLCI; i++)
@@ -3042,6 +3399,7 @@ static ssize_t gsmld_write(struct tty_struct *tty, struct file *file,
const unsigned char *buf, size_t nr)
{
struct gsm_mux *gsm = tty->disc_data;
+ unsigned long flags;
int space;
int ret;
@@ -3049,13 +3407,13 @@ static ssize_t gsmld_write(struct tty_struct *tty, struct file *file,
return -ENODEV;
ret = -ENOBUFS;
- mutex_lock(&gsm->tx_mutex);
+ spin_lock_irqsave(&gsm->tx_lock, flags);
space = tty_write_room(tty);
if (space >= nr)
ret = tty->ops->write(tty, buf, nr);
else
set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
- mutex_unlock(&gsm->tx_mutex);
+ spin_unlock_irqrestore(&gsm->tx_lock, flags);
return ret;
}
@@ -3295,9 +3653,9 @@ static int gsm_create_network(struct gsm_dlci *dlci, struct gsm_netconfig *nc)
pr_err("alloc_netdev failed\n");
return -ENOMEM;
}
- net->mtu = dlci->gsm->mtu;
- net->min_mtu = 8;
- net->max_mtu = dlci->gsm->mtu;
+ net->mtu = dlci->mtu;
+ net->min_mtu = MIN_MTU;
+ net->max_mtu = dlci->mtu;
mux_net = netdev_priv(net);
mux_net->dlci = dlci;
kref_init(&mux_net->ref);
@@ -3352,13 +3710,14 @@ static struct tty_ldisc_ops tty_ldisc_packet = {
static void gsm_modem_upd_via_data(struct gsm_dlci *dlci, u8 brk)
{
struct gsm_mux *gsm = dlci->gsm;
+ unsigned long flags;
if (dlci->state != DLCI_OPEN || dlci->adaption != 2)
return;
- mutex_lock(&gsm->tx_mutex);
+ spin_lock_irqsave(&gsm->tx_lock, flags);
gsm_dlci_modem_output(gsm, dlci, brk);
- mutex_unlock(&gsm->tx_mutex);
+ spin_unlock_irqrestore(&gsm->tx_lock, flags);
}
/**