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.c728
1 files changed, 613 insertions, 115 deletions
diff --git a/drivers/tty/n_gsm.c b/drivers/tty/n_gsm.c
index 348d85c13f91..214abeb89aaa 100644
--- a/drivers/tty/n_gsm.c
+++ b/drivers/tty/n_gsm.c
@@ -2,6 +2,7 @@
/*
* n_gsm.c GSM 0710 tty multiplexor
* Copyright (c) 2009/10 Intel Corporation
+ * Copyright (c) 2022/23 Siemens Mobility GmbH
*
* * THIS IS A DEVELOPMENT SNAPSHOT IT IS NOT A FINAL RELEASE *
*
@@ -42,6 +43,7 @@
#include <linux/ctype.h>
#include <linux/mm.h>
#include <linux/math.h>
+#include <linux/nospec.h>
#include <linux/string.h>
#include <linux/slab.h>
#include <linux/poll.h>
@@ -122,12 +124,13 @@ struct gsm_msg {
u8 addr; /* DLCI address + flags */
u8 ctrl; /* Control byte + flags */
unsigned int len; /* Length of data block (can be zero) */
- unsigned char *data; /* Points into buffer but not at the start */
- unsigned char buffer[];
+ u8 *data; /* Points into buffer but not at the start */
+ u8 buffer[];
};
enum gsm_dlci_state {
DLCI_CLOSED,
+ DLCI_WAITING_CONFIG, /* Waiting for DLCI configuration from user */
DLCI_CONFIGURE, /* Sending PN (for adaption > 1) */
DLCI_OPENING, /* Sending SABM not seen UA */
DLCI_OPEN, /* SABM/UA complete */
@@ -242,16 +245,18 @@ enum gsm_encoding {
enum gsm_mux_state {
GSM_SEARCH,
- GSM_START,
- GSM_ADDRESS,
- GSM_CONTROL,
- GSM_LEN,
- GSM_DATA,
- GSM_FCS,
- GSM_OVERRUN,
- GSM_LEN0,
- GSM_LEN1,
- GSM_SSOF,
+ GSM0_ADDRESS,
+ GSM0_CONTROL,
+ GSM0_LEN0,
+ GSM0_LEN1,
+ GSM0_DATA,
+ GSM0_FCS,
+ GSM0_SSOF,
+ GSM1_START,
+ GSM1_ADDRESS,
+ GSM1_CONTROL,
+ GSM1_DATA,
+ GSM1_OVERRUN,
};
/*
@@ -280,7 +285,7 @@ struct gsm_mux {
/* Bits for GSM mode decoding */
/* Framing Layer */
- unsigned char *buf;
+ u8 *buf;
enum gsm_mux_state state;
unsigned int len;
unsigned int address;
@@ -318,6 +323,11 @@ struct gsm_mux {
struct gsm_control *pending_cmd;/* Our current pending command */
spinlock_t control_lock; /* Protects the pending command */
+ /* Keep-alive */
+ struct timer_list ka_timer; /* Keep-alive response timer */
+ u8 ka_num; /* Keep-alive match pattern */
+ signed int ka_retries; /* Keep-alive retry counter, -1 if not yet initialized */
+
/* Configuration */
int adaption; /* 1 or 2 supported */
u8 ftype; /* UI or UIH */
@@ -325,11 +335,14 @@ struct gsm_mux {
unsigned int t3; /* Power wake-up timer in seconds. */
int n2; /* Retry count */
u8 k; /* Window size */
+ bool wait_config; /* Wait for configuration by ioctl before DLCI open */
+ u32 keep_alive; /* Control channel keep-alive in 10ms */
/* Statistics (not currently exposed) */
unsigned long bad_fcs;
unsigned long malformed;
unsigned long io_error;
+ unsigned long open_error;
unsigned long bad_size;
unsigned long unsupported;
};
@@ -445,8 +458,10 @@ 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,
u8 ctrl);
static int gsm_send_packet(struct gsm_mux *gsm, struct gsm_msg *msg);
+static struct gsm_dlci *gsm_dlci_alloc(struct gsm_mux *gsm, int addr);
static void gsmld_write_trigger(struct gsm_mux *gsm);
static void gsmld_write_task(struct work_struct *work);
+static int gsm_modem_send_initial_msc(struct gsm_dlci *dlci);
/**
* gsm_fcs_add - update FCS
@@ -540,6 +555,11 @@ static u8 gsm_encode_modem(const struct gsm_dlci *dlci)
modembits |= MDM_IC;
if (dlci->modem_tx & TIOCM_CD || dlci->gsm->initiator)
modembits |= MDM_DV;
+ /* special mappings for passive side to operate as UE */
+ if (dlci->modem_tx & TIOCM_OUT1)
+ modembits |= MDM_IC;
+ if (dlci->modem_tx & TIOCM_OUT2)
+ modembits |= MDM_DV;
return modembits;
}
@@ -1435,15 +1455,16 @@ static int gsm_control_command(struct gsm_mux *gsm, int cmd, const u8 *data,
int dlen)
{
struct gsm_msg *msg;
+ struct gsm_dlci *dlci = gsm->dlci[0];
- msg = gsm_data_alloc(gsm, 0, dlen + 2, gsm->dlci[0]->ftype);
+ msg = gsm_data_alloc(gsm, 0, dlen + 2, dlci->ftype);
if (msg == NULL)
return -ENOMEM;
msg->data[0] = (cmd << 1) | CR | EA; /* Set C/R */
msg->data[1] = (dlen << 1) | EA;
memcpy(msg->data + 2, data, dlen);
- gsm_data_queue(gsm->dlci[0], msg);
+ gsm_data_queue(dlci, msg);
return 0;
}
@@ -1462,14 +1483,15 @@ static void gsm_control_reply(struct gsm_mux *gsm, int cmd, const u8 *data,
int dlen)
{
struct gsm_msg *msg;
+ struct gsm_dlci *dlci = gsm->dlci[0];
- msg = gsm_data_alloc(gsm, 0, dlen + 2, gsm->dlci[0]->ftype);
+ msg = gsm_data_alloc(gsm, 0, dlen + 2, dlci->ftype);
if (msg == NULL)
return;
msg->data[0] = (cmd & 0xFE) << 1 | EA; /* Clear C/R */
msg->data[1] = (dlen << 1) | EA;
memcpy(msg->data + 2, data, dlen);
- gsm_data_queue(gsm->dlci[0], msg);
+ gsm_data_queue(dlci, msg);
}
/**
@@ -1531,6 +1553,7 @@ static void gsm_process_modem(struct tty_struct *tty, struct gsm_dlci *dlci,
if (brk & 0x01)
tty_insert_flip_char(&dlci->port, 0, TTY_BREAK);
dlci->modem_rx = mlines;
+ wake_up_interruptible(&dlci->gsm->event);
}
/**
@@ -1573,6 +1596,7 @@ static int gsm_process_negotiation(struct gsm_mux *gsm, unsigned int addr,
if (debug & DBG_ERRORS)
pr_info("%s unsupported I frame request in PN\n",
__func__);
+ gsm->unsupported++;
return -EINVAL;
default:
if (debug & DBG_ERRORS)
@@ -1714,25 +1738,32 @@ static void gsm_control_negotiation(struct gsm_mux *gsm, unsigned int cr,
struct gsm_dlci *dlci;
struct gsm_dlci_param_bits *params;
- if (dlen < sizeof(struct gsm_dlci_param_bits))
+ if (dlen < sizeof(struct gsm_dlci_param_bits)) {
+ gsm->open_error++;
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])
+ if (addr == 0 || addr >= NUM_DLCI || !gsm->dlci[addr]) {
+ gsm->open_error++;
return;
+ }
dlci = gsm->dlci[addr];
/* Too late for parameter negotiation? */
- if ((!cr && dlci->state == DLCI_OPENING) || dlci->state == DLCI_OPEN)
+ if ((!cr && dlci->state == DLCI_OPENING) || dlci->state == DLCI_OPEN) {
+ gsm->open_error++;
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->open_error++;
gsm_dlci_close(dlci);
return;
}
@@ -1752,6 +1783,7 @@ static void gsm_control_negotiation(struct gsm_mux *gsm, unsigned int cr,
} else {
if (debug & DBG_ERRORS)
pr_info("%s PN in invalid state\n", __func__);
+ gsm->open_error++;
}
}
@@ -1872,6 +1904,8 @@ static void gsm_control_message(struct gsm_mux *gsm, unsigned int command,
/* Optional unsupported commands */
case CMD_RPN: /* Remote port negotiation */
case CMD_SNC: /* Service negotiation command */
+ gsm->unsupported++;
+ fallthrough;
default:
/* Reply to bad commands with an NSC */
buf[0] = command;
@@ -1897,16 +1931,18 @@ static void gsm_control_response(struct gsm_mux *gsm, unsigned int command,
const u8 *data, int clen)
{
struct gsm_control *ctrl;
+ struct gsm_dlci *dlci;
unsigned long flags;
spin_lock_irqsave(&gsm->control_lock, flags);
ctrl = gsm->pending_cmd;
+ dlci = gsm->dlci[0];
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);
+ timer_delete(&gsm->t2_timer);
gsm->pending_cmd = NULL;
/* Rejected by the other end */
if (command == CMD_NSC)
@@ -1916,6 +1952,53 @@ static void gsm_control_response(struct gsm_mux *gsm, unsigned int command,
/* Or did we receive the PN response to our PN command */
} else if (command == CMD_PN) {
gsm_control_negotiation(gsm, 0, data, clen);
+ /* Or did we receive the TEST response to our TEST command */
+ } else if (command == CMD_TEST && clen == 1 && *data == gsm->ka_num) {
+ gsm->ka_retries = -1; /* trigger new keep-alive message */
+ if (dlci && !dlci->dead)
+ mod_timer(&gsm->ka_timer, jiffies + gsm->keep_alive * HZ / 100);
+ }
+ spin_unlock_irqrestore(&gsm->control_lock, flags);
+}
+
+/**
+ * gsm_control_keep_alive - check timeout or start keep-alive
+ * @t: timer contained in our gsm object
+ *
+ * Called off the keep-alive timer expiry signaling that our link
+ * partner is not responding anymore. Link will be closed.
+ * This is also called to startup our timer.
+ */
+
+static void gsm_control_keep_alive(struct timer_list *t)
+{
+ struct gsm_mux *gsm = timer_container_of(gsm, t, ka_timer);
+ unsigned long flags;
+
+ spin_lock_irqsave(&gsm->control_lock, flags);
+ if (gsm->ka_num && gsm->ka_retries == 0) {
+ /* Keep-alive expired -> close the link */
+ if (debug & DBG_ERRORS)
+ pr_debug("%s keep-alive timed out\n", __func__);
+ spin_unlock_irqrestore(&gsm->control_lock, flags);
+ if (gsm->dlci[0])
+ gsm_dlci_begin_close(gsm->dlci[0]);
+ return;
+ } else if (gsm->keep_alive && gsm->dlci[0] && !gsm->dlci[0]->dead) {
+ if (gsm->ka_retries > 0) {
+ /* T2 expired for keep-alive -> resend */
+ gsm->ka_retries--;
+ } else {
+ /* Start keep-alive timer */
+ gsm->ka_num++;
+ if (!gsm->ka_num)
+ gsm->ka_num++;
+ gsm->ka_retries = (signed int)gsm->n2;
+ }
+ gsm_control_command(gsm, CMD_TEST, &gsm->ka_num,
+ sizeof(gsm->ka_num));
+ mod_timer(&gsm->ka_timer,
+ jiffies + gsm->t2 * HZ / 100);
}
spin_unlock_irqrestore(&gsm->control_lock, flags);
}
@@ -1946,7 +2029,7 @@ static void gsm_control_transmit(struct gsm_mux *gsm, struct gsm_control *ctrl)
static void gsm_control_retransmit(struct timer_list *t)
{
- struct gsm_mux *gsm = from_timer(gsm, t, t2_timer);
+ struct gsm_mux *gsm = timer_container_of(gsm, t, t2_timer);
struct gsm_control *ctrl;
unsigned long flags;
spin_lock_irqsave(&gsm->control_lock, flags);
@@ -2049,7 +2132,7 @@ static int gsm_control_wait(struct gsm_mux *gsm, struct gsm_control *control)
static void gsm_dlci_close(struct gsm_dlci *dlci)
{
- del_timer(&dlci->t1);
+ timer_delete(&dlci->t1);
if (debug & DBG_ERRORS)
pr_debug("DLCI %d goes closed.\n", dlci->addr);
dlci->state = DLCI_CLOSED;
@@ -2059,14 +2142,16 @@ static void gsm_dlci_close(struct gsm_dlci *dlci)
tty_port_tty_hangup(&dlci->port, false);
gsm_dlci_clear_queues(dlci->gsm, dlci);
/* Ensure that gsmtty_open() can return. */
- tty_port_set_initialized(&dlci->port, 0);
+ tty_port_set_initialized(&dlci->port, false);
wake_up_interruptible(&dlci->port.open_wait);
- } else
+ } else {
+ timer_delete(&dlci->gsm->ka_timer);
dlci->gsm->dead = true;
+ }
/* A DLCI 0 close is a MUX termination so we need to kick that
back to userspace somehow */
gsm_dlci_data_kick(dlci);
- wake_up(&dlci->gsm->event);
+ wake_up_all(&dlci->gsm->event);
}
/**
@@ -2078,17 +2163,26 @@ static void gsm_dlci_close(struct gsm_dlci *dlci)
static void gsm_dlci_open(struct gsm_dlci *dlci)
{
+ struct gsm_mux *gsm = dlci->gsm;
+
/* Note that SABM UA .. SABM UA first UA lost can mean that we go
open -> open */
- del_timer(&dlci->t1);
+ timer_delete(&dlci->t1);
/* This will let a tty open continue */
dlci->state = DLCI_OPEN;
dlci->constipated = false;
if (debug & DBG_ERRORS)
pr_debug("DLCI %d goes open.\n", dlci->addr);
/* Send current modem state */
- if (dlci->addr)
- gsm_modem_update(dlci, 0);
+ if (dlci->addr) {
+ gsm_modem_send_initial_msc(dlci);
+ } else {
+ /* Start keep-alive control */
+ gsm->ka_num = 0;
+ gsm->ka_retries = -1;
+ mod_timer(&gsm->ka_timer,
+ jiffies + gsm->keep_alive * HZ / 100);
+ }
gsm_dlci_data_kick(dlci);
wake_up(&dlci->gsm->event);
}
@@ -2131,12 +2225,12 @@ static int gsm_dlci_negotiate(struct gsm_dlci *dlci)
*
* Some control dlci can stay in ADM mode with other dlci working just
* fine. In that case we can just keep the control dlci open after the
- * DLCI_OPENING retries time out.
+ * DLCI_OPENING receives DM.
*/
static void gsm_dlci_t1(struct timer_list *t)
{
- struct gsm_dlci *dlci = from_timer(dlci, t, t1);
+ struct gsm_dlci *dlci = timer_container_of(dlci, t, t1);
struct gsm_mux *gsm = dlci->gsm;
switch (dlci->state) {
@@ -2145,21 +2239,26 @@ static void gsm_dlci_t1(struct timer_list *t)
dlci->retries--;
mod_timer(&dlci->t1, jiffies + gsm->t1 * HZ / 100);
} else {
+ gsm->open_error++;
gsm_dlci_begin_close(dlci); /* prevent half open link */
}
break;
case DLCI_OPENING:
- if (dlci->retries) {
- dlci->retries--;
- gsm_command(dlci->gsm, dlci->addr, SABM|PF);
- mod_timer(&dlci->t1, jiffies + gsm->t1 * HZ / 100);
- } else if (!dlci->addr && gsm->control == (DM | PF)) {
+ if (!dlci->addr && gsm->control == (DM | PF)) {
if (debug & DBG_ERRORS)
- pr_info("DLCI %d opening in ADM mode.\n",
- dlci->addr);
+ pr_info("DLCI 0 opening in ADM mode.\n");
dlci->mode = DLCI_MODE_ADM;
gsm_dlci_open(dlci);
+ } else if (dlci->retries) {
+ if (!dlci->addr || !gsm->dlci[0] ||
+ gsm->dlci[0]->state != DLCI_OPENING) {
+ dlci->retries--;
+ gsm_command(dlci->gsm, dlci->addr, SABM|PF);
+ }
+
+ mod_timer(&dlci->t1, jiffies + gsm->t1 * HZ / 100);
} else {
+ gsm->open_error++;
gsm_dlci_begin_close(dlci); /* prevent half open link */
}
@@ -2208,11 +2307,14 @@ static void gsm_dlci_begin_open(struct gsm_dlci *dlci)
switch (dlci->state) {
case DLCI_CLOSED:
+ case DLCI_WAITING_CONFIG:
case DLCI_CLOSING:
dlci->retries = gsm->n2;
if (!need_pn) {
dlci->state = DLCI_OPENING;
- gsm_command(gsm, dlci->addr, SABM|PF);
+ if (!dlci->addr || !gsm->dlci[0] ||
+ gsm->dlci[0]->state != DLCI_OPENING)
+ gsm_command(gsm, dlci->addr, SABM|PF);
} else {
/* Configure DLCI before setup */
dlci->state = DLCI_CONFIGURE;
@@ -2239,6 +2341,7 @@ static void gsm_dlci_set_opening(struct gsm_dlci *dlci)
{
switch (dlci->state) {
case DLCI_CLOSED:
+ case DLCI_WAITING_CONFIG:
case DLCI_CLOSING:
dlci->state = DLCI_OPENING;
break;
@@ -2248,6 +2351,24 @@ static void gsm_dlci_set_opening(struct gsm_dlci *dlci)
}
/**
+ * gsm_dlci_set_wait_config - wait for channel configuration
+ * @dlci: DLCI to configure
+ *
+ * Wait for a DLCI configuration from the application.
+ */
+static void gsm_dlci_set_wait_config(struct gsm_dlci *dlci)
+{
+ switch (dlci->state) {
+ case DLCI_CLOSED:
+ case DLCI_CLOSING:
+ dlci->state = DLCI_WAITING_CONFIG;
+ break;
+ default:
+ break;
+ }
+}
+
+/**
* gsm_dlci_begin_close - start channel open procedure
* @dlci: DLCI to open
*
@@ -2267,6 +2388,7 @@ static void gsm_dlci_begin_close(struct gsm_dlci *dlci)
dlci->state = DLCI_CLOSING;
gsm_command(dlci->gsm, dlci->addr, DISC|PF);
mod_timer(&dlci->t1, jiffies + gsm->t1 * HZ / 100);
+ wake_up_interruptible(&gsm->event);
}
/**
@@ -2347,8 +2469,10 @@ static void gsm_dlci_command(struct gsm_dlci *dlci, const u8 *data, int len)
data += dlen;
/* Malformed command? */
- if (clen > len)
+ if (clen > len) {
+ dlci->gsm->malformed++;
return;
+ }
if (command & 1)
gsm_control_message(dlci->gsm, command, data, clen);
@@ -2366,7 +2490,7 @@ static void gsm_dlci_command(struct gsm_dlci *dlci, const u8 *data, int len)
*/
static void gsm_kick_timer(struct timer_list *t)
{
- struct gsm_mux *gsm = from_timer(gsm, t, kick_timer);
+ struct gsm_mux *gsm = timer_container_of(gsm, t, kick_timer);
unsigned long flags;
int sent = 0;
@@ -2380,6 +2504,132 @@ static void gsm_kick_timer(struct timer_list *t)
pr_info("%s TX queue stalled\n", __func__);
}
+/**
+ * gsm_dlci_copy_config_values - copy DLCI configuration
+ * @dlci: source DLCI
+ * @dc: configuration structure to fill
+ */
+static void gsm_dlci_copy_config_values(struct gsm_dlci *dlci, struct gsm_dlci_config *dc)
+{
+ memset(dc, 0, sizeof(*dc));
+ dc->channel = (u32)dlci->addr;
+ dc->adaption = (u32)dlci->adaption;
+ dc->mtu = (u32)dlci->mtu;
+ dc->priority = (u32)dlci->prio;
+ if (dlci->ftype == UIH)
+ dc->i = 1;
+ else
+ dc->i = 2;
+ dc->k = (u32)dlci->k;
+}
+
+/**
+ * gsm_dlci_config - configure DLCI from configuration
+ * @dlci: DLCI to configure
+ * @dc: DLCI configuration
+ * @open: open DLCI after configuration?
+ */
+static int gsm_dlci_config(struct gsm_dlci *dlci, struct gsm_dlci_config *dc, int open)
+{
+ struct gsm_mux *gsm;
+ bool need_restart = false;
+ bool need_open = false;
+ unsigned int i;
+
+ /*
+ * Check that userspace doesn't put stuff in here to prevent breakages
+ * in the future.
+ */
+ for (i = 0; i < ARRAY_SIZE(dc->reserved); i++)
+ if (dc->reserved[i])
+ return -EINVAL;
+
+ if (!dlci)
+ return -EINVAL;
+ gsm = dlci->gsm;
+
+ /* Stuff we don't support yet - I frame transport */
+ if (dc->adaption != 1 && dc->adaption != 2)
+ return -EOPNOTSUPP;
+ if (dc->mtu > MAX_MTU || dc->mtu < MIN_MTU || dc->mtu > gsm->mru)
+ return -EINVAL;
+ if (dc->priority >= 64)
+ return -EINVAL;
+ if (dc->i == 0 || dc->i > 2) /* UIH and UI only */
+ return -EINVAL;
+ if (dc->k > 7)
+ return -EINVAL;
+ if (dc->flags & ~GSM_FL_RESTART) /* allow future extensions */
+ return -EINVAL;
+
+ /*
+ * See what is needed for reconfiguration
+ */
+ /* Framing fields */
+ if (dc->adaption != dlci->adaption)
+ need_restart = true;
+ if (dc->mtu != dlci->mtu)
+ need_restart = true;
+ if (dc->i != dlci->ftype)
+ need_restart = true;
+ /* Requires care */
+ if (dc->priority != dlci->prio)
+ need_restart = true;
+ if (dc->flags & GSM_FL_RESTART)
+ need_restart = true;
+
+ if ((open && gsm->wait_config) || need_restart)
+ need_open = true;
+ if (dlci->state == DLCI_WAITING_CONFIG) {
+ need_restart = false;
+ need_open = true;
+ }
+
+ /*
+ * Close down what is needed, restart and initiate the new
+ * configuration.
+ */
+ if (need_restart) {
+ gsm_dlci_begin_close(dlci);
+ wait_event_interruptible(gsm->event, dlci->state == DLCI_CLOSED);
+ if (signal_pending(current))
+ return -EINTR;
+ }
+ /*
+ * Setup the new configuration values
+ */
+ dlci->adaption = (int)dc->adaption;
+
+ if (dc->mtu)
+ dlci->mtu = (unsigned int)dc->mtu;
+ else
+ dlci->mtu = gsm->mtu;
+
+ if (dc->priority)
+ dlci->prio = (u8)dc->priority;
+ else
+ dlci->prio = roundup(dlci->addr + 1, 8) - 1;
+
+ if (dc->i == 1)
+ dlci->ftype = UIH;
+ else if (dc->i == 2)
+ dlci->ftype = UI;
+
+ if (dc->k)
+ dlci->k = (u8)dc->k;
+ else
+ dlci->k = gsm->k;
+
+ if (need_open) {
+ if (gsm->initiator)
+ gsm_dlci_begin_open(dlci);
+ else
+ gsm_dlci_set_opening(dlci);
+ }
+
+ return 0;
+}
+
/*
* Allocate/Free DLCI channels
*/
@@ -2534,12 +2784,16 @@ static void gsm_queue(struct gsm_mux *gsm)
switch (gsm->control) {
case SABM|PF:
- if (cr == 1)
+ if (cr == 1) {
+ gsm->open_error++;
goto invalid;
+ }
if (dlci == NULL)
dlci = gsm_dlci_alloc(gsm, address);
- if (dlci == NULL)
+ if (dlci == NULL) {
+ gsm->open_error++;
return;
+ }
if (dlci->dead)
gsm_response(gsm, address, DM|PF);
else {
@@ -2601,6 +2855,30 @@ invalid:
return;
}
+/**
+ * gsm0_receive_state_check_and_fix - check and correct receive state
+ * @gsm: gsm data for this ldisc instance
+ *
+ * Ensures that the current receive state is valid for basic option mode.
+ */
+
+static void gsm0_receive_state_check_and_fix(struct gsm_mux *gsm)
+{
+ switch (gsm->state) {
+ case GSM_SEARCH:
+ case GSM0_ADDRESS:
+ case GSM0_CONTROL:
+ case GSM0_LEN0:
+ case GSM0_LEN1:
+ case GSM0_DATA:
+ case GSM0_FCS:
+ case GSM0_SSOF:
+ break;
+ default:
+ gsm->state = GSM_SEARCH;
+ break;
+ }
+}
/**
* gsm0_receive - perform processing for non-transparency
@@ -2610,30 +2888,31 @@ invalid:
* Receive bytes in gsm mode 0
*/
-static void gsm0_receive(struct gsm_mux *gsm, unsigned char c)
+static void gsm0_receive(struct gsm_mux *gsm, u8 c)
{
unsigned int len;
+ gsm0_receive_state_check_and_fix(gsm);
switch (gsm->state) {
case GSM_SEARCH: /* SOF marker */
if (c == GSM0_SOF) {
- gsm->state = GSM_ADDRESS;
+ gsm->state = GSM0_ADDRESS;
gsm->address = 0;
gsm->len = 0;
gsm->fcs = INIT_FCS;
}
break;
- case GSM_ADDRESS: /* Address EA */
+ case GSM0_ADDRESS: /* Address EA */
gsm->fcs = gsm_fcs_add(gsm->fcs, c);
if (gsm_read_ea(&gsm->address, c))
- gsm->state = GSM_CONTROL;
+ gsm->state = GSM0_CONTROL;
break;
- case GSM_CONTROL: /* Control Byte */
+ case GSM0_CONTROL: /* Control Byte */
gsm->fcs = gsm_fcs_add(gsm->fcs, c);
gsm->control = c;
- gsm->state = GSM_LEN0;
+ gsm->state = GSM0_LEN0;
break;
- case GSM_LEN0: /* Length EA */
+ case GSM0_LEN0: /* Length EA */
gsm->fcs = gsm_fcs_add(gsm->fcs, c);
if (gsm_read_ea(&gsm->len, c)) {
if (gsm->len > gsm->mru) {
@@ -2643,14 +2922,14 @@ static void gsm0_receive(struct gsm_mux *gsm, unsigned char c)
}
gsm->count = 0;
if (!gsm->len)
- gsm->state = GSM_FCS;
+ gsm->state = GSM0_FCS;
else
- gsm->state = GSM_DATA;
+ gsm->state = GSM0_DATA;
break;
}
- gsm->state = GSM_LEN1;
+ gsm->state = GSM0_LEN1;
break;
- case GSM_LEN1:
+ case GSM0_LEN1:
gsm->fcs = gsm_fcs_add(gsm->fcs, c);
len = c;
gsm->len |= len << 7;
@@ -2661,26 +2940,29 @@ static void gsm0_receive(struct gsm_mux *gsm, unsigned char c)
}
gsm->count = 0;
if (!gsm->len)
- gsm->state = GSM_FCS;
+ gsm->state = GSM0_FCS;
else
- gsm->state = GSM_DATA;
+ gsm->state = GSM0_DATA;
break;
- case GSM_DATA: /* Data */
+ case GSM0_DATA: /* Data */
gsm->buf[gsm->count++] = c;
- if (gsm->count == gsm->len) {
+ if (gsm->count >= MAX_MRU) {
+ gsm->bad_size++;
+ gsm->state = GSM_SEARCH;
+ } else if (gsm->count >= gsm->len) {
/* Calculate final FCS for UI frames over all data */
if ((gsm->control & ~PF) != UIH) {
gsm->fcs = gsm_fcs_add_block(gsm->fcs, gsm->buf,
gsm->count);
}
- gsm->state = GSM_FCS;
+ gsm->state = GSM0_FCS;
}
break;
- case GSM_FCS: /* FCS follows the packet */
+ case GSM0_FCS: /* FCS follows the packet */
gsm->fcs = gsm_fcs_add(gsm->fcs, c);
- gsm->state = GSM_SSOF;
+ gsm->state = GSM0_SSOF;
break;
- case GSM_SSOF:
+ case GSM0_SSOF:
gsm->state = GSM_SEARCH;
if (c == GSM0_SOF)
gsm_queue(gsm);
@@ -2694,6 +2976,29 @@ static void gsm0_receive(struct gsm_mux *gsm, unsigned char c)
}
/**
+ * gsm1_receive_state_check_and_fix - check and correct receive state
+ * @gsm: gsm data for this ldisc instance
+ *
+ * Ensures that the current receive state is valid for advanced option mode.
+ */
+
+static void gsm1_receive_state_check_and_fix(struct gsm_mux *gsm)
+{
+ switch (gsm->state) {
+ case GSM_SEARCH:
+ case GSM1_START:
+ case GSM1_ADDRESS:
+ case GSM1_CONTROL:
+ case GSM1_DATA:
+ case GSM1_OVERRUN:
+ break;
+ default:
+ gsm->state = GSM_SEARCH;
+ break;
+ }
+}
+
+/**
* gsm1_receive - perform processing for non-transparency
* @gsm: gsm data for this ldisc instance
* @c: character
@@ -2701,8 +3006,9 @@ static void gsm0_receive(struct gsm_mux *gsm, unsigned char c)
* Receive bytes in mode 1 (Advanced option)
*/
-static void gsm1_receive(struct gsm_mux *gsm, unsigned char c)
+static void gsm1_receive(struct gsm_mux *gsm, u8 c)
{
+ gsm1_receive_state_check_and_fix(gsm);
/* handle XON/XOFF */
if ((c & ISO_IEC_646_MASK) == XON) {
gsm->constipated = true;
@@ -2715,11 +3021,11 @@ static void gsm1_receive(struct gsm_mux *gsm, unsigned char c)
}
if (c == GSM1_SOF) {
/* EOF is only valid in frame if we have got to the data state */
- if (gsm->state == GSM_DATA) {
+ if (gsm->state == GSM1_DATA) {
if (gsm->count < 1) {
/* Missing FSC */
gsm->malformed++;
- gsm->state = GSM_START;
+ gsm->state = GSM1_START;
return;
}
/* Remove the FCS from data */
@@ -2735,14 +3041,14 @@ static void gsm1_receive(struct gsm_mux *gsm, unsigned char c)
gsm->fcs = gsm_fcs_add(gsm->fcs, gsm->buf[gsm->count]);
gsm->len = gsm->count;
gsm_queue(gsm);
- gsm->state = GSM_START;
+ gsm->state = GSM1_START;
return;
}
/* Any partial frame was a runt so go back to start */
- if (gsm->state != GSM_START) {
+ if (gsm->state != GSM1_START) {
if (gsm->state != GSM_SEARCH)
gsm->malformed++;
- gsm->state = GSM_START;
+ gsm->state = GSM1_START;
}
/* A SOF in GSM_START means we are still reading idling or
framing bytes */
@@ -2763,30 +3069,30 @@ static void gsm1_receive(struct gsm_mux *gsm, unsigned char c)
gsm->escape = false;
}
switch (gsm->state) {
- case GSM_START: /* First byte after SOF */
+ case GSM1_START: /* First byte after SOF */
gsm->address = 0;
- gsm->state = GSM_ADDRESS;
+ gsm->state = GSM1_ADDRESS;
gsm->fcs = INIT_FCS;
fallthrough;
- case GSM_ADDRESS: /* Address continuation */
+ case GSM1_ADDRESS: /* Address continuation */
gsm->fcs = gsm_fcs_add(gsm->fcs, c);
if (gsm_read_ea(&gsm->address, c))
- gsm->state = GSM_CONTROL;
+ gsm->state = GSM1_CONTROL;
break;
- case GSM_CONTROL: /* Control Byte */
+ case GSM1_CONTROL: /* Control Byte */
gsm->fcs = gsm_fcs_add(gsm->fcs, c);
gsm->control = c;
gsm->count = 0;
- gsm->state = GSM_DATA;
+ gsm->state = GSM1_DATA;
break;
- case GSM_DATA: /* Data */
- if (gsm->count > gsm->mru) { /* Allow one for the FCS */
- gsm->state = GSM_OVERRUN;
+ case GSM1_DATA: /* Data */
+ if (gsm->count > gsm->mru || gsm->count > MAX_MRU) { /* Allow one for the FCS */
+ gsm->state = GSM1_OVERRUN;
gsm->bad_size++;
} else
gsm->buf[gsm->count++] = c;
break;
- case GSM_OVERRUN: /* Over-long - eg a dropped SOF */
+ case GSM1_OVERRUN: /* Over-long - eg a dropped SOF */
break;
default:
pr_debug("%s: unhandled state: %d\n", __func__, gsm->state);
@@ -2823,12 +3129,13 @@ static void gsm_error(struct gsm_mux *gsm)
static void gsm_cleanup_mux(struct gsm_mux *gsm, bool disc)
{
int i;
- struct gsm_dlci *dlci = gsm->dlci[0];
+ struct gsm_dlci *dlci;
struct gsm_msg *txq, *ntxq;
gsm->dead = true;
mutex_lock(&gsm->mutex);
+ dlci = gsm->dlci[0];
if (dlci) {
if (disc && dlci->state != DLCI_CLOSED) {
gsm_dlci_begin_close(dlci);
@@ -2838,8 +3145,9 @@ static void gsm_cleanup_mux(struct gsm_mux *gsm, bool disc)
}
/* Finish outstanding timers, making sure they are done */
- del_timer_sync(&gsm->kick_timer);
- del_timer_sync(&gsm->t2_timer);
+ timer_delete_sync(&gsm->kick_timer);
+ timer_delete_sync(&gsm->t2_timer);
+ timer_delete_sync(&gsm->ka_timer);
/* Finish writing to ldisc */
flush_work(&gsm->tx_work);
@@ -2855,6 +3163,8 @@ static void gsm_cleanup_mux(struct gsm_mux *gsm, bool disc)
mutex_unlock(&gsm->mutex);
/* Now wipe the queues */
tty_ldisc_flush(gsm->tty);
+
+ guard(spinlock_irqsave)(&gsm->tx_lock);
list_for_each_entry_safe(txq, ntxq, &gsm->tx_ctrl_list, list)
kfree(txq);
INIT_LIST_HEAD(&gsm->tx_ctrl_list);
@@ -2987,6 +3297,7 @@ static struct gsm_mux *gsm_alloc_mux(void)
INIT_LIST_HEAD(&gsm->tx_data_list);
timer_setup(&gsm->kick_timer, gsm_kick_timer, 0);
timer_setup(&gsm->t2_timer, gsm_control_retransmit, 0);
+ timer_setup(&gsm->ka_timer, gsm_control_keep_alive, 0);
INIT_WORK(&gsm->tx_work, gsmld_write_task);
init_waitqueue_head(&gsm->event);
spin_lock_init(&gsm->control_lock);
@@ -3003,6 +3314,8 @@ static struct gsm_mux *gsm_alloc_mux(void)
gsm->mru = 64; /* Default to encoding 1 so these should be 64 */
gsm->mtu = 64;
gsm->dead = true; /* Avoid early tty opens */
+ gsm->wait_config = false; /* Disabled */
+ gsm->keep_alive = 0; /* Disabled */
/* Store the instance to the mux array or abort if no space is
* available.
@@ -3050,12 +3363,11 @@ static void gsm_copy_config_values(struct gsm_mux *gsm,
static int gsm_config(struct gsm_mux *gsm, struct gsm_config *c)
{
- int ret = 0;
int need_close = 0;
int need_restart = 0;
- /* Stuff we don't support yet - UI or I frame transport, windowing */
- if ((c->adaption != 1 && c->adaption != 2) || c->k)
+ /* Stuff we don't support yet - UI or I frame transport */
+ if (c->adaption != 1 && c->adaption != 2)
return -EOPNOTSUPP;
/* Check the MRU/MTU range looks sane */
if (c->mru < MIN_MTU || c->mtu < MIN_MTU)
@@ -3129,7 +3441,7 @@ static int gsm_config(struct gsm_mux *gsm, struct gsm_config *c)
* and removing from the mux array
*/
if (gsm->dead) {
- ret = gsm_activate_mux(gsm);
+ int ret = gsm_activate_mux(gsm);
if (ret)
return ret;
if (gsm->initiator)
@@ -3138,6 +3450,58 @@ static int gsm_config(struct gsm_mux *gsm, struct gsm_config *c)
return 0;
}
+static void gsm_copy_config_ext_values(struct gsm_mux *gsm,
+ struct gsm_config_ext *ce)
+{
+ memset(ce, 0, sizeof(*ce));
+ ce->wait_config = gsm->wait_config ? 1 : 0;
+ ce->keep_alive = gsm->keep_alive;
+}
+
+static int gsm_config_ext(struct gsm_mux *gsm, struct gsm_config_ext *ce)
+{
+ bool need_restart = false;
+ unsigned int i;
+
+ /*
+ * Check that userspace doesn't put stuff in here to prevent breakages
+ * in the future.
+ */
+ for (i = 0; i < ARRAY_SIZE(ce->reserved); i++)
+ if (ce->reserved[i])
+ return -EINVAL;
+ if (ce->flags & ~GSM_FL_RESTART)
+ return -EINVAL;
+
+ /* Requires care */
+ if (ce->flags & GSM_FL_RESTART)
+ need_restart = true;
+
+ /*
+ * Close down what is needed, restart and initiate the new
+ * configuration. On the first time there is no DLCI[0]
+ * and closing or cleaning up is not necessary.
+ */
+ if (need_restart)
+ gsm_cleanup_mux(gsm, true);
+
+ /*
+ * Setup the new configuration values
+ */
+ gsm->wait_config = ce->wait_config ? true : false;
+ gsm->keep_alive = ce->keep_alive;
+
+ if (gsm->dead) {
+ int ret = gsm_activate_mux(gsm);
+ if (ret)
+ return ret;
+ if (gsm->initiator)
+ gsm_dlci_begin_open(gsm->dlci[0]);
+ }
+
+ return 0;
+}
+
/**
* gsmld_output - write to link
* @gsm: our mux
@@ -3235,11 +3599,11 @@ static void gsmld_detach_gsm(struct tty_struct *tty, struct gsm_mux *gsm)
gsm->tty = NULL;
}
-static void gsmld_receive_buf(struct tty_struct *tty, const unsigned char *cp,
- const char *fp, int count)
+static void gsmld_receive_buf(struct tty_struct *tty, const u8 *cp,
+ const u8 *fp, size_t count)
{
struct gsm_mux *gsm = tty->disc_data;
- char flags = TTY_NORMAL;
+ u8 flags = TTY_NORMAL;
if (debug & DBG_DATA)
gsm_hex_dump_bytes(__func__, cp, count);
@@ -3322,6 +3686,9 @@ static int gsmld_open(struct tty_struct *tty)
{
struct gsm_mux *gsm;
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+
if (tty->ops->write == NULL)
return -EINVAL;
@@ -3334,9 +3701,16 @@ static int gsmld_open(struct tty_struct *tty)
tty->receive_room = 65536;
/* Attach the initial passive connection */
- gsm->encoding = GSM_ADV_OPT;
gsmld_attach_gsm(tty, gsm);
+ /* The mux will not be activated yet, we wait for correct
+ * configuration first.
+ */
+ if (gsm->encoding == GSM_BASIC_OPT)
+ gsm->receive = gsm0_receive;
+ else
+ gsm->receive = gsm1_receive;
+
return 0;
}
@@ -3374,9 +3748,8 @@ static void gsmld_write_wakeup(struct tty_struct *tty)
* This code must be sure never to sleep through a hangup.
*/
-static ssize_t gsmld_read(struct tty_struct *tty, struct file *file,
- unsigned char *buf, size_t nr,
- void **cookie, unsigned long offset)
+static ssize_t gsmld_read(struct tty_struct *tty, struct file *file, u8 *buf,
+ size_t nr, void **cookie, unsigned long offset)
{
return -EOPNOTSUPP;
}
@@ -3396,11 +3769,11 @@ static ssize_t gsmld_read(struct tty_struct *tty, struct file *file,
*/
static ssize_t gsmld_write(struct tty_struct *tty, struct file *file,
- const unsigned char *buf, size_t nr)
+ const u8 *buf, size_t nr)
{
struct gsm_mux *gsm = tty->disc_data;
unsigned long flags;
- int space;
+ size_t space;
int ret;
if (!gsm)
@@ -3456,8 +3829,11 @@ static int gsmld_ioctl(struct tty_struct *tty, unsigned int cmd,
unsigned long arg)
{
struct gsm_config c;
+ struct gsm_config_ext ce;
+ struct gsm_dlci_config dc;
struct gsm_mux *gsm = tty->disc_data;
- unsigned int base;
+ unsigned int base, addr;
+ struct gsm_dlci *dlci;
switch (cmd) {
case GSMIOC_GETCONF:
@@ -3472,6 +3848,44 @@ static int gsmld_ioctl(struct tty_struct *tty, unsigned int cmd,
case GSMIOC_GETFIRST:
base = mux_num_to_base(gsm);
return put_user(base + 1, (__u32 __user *)arg);
+ case GSMIOC_GETCONF_EXT:
+ gsm_copy_config_ext_values(gsm, &ce);
+ if (copy_to_user((void __user *)arg, &ce, sizeof(ce)))
+ return -EFAULT;
+ return 0;
+ case GSMIOC_SETCONF_EXT:
+ if (copy_from_user(&ce, (void __user *)arg, sizeof(ce)))
+ return -EFAULT;
+ return gsm_config_ext(gsm, &ce);
+ case GSMIOC_GETCONF_DLCI:
+ if (copy_from_user(&dc, (void __user *)arg, sizeof(dc)))
+ return -EFAULT;
+ if (dc.channel == 0 || dc.channel >= NUM_DLCI)
+ return -EINVAL;
+ addr = array_index_nospec(dc.channel, NUM_DLCI);
+ dlci = gsm->dlci[addr];
+ if (!dlci) {
+ dlci = gsm_dlci_alloc(gsm, addr);
+ if (!dlci)
+ return -ENOMEM;
+ }
+ gsm_dlci_copy_config_values(dlci, &dc);
+ if (copy_to_user((void __user *)arg, &dc, sizeof(dc)))
+ return -EFAULT;
+ return 0;
+ case GSMIOC_SETCONF_DLCI:
+ if (copy_from_user(&dc, (void __user *)arg, sizeof(dc)))
+ return -EFAULT;
+ if (dc.channel == 0 || dc.channel >= NUM_DLCI)
+ return -EINVAL;
+ addr = array_index_nospec(dc.channel, NUM_DLCI);
+ dlci = gsm->dlci[addr];
+ if (!dlci) {
+ dlci = gsm_dlci_alloc(gsm, addr);
+ if (!dlci)
+ return -ENOMEM;
+ }
+ return gsm_dlci_config(dlci, &dc, 0);
default:
return n_tty_ioctl_helper(tty, cmd, arg);
}
@@ -3557,8 +3971,7 @@ static void gsm_mux_net_tx_timeout(struct net_device *net, unsigned int txqueue)
net->stats.tx_errors++;
}
-static void gsm_mux_rx_netchar(struct gsm_dlci *dlci,
- const unsigned char *in_buf, int size)
+static void gsm_mux_rx_netchar(struct gsm_dlci *dlci, const u8 *in_buf, int size)
{
struct net_device *net = dlci->net;
struct sk_buff *skb;
@@ -3659,7 +4072,7 @@ static int gsm_create_network(struct gsm_dlci *dlci, struct gsm_netconfig *nc)
mux_net = netdev_priv(net);
mux_net->dlci = dlci;
kref_init(&mux_net->ref);
- strncpy(nc->if_name, net->name, IFNAMSIZ); /* return net name */
+ strscpy(nc->if_name, net->name); /* return net name */
/* reconfigure dlci for network */
dlci->prev_adaption = dlci->adaption;
@@ -3750,6 +4163,28 @@ static int gsm_modem_upd_via_msc(struct gsm_dlci *dlci, u8 brk)
}
/**
+ * gsm_modem_send_initial_msc - Send initial modem status message
+ *
+ * @dlci: channel
+ *
+ * Send an initial MSC message after DLCI open to set the initial
+ * modem status lines. This is only done for basic mode.
+ * Does not wait for a response as we cannot block the input queue
+ * processing.
+ */
+static int gsm_modem_send_initial_msc(struct gsm_dlci *dlci)
+{
+ u8 modembits[2];
+
+ if (dlci->adaption != 1 || dlci->gsm->encoding != GSM_BASIC_OPT)
+ return 0;
+
+ modembits[0] = (dlci->addr << 2) | 2 | EA; /* DLCI, Valid, EA */
+ modembits[1] = (gsm_encode_modem(dlci) << 1) | EA;
+ return gsm_control_command(dlci->gsm, CMD_MSC, (const u8 *)&modembits, 2);
+}
+
+/**
* gsm_modem_update - send modem status line state
* @dlci: channel
* @brk: break signal
@@ -3757,6 +4192,8 @@ static int gsm_modem_upd_via_msc(struct gsm_dlci *dlci, u8 brk)
static int gsm_modem_update(struct gsm_dlci *dlci, u8 brk)
{
+ if (dlci->gsm->dead)
+ return -EL2HLT;
if (dlci->adaption == 2) {
/* Send convergence layer type 2 empty data frame. */
gsm_modem_upd_via_data(dlci, brk);
@@ -3770,16 +4207,43 @@ static int gsm_modem_update(struct gsm_dlci *dlci, u8 brk)
return -EPROTONOSUPPORT;
}
-static int gsm_carrier_raised(struct tty_port *port)
+/**
+ * gsm_wait_modem_change - wait for modem status line change
+ * @dlci: channel
+ * @mask: modem status line bits
+ *
+ * The function returns if:
+ * - any given modem status line bit changed
+ * - the wait event function got interrupted (e.g. by a signal)
+ * - the underlying DLCI was closed
+ * - the underlying ldisc device was removed
+ */
+static int gsm_wait_modem_change(struct gsm_dlci *dlci, u32 mask)
+{
+ struct gsm_mux *gsm = dlci->gsm;
+ u32 old = dlci->modem_rx;
+ int ret;
+
+ ret = wait_event_interruptible(gsm->event, gsm->dead ||
+ dlci->state != DLCI_OPEN ||
+ (old ^ dlci->modem_rx) & mask);
+ if (gsm->dead)
+ return -ENODEV;
+ if (dlci->state != DLCI_OPEN)
+ return -EL2NSYNC;
+ return ret;
+}
+
+static bool gsm_carrier_raised(struct tty_port *port)
{
struct gsm_dlci *dlci = container_of(port, struct gsm_dlci, port);
struct gsm_mux *gsm = dlci->gsm;
/* Not yet open so no carrier info */
if (dlci->state != DLCI_OPEN)
- return 0;
+ return false;
if (debug & DBG_CD_ON)
- return 1;
+ return true;
/*
* Basic mode with control channel in ADM mode may not respond
@@ -3787,16 +4251,16 @@ static int gsm_carrier_raised(struct tty_port *port)
*/
if (gsm->encoding == GSM_BASIC_OPT &&
gsm->dlci[0]->mode == DLCI_MODE_ADM && !dlci->modem_rx)
- return 1;
+ return true;
return dlci->modem_rx & TIOCM_CD;
}
-static void gsm_dtr_rts(struct tty_port *port, int onoff)
+static void gsm_dtr_rts(struct tty_port *port, bool active)
{
struct gsm_dlci *dlci = container_of(port, struct gsm_dlci, port);
unsigned int modem_tx = dlci->modem_tx;
- if (onoff)
+ if (active)
modem_tx |= TIOCM_DTR | TIOCM_RTS;
else
modem_tx &= ~(TIOCM_DTR | TIOCM_RTS);
@@ -3815,7 +4279,7 @@ static const struct tty_port_operations gsm_port_ops = {
static int gsmtty_install(struct tty_driver *driver, struct tty_struct *tty)
{
struct gsm_mux *gsm;
- struct gsm_dlci *dlci;
+ struct gsm_dlci *dlci, *dlci0;
unsigned int line = tty->index;
unsigned int mux = mux_line_to_num(line);
bool alloc = false;
@@ -3838,10 +4302,20 @@ static int gsmtty_install(struct tty_driver *driver, struct tty_struct *tty)
perspective as we don't have to worry about this
if DLCI0 is lost */
mutex_lock(&gsm->mutex);
- if (gsm->dlci[0] && gsm->dlci[0]->state != DLCI_OPEN) {
+
+ dlci0 = gsm->dlci[0];
+ if (dlci0 && dlci0->state != DLCI_OPEN) {
mutex_unlock(&gsm->mutex);
- return -EL2NSYNC;
+
+ if (dlci0->state == DLCI_OPENING)
+ wait_event(gsm->event, dlci0->state != DLCI_OPENING);
+
+ if (dlci0->state != DLCI_OPEN)
+ return -EL2NSYNC;
+
+ mutex_lock(&gsm->mutex);
}
+
dlci = gsm->dlci[line];
if (dlci == NULL) {
alloc = true;
@@ -3872,7 +4346,6 @@ static int gsmtty_open(struct tty_struct *tty, struct file *filp)
{
struct gsm_dlci *dlci = tty->driver_data;
struct tty_port *port = &dlci->port;
- struct gsm_mux *gsm = dlci->gsm;
port->count++;
tty_port_tty_set(port, tty);
@@ -3880,12 +4353,17 @@ static int gsmtty_open(struct tty_struct *tty, struct file *filp)
dlci->modem_rx = 0;
/* We could in theory open and close before we wait - eg if we get
a DM straight back. This is ok as that will have caused a hangup */
- tty_port_set_initialized(port, 1);
+ tty_port_set_initialized(port, true);
/* Start sending off SABM messages */
- if (gsm->initiator)
- gsm_dlci_begin_open(dlci);
- else
- gsm_dlci_set_opening(dlci);
+ if (!dlci->gsm->wait_config) {
+ /* Start sending off SABM messages */
+ if (dlci->gsm->initiator)
+ gsm_dlci_begin_open(dlci);
+ else
+ gsm_dlci_set_opening(dlci);
+ } else {
+ gsm_dlci_set_wait_config(dlci);
+ }
/* And wait for virtual carrier */
return tty_port_block_til_ready(port, tty, filp);
}
@@ -3920,8 +4398,7 @@ static void gsmtty_hangup(struct tty_struct *tty)
gsm_dlci_begin_close(dlci);
}
-static int gsmtty_write(struct tty_struct *tty, const unsigned char *buf,
- int len)
+static ssize_t gsmtty_write(struct tty_struct *tty, const u8 *buf, size_t len)
{
int sent;
struct gsm_dlci *dlci = tty->driver_data;
@@ -4006,6 +4483,7 @@ static int gsmtty_ioctl(struct tty_struct *tty,
{
struct gsm_dlci *dlci = tty->driver_data;
struct gsm_netconfig nc;
+ struct gsm_dlci_config dc;
int index;
if (dlci->state == DLCI_CLOSED)
@@ -4029,6 +4507,25 @@ static int gsmtty_ioctl(struct tty_struct *tty,
gsm_destroy_network(dlci);
mutex_unlock(&dlci->mutex);
return 0;
+ case GSMIOC_GETCONF_DLCI:
+ if (copy_from_user(&dc, (void __user *)arg, sizeof(dc)))
+ return -EFAULT;
+ if (dc.channel != dlci->addr)
+ return -EPERM;
+ gsm_dlci_copy_config_values(dlci, &dc);
+ if (copy_to_user((void __user *)arg, &dc, sizeof(dc)))
+ return -EFAULT;
+ return 0;
+ case GSMIOC_SETCONF_DLCI:
+ if (copy_from_user(&dc, (void __user *)arg, sizeof(dc)))
+ return -EFAULT;
+ if (dc.channel >= NUM_DLCI)
+ return -EINVAL;
+ if (dc.channel != 0 && dc.channel != dlci->addr)
+ return -EPERM;
+ return gsm_dlci_config(dlci, &dc, 1);
+ case TIOCMIWAIT:
+ return gsm_wait_modem_change(dlci, (u32)arg);
default:
return -ENOIOCTLCMD;
}
@@ -4177,5 +4674,6 @@ module_init(gsm_init);
module_exit(gsm_exit);
+MODULE_DESCRIPTION("GSM 0710 tty multiplexor");
MODULE_LICENSE("GPL");
MODULE_ALIAS_LDISC(N_GSM0710);