Received: by 2002:a19:771d:0:0:0:0:0 with SMTP id s29csp1279666lfc; Wed, 1 Jun 2022 14:02:45 -0700 (PDT) X-Google-Smtp-Source: ABdhPJyQYwmlKdgH825ypRMzirTDDiqnqFm8Uiw4Kv7y42UtmxSj9z3aE52OAI7IkbyoFmrVjMru X-Received: by 2002:aa7:9047:0:b0:519:36a2:9f63 with SMTP id n7-20020aa79047000000b0051936a29f63mr1498853pfo.60.1654117365600; Wed, 01 Jun 2022 14:02:45 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1654117365; cv=none; d=google.com; s=arc-20160816; b=TLNRm0cJJB+ME2nVJd+Gl8B4FV6LqjBzqEcLjl6nuR/xJOcuoh5o1+6o88X40BW/QG 8Pgg5SiqvdRrpHbRuYX4udOohoy3NELpOypbD0uTPZJxqF49GYIS+z6q5tZme8Kh9HBU bgdgRKRLU4Qaw6STaVv166t6jvqjkK7ilEreopC61uCsdNyPTWx6mhWvOOisDvZCoTnp QYBczQ4ppkXy91VSLTkoEbq7FaondpwnDo71ZAhd0cJ8th7ais8h5EpoCCBYixqneEW+ dVZ8HmNune1fzkSomuMHdOaYhdDst+szPMMUJNDxBgVjvPM3hBTsmWYDvnr8OxahzjVi 7N6g== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:feedback-id:content-transfer-encoding :mime-version:references:in-reply-to:message-id:date:subject:cc:to :from:dkim-signature; bh=SU5/lGFJR4RbkRXYBt9mKgl6seUGbFFUXysf8r7lpmw=; b=rTbDWBp48dFPBfqYgf+y1hqPgw4/UadgTSKG36ffY+pHJx2CoCHP4d3OWGJ1/w/q8V TjhAr93wiBZh24wMQm9SZYwU12UkpXKZC+/MQPgHJIRcqLQ+nkTZTPbOrMyxtjKACdrW zfG8C4/F23b6AcfEtEOsg6im+Qe8LO8EWCWv+0EOuEKZhNL6etB2rbhUAbCh/ZKWpmuq 7HZDsorpFtnsB3ft+fxGKAk2kYpTDtsXHR5QS2Jfzg+3kBkZLmcGQ8i6YwiekQUm5sPw 2pj1WrSxLRlxt4nL3QCNJY8hLVFRJAgtEtomMfOtVRIMOD3bdEcRXEgoybsphAGXAKyV TGWA== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@siemens.com header.s=fm1 header.b="h/2Je6Ch"; spf=softfail (google.com: domain of transitioning linux-kernel-owner@vger.kernel.org does not designate 23.128.96.19 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=siemens.com Return-Path: Received: from lindbergh.monkeyblade.net (lindbergh.monkeyblade.net. [23.128.96.19]) by mx.google.com with ESMTPS id k5-20020a632405000000b003fc75297697si3971072pgk.4.2022.06.01.14.02.45 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 01 Jun 2022 14:02:45 -0700 (PDT) Received-SPF: softfail (google.com: domain of transitioning linux-kernel-owner@vger.kernel.org does not designate 23.128.96.19 as permitted sender) client-ip=23.128.96.19; Authentication-Results: mx.google.com; dkim=pass header.i=@siemens.com header.s=fm1 header.b="h/2Je6Ch"; spf=softfail (google.com: domain of transitioning linux-kernel-owner@vger.kernel.org does not designate 23.128.96.19 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=siemens.com Received: from out1.vger.email (out1.vger.email [IPv6:2620:137:e000::1:20]) by lindbergh.monkeyblade.net (Postfix) with ESMTP id 3D95522FE4E; Wed, 1 Jun 2022 13:08:11 -0700 (PDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S241020AbiE3PjF (ORCPT + 99 others); Mon, 30 May 2022 11:39:05 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:43164 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S236695AbiE3Pi4 (ORCPT ); Mon, 30 May 2022 11:38:56 -0400 Received: from mta-64-228.siemens.flowmailer.net (mta-64-228.siemens.flowmailer.net [185.136.64.228]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id E542D27B for ; Mon, 30 May 2022 07:46:31 -0700 (PDT) Received: by mta-64-228.siemens.flowmailer.net with ESMTPSA id 202205301446299fbda42daf23deceb0 for ; Mon, 30 May 2022 16:46:30 +0200 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=fm1; d=siemens.com; i=daniel.starke@siemens.com; h=Date:From:Subject:To:Message-ID:MIME-Version:Content-Type:Content-Transfer-Encoding:Cc:References:In-Reply-To; bh=SU5/lGFJR4RbkRXYBt9mKgl6seUGbFFUXysf8r7lpmw=; b=h/2Je6ChH1f2NTGMynt7u3N+hrT344aZmTNnQJFClFe9pL6BsICBkAC6kBSRGF+IynFIuQ Od861ccpZ8vosOSI+uP4lmkIQkHnuz9GH5GuW8iCUEDwP7Cjc7lETP3KFsc2tNMIUWQdlQbT FMkjWDfUaEXOdoi3lq7I1wB5yROvk=; From: "D. Starke" To: linux-serial@vger.kernel.org, gregkh@linuxfoundation.org, jirislaby@kernel.org Cc: linux-kernel@vger.kernel.org, Daniel Starke Subject: [PATCH v3 6/9] tty: n_gsm: fix deadlock and link starvation in outgoing data path Date: Mon, 30 May 2022 16:45:09 +0200 Message-Id: <20220530144512.2731-6-daniel.starke@siemens.com> In-Reply-To: <20220530144512.2731-1-daniel.starke@siemens.com> References: <20220530144512.2731-1-daniel.starke@siemens.com> MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-Flowmailer-Platform: Siemens Feedback-ID: 519:519-314044:519-21489:flowmailer X-Spam-Status: No, score=-2.0 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,HEADER_FROM_DIFFERENT_DOMAINS, MAILING_LIST_MULTI,RDNS_NONE,SPF_HELO_NONE,T_SCC_BODY_TEXT_LINE autolearn=no autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org From: Daniel Starke The current implementation queues up new control and user packets as needed and processes this queue down to the ldisc in the same code path. That means that the upper and the lower layer are hard coupled in the code. Due to this deadlocks can happen as seen below while transmitting data, especially during ldisc congestion. Furthermore, the data channels starve the control channel on high transmission load on the ldisc. Introduce an additional control channel data queue to prevent timeouts and link hangups during ldisc congestion. This is being processed before the user channel data queue in gsm_data_kick(), i.e. with the highest priority. Put the queue to ldisc data path into a workqueue and trigger it whenever new data has been put into the transmission queue. Change gsm_dlci_data_sweep() accordingly to fill up the transmission queue until TX_THRESH_HI. This solves the locking issue, keeps latency low and provides good performance on high data load. Note that now all packets from a DLCI are removed from the internal queue if the associated DLCI was closed. This ensures that no data is sent by the introduced write task to an already closed DLCI. BUG: spinlock recursion on CPU#0, test_v24_loop/124 lock: serial8250_ports+0x3a8/0x7500, .magic: dead4ead, .owner: test_v24_loop/124, .owner_cpu: 0 CPU: 0 PID: 124 Comm: test_v24_loop Tainted: G O 5.18.0-rc2 #3 Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.15.0-1 04/01/2014 Call Trace: dump_stack_lvl+0x34/0x44 do_raw_spin_lock+0x76/0xa0 _raw_spin_lock_irqsave+0x72/0x80 uart_write_room+0x3b/0xc0 gsm_data_kick+0x14b/0x240 [n_gsm] gsmld_write_wakeup+0x35/0x70 [n_gsm] tty_wakeup+0x53/0x60 tty_port_default_wakeup+0x1b/0x30 serial8250_tx_chars+0x12f/0x220 serial8250_handle_irq.part.0+0xfe/0x150 serial8250_default_handle_irq+0x48/0x80 serial8250_interrupt+0x56/0xa0 __handle_irq_event_percpu+0x78/0x1f0 handle_irq_event+0x34/0x70 handle_fasteoi_irq+0x90/0x1e0 __common_interrupt+0x69/0x100 common_interrupt+0x48/0xc0 asm_common_interrupt+0x1e/0x40 RIP: 0010:__do_softirq+0x83/0x34e Code: 2a 0a ff 0f b7 ed c7 44 24 10 0a 00 00 00 48 c7 c7 51 2a 64 82 e8 2d e2 d5 ff 65 66 c7 05 83 af 1e 7e 00 00 fb b8 ff ff ff ff <49> c7 c2 40 61 80 82 0f bc c5 41 89 c4 41 83 c4 01 0f 84 e6 00 00 RSP: 0018:ffffc90000003f98 EFLAGS: 00000286 RAX: 00000000ffffffff RBX: 0000000000000000 RCX: 0000000000000000 RDX: 0000000000000000 RSI: ffffffff82642a51 RDI: ffffffff825bb5e7 RBP: 0000000000000200 R08: 00000008de3271a8 R09: 0000000000000000 R10: 0000000000000001 R11: 0000000000000000 R12: 0000000000000000 R13: 0000000000000030 R14: 0000000000000000 R15: 0000000000000000 ? __do_softirq+0x73/0x34e irq_exit_rcu+0xb5/0x100 common_interrupt+0xa4/0xc0 asm_common_interrupt+0x1e/0x40 RIP: 0010:_raw_spin_unlock_irqrestore+0x2e/0x50 Code: 00 55 48 89 fd 48 83 c7 18 53 48 89 f3 48 8b 74 24 10 e8 85 28 36 ff 48 89 ef e8 cd 58 36 ff 80 e7 02 74 01 fb bf 01 00 00 00 3d 97 33 ff 65 8b 05 96 23 2b 7e 85 c0 74 03 5b 5d c3 0f 1f 44 RSP: 0018:ffffc9000020fd08 EFLAGS: 00000202 RAX: 0000000000000000 RBX: 0000000000000246 RCX: 0000000000000000 RDX: 0000000000000004 RSI: ffffffff8257fd74 RDI: 0000000000000001 RBP: ffff8880057de3a0 R08: 00000008de233000 R09: 0000000000000000 R10: 0000000000000001 R11: 0000000000000000 R12: 0000000000000000 R13: 0000000000000100 R14: 0000000000000202 R15: ffff8880057df0b8 ? _raw_spin_unlock_irqrestore+0x23/0x50 gsmtty_write+0x65/0x80 [n_gsm] n_tty_write+0x33f/0x530 ? swake_up_all+0xe0/0xe0 file_tty_write.constprop.0+0x1b1/0x320 ? n_tty_flush_buffer+0xb0/0xb0 new_sync_write+0x10c/0x190 vfs_write+0x282/0x310 ksys_write+0x68/0xe0 do_syscall_64+0x3b/0x90 entry_SYSCALL_64_after_hwframe+0x44/0xae RIP: 0033:0x7f3e5e35c15c Code: 8b 7c 24 08 89 c5 e8 c5 ff ff ff 89 ef 89 44 24 08 e8 58 bc 02 00 8b 44 24 08 48 83 c4 10 5d c3 48 63 ff b8 01 00 00 00 0f 05 <48> 3d 00 f0 ff ff 76 10 48 8b 15 fd fc 05 00 f7 d8 64 89 02 48 83 RSP: 002b:00007ffcee77cd18 EFLAGS: 00000246 ORIG_RAX: 0000000000000001 RAX: ffffffffffffffda RBX: 00007ffcee77cd70 RCX: 00007f3e5e35c15c RDX: 0000000000000100 RSI: 00007ffcee77cd90 RDI: 0000000000000003 RBP: 0000000000000100 R08: 0000000000000000 R09: 7efefefefefefeff R10: 00007f3e5e3bddeb R11: 0000000000000246 R12: 00007ffcee77ce8f R13: 0000000000000001 R14: 000056214404e010 R15: 00007ffcee77cd90 Fixes: e1eaea46bb40 ("tty: n_gsm line discipline") Cc: stable@vger.kernel.org Signed-off-by: Daniel Starke --- drivers/tty/n_gsm.c | 410 ++++++++++++++++++++++++++++++-------------- 1 file changed, 280 insertions(+), 130 deletions(-) There have been no comments on v2, hence, no change was done. Link: https://lore.kernel.org/all/20220519070757.2096-6-daniel.starke@siemens.com/ diff --git a/drivers/tty/n_gsm.c b/drivers/tty/n_gsm.c index 1a6701ae11e7..b82efb63f4e6 100644 --- a/drivers/tty/n_gsm.c +++ b/drivers/tty/n_gsm.c @@ -5,6 +5,14 @@ * * * 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 @@ -210,6 +218,9 @@ struct gsm_mux { /* Events on the GSM channel */ wait_queue_head_t event; + /* ldisc send work */ + struct work_struct tx_work; + /* Bits for GSM mode decoding */ /* Framing Layer */ @@ -241,7 +252,8 @@ struct gsm_mux { unsigned int tx_bytes; /* TX data outstanding */ #define TX_THRESH_HI 8192 #define TX_THRESH_LO 2048 - struct list_head tx_list; /* Pending data packets */ + struct list_head tx_ctrl_list; /* Pending control packets */ + struct list_head tx_data_list; /* Pending data packets */ /* Control messages */ struct timer_list kick_timer; /* Kick TX queuing on timeout */ @@ -371,6 +383,11 @@ static const u8 gsm_fcs8[256] = { 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, + u8 ctrl); +static int gsm_send_packet(struct gsm_mux *gsm, struct gsm_msg *msg); +static void gsmld_write_trigger(struct gsm_mux *gsm); +static void gsmld_write_task(struct work_struct *work); /** * gsm_fcs_add - update FCS @@ -636,57 +653,73 @@ static int gsm_stuff_frame(const u8 *input, u8 *output, int len) * @cr: command/response bit seen as initiator * @control: control byte including PF bit * - * Format up and transmit a control frame. These do not go via the - * queueing logic as they should be transmitted ahead of data when - * they are needed. - * - * FIXME: Lock versus data TX path + * Format up and transmit a control frame. These should be transmitted + * ahead of data when they are needed. */ - -static void gsm_send(struct gsm_mux *gsm, int addr, int cr, int control) +static int gsm_send(struct gsm_mux *gsm, int addr, int cr, int control) { - int len; - u8 cbuf[10]; - u8 ibuf[3]; + struct gsm_msg *msg; + u8 *dp; int ocr; + unsigned long 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); - switch (gsm->encoding) { - case 0: - cbuf[0] = GSM0_SOF; - cbuf[1] = (addr << 2) | (ocr << 1) | EA; - cbuf[2] = control; - cbuf[3] = EA; /* Length of data = 0 */ - cbuf[4] = 0xFF - gsm_fcs_add_block(INIT_FCS, cbuf + 1, 3); - cbuf[5] = GSM0_SOF; - len = 6; - break; - case 1: - case 2: - /* Control frame + packing (but not frame stuffing) in mode 1 */ - ibuf[0] = (addr << 2) | (ocr << 1) | EA; - ibuf[1] = control; - ibuf[2] = 0xFF - gsm_fcs_add_block(INIT_FCS, ibuf, 2); - /* Stuffing may double the size worst case */ - len = gsm_stuff_frame(ibuf, cbuf + 1, 3); - /* Now add the SOF markers */ - cbuf[0] = GSM1_SOF; - cbuf[len + 1] = GSM1_SOF; - /* FIXME: we can omit the lead one in many cases */ - len += 2; - break; - default: - WARN_ON(1); - return; - } - gsmld_output(gsm, cbuf, len); - if (!gsm->initiator) { - cr = cr & gsm->initiator; - control = control & ~PF; + msg->data -= 3; + dp = msg->data; + *dp++ = (addr << 2) | (ocr << 1) | EA; + *dp++ = control; + + if (gsm->encoding == 0) + *dp++ = EA; /* Length of data = 0 */ + + *dp = 0xFF - gsm_fcs_add_block(INIT_FCS, msg->data, dp - msg->data); + msg->len = (dp - msg->data) + 1; + + gsm_print_packet("Q->", addr, cr, control, NULL, 0); + + spin_lock_irqsave(&gsm->tx_lock, flags); + list_add_tail(&msg->list, &gsm->tx_ctrl_list); + gsm->tx_bytes += msg->len; + spin_unlock_irqrestore(&gsm->tx_lock, flags); + gsmld_write_trigger(gsm); + + return 0; +} + +/** + * 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. + */ +static void gsm_dlci_clear_queues(struct gsm_mux *gsm, struct gsm_dlci *dlci) +{ + struct gsm_msg *msg, *nmsg; + int addr = dlci->addr; + unsigned long flags; + + /* Clear DLCI write fifo first */ + spin_lock_irqsave(&dlci->lock, flags); + kfifo_reset(&dlci->fifo); + spin_unlock_irqrestore(&dlci->lock, 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); } - gsm_print_packet("-->", addr, cr, control, NULL, 0); + spin_unlock_irqrestore(&gsm->tx_lock, flags); } /** @@ -748,6 +781,46 @@ static struct gsm_msg *gsm_data_alloc(struct gsm_mux *gsm, u8 addr, int len, return m; } +/** + * 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. + */ +static int gsm_send_packet(struct gsm_mux *gsm, struct gsm_msg *msg) +{ + int len, ret; + + + if (gsm->encoding == 0) { + gsm->txframe[0] = GSM0_SOF; + memcpy(gsm->txframe + 1, msg->data, msg->len); + gsm->txframe[msg->len + 1] = GSM0_SOF; + len = msg->len + 2; + } else { + gsm->txframe[0] = GSM1_SOF; + len = gsm_stuff_frame(msg->data, gsm->txframe + 1, msg->len); + gsm->txframe[len + 1] = GSM1_SOF; + len += 2; + } + + if (debug & 4) + print_hex_dump_bytes("gsm_send_packet: ", DUMP_PREFIX_OFFSET, + gsm->txframe, len); + gsm_print_packet("-->", msg->addr, gsm->initiator, msg->ctrl, msg->data, + msg->len); + + 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 @@ -780,61 +853,81 @@ static bool gsm_is_flow_ctrl_msg(struct gsm_msg *msg) } /** - * gsm_data_kick - poke the queue + * gsm_data_kick - poke the queue * @gsm: GSM Mux - * @dlci: DLCI sending the data * * 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 + * 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 - * - * FIXME: lock against link layer control transmissions + * send messages on DLCI0 until CMD_FCON. The caller must hold + * the gsm tx lock. */ - -static void gsm_data_kick(struct gsm_mux *gsm, struct gsm_dlci *dlci) +static int gsm_data_kick(struct gsm_mux *gsm) { struct gsm_msg *msg, *nmsg; - int len; + struct gsm_dlci *dlci; + int ret; - list_for_each_entry_safe(msg, nmsg, &gsm->tx_list, list) { + 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)) + return -EAGAIN; + 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; - if (gsm->encoding != 0) { - gsm->txframe[0] = GSM1_SOF; - len = gsm_stuff_frame(msg->data, - gsm->txframe + 1, msg->len); - gsm->txframe[len + 1] = GSM1_SOF; - len += 2; - } else { - gsm->txframe[0] = GSM0_SOF; - memcpy(gsm->txframe + 1 , msg->data, msg->len); - gsm->txframe[msg->len + 1] = GSM0_SOF; - len = msg->len + 2; - } - - if (debug & 4) - print_hex_dump_bytes("gsm_data_kick: ", - DUMP_PREFIX_OFFSET, - gsm->txframe, len); - if (gsmld_output(gsm, gsm->txframe, len) <= 0) + default: + if (ret >= 0) { + list_del(&msg->list); + kfree(msg); + } break; - /* FIXME: Can eliminate one SOF in many more cases */ - gsm->tx_bytes -= msg->len; - - list_del(&msg->list); - kfree(msg); + } + } - if (dlci) { - tty_port_tty_wakeup(&dlci->port); - } else { - int i = 0; + if (gsm->constipated) + return -EAGAIN; - for (i = 0; i < NUM_DLCI; i++) - if (gsm->dlci[i]) - tty_port_tty_wakeup(&gsm->dlci[i]->port); + /* 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; } /** @@ -883,9 +976,21 @@ static void __gsm_data_queue(struct gsm_dlci *dlci, struct gsm_msg *msg) msg->data = dp; /* Add to the actual output queue */ - list_add_tail(&msg->list, &gsm->tx_list); + switch (msg->ctrl & ~PF) { + case UI: + case UIH: + if (msg->addr > 0) { + list_add_tail(&msg->list, &gsm->tx_data_list); + break; + } + fallthrough; + default: + list_add_tail(&msg->list, &gsm->tx_ctrl_list); + break; + } gsm->tx_bytes += msg->len; - gsm_data_kick(gsm, dlci); + + gsmld_write_trigger(gsm); mod_timer(&gsm->kick_timer, jiffies + 10 * gsm->t1 * HZ / 100); } @@ -1112,32 +1217,39 @@ static int gsm_dlci_modem_output(struct gsm_mux *gsm, struct gsm_dlci *dlci, static int gsm_dlci_data_sweep(struct gsm_mux *gsm) { - int len, ret = 0; /* Priority ordering: We should do priority with RR of the groups */ - int i = 1; - - while (i < NUM_DLCI) { - struct gsm_dlci *dlci; + int i, len, ret = 0; + bool sent; + struct gsm_dlci *dlci; - if (gsm->tx_bytes > TX_THRESH_HI) - break; - dlci = gsm->dlci[i]; - if (dlci == NULL || dlci->constipated) { - i++; - continue; + 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 (dlci->adaption < 3 && !dlci->net) - len = gsm_dlci_data_output(gsm, dlci); - else - len = gsm_dlci_data_output_framed(gsm, dlci); - if (len < 0) + if (!sent) break; - /* DLCI empty - try the next */ - if (len == 0) - i++; - else - ret++; - } + }; return ret; } @@ -1385,7 +1497,6 @@ static void gsm_control_message(struct gsm_mux *gsm, unsigned int command, const u8 *data, int clen) { u8 buf[1]; - unsigned long flags; switch (command) { case CMD_CLD: { @@ -1407,9 +1518,7 @@ static void gsm_control_message(struct gsm_mux *gsm, unsigned int command, gsm->constipated = false; gsm_control_reply(gsm, CMD_FCON, NULL, 0); /* Kick the link in case it is idling */ - spin_lock_irqsave(&gsm->tx_lock, flags); - gsm_data_kick(gsm, NULL); - spin_unlock_irqrestore(&gsm->tx_lock, flags); + gsmld_write_trigger(gsm); break; case CMD_FCOFF: /* Modem wants us to STFU */ @@ -1612,8 +1721,6 @@ static int gsm_control_wait(struct gsm_mux *gsm, struct gsm_control *control) static void gsm_dlci_close(struct gsm_dlci *dlci) { - unsigned long flags; - del_timer(&dlci->t1); if (debug & 8) pr_debug("DLCI %d goes closed.\n", dlci->addr); @@ -1622,17 +1729,16 @@ static void gsm_dlci_close(struct gsm_dlci *dlci) dlci->constipated = true; if (dlci->addr != 0) { tty_port_tty_hangup(&dlci->port, false); - spin_lock_irqsave(&dlci->lock, flags); - kfifo_reset(&dlci->fifo); - spin_unlock_irqrestore(&dlci->lock, flags); + gsm_dlci_clear_queues(dlci->gsm, dlci); /* Ensure that gsmtty_open() can return. */ tty_port_set_initialized(&dlci->port, 0); wake_up_interruptible(&dlci->port.open_wait); } else dlci->gsm->dead = true; - wake_up(&dlci->gsm->event); /* 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); } /** @@ -1655,6 +1761,7 @@ static void gsm_dlci_open(struct gsm_dlci *dlci) /* Send current modem state */ if (dlci->addr) gsm_modem_update(dlci, 0); + gsm_dlci_data_kick(dlci); wake_up(&dlci->gsm->event); } @@ -2209,7 +2316,7 @@ static void gsm1_receive(struct gsm_mux *gsm, unsigned char c) } else if ((c & ISO_IEC_646_MASK) == XOFF) { gsm->constipated = false; /* Kick the link in case it is idling */ - gsm_data_kick(gsm, NULL); + gsmld_write_trigger(gsm); return; } if (c == GSM1_SOF) { @@ -2340,6 +2447,9 @@ static void gsm_cleanup_mux(struct gsm_mux *gsm, bool disc) del_timer_sync(&gsm->kick_timer); del_timer_sync(&gsm->t2_timer); + /* Finish writing to ldisc */ + flush_work(&gsm->tx_work); + /* Free up any link layer users and finally the control channel */ if (gsm->has_devices) { gsm_unregister_devices(gsm_tty_driver, gsm->num); @@ -2351,9 +2461,12 @@ static void gsm_cleanup_mux(struct gsm_mux *gsm, bool disc) mutex_unlock(&gsm->mutex); /* Now wipe the queues */ tty_ldisc_flush(gsm->tty); - list_for_each_entry_safe(txq, ntxq, &gsm->tx_list, list) + list_for_each_entry_safe(txq, ntxq, &gsm->tx_ctrl_list, list) + kfree(txq); + INIT_LIST_HEAD(&gsm->tx_ctrl_list); + list_for_each_entry_safe(txq, ntxq, &gsm->tx_data_list, list) kfree(txq); - INIT_LIST_HEAD(&gsm->tx_list); + INIT_LIST_HEAD(&gsm->tx_data_list); } /** @@ -2372,6 +2485,7 @@ static int gsm_activate_mux(struct gsm_mux *gsm) timer_setup(&gsm->kick_timer, gsm_kick_timer, 0); timer_setup(&gsm->t2_timer, gsm_control_retransmit, 0); + INIT_WORK(&gsm->tx_work, gsmld_write_task); init_waitqueue_head(&gsm->event); spin_lock_init(&gsm->control_lock); spin_lock_init(&gsm->tx_lock); @@ -2481,7 +2595,8 @@ static struct gsm_mux *gsm_alloc_mux(void) spin_lock_init(&gsm->lock); mutex_init(&gsm->mutex); kref_init(&gsm->ref); - INIT_LIST_HEAD(&gsm->tx_list); + INIT_LIST_HEAD(&gsm->tx_ctrl_list); + INIT_LIST_HEAD(&gsm->tx_data_list); gsm->t1 = T1; gsm->t2 = T2; @@ -2639,6 +2754,47 @@ static int gsmld_output(struct gsm_mux *gsm, u8 *data, int len) return gsm->tty->ops->write(gsm->tty, data, len); } + +/** + * gsmld_write_trigger - schedule ldisc write task + * @gsm: our mux + */ +static void gsmld_write_trigger(struct gsm_mux *gsm) +{ + if (!gsm || !gsm->dlci[0] || gsm->dlci[0]->dead) + return; + schedule_work(&gsm->tx_work); +} + + +/** + * gsmld_write_task - ldisc write task + * @work: our tx write work + * + * Writes out data to the ldisc if possible. We are doing this here to + * avoid dead-locking. This returns if no space or data is left for output. + */ +static void gsmld_write_task(struct work_struct *work) +{ + struct gsm_mux *gsm = container_of(work, struct gsm_mux, tx_work); + unsigned long flags; + int i, ret; + + /* All outstanding control channel and control messages and one data + * frame is sent. + */ + ret = -ENODEV; + spin_lock_irqsave(&gsm->tx_lock, flags); + if (gsm->tty) + ret = gsm_data_kick(gsm); + spin_unlock_irqrestore(&gsm->tx_lock, flags); + + if (ret >= 0) + for (i = 0; i < NUM_DLCI; i++) + if (gsm->dlci[i]) + tty_port_tty_wakeup(&gsm->dlci[i]->port); +} + /** * gsmld_attach_gsm - mode set up * @tty: our tty structure @@ -2779,6 +2935,7 @@ static int gsmld_open(struct tty_struct *tty) timer_setup(&gsm->kick_timer, gsm_kick_timer, 0); timer_setup(&gsm->t2_timer, gsm_control_retransmit, 0); + INIT_WORK(&gsm->tx_work, gsmld_write_task); return 0; } @@ -2795,16 +2952,9 @@ static int gsmld_open(struct tty_struct *tty) static void gsmld_write_wakeup(struct tty_struct *tty) { struct gsm_mux *gsm = tty->disc_data; - unsigned long flags; /* Queue poll */ - clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); - spin_lock_irqsave(&gsm->tx_lock, flags); - gsm_data_kick(gsm, NULL); - if (gsm->tx_bytes < TX_THRESH_LO) { - gsm_dlci_data_sweep(gsm); - } - spin_unlock_irqrestore(&gsm->tx_lock, flags); + gsmld_write_trigger(gsm); } /** -- 2.34.1