[PATCH 5.17 220/225] tty: n_gsm: fix invalid use of MSC in advanced option

From: Greg Kroah-Hartman
Date: Wed May 04 2022 - 13:47:08 EST


From: Daniel Starke <daniel.starke@xxxxxxxxxxx>

commit c19ffe00fed6bb423d81406d2a7e5793074c7d83 upstream.

n_gsm is based on the 3GPP 07.010 and its newer version is the 3GPP 27.010.
See https://portal.3gpp.org/desktopmodules/Specifications/SpecificationDetails.aspx?specificationId=1516
The changes from 07.010 to 27.010 are non-functional. Therefore, I refer to
the newer 27.010 here. Chapter 5.4.6.3.7 states that the Modem Status
Command (MSC) shall only be used if the basic option was chosen.
The current implementation uses MSC frames even if advanced option was
chosen to inform the peer about modem line state updates. A standard
conform peer may choose to discard these frames in advanced option mode.
Furthermore, gsmtty_modem_update() is not part of the 'tty_operations'
functions despite its name.
Rename gsmtty_modem_update() to gsm_modem_update() to clarify this. Split
its function into gsm_modem_upd_via_data() and gsm_modem_upd_via_msc()
depending on the encoding and adaption. Introduce gsm_dlci_modem_output()
as adaption of gsm_dlci_data_output() to encode and queue empty frames in
advanced option mode. Use it in gsm_modem_upd_via_data().
gsm_modem_upd_via_msc() is based on the initial gsmtty_modem_update()
function which used only MSC frames to update modem states.

Fixes: e1eaea46bb40 ("tty: n_gsm line discipline")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Daniel Starke <daniel.starke@xxxxxxxxxxx>
Link: https://lore.kernel.org/r/20220422071025.5490-2-daniel.starke@xxxxxxxxxxx
Signed-off-by: Greg Kroah-Hartman <gregkh@xxxxxxxxxxxxxxxxxxx>
---
drivers/tty/n_gsm.c | 125 ++++++++++++++++++++++++++++++++++++++++++++++++----
1 file changed, 117 insertions(+), 8 deletions(-)

--- a/drivers/tty/n_gsm.c
+++ b/drivers/tty/n_gsm.c
@@ -366,7 +366,7 @@ static const u8 gsm_fcs8[256] = {
#define GOOD_FCS 0xCF

static int gsmld_output(struct gsm_mux *gsm, u8 *data, int len);
-static int gsmtty_modem_update(struct gsm_dlci *dlci, u8 brk);
+static int gsm_modem_update(struct gsm_dlci *dlci, u8 brk);

/**
* gsm_fcs_add - update FCS
@@ -915,6 +915,63 @@ static int gsm_dlci_data_output_framed(s
}

/**
+ * gsm_dlci_modem_output - try and push modem status out of a DLCI
+ * @gsm: mux
+ * @dlci: the DLCI to pull modem status from
+ * @brk: break signal
+ *
+ * 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_lock of the mux.
+ */
+
+static int gsm_dlci_modem_output(struct gsm_mux *gsm, struct gsm_dlci *dlci,
+ u8 brk)
+{
+ u8 *dp = NULL;
+ struct gsm_msg *msg;
+ int size;
+
+ /* for modem bits without break data */
+ if (dlci->adaption == 1) {
+ size = 0;
+ } else if (dlci->adaption == 2) {
+ size = 1;
+ if (brk > 0)
+ size++;
+ } else {
+ pr_err("%s: unsupported adaption %d\n", __func__,
+ dlci->adaption);
+ }
+
+ msg = gsm_data_alloc(gsm, dlci->addr, size, gsm->ftype);
+ if (!msg) {
+ pr_err("%s: gsm_data_alloc error", __func__);
+ return -ENOMEM;
+ }
+ dp = msg->data;
+ switch (dlci->adaption) {
+ case 1: /* Unstructured */
+ break;
+ case 2: /* Unstructured with modem bits. */
+ if (brk == 0) {
+ *dp++ = (gsm_encode_modem(dlci) << 1) | EA;
+ } else {
+ *dp++ = gsm_encode_modem(dlci) << 1;
+ *dp++ = (brk << 4) | 2 | EA; /* Length, Break, EA */
+ }
+ break;
+ default:
+ /* Handled above */
+ break;
+ }
+
+ __gsm_data_queue(dlci, msg);
+ return size;
+}
+
+/**
* gsm_dlci_data_sweep - look for data to send
* @gsm: the GSM mux
*
@@ -1464,7 +1521,7 @@ static void gsm_dlci_open(struct gsm_dlc
pr_debug("DLCI %d goes open.\n", dlci->addr);
/* Send current modem state */
if (dlci->addr)
- gsmtty_modem_update(dlci, 0);
+ gsm_modem_update(dlci, 0);
wake_up(&dlci->gsm->event);
}

@@ -2897,12 +2954,43 @@ static struct tty_ldisc_ops tty_ldisc_pa

#define TX_SIZE 512

-static int gsmtty_modem_update(struct gsm_dlci *dlci, u8 brk)
+/**
+ * gsm_modem_upd_via_data - send modem bits via convergence layer
+ * @dlci: channel
+ * @brk: break signal
+ *
+ * Send an empty frame to signal mobile state changes and to transmit the
+ * break signal for adaption 2.
+ */
+
+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;
+
+ spin_lock_irqsave(&gsm->tx_lock, flags);
+ gsm_dlci_modem_output(gsm, dlci, brk);
+ spin_unlock_irqrestore(&gsm->tx_lock, flags);
+}
+
+/**
+ * gsm_modem_upd_via_msc - send modem bits via control frame
+ * @dlci: channel
+ * @brk: break signal
+ */
+
+static int gsm_modem_upd_via_msc(struct gsm_dlci *dlci, u8 brk)
{
u8 modembits[3];
struct gsm_control *ctrl;
int len = 2;

+ if (dlci->gsm->encoding != 0)
+ return 0;
+
modembits[0] = (dlci->addr << 2) | 2 | EA; /* DLCI, Valid, EA */
if (!brk) {
modembits[1] = (gsm_encode_modem(dlci) << 1) | EA;
@@ -2917,6 +3005,27 @@ static int gsmtty_modem_update(struct gs
return gsm_control_wait(dlci->gsm, ctrl);
}

+/**
+ * gsm_modem_update - send modem status line state
+ * @dlci: channel
+ * @brk: break signal
+ */
+
+static int gsm_modem_update(struct gsm_dlci *dlci, u8 brk)
+{
+ if (dlci->adaption == 2) {
+ /* Send convergence layer type 2 empty data frame. */
+ gsm_modem_upd_via_data(dlci, brk);
+ return 0;
+ } else if (dlci->gsm->encoding == 0) {
+ /* Send as MSC control message. */
+ return gsm_modem_upd_via_msc(dlci, brk);
+ }
+
+ /* Modem status lines are not supported. */
+ return -EPROTONOSUPPORT;
+}
+
static int gsm_carrier_raised(struct tty_port *port)
{
struct gsm_dlci *dlci = container_of(port, struct gsm_dlci, port);
@@ -2949,7 +3058,7 @@ static void gsm_dtr_rts(struct tty_port
modem_tx &= ~(TIOCM_DTR | TIOCM_RTS);
if (modem_tx != dlci->modem_tx) {
dlci->modem_tx = modem_tx;
- gsmtty_modem_update(dlci, 0);
+ gsm_modem_update(dlci, 0);
}
}

@@ -3140,7 +3249,7 @@ static int gsmtty_tiocmset(struct tty_st

if (modem_tx != dlci->modem_tx) {
dlci->modem_tx = modem_tx;
- return gsmtty_modem_update(dlci, 0);
+ return gsm_modem_update(dlci, 0);
}
return 0;
}
@@ -3201,7 +3310,7 @@ static void gsmtty_throttle(struct tty_s
dlci->modem_tx &= ~TIOCM_RTS;
dlci->throttled = true;
/* Send an MSC with RTS cleared */
- gsmtty_modem_update(dlci, 0);
+ gsm_modem_update(dlci, 0);
}

static void gsmtty_unthrottle(struct tty_struct *tty)
@@ -3213,7 +3322,7 @@ static void gsmtty_unthrottle(struct tty
dlci->modem_tx |= TIOCM_RTS;
dlci->throttled = false;
/* Send an MSC with RTS set */
- gsmtty_modem_update(dlci, 0);
+ gsm_modem_update(dlci, 0);
}

static int gsmtty_break_ctl(struct tty_struct *tty, int state)
@@ -3231,7 +3340,7 @@ static int gsmtty_break_ctl(struct tty_s
if (encode > 0x0F)
encode = 0x0F; /* Best effort */
}
- return gsmtty_modem_update(dlci, encode);
+ return gsm_modem_update(dlci, encode);
}

static void gsmtty_cleanup(struct tty_struct *tty)