// SPDX-License-Identifier: GPL-2.0 /* * 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 * * * Outgoing path: * tty -> DLCI fifo -> scheduler -> GSM MUX data queue ---o-> ldisc * control message -> GSM MUX control queue --´ * * Incoming path: * ldisc -> gsm_queue() -o--> tty * `-> gsm_control_response() * * TO DO: * Mostly done: ioctls for setting modes/timing * Partly done: hooks so you can pull off frames to non tty devs * Restart DLCI 0 when it closes ? * Improve the tx engine * Resolve tx side locking by adding a queue_head and routing * all control traffic via it * General tidy/document * Review the locking/move to refcounts more (mux now moved to an * alloc/free model ready) * Use newest tty open/close port helpers and install hooks * What to do about power functions ? * Termios setting and negotiation * Do we need a 'which mux are you' ioctl to correlate mux and tty sets *
*/
/* * Each active data link has a gsm_dlci structure associated which ties * the link layer to an optional tty (if the tty side is open). To avoid * complexity right now these are only ever freed up when the mux is * shut down. * * At the moment we don't free DLCI objects until the mux is torn down * this avoid object life time issues but might be worth review later.
*/
/* Total number of supported devices */ #define GSM_TTY_MINORS 256
/* DLCI 0, 62/63 are special or reserved see gsmtty_open */
#define NUM_DLCI 64
/* * DLCI 0 is used to pass control blocks out of band of the data * flow (and with a higher link priority). One command can be outstanding * at a time and we use this structure to manage them. They are created * and destroyed by the user context, and updated by the receive paths * and timers
*/
struct gsm_control {
u8 cmd; /* Command we are issuing */
u8 *data; /* Data for the command in case we retransmit */ int len; /* Length of block for retransmission */ int done; /* Done flag */ int error; /* Error if any */
};
/* * Each GSM mux we have is represented by this structure. If we are * operating as an ldisc then we use this structure as our ldisc * state. We need to sort out lifetimes and locking with respect * to the gsm mux array. For now we don't free DLCI objects that * have been instantiated until the mux itself is terminated. * * To consider further: tty open versus mux shutdown.
*/
struct gsm_mux { struct tty_struct *tty; /* The tty our ldisc is bound to */
spinlock_t lock; struct mutex mutex; unsignedint num; struct kref ref;
/* Events on the GSM channel */
wait_queue_head_t event;
/* Method for the receiver side */ void (*receive)(struct gsm_mux *gsm, u8 ch);
/* Link Layer */ unsignedint mru; unsignedint mtu; int initiator; /* Did we initiate connection */ bool dead; /* Has the mux been shut down */ struct gsm_dlci *dlci[NUM_DLCI]; int old_c_iflag; /* termios c_iflag value before attach */ bool constipated; /* Asked by remote to shut up */ bool has_devices; /* Devices were registered */
/* Control messages */ 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 */
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 */ signedint ka_retries; /* Keep-alive retry counter, -1 if not yet initialized */
/* Configuration */ int adaption; /* 1 or 2 supported */
u8 ftype; /* UI or UIH */ int t1, t2; /* Timers in 1/100th of a sec */ unsignedint 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 */
/** * gsm_fcs_add - update FCS * @fcs: Current FCS * @c: Next data * * Update the FCS to include c. Uses the algorithm in the specification * notes.
*/
/** * gsm_fcs_add_block - update FCS for a block * @fcs: Current FCS * @c: buffer of data * @len: length of buffer * * Update the FCS to include c. Uses the algorithm in the specification * notes.
*/
staticinline u8 gsm_fcs_add_block(u8 fcs, u8 *c, int len)
{ while (len--)
fcs = gsm_fcs8[fcs ^ *c++]; return fcs;
}
/** * gsm_read_ea - read a byte into an EA * @val: variable holding value * @c: byte going into the EA * * Processes one byte of an EA. Updates the passed variable * and returns 1 if the EA is now completely read
*/
staticint gsm_read_ea(unsignedint *val, u8 c)
{ /* Add the next 7 bits into the value */
*val <<= 7;
*val |= c >> 1; /* Was this the last byte of the EA 1 = yes*/ return c & EA;
}
/** * gsm_read_ea_val - read a value until EA * @val: variable holding value * @data: buffer of data * @dlen: length of data * * Processes an EA value. Updates the passed variable and * returns the processed data length.
*/ staticunsignedint gsm_read_ea_val(unsignedint *val, const u8 *data, int dlen)
{ unsignedint len = 0;
for (; dlen > 0; dlen--) {
len++; if (gsm_read_ea(val, *data++)) break;
} return len;
}
/** * gsm_encode_modem - encode modem data bits * @dlci: DLCI to encode from * * Returns the correct GSM encoded modem status bits (6 bit field) for * the current status of the DLCI and attached tty object
*/
static u8 gsm_encode_modem(conststruct gsm_dlci *dlci)
{
u8 modembits = 0; /* FC is true flow control not modem bits */ if (dlci->throttled)
modembits |= MDM_FC; if (dlci->modem_tx & TIOCM_DTR)
modembits |= MDM_RTC; if (dlci->modem_tx & TIOCM_RTS)
modembits |= MDM_RTR; if (dlci->modem_tx & TIOCM_RI)
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;
}
/** * gsm_register_devices - register all tty devices for a given mux index * * @driver: the tty driver that describes the tty devices * @index: the mux number is used to calculate the minor numbers of the * ttys for this mux and may differ from the position in the * mux array.
*/ staticint gsm_register_devices(struct tty_driver *driver, unsignedint index)
{ struct device *dev; int i; unsignedint base;
if (!driver || index >= MAX_MUX) return -EINVAL;
base = index * NUM_DLCI; /* first minor for this index */ for (i = 1; i < NUM_DLCI; i++) { /* Don't register device 0 - this is the control channel * and not a usable tty interface
*/
dev = tty_register_device(gsm_tty_driver, base + i, NULL); if (IS_ERR(dev)) { if (debug & DBG_ERRORS)
pr_info("%s failed to register device minor %u",
__func__, base + i); for (i--; i >= 1; i--)
tty_unregister_device(gsm_tty_driver, base + i); return PTR_ERR(dev);
}
}
return 0;
}
/** * gsm_unregister_devices - unregister all tty devices for a given mux index * * @driver: the tty driver that describes the tty devices * @index: the mux number is used to calculate the minor numbers of the * ttys for this mux and may differ from the position in the * mux array.
*/ staticvoid gsm_unregister_devices(struct tty_driver *driver, unsignedint index)
{ int i; unsignedint base;
if (!driver || index >= MAX_MUX) return;
base = index * NUM_DLCI; /* first minor for this index */ for (i = 1; i < NUM_DLCI; i++) { /* Don't unregister device 0 - this is the control * channel and not a usable tty interface
*/
tty_unregister_device(gsm_tty_driver, base + i);
}
}
/** * gsm_print_packet - display a frame for debug * @hdr: header to print before decode * @addr: address EA from the frame * @cr: C/R bit seen as initiator * @control: control including PF bit * @data: following data bytes * @dlen: length of data * * Displays a packet in human readable format for debugging purposes. The * style is based on amateur radio LAP-B dump display.
*/
staticvoid gsm_print_packet(constchar *hdr, int addr, int cr,
u8 control, const u8 *data, int dlen)
{ if (!(debug & DBG_DUMP)) return; /* Only show user payload frames if debug & DBG_PAYLOAD */ if (!(debug & DBG_PAYLOAD) && addr != 0) if ((control & ~PF) == UI || (control & ~PF) == UIH) return;
pr_info("%s %d) %c: ", hdr, addr, "RC"[cr]);
switch (control & ~PF) { case SABM:
pr_cont("SABM"); break; case UA:
pr_cont("UA"); break; case DISC:
pr_cont("DISC"); break; case DM:
pr_cont("DM"); break; case UI:
pr_cont("UI"); break; case UIH:
pr_cont("UIH"); break; default: if (!(control & 0x01)) {
pr_cont("I N(S)%d N(R)%d",
(control & 0x0E) >> 1, (control & 0xE0) >> 5);
} elseswitch (control & 0x0F) { case RR:
pr_cont("RR(%d)", (control & 0xE0) >> 5); break; case RNR:
pr_cont("RNR(%d)", (control & 0xE0) >> 5); break; case REJ:
pr_cont("REJ(%d)", (control & 0xE0) >> 5); break; default:
pr_cont("[%02X]", control);
}
}
if (control & PF)
pr_cont("(P)"); else
pr_cont("(F)");
gsm_hex_dump_bytes(NULL, data, dlen);
}
/* * Link level transmission side
*/
/** * gsm_stuff_frame - bytestuff a packet * @input: input buffer * @output: output buffer * @len: length of input * * Expand a buffer by bytestuffing it. The worst case size change * is doubling and the caller is responsible for handing out * suitable sized buffers.
*/
staticint gsm_stuff_frame(const u8 *input, u8 *output, int len)
{ int olen = 0; while (len--) { if (*input == GSM1_SOF || *input == GSM1_ESCAPE
|| (*input & ISO_IEC_646_MASK) == XON
|| (*input & ISO_IEC_646_MASK) == XOFF) {
*output++ = GSM1_ESCAPE;
*output++ = *input++ ^ GSM1_ESCAPE_BITS;
olen++;
} else
*output++ = *input++;
olen++;
} return olen;
}
/** * gsm_send - send a control frame * @gsm: our GSM mux * @addr: address for control frame * @cr: command/response bit seen as initiator * @control: control byte including PF bit * * Format up and transmit a control frame. These should be transmitted * ahead of data when they are needed.
*/ staticint gsm_send(struct gsm_mux *gsm, int addr, int cr, int control)
{ struct gsm_msg *msg;
u8 *dp; int ocr; unsignedlong flags;
msg = gsm_data_alloc(gsm, addr, 0, control); if (!msg) return -ENOMEM;
/* toggle C/R coding if not initiator */
ocr = cr ^ (gsm->initiator ? 0 : 1);
/** * gsm_dlci_clear_queues - remove outstanding data for a DLCI * @gsm: mux * @dlci: clear for this DLCI * * Clears the data queues for a given DLCI.
*/ staticvoid gsm_dlci_clear_queues(struct gsm_mux *gsm, struct gsm_dlci *dlci)
{ struct gsm_msg *msg, *nmsg; int addr = dlci->addr; unsignedlong flags;
/* Clear data packets in MUX write queue */
spin_lock_irqsave(&gsm->tx_lock, flags);
list_for_each_entry_safe(msg, nmsg, &gsm->tx_data_list, list) { if (msg->addr != addr) continue;
gsm->tx_bytes -= msg->len;
list_del(&msg->list);
kfree(msg);
}
spin_unlock_irqrestore(&gsm->tx_lock, flags);
}
/** * gsm_response - send a control response * @gsm: our GSM mux * @addr: address for control frame * @control: control byte including PF bit * * Format up and transmit a link level response frame.
*/
staticinlinevoid gsm_response(struct gsm_mux *gsm, int addr, int control)
{
gsm_send(gsm, addr, 0, control);
}
/** * gsm_command - send a control command * @gsm: our GSM mux * @addr: address for control frame * @control: control byte including PF bit * * Format up and transmit a link level command frame.
*/
staticinlinevoid gsm_command(struct gsm_mux *gsm, int addr, int control)
{
gsm_send(gsm, addr, 1, control);
}
/* Data transmission */
#define HDR_LEN 6 /* ADDR CTRL [LEN.2] DATA FCS */
/** * gsm_data_alloc - allocate data frame * @gsm: GSM mux * @addr: DLCI address * @len: length excluding header and FCS * @ctrl: control byte * * Allocate a new data buffer for sending frames with data. Space is left * at the front for header bytes but that is treated as an implementation * detail and not for the high level code to use
*/
/** * gsm_send_packet - sends a single packet * @gsm: GSM Mux * @msg: packet to send * * The given packet is encoded and sent out. No memory is freed. * The caller must hold the gsm tx lock.
*/ staticint gsm_send_packet(struct gsm_mux *gsm, struct gsm_msg *msg)
{ int len, ret;
ret = gsmld_output(gsm, gsm->txframe, len); if (ret <= 0) return ret; /* FIXME: Can eliminate one SOF in many more cases */
gsm->tx_bytes -= msg->len;
return 0;
}
/** * gsm_is_flow_ctrl_msg - checks if flow control message * @msg: message to check * * Returns true if the given message is a flow control command of the * control channel. False is returned in any other case.
*/ staticbool gsm_is_flow_ctrl_msg(struct gsm_msg *msg)
{ unsignedint cmd;
if (msg->addr > 0) returnfalse;
switch (msg->ctrl & ~PF) { case UI: case UIH:
cmd = 0; if (gsm_read_ea_val(&cmd, msg->data + 2, msg->len - 2) < 1) break; switch (cmd & ~PF) { case CMD_FCOFF: case CMD_FCON: returntrue;
} break;
}
returnfalse;
}
/** * gsm_data_kick - poke the queue * @gsm: GSM Mux * * The tty device has called us to indicate that room has appeared in * the transmit queue. Ram more data into the pipe if we have any. * If we have been flow-stopped by a CMD_FCOFF, then we can only * send messages on DLCI0 until CMD_FCON. The caller must hold * the gsm tx lock.
*/ staticint gsm_data_kick(struct gsm_mux *gsm)
{ struct gsm_msg *msg, *nmsg; struct gsm_dlci *dlci; int ret;
clear_bit(TTY_DO_WRITE_WAKEUP, &gsm->tty->flags);
/* Serialize control messages and control channel messages first */
list_for_each_entry_safe(msg, nmsg, &gsm->tx_ctrl_list, list) { if (gsm->constipated && !gsm_is_flow_ctrl_msg(msg)) continue;
ret = gsm_send_packet(gsm, msg); switch (ret) { case -ENOSPC: return -ENOSPC; case -ENODEV: /* ldisc not open */
gsm->tx_bytes -= msg->len;
list_del(&msg->list);
kfree(msg); continue; default: if (ret >= 0) {
list_del(&msg->list);
kfree(msg);
} break;
}
}
if (gsm->constipated) return -EAGAIN;
/* Serialize other channels */ if (list_empty(&gsm->tx_data_list)) return 0;
list_for_each_entry_safe(msg, nmsg, &gsm->tx_data_list, list) {
dlci = gsm->dlci[msg->addr]; /* Send only messages for DLCIs with valid state */ if (dlci->state != DLCI_OPEN) {
gsm->tx_bytes -= msg->len;
list_del(&msg->list);
kfree(msg); continue;
}
ret = gsm_send_packet(gsm, msg); switch (ret) { case -ENOSPC: return -ENOSPC; case -ENODEV: /* ldisc not open */
gsm->tx_bytes -= msg->len;
list_del(&msg->list);
kfree(msg); continue; default: if (ret >= 0) {
list_del(&msg->list);
kfree(msg);
} break;
}
}
return 1;
}
/** * __gsm_data_queue - queue a UI or UIH frame * @dlci: DLCI sending the data * @msg: message queued * * Add data to the transmit queue and try and get stuff moving * out of the mux tty if not already doing so. The Caller must hold * the gsm tx lock.
*/
/** * gsm_data_queue - queue a UI or UIH frame * @dlci: DLCI sending the data * @msg: message queued * * Add data to the transmit queue and try and get stuff moving * out of the mux tty if not already doing so. Take the * the gsm tx lock and dlci lock.
*/
/** * gsm_dlci_data_output - try and push data out of a DLCI * @gsm: mux * @dlci: the DLCI to pull data from * * Pull data from a DLCI and send it into the transmit queue if there * 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_lock of the mux.
*/
/* Notify upper layer about available send space. */
tty_port_tty_wakeup(&dlci->port);
__gsm_data_queue(dlci, msg); /* Bytes of data we used up */ return size;
}
/** * gsm_dlci_data_output_framed - try and push data out of a DLCI * @gsm: mux * @dlci: the DLCI to pull data from * * Pull data from a DLCI and send it into the transmit queue if there * 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_lock of the mux.
*/
staticint gsm_dlci_data_output_framed(struct gsm_mux *gsm, struct gsm_dlci *dlci)
{ struct gsm_msg *msg;
u8 *dp; int len, size; int last = 0, first = 0; int overhead = 0;
/* One byte per frame is used for B/F flags */ if (dlci->adaption == 4)
overhead = 1;
/* dlci->skb is locked by tx_lock */ if (dlci->skb == NULL) {
dlci->skb = skb_dequeue_tail(&dlci->skb_list); if (dlci->skb == NULL) return 0;
first = 1;
}
len = dlci->skb->len + overhead;
/* MTU/MRU count only the data bits */ 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 = dlci->mtu;
} else
last = 1;
if (dlci->adaption == 4) { /* Interruptible framed (Packetised Data) */ /* Flag byte to carry the start/end info */
*dp++ = last << 7 | first << 6 | 1; /* EA */
len--;
}
memcpy(dp, dlci->skb->data, len);
skb_pull(dlci->skb, len);
__gsm_data_queue(dlci, msg); if (last) {
dev_kfree_skb_any(dlci->skb);
dlci->skb = NULL;
} return size;
}
/** * 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.
*/
/** * gsm_dlci_data_sweep - look for data to send * @gsm: the GSM mux * * Sweep the GSM mux channels in priority order looking for ones with * data to send. We could do with optimising this scan a bit. We aim * to fill the queue totally or up to TX_THRESH_HI bytes. Once we hit * TX_THRESH_LO we get called again * * FIXME: We should round robin between groups and in theory you can * renegotiate DLCI priorities with optional stuff. Needs optimising.
*/
staticint gsm_dlci_data_sweep(struct gsm_mux *gsm)
{ /* Priority ordering: We should do priority with RR of the groups */ int i, len, ret = 0; bool sent; struct gsm_dlci *dlci;
while (gsm->tx_bytes < TX_THRESH_HI) { for (sent = false, i = 1; i < NUM_DLCI; i++) {
dlci = gsm->dlci[i]; /* skip unused or blocked channel */ if (!dlci || dlci->constipated) continue; /* skip channels with invalid state */ if (dlci->state != DLCI_OPEN) continue; /* count the sent data per adaption */ if (dlci->adaption < 3 && !dlci->net)
len = gsm_dlci_data_output(gsm, dlci); else
len = gsm_dlci_data_output_framed(gsm, dlci); /* on error exit */ if (len < 0) return ret; if (len > 0) {
ret++;
sent = true; /* The lower DLCs can starve the higher DLCs! */ break;
} /* try next */
} if (!sent) break;
}
return ret;
}
/** * gsm_dlci_data_kick - transmit if possible * @dlci: DLCI to kick * * Transmit data from this DLCI 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.
*/
staticvoid gsm_dlci_data_kick(struct gsm_dlci *dlci)
{ unsignedlong flags; int sweep;
if (dlci->constipated) return;
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) { if (dlci->net)
gsm_dlci_data_output_framed(dlci->gsm, dlci); else
gsm_dlci_data_output(dlci->gsm, dlci);
} if (sweep)
gsm_dlci_data_sweep(dlci->gsm);
spin_unlock_irqrestore(&dlci->gsm->tx_lock, flags);
}
/* * Control message processing
*/
/** * gsm_control_command - send a command frame to a control * @gsm: gsm channel * @cmd: the command to use * @data: data to follow encoded info * @dlen: length of data * * Encode up and queue a UI/UIH frame containing our command.
*/ staticint 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];
/** * gsm_control_reply - send a response frame to a control * @gsm: gsm channel * @cmd: the command to use * @data: data to follow encoded info * @dlen: length of data * * Encode up and queue a UI/UIH frame containing our response.
*/
staticvoid 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];
/** * gsm_process_modem - process received modem status * @tty: virtual tty bound to the DLCI * @dlci: DLCI to affect * @modem: modem bits (full EA) * @slen: number of signal octets * * Used when a modem control message or line state inline in adaption * layer 2 is processed. Sort out the local modem state and throttles
*/
staticvoid gsm_process_modem(struct tty_struct *tty, struct gsm_dlci *dlci,
u32 modem, int slen)
{ int mlines = 0;
u8 brk = 0; int fc;
/* The modem status command can either contain one octet (V.24 signals) * or two octets (V.24 signals + break signals). This is specified in * section 5.4.6.3.7 of the 07.10 mux spec.
*/
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__);
gsm->unsupported++; 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;
} elseif (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;
} elseif (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 * @clen: command length * * We have received a modem status control message. This is used by * the GSM mux protocol to pass virtual modem line status and optionally * to indicate break signals. Unpack it, convert to Linux representation * and if need be stuff a break message down the tty.
*/
staticvoid gsm_control_modem(struct gsm_mux *gsm, const u8 *data, int clen)
{ unsignedint addr = 0; unsignedint modem = 0; struct gsm_dlci *dlci; int len = clen; int cl = clen; const u8 *dp = data; struct tty_struct *tty;
len = gsm_read_ea_val(&addr, data, cl); if (len < 1) return;
/** * 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.
*/ staticvoid gsm_control_negotiation(struct gsm_mux *gsm, unsignedint cr, const u8 *data, unsignedint dlen)
{ unsignedint 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)) {
gsm->open_error++; return;
}
/* Too late for parameter negotiation? */ 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;
}
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)); elseif (debug & DBG_ERRORS)
pr_info("%s PN invalid\n", __func__);
} elseif (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->open_error++;
}
}
/** * gsm_control_rls - remote line status * @gsm: GSM channel * @data: data bytes * @clen: data length * * The modem sends us a two byte message on the control channel whenever * it wishes to send us an error state from the virtual link. Stuff * this into the uplink tty if present
*/
staticvoid gsm_control_rls(struct gsm_mux *gsm, const u8 *data, int clen)
{ struct tty_port *port; unsignedint addr = 0;
u8 bits; int len = clen; const u8 *dp = data;
while (gsm_read_ea(&addr, *dp++) == 0) {
len--; if (len == 0) return;
} /* Must be at least one byte following ea */
len--; if (len <= 0) return;
addr >>= 1; /* Closed port, or invalid ? */ if (addr == 0 || addr >= NUM_DLCI || gsm->dlci[addr] == NULL) return; /* No error ? */
bits = *dp; if ((bits & 1) == 0) return;
port = &gsm->dlci[addr]->port;
if (bits & 2)
tty_insert_flip_char(port, 0, TTY_OVERRUN); if (bits & 4)
tty_insert_flip_char(port, 0, TTY_PARITY); if (bits & 8)
tty_insert_flip_char(port, 0, TTY_FRAME);
/** * gsm_control_message - DLCI 0 control processing * @gsm: our GSM mux * @command: the command EA * @data: data beyond the command/length EAs * @clen: length * * Input processor for control messages from the other end of the link. * Processes the incoming request and queues a response frame or an * NSC response if not supported
*/
switch (command) { case CMD_CLD: { struct gsm_dlci *dlci = gsm->dlci[0]; /* Modem wishes to close down */ if (dlci) {
dlci->dead = true;
gsm->dead = true;
gsm_dlci_begin_close(dlci);
}
} break; case CMD_TEST: /* Modem wishes to test, reply with the data */
gsm_control_reply(gsm, CMD_TEST, data, clen); break; case CMD_FCON: /* Modem can accept data again */
gsm->constipated = false;
gsm_control_reply(gsm, CMD_FCON, NULL, 0); /* Kick the link in case it is idling */
gsmld_write_trigger(gsm); break; case CMD_FCOFF: /* Modem wants us to STFU */
gsm->constipated = true;
gsm_control_reply(gsm, CMD_FCOFF, NULL, 0); break; case CMD_MSC: /* Out of band modem line change indicator for a DLCI */
gsm_control_modem(gsm, data, clen); break; case CMD_RLS: /* Out of band error reception for a DLCI */
gsm_control_rls(gsm, data, clen); break; case CMD_PSC: /* 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_RPN: /* Remote port negotiation */ case CMD_SNC: /* Service negotiation command */
gsm->unsupported++;
fallthrough; default: /* Reply to bad commands with an NSC */
buf[0] = command;
gsm_control_reply(gsm, CMD_NSC, buf, 1); break;
}
}
/** * gsm_control_response - process a response to our control * @gsm: our GSM mux * @command: the command (response) EA * @data: data beyond the command/length EA * @clen: length * * Process a response to an outstanding command. We only allow a single * control message in flight so this is fairly easy. All the clean up * is done by the caller, we just update the fields, flag it as done * and return
*/
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 */
timer_delete(&gsm->t2_timer);
gsm->pending_cmd = NULL; /* Rejected by the other end */ if (command == CMD_NSC)
ctrl->error = -EOPNOTSUPP;
ctrl->done = 1;
wake_up(&gsm->event); /* Or did we receive the PN response to our PN command */
} elseif (command == CMD_PN) {
gsm_control_negotiation(gsm, 0, data, clen); /* Or did we receive the TEST response to our TEST command */
} elseif (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.
*/
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;
} elseif (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 = (signedint)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);
}
/** * gsm_control_transmit - send control packet * @gsm: gsm mux * @ctrl: frame to send * * Send out a pending control command (called under control lock)
*/
/** * gsm_control_retransmit - retransmit a control frame * @t: timer contained in our gsm object * * Called off the T2 timer expiry in order to retransmit control frames * that have been lost in the system somewhere. The control_lock protects * us from colliding with another sender or a receive completion event. * In that situation the timer may still occur in a small window but * gsm->pending_cmd will be NULL and we just let the timer expire.
*/
/** * gsm_control_send - send a control frame on DLCI 0 * @gsm: the GSM channel * @command: command to send including CR bit * @data: bytes of data (must be kmalloced) * @clen: length of the block to send * * Queue and dispatch a control command. Only one command can be * active at a time. In theory more can be outstanding but the matching * gets really complicated so for now stick to one outstanding.
*/
/** * gsm_control_wait - wait for a control to finish * @gsm: GSM mux * @control: control we are waiting on * * Waits for the control to complete or time out. Frees any used * resources and returns 0 for success, or an error if the remote * rejected or ignored the request.
*/
/** * gsm_dlci_close - a DLCI has closed * @dlci: DLCI that closed * * Perform processing when moving a DLCI into closed state. If there * is an attached tty this is hung up
*/
staticvoid gsm_dlci_close(struct gsm_dlci *dlci)
{
timer_delete(&dlci->t1); if (debug & DBG_ERRORS)
pr_debug("DLCI %d goes closed.\n", dlci->addr);
dlci->state = DLCI_CLOSED; /* Prevent us from sending data before the link is up again */
dlci->constipated = true; if (dlci->addr != 0) {
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, false);
wake_up_interruptible(&dlci->port.open_wait);
} 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_all(&dlci->gsm->event);
}
/** * gsm_dlci_open - a DLCI has opened * @dlci: DLCI that opened * * Perform processing when moving a DLCI into open state.
*/
/* Note that SABM UA .. SABM UA first UA lost can mean that we go
open -> open */
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_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);
}
/** * 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.
*/ staticint 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, ¶ms); 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 *)¶ms, sizeof(params));
return ret;
}
/** * gsm_dlci_t1 - T1 timer expiry * @t: timer contained in the DLCI that opened * * The T1 timer handles retransmits of control frames (essentially of * SABM and DISC). We resend the command until the retry count runs out * in which case an opening port goes back to closed and a closing port * is simply put into closed state (any further frames from the other * end will get a DM response) * * 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 receives DM.
*/
/** * gsm_dlci_begin_open - start channel open procedure * @dlci: DLCI to open * * Commence opening a DLCI from the Linux side. We issue SABM messages * 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.
*/
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_WAITING_CONFIG: case DLCI_CLOSING:
dlci->retries = gsm->n2; if (!need_pn) {
dlci->state = DLCI_OPENING; 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; if (gsm_dlci_negotiate(dlci) != 0) {
gsm_dlci_close(dlci); return;
}
}
mod_timer(&dlci->t1, jiffies + gsm->t1 * HZ / 100); break; default: break;
}
}
/** * gsm_dlci_set_opening - change state to opening * @dlci: DLCI to open * * Change internal state to wait for DLCI open from initiator side. * We set off timers and responses upon reception of an SABM.
*/ staticvoid 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; default: break;
}
}
/** * gsm_dlci_set_wait_config - wait for channel configuration * @dlci: DLCI to configure * * Wait for a DLCI configuration from the application.
*/ staticvoid 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 * * Commence closing a DLCI from the Linux side. We issue DISC messages * to the modem which should then reply with a UA, at which point we * will move into closed state. Closing is done asynchronously with retry * off timers. We may also receive a DM reply from the other end which * indicates the channel was already closed.
*/
/** * gsm_dlci_data - data arrived * @dlci: channel * @data: block of bytes received * @clen: length of received block * * A UI or UIH frame has arrived which contains data for a channel * other than the control channel. If the relevant virtual tty is * open we shovel the bits down it, if not we drop them.
*/
if (debug & DBG_TTY)
pr_debug("%d bytes for tty\n", clen); switch (dlci->adaption) { /* Unsupported types */ case 4: /* Packetised interruptible data */ break; case 3: /* Packetised uininterruptible voice/data */ break; case 2: /* Asynchronous serial with line state in each frame */
len = gsm_read_ea_val(&modem, data, clen); if (len < 1) return;
tty = tty_port_tty_get(port); if (tty) {
gsm_process_modem(tty, dlci, modem, len);
tty_wakeup(tty);
tty_kref_put(tty);
} /* Skip processed modem data */
data += len;
clen -= len;
fallthrough; case 1: /* Line state will go via DLCI 0 controls only */ default:
tty_insert_flip_string(port, data, clen);
tty_flip_buffer_push(port);
}
}
/** * gsm_dlci_command - data arrived on control channel * @dlci: channel * @data: block of bytes received * @len: length of received block * * A UI or UIH frame has arrived which contains data for DLCI 0 the * control channel. This should contain a command EA followed by * control data bytes. The command EA contains a command/response bit * and we divide up the work accordingly.
*/
staticvoid gsm_dlci_command(struct gsm_dlci *dlci, const u8 *data, int len)
{ /* See what command is involved */ unsignedint command = 0; unsignedint clen = 0; unsignedint dlen;
/* read the command */
dlen = gsm_read_ea_val(&command, data, len);
len -= dlen;
data += dlen;
/* read any control data */
dlen = gsm_read_ea_val(&clen, data, len);
len -= dlen;
data += dlen;
/** * 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.
*/ staticvoid gsm_kick_timer(struct timer_list *t)
{ struct gsm_mux *gsm = timer_container_of(gsm, t, kick_timer); unsignedlong flags; int sent = 0;
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);
spin_unlock_irqrestore(&gsm->tx_lock, flags);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.