@@ -123,6 +123,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 */
@@ -406,6 +407,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,
@@ -528,6 +530,55 @@ static void gsm_hex_dump_bytes(const char *fname, const u8 *data,
kfree(prefix);
}
+/**
+ * gsm_encode_params - encode DLCI parameters
+ * @dlci: DLCI to encode from
+ * @data: 8 byte buffer for encoded data
+ * @dlen: length of buffer
+ *
+ * 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, u8 *data,
+ unsigned int dlen)
+{
+ struct gsm_mux *gsm = dlci->gsm;
+
+ if (dlen < 8)
+ return -EINVAL;
+
+ data[0] = dlci->addr;
+ data[1] = 0x00; /* UIH, convergence layer type 1 */
+ data[2] = dlci->prio;
+ data[3] = gsm->t1;
+ data[4] = dlci->mtu & 0xFF;
+ data[5] = (dlci->mtu >> 8) & 0xFF;
+ data[6] = gsm->n2;
+ data[7] = dlci->k;
+
+ if (dlci->ftype == UI) {
+ data[1] = 0x01; /* UI */
+ } else if (dlci->ftype != UIH) {
+ pr_err("%s: unsupported frame type %d\n", __func__,
+ dlci->ftype);
+ return -EINVAL;
+ }
+
+ switch (dlci->adaption) {
+ case 1: /* Unstructured */
+ break;
+ case 2: /* Unstructured with modem bits. */
+ data[1] |= 0x10; /* convergence layer type 2 */
+ break;
+ default:
+ pr_err("%s: unsupported adaption %d\n", __func__,
+ dlci->adaption);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
/**
* gsm_register_devices - register all tty devices for a given mux index
*
@@ -1445,6 +1496,122 @@ static void gsm_process_modem(struct tty_struct *tty, struct gsm_dlci *dlci,
dlci->modem_rx = mlines;
}
+/**
+ * gsm_process_negotiation - process received parameters
+ * @gsm: GSM channel
+ * @addr: DLCI address
+ * @cr: command/response
+ * @data: data following command
+ * @clen: length of data
+ *
+ * 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 u8 *data,
+ unsigned int clen)
+{
+ struct gsm_dlci *dlci = gsm->dlci[addr];
+ unsigned int ftype, i, adaption, prio, n1, k;
+
+ if (clen < 8)
+ return -EINVAL;
+
+ i = data[1] & 0x0F;
+ adaption = ((data[1] >> 4) & 0x0F) + 1;
+ prio = data[2] & 0x3F;
+ /* t1 = data[3]; */
+ n1 = data[4] | (data[5] << 8);
+ /* n2 = data[6]; */
+ k = data[7] & 0x07;
+
+ if (n1 < MIN_UNIT_SIZE) {
+ 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
@@ -1498,6 +1665,62 @@ static void gsm_control_modem(struct gsm_mux *gsm, const u8 *data, int clen)
gsm_control_reply(gsm, CMD_MSC, data, 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;
+ u8 params[8];
+ struct gsm_dlci *dlci;
+
+ if (dlen < 8)
+ return;
+
+ /* Invalid DLCI? */
+ addr = data[0] & 0x3F;
+ 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, data, dlen) != 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, params, sizeof(params)) == 0)
+ gsm_control_reply(gsm, CMD_PN, params, sizeof(params));
+ 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
@@ -1607,8 +1830,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:
@@ -1641,8 +1868,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);
@@ -1652,6 +1879,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);
}
@@ -1829,6 +2059,31 @@ static void gsm_dlci_open(struct gsm_dlci *dlci)
wake_up(&dlci->gsm->event);
}
+/**
+ * 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;
+ u8 params[8];
+ int ret;
+
+ ret = gsm_encode_params(dlci, params, sizeof(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, params, sizeof(params));
+
+ return ret;
+}
+
/**
* gsm_dlci_t1 - T1 timer expiry
* @t: timer contained in the DLCI that opened
@@ -1850,6 +2105,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--;
@@ -1888,17 +2151,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 != ((((dlci->addr / 8) + 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;
+ }
}
/**