xref: /openbmc/linux/drivers/tty/n_hdlc.c (revision c900529f3d9161bfde5cca0754f83b4d3c3e0220)
1e3b3d0f5SGreg Kroah-Hartman // SPDX-License-Identifier: GPL-1.0+
296fd7ce5SGreg Kroah-Hartman /* generic HDLC line discipline for Linux
396fd7ce5SGreg Kroah-Hartman  *
496fd7ce5SGreg Kroah-Hartman  * Written by Paul Fulghum paulkf@microgate.com
596fd7ce5SGreg Kroah-Hartman  * for Microgate Corporation
696fd7ce5SGreg Kroah-Hartman  *
796fd7ce5SGreg Kroah-Hartman  * Microgate and SyncLink are registered trademarks of Microgate Corporation
896fd7ce5SGreg Kroah-Hartman  *
996fd7ce5SGreg Kroah-Hartman  * Adapted from ppp.c, written by Michael Callahan <callahan@maths.ox.ac.uk>,
1096fd7ce5SGreg Kroah-Hartman  *	Al Longyear <longyear@netcom.com>,
1196fd7ce5SGreg Kroah-Hartman  *	Paul Mackerras <Paul.Mackerras@cs.anu.edu.au>
1296fd7ce5SGreg Kroah-Hartman  *
1396fd7ce5SGreg Kroah-Hartman  * Original release 01/11/99
1496fd7ce5SGreg Kroah-Hartman  *
1596fd7ce5SGreg Kroah-Hartman  * This module implements the tty line discipline N_HDLC for use with
1696fd7ce5SGreg Kroah-Hartman  * tty device drivers that support bit-synchronous HDLC communications.
1796fd7ce5SGreg Kroah-Hartman  *
1896fd7ce5SGreg Kroah-Hartman  * All HDLC data is frame oriented which means:
1996fd7ce5SGreg Kroah-Hartman  *
2096fd7ce5SGreg Kroah-Hartman  * 1. tty write calls represent one complete transmit frame of data
2196fd7ce5SGreg Kroah-Hartman  *    The device driver should accept the complete frame or none of
2296fd7ce5SGreg Kroah-Hartman  *    the frame (busy) in the write method. Each write call should have
2396fd7ce5SGreg Kroah-Hartman  *    a byte count in the range of 2-65535 bytes (2 is min HDLC frame
2496fd7ce5SGreg Kroah-Hartman  *    with 1 addr byte and 1 ctrl byte). The max byte count of 65535
2596fd7ce5SGreg Kroah-Hartman  *    should include any crc bytes required. For example, when using
2696fd7ce5SGreg Kroah-Hartman  *    CCITT CRC32, 4 crc bytes are required, so the maximum size frame
2796fd7ce5SGreg Kroah-Hartman  *    the application may transmit is limited to 65531 bytes. For CCITT
2896fd7ce5SGreg Kroah-Hartman  *    CRC16, the maximum application frame size would be 65533.
2996fd7ce5SGreg Kroah-Hartman  *
3096fd7ce5SGreg Kroah-Hartman  *
3196fd7ce5SGreg Kroah-Hartman  * 2. receive callbacks from the device driver represents
3296fd7ce5SGreg Kroah-Hartman  *    one received frame. The device driver should bypass
3396fd7ce5SGreg Kroah-Hartman  *    the tty flip buffer and call the line discipline receive
3496fd7ce5SGreg Kroah-Hartman  *    callback directly to avoid fragmenting or concatenating
3596fd7ce5SGreg Kroah-Hartman  *    multiple frames into a single receive callback.
3696fd7ce5SGreg Kroah-Hartman  *
3796fd7ce5SGreg Kroah-Hartman  *    The HDLC line discipline queues the receive frames in separate
3896fd7ce5SGreg Kroah-Hartman  *    buffers so complete receive frames can be returned by the
3996fd7ce5SGreg Kroah-Hartman  *    tty read calls.
4096fd7ce5SGreg Kroah-Hartman  *
4196fd7ce5SGreg Kroah-Hartman  * 3. tty read calls returns an entire frame of data or nothing.
4296fd7ce5SGreg Kroah-Hartman  *
4396fd7ce5SGreg Kroah-Hartman  * 4. all send and receive data is considered raw. No processing
4496fd7ce5SGreg Kroah-Hartman  *    or translation is performed by the line discipline, regardless
4596fd7ce5SGreg Kroah-Hartman  *    of the tty flags
4696fd7ce5SGreg Kroah-Hartman  *
4796fd7ce5SGreg Kroah-Hartman  * 5. When line discipline is queried for the amount of receive
4896fd7ce5SGreg Kroah-Hartman  *    data available (FIOC), 0 is returned if no data available,
4996fd7ce5SGreg Kroah-Hartman  *    otherwise the count of the next available frame is returned.
5096fd7ce5SGreg Kroah-Hartman  *    (instead of the sum of all received frame counts).
5196fd7ce5SGreg Kroah-Hartman  *
5296fd7ce5SGreg Kroah-Hartman  * These conventions allow the standard tty programming interface
5396fd7ce5SGreg Kroah-Hartman  * to be used for synchronous HDLC applications when used with
5496fd7ce5SGreg Kroah-Hartman  * this line discipline (or another line discipline that is frame
5596fd7ce5SGreg Kroah-Hartman  * oriented such as N_PPP).
5696fd7ce5SGreg Kroah-Hartman  *
5796fd7ce5SGreg Kroah-Hartman  * The SyncLink driver (synclink.c) implements both asynchronous
5896fd7ce5SGreg Kroah-Hartman  * (using standard line discipline N_TTY) and synchronous HDLC
5996fd7ce5SGreg Kroah-Hartman  * (using N_HDLC) communications, with the latter using the above
6096fd7ce5SGreg Kroah-Hartman  * conventions.
6196fd7ce5SGreg Kroah-Hartman  *
6296fd7ce5SGreg Kroah-Hartman  * This implementation is very basic and does not maintain
6396fd7ce5SGreg Kroah-Hartman  * any statistics. The main point is to enforce the raw data
6496fd7ce5SGreg Kroah-Hartman  * and frame orientation of HDLC communications.
6596fd7ce5SGreg Kroah-Hartman  *
6696fd7ce5SGreg Kroah-Hartman  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
6796fd7ce5SGreg Kroah-Hartman  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
6896fd7ce5SGreg Kroah-Hartman  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
6996fd7ce5SGreg Kroah-Hartman  * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
7096fd7ce5SGreg Kroah-Hartman  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
7196fd7ce5SGreg Kroah-Hartman  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
7296fd7ce5SGreg Kroah-Hartman  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
7396fd7ce5SGreg Kroah-Hartman  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
7496fd7ce5SGreg Kroah-Hartman  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
7596fd7ce5SGreg Kroah-Hartman  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
7696fd7ce5SGreg Kroah-Hartman  * OF THE POSSIBILITY OF SUCH DAMAGE.
7796fd7ce5SGreg Kroah-Hartman  */
7896fd7ce5SGreg Kroah-Hartman 
7996fd7ce5SGreg Kroah-Hartman #include <linux/module.h>
8096fd7ce5SGreg Kroah-Hartman #include <linux/init.h>
8196fd7ce5SGreg Kroah-Hartman #include <linux/kernel.h>
8296fd7ce5SGreg Kroah-Hartman #include <linux/sched.h>
8396fd7ce5SGreg Kroah-Hartman #include <linux/types.h>
8496fd7ce5SGreg Kroah-Hartman #include <linux/fcntl.h>
8596fd7ce5SGreg Kroah-Hartman #include <linux/interrupt.h>
8696fd7ce5SGreg Kroah-Hartman #include <linux/ptrace.h>
8796fd7ce5SGreg Kroah-Hartman 
8896fd7ce5SGreg Kroah-Hartman #include <linux/poll.h>
8996fd7ce5SGreg Kroah-Hartman #include <linux/in.h>
9096fd7ce5SGreg Kroah-Hartman #include <linux/ioctl.h>
9196fd7ce5SGreg Kroah-Hartman #include <linux/slab.h>
9296fd7ce5SGreg Kroah-Hartman #include <linux/tty.h>
9396fd7ce5SGreg Kroah-Hartman #include <linux/errno.h>
9496fd7ce5SGreg Kroah-Hartman #include <linux/string.h>	/* used in new tty drivers */
9596fd7ce5SGreg Kroah-Hartman #include <linux/signal.h>	/* used in new tty drivers */
9696fd7ce5SGreg Kroah-Hartman #include <linux/if.h>
9796fd7ce5SGreg Kroah-Hartman #include <linux/bitops.h>
9896fd7ce5SGreg Kroah-Hartman 
997c0f6ba6SLinus Torvalds #include <linux/uaccess.h>
1005ffa6e34SGreg Kroah-Hartman #include "tty.h"
10196fd7ce5SGreg Kroah-Hartman 
10296fd7ce5SGreg Kroah-Hartman /*
10396fd7ce5SGreg Kroah-Hartman  * Buffers for individual HDLC frames
10496fd7ce5SGreg Kroah-Hartman  */
10596fd7ce5SGreg Kroah-Hartman #define MAX_HDLC_FRAME_SIZE 65535
10696fd7ce5SGreg Kroah-Hartman #define DEFAULT_RX_BUF_COUNT 10
10796fd7ce5SGreg Kroah-Hartman #define MAX_RX_BUF_COUNT 60
10896fd7ce5SGreg Kroah-Hartman #define DEFAULT_TX_BUF_COUNT 3
10996fd7ce5SGreg Kroah-Hartman 
11096fd7ce5SGreg Kroah-Hartman struct n_hdlc_buf {
11182f2341cSAlexander Popov 	struct list_head  list_item;
11296fd7ce5SGreg Kroah-Hartman 	int		  count;
11385f4c951SGustavo A. R. Silva 	char		  buf[];
11496fd7ce5SGreg Kroah-Hartman };
11596fd7ce5SGreg Kroah-Hartman 
11696fd7ce5SGreg Kroah-Hartman struct n_hdlc_buf_list {
11782f2341cSAlexander Popov 	struct list_head  list;
11896fd7ce5SGreg Kroah-Hartman 	int		  count;
11996fd7ce5SGreg Kroah-Hartman 	spinlock_t	  spinlock;
12096fd7ce5SGreg Kroah-Hartman };
12196fd7ce5SGreg Kroah-Hartman 
12296fd7ce5SGreg Kroah-Hartman /**
12396fd7ce5SGreg Kroah-Hartman  * struct n_hdlc - per device instance data structure
124724ac070SJiri Slaby  * @tbusy: reentrancy flag for tx wakeup code
125724ac070SJiri Slaby  * @woke_up: tx wakeup needs to be run again as it was called while @tbusy
126724ac070SJiri Slaby  * @tx_buf_list: list of pending transmit frame buffers
127724ac070SJiri Slaby  * @rx_buf_list: list of received frame buffers
128724ac070SJiri Slaby  * @tx_free_buf_list: list unused transmit frame buffers
129724ac070SJiri Slaby  * @rx_free_buf_list: list unused received frame buffers
13096fd7ce5SGreg Kroah-Hartman  */
13196fd7ce5SGreg Kroah-Hartman struct n_hdlc {
1320f238298SJiri Slaby 	bool			tbusy;
1330f238298SJiri Slaby 	bool			woke_up;
13496fd7ce5SGreg Kroah-Hartman 	struct n_hdlc_buf_list	tx_buf_list;
13596fd7ce5SGreg Kroah-Hartman 	struct n_hdlc_buf_list	rx_buf_list;
13696fd7ce5SGreg Kroah-Hartman 	struct n_hdlc_buf_list	tx_free_buf_list;
13796fd7ce5SGreg Kroah-Hartman 	struct n_hdlc_buf_list	rx_free_buf_list;
1381ee33b1cSTetsuo Handa 	struct work_struct	write_work;
1391ee33b1cSTetsuo Handa 	struct tty_struct	*tty_for_write_work;
14096fd7ce5SGreg Kroah-Hartman };
14196fd7ce5SGreg Kroah-Hartman 
14296fd7ce5SGreg Kroah-Hartman /*
14396fd7ce5SGreg Kroah-Hartman  * HDLC buffer list manipulation functions
14496fd7ce5SGreg Kroah-Hartman  */
14582f2341cSAlexander Popov static void n_hdlc_buf_return(struct n_hdlc_buf_list *buf_list,
14682f2341cSAlexander Popov 						struct n_hdlc_buf *buf);
14796fd7ce5SGreg Kroah-Hartman static void n_hdlc_buf_put(struct n_hdlc_buf_list *list,
14896fd7ce5SGreg Kroah-Hartman 			   struct n_hdlc_buf *buf);
14996fd7ce5SGreg Kroah-Hartman static struct n_hdlc_buf *n_hdlc_buf_get(struct n_hdlc_buf_list *list);
15096fd7ce5SGreg Kroah-Hartman 
15196fd7ce5SGreg Kroah-Hartman /* Local functions */
15296fd7ce5SGreg Kroah-Hartman 
15396fd7ce5SGreg Kroah-Hartman static struct n_hdlc *n_hdlc_alloc(void);
1541ee33b1cSTetsuo Handa static void n_hdlc_tty_write_work(struct work_struct *work);
15596fd7ce5SGreg Kroah-Hartman 
15696fd7ce5SGreg Kroah-Hartman /* max frame size for memory allocations */
15796fd7ce5SGreg Kroah-Hartman static int maxframe = 4096;
15896fd7ce5SGreg Kroah-Hartman 
flush_rx_queue(struct tty_struct * tty)15996fd7ce5SGreg Kroah-Hartman static void flush_rx_queue(struct tty_struct *tty)
16096fd7ce5SGreg Kroah-Hartman {
16175011682SJiri Slaby 	struct n_hdlc *n_hdlc = tty->disc_data;
16296fd7ce5SGreg Kroah-Hartman 	struct n_hdlc_buf *buf;
16396fd7ce5SGreg Kroah-Hartman 
16496fd7ce5SGreg Kroah-Hartman 	while ((buf = n_hdlc_buf_get(&n_hdlc->rx_buf_list)))
16596fd7ce5SGreg Kroah-Hartman 		n_hdlc_buf_put(&n_hdlc->rx_free_buf_list, buf);
16696fd7ce5SGreg Kroah-Hartman }
16796fd7ce5SGreg Kroah-Hartman 
flush_tx_queue(struct tty_struct * tty)16896fd7ce5SGreg Kroah-Hartman static void flush_tx_queue(struct tty_struct *tty)
16996fd7ce5SGreg Kroah-Hartman {
17075011682SJiri Slaby 	struct n_hdlc *n_hdlc = tty->disc_data;
17196fd7ce5SGreg Kroah-Hartman 	struct n_hdlc_buf *buf;
17296fd7ce5SGreg Kroah-Hartman 
17396fd7ce5SGreg Kroah-Hartman 	while ((buf = n_hdlc_buf_get(&n_hdlc->tx_buf_list)))
17496fd7ce5SGreg Kroah-Hartman 		n_hdlc_buf_put(&n_hdlc->tx_free_buf_list, buf);
17596fd7ce5SGreg Kroah-Hartman }
17696fd7ce5SGreg Kroah-Hartman 
n_hdlc_free_buf_list(struct n_hdlc_buf_list * list)17730fafd92SJiri Slaby static void n_hdlc_free_buf_list(struct n_hdlc_buf_list *list)
17830fafd92SJiri Slaby {
17930fafd92SJiri Slaby 	struct n_hdlc_buf *buf;
18030fafd92SJiri Slaby 
18130fafd92SJiri Slaby 	do {
18230fafd92SJiri Slaby 		buf = n_hdlc_buf_get(list);
18330fafd92SJiri Slaby 		kfree(buf);
18430fafd92SJiri Slaby 	} while (buf);
18530fafd92SJiri Slaby }
18630fafd92SJiri Slaby 
18796fd7ce5SGreg Kroah-Hartman /**
18896fd7ce5SGreg Kroah-Hartman  * n_hdlc_tty_close - line discipline close
189724ac070SJiri Slaby  * @tty: pointer to tty info structure
19096fd7ce5SGreg Kroah-Hartman  *
19196fd7ce5SGreg Kroah-Hartman  * Called when the line discipline is changed to something
19296fd7ce5SGreg Kroah-Hartman  * else, the tty is closed, or the tty detects a hangup.
19396fd7ce5SGreg Kroah-Hartman  */
n_hdlc_tty_close(struct tty_struct * tty)19496fd7ce5SGreg Kroah-Hartman static void n_hdlc_tty_close(struct tty_struct *tty)
19596fd7ce5SGreg Kroah-Hartman {
19675011682SJiri Slaby 	struct n_hdlc *n_hdlc = tty->disc_data;
19796fd7ce5SGreg Kroah-Hartman 
19896fd7ce5SGreg Kroah-Hartman #if defined(TTY_NO_WRITE_SPLIT)
19996fd7ce5SGreg Kroah-Hartman 	clear_bit(TTY_NO_WRITE_SPLIT, &tty->flags);
20096fd7ce5SGreg Kroah-Hartman #endif
20196fd7ce5SGreg Kroah-Hartman 	tty->disc_data = NULL;
20243e784ecSJiri Slaby 
20343e784ecSJiri Slaby 	/* Ensure that the n_hdlcd process is not hanging on select()/poll() */
20443e784ecSJiri Slaby 	wake_up_interruptible(&tty->read_wait);
20543e784ecSJiri Slaby 	wake_up_interruptible(&tty->write_wait);
20643e784ecSJiri Slaby 
2071ee33b1cSTetsuo Handa 	cancel_work_sync(&n_hdlc->write_work);
2081ee33b1cSTetsuo Handa 
20943e784ecSJiri Slaby 	n_hdlc_free_buf_list(&n_hdlc->rx_free_buf_list);
21043e784ecSJiri Slaby 	n_hdlc_free_buf_list(&n_hdlc->tx_free_buf_list);
21143e784ecSJiri Slaby 	n_hdlc_free_buf_list(&n_hdlc->rx_buf_list);
21243e784ecSJiri Slaby 	n_hdlc_free_buf_list(&n_hdlc->tx_buf_list);
21343e784ecSJiri Slaby 	kfree(n_hdlc);
21496fd7ce5SGreg Kroah-Hartman }	/* end of n_hdlc_tty_close() */
21596fd7ce5SGreg Kroah-Hartman 
21696fd7ce5SGreg Kroah-Hartman /**
21796fd7ce5SGreg Kroah-Hartman  * n_hdlc_tty_open - called when line discipline changed to n_hdlc
218724ac070SJiri Slaby  * @tty: pointer to tty info structure
21996fd7ce5SGreg Kroah-Hartman  *
22096fd7ce5SGreg Kroah-Hartman  * Returns 0 if success, otherwise error code
22196fd7ce5SGreg Kroah-Hartman  */
n_hdlc_tty_open(struct tty_struct * tty)22296fd7ce5SGreg Kroah-Hartman static int n_hdlc_tty_open(struct tty_struct *tty)
22396fd7ce5SGreg Kroah-Hartman {
22475011682SJiri Slaby 	struct n_hdlc *n_hdlc = tty->disc_data;
22596fd7ce5SGreg Kroah-Hartman 
226b18d1c2eSJiri Slaby 	pr_debug("%s() called (device=%s)\n", __func__, tty->name);
22796fd7ce5SGreg Kroah-Hartman 
22896fd7ce5SGreg Kroah-Hartman 	/* There should not be an existing table for this slot. */
22996fd7ce5SGreg Kroah-Hartman 	if (n_hdlc) {
230d86b05cbSJiri Slaby 		pr_err("%s: tty already associated!\n", __func__);
23196fd7ce5SGreg Kroah-Hartman 		return -EEXIST;
23296fd7ce5SGreg Kroah-Hartman 	}
23396fd7ce5SGreg Kroah-Hartman 
23496fd7ce5SGreg Kroah-Hartman 	n_hdlc = n_hdlc_alloc();
23596fd7ce5SGreg Kroah-Hartman 	if (!n_hdlc) {
236d86b05cbSJiri Slaby 		pr_err("%s: n_hdlc_alloc failed\n", __func__);
23796fd7ce5SGreg Kroah-Hartman 		return -ENFILE;
23896fd7ce5SGreg Kroah-Hartman 	}
23996fd7ce5SGreg Kroah-Hartman 
2401ee33b1cSTetsuo Handa 	INIT_WORK(&n_hdlc->write_work, n_hdlc_tty_write_work);
2411ee33b1cSTetsuo Handa 	n_hdlc->tty_for_write_work = tty;
24296fd7ce5SGreg Kroah-Hartman 	tty->disc_data = n_hdlc;
24396fd7ce5SGreg Kroah-Hartman 	tty->receive_room = 65536;
24496fd7ce5SGreg Kroah-Hartman 
24596fd7ce5SGreg Kroah-Hartman 	/* change tty_io write() to not split large writes into 8K chunks */
24696fd7ce5SGreg Kroah-Hartman 	set_bit(TTY_NO_WRITE_SPLIT, &tty->flags);
24796fd7ce5SGreg Kroah-Hartman 
24896fd7ce5SGreg Kroah-Hartman 	/* flush receive data from driver */
24996fd7ce5SGreg Kroah-Hartman 	tty_driver_flush_buffer(tty);
25096fd7ce5SGreg Kroah-Hartman 
25196fd7ce5SGreg Kroah-Hartman 	return 0;
25296fd7ce5SGreg Kroah-Hartman 
25396fd7ce5SGreg Kroah-Hartman }	/* end of n_tty_hdlc_open() */
25496fd7ce5SGreg Kroah-Hartman 
25596fd7ce5SGreg Kroah-Hartman /**
25696fd7ce5SGreg Kroah-Hartman  * n_hdlc_send_frames - send frames on pending send buffer list
257724ac070SJiri Slaby  * @n_hdlc: pointer to ldisc instance data
258724ac070SJiri Slaby  * @tty: pointer to tty instance data
25996fd7ce5SGreg Kroah-Hartman  *
26096fd7ce5SGreg Kroah-Hartman  * Send frames on pending send buffer list until the driver does not accept a
26196fd7ce5SGreg Kroah-Hartman  * frame (busy) this function is called after adding a frame to the send buffer
26296fd7ce5SGreg Kroah-Hartman  * list and by the tty wakeup callback.
26396fd7ce5SGreg Kroah-Hartman  */
n_hdlc_send_frames(struct n_hdlc * n_hdlc,struct tty_struct * tty)26496fd7ce5SGreg Kroah-Hartman static void n_hdlc_send_frames(struct n_hdlc *n_hdlc, struct tty_struct *tty)
26596fd7ce5SGreg Kroah-Hartman {
26696fd7ce5SGreg Kroah-Hartman 	register int actual;
26796fd7ce5SGreg Kroah-Hartman 	unsigned long flags;
26896fd7ce5SGreg Kroah-Hartman 	struct n_hdlc_buf *tbuf;
26996fd7ce5SGreg Kroah-Hartman 
27096fd7ce5SGreg Kroah-Hartman check_again:
27196fd7ce5SGreg Kroah-Hartman 
27296fd7ce5SGreg Kroah-Hartman 	spin_lock_irqsave(&n_hdlc->tx_buf_list.spinlock, flags);
27396fd7ce5SGreg Kroah-Hartman 	if (n_hdlc->tbusy) {
2740f238298SJiri Slaby 		n_hdlc->woke_up = true;
27596fd7ce5SGreg Kroah-Hartman 		spin_unlock_irqrestore(&n_hdlc->tx_buf_list.spinlock, flags);
27696fd7ce5SGreg Kroah-Hartman 		return;
27796fd7ce5SGreg Kroah-Hartman 	}
2780f238298SJiri Slaby 	n_hdlc->tbusy = true;
2790f238298SJiri Slaby 	n_hdlc->woke_up = false;
28096fd7ce5SGreg Kroah-Hartman 	spin_unlock_irqrestore(&n_hdlc->tx_buf_list.spinlock, flags);
28196fd7ce5SGreg Kroah-Hartman 
28296fd7ce5SGreg Kroah-Hartman 	tbuf = n_hdlc_buf_get(&n_hdlc->tx_buf_list);
28396fd7ce5SGreg Kroah-Hartman 	while (tbuf) {
284b18d1c2eSJiri Slaby 		pr_debug("sending frame %p, count=%d\n", tbuf, tbuf->count);
28596fd7ce5SGreg Kroah-Hartman 
28696fd7ce5SGreg Kroah-Hartman 		/* Send the next block of data to device */
2877962fce9SIlya Zykov 		set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
28896fd7ce5SGreg Kroah-Hartman 		actual = tty->ops->write(tty, tbuf->buf, tbuf->count);
28996fd7ce5SGreg Kroah-Hartman 
29096fd7ce5SGreg Kroah-Hartman 		/* rollback was possible and has been done */
29196fd7ce5SGreg Kroah-Hartman 		if (actual == -ERESTARTSYS) {
29282f2341cSAlexander Popov 			n_hdlc_buf_return(&n_hdlc->tx_buf_list, tbuf);
29396fd7ce5SGreg Kroah-Hartman 			break;
29496fd7ce5SGreg Kroah-Hartman 		}
29596fd7ce5SGreg Kroah-Hartman 		/* if transmit error, throw frame away by */
29696fd7ce5SGreg Kroah-Hartman 		/* pretending it was accepted by driver */
29796fd7ce5SGreg Kroah-Hartman 		if (actual < 0)
29896fd7ce5SGreg Kroah-Hartman 			actual = tbuf->count;
29996fd7ce5SGreg Kroah-Hartman 
30096fd7ce5SGreg Kroah-Hartman 		if (actual == tbuf->count) {
301b18d1c2eSJiri Slaby 			pr_debug("frame %p completed\n", tbuf);
30296fd7ce5SGreg Kroah-Hartman 
30396fd7ce5SGreg Kroah-Hartman 			/* free current transmit buffer */
30496fd7ce5SGreg Kroah-Hartman 			n_hdlc_buf_put(&n_hdlc->tx_free_buf_list, tbuf);
30596fd7ce5SGreg Kroah-Hartman 
30696fd7ce5SGreg Kroah-Hartman 			/* wait up sleeping writers */
30796fd7ce5SGreg Kroah-Hartman 			wake_up_interruptible(&tty->write_wait);
30896fd7ce5SGreg Kroah-Hartman 
30996fd7ce5SGreg Kroah-Hartman 			/* get next pending transmit buffer */
31096fd7ce5SGreg Kroah-Hartman 			tbuf = n_hdlc_buf_get(&n_hdlc->tx_buf_list);
31196fd7ce5SGreg Kroah-Hartman 		} else {
312b18d1c2eSJiri Slaby 			pr_debug("frame %p pending\n", tbuf);
31396fd7ce5SGreg Kroah-Hartman 
31482f2341cSAlexander Popov 			/*
31582f2341cSAlexander Popov 			 * the buffer was not accepted by driver,
31682f2341cSAlexander Popov 			 * return it back into tx queue
31782f2341cSAlexander Popov 			 */
31882f2341cSAlexander Popov 			n_hdlc_buf_return(&n_hdlc->tx_buf_list, tbuf);
31996fd7ce5SGreg Kroah-Hartman 			break;
32096fd7ce5SGreg Kroah-Hartman 		}
32196fd7ce5SGreg Kroah-Hartman 	}
32296fd7ce5SGreg Kroah-Hartman 
32396fd7ce5SGreg Kroah-Hartman 	if (!tbuf)
3247962fce9SIlya Zykov 		clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
32596fd7ce5SGreg Kroah-Hartman 
32696fd7ce5SGreg Kroah-Hartman 	/* Clear the re-entry flag */
32796fd7ce5SGreg Kroah-Hartman 	spin_lock_irqsave(&n_hdlc->tx_buf_list.spinlock, flags);
3280f238298SJiri Slaby 	n_hdlc->tbusy = false;
32996fd7ce5SGreg Kroah-Hartman 	spin_unlock_irqrestore(&n_hdlc->tx_buf_list.spinlock, flags);
33096fd7ce5SGreg Kroah-Hartman 
33196fd7ce5SGreg Kroah-Hartman 	if (n_hdlc->woke_up)
33296fd7ce5SGreg Kroah-Hartman 		goto check_again;
33396fd7ce5SGreg Kroah-Hartman }	/* end of n_hdlc_send_frames() */
33496fd7ce5SGreg Kroah-Hartman 
33596fd7ce5SGreg Kroah-Hartman /**
3361ee33b1cSTetsuo Handa  * n_hdlc_tty_write_work - Asynchronous callback for transmit wakeup
3371ee33b1cSTetsuo Handa  * @work: pointer to work_struct
3381ee33b1cSTetsuo Handa  *
3391ee33b1cSTetsuo Handa  * Called when low level device driver can accept more send data.
3401ee33b1cSTetsuo Handa  */
n_hdlc_tty_write_work(struct work_struct * work)3411ee33b1cSTetsuo Handa static void n_hdlc_tty_write_work(struct work_struct *work)
3421ee33b1cSTetsuo Handa {
3431ee33b1cSTetsuo Handa 	struct n_hdlc *n_hdlc = container_of(work, struct n_hdlc, write_work);
3441ee33b1cSTetsuo Handa 	struct tty_struct *tty = n_hdlc->tty_for_write_work;
3451ee33b1cSTetsuo Handa 
3461ee33b1cSTetsuo Handa 	n_hdlc_send_frames(n_hdlc, tty);
3471ee33b1cSTetsuo Handa }	/* end of n_hdlc_tty_write_work() */
3481ee33b1cSTetsuo Handa 
3491ee33b1cSTetsuo Handa /**
35096fd7ce5SGreg Kroah-Hartman  * n_hdlc_tty_wakeup - Callback for transmit wakeup
351724ac070SJiri Slaby  * @tty: pointer to associated tty instance data
35296fd7ce5SGreg Kroah-Hartman  *
35396fd7ce5SGreg Kroah-Hartman  * Called when low level device driver can accept more send data.
35496fd7ce5SGreg Kroah-Hartman  */
n_hdlc_tty_wakeup(struct tty_struct * tty)35596fd7ce5SGreg Kroah-Hartman static void n_hdlc_tty_wakeup(struct tty_struct *tty)
35696fd7ce5SGreg Kroah-Hartman {
35775011682SJiri Slaby 	struct n_hdlc *n_hdlc = tty->disc_data;
35896fd7ce5SGreg Kroah-Hartman 
3591ee33b1cSTetsuo Handa 	schedule_work(&n_hdlc->write_work);
36096fd7ce5SGreg Kroah-Hartman }	/* end of n_hdlc_tty_wakeup() */
36196fd7ce5SGreg Kroah-Hartman 
36296fd7ce5SGreg Kroah-Hartman /**
36396fd7ce5SGreg Kroah-Hartman  * n_hdlc_tty_receive - Called by tty driver when receive data is available
364724ac070SJiri Slaby  * @tty: pointer to tty instance data
365724ac070SJiri Slaby  * @data: pointer to received data
366724ac070SJiri Slaby  * @flags: pointer to flags for data
367724ac070SJiri Slaby  * @count: count of received data in bytes
36896fd7ce5SGreg Kroah-Hartman  *
36996fd7ce5SGreg Kroah-Hartman  * Called by tty low level driver when receive data is available. Data is
37096fd7ce5SGreg Kroah-Hartman  * interpreted as one HDLC frame.
37196fd7ce5SGreg Kroah-Hartman  */
n_hdlc_tty_receive(struct tty_struct * tty,const u8 * data,const u8 * flags,size_t count)372a8d9cd23SJiri Slaby (SUSE) static void n_hdlc_tty_receive(struct tty_struct *tty, const u8 *data,
373892bc209SJiri Slaby (SUSE) 			       const u8 *flags, size_t count)
37496fd7ce5SGreg Kroah-Hartman {
37575011682SJiri Slaby 	register struct n_hdlc *n_hdlc = tty->disc_data;
37696fd7ce5SGreg Kroah-Hartman 	register struct n_hdlc_buf *buf;
37796fd7ce5SGreg Kroah-Hartman 
378e8161447SJiri Slaby (SUSE) 	pr_debug("%s() called count=%zu\n", __func__, count);
37996fd7ce5SGreg Kroah-Hartman 
38096fd7ce5SGreg Kroah-Hartman 	if (count > maxframe) {
381b18d1c2eSJiri Slaby 		pr_debug("rx count>maxframesize, data discarded\n");
38255db4c64SLinus Torvalds 		return;
38396fd7ce5SGreg Kroah-Hartman 	}
38496fd7ce5SGreg Kroah-Hartman 
38596fd7ce5SGreg Kroah-Hartman 	/* get a free HDLC buffer */
38696fd7ce5SGreg Kroah-Hartman 	buf = n_hdlc_buf_get(&n_hdlc->rx_free_buf_list);
38796fd7ce5SGreg Kroah-Hartman 	if (!buf) {
38880967ff2SJiri Slaby 		/*
38980967ff2SJiri Slaby 		 * no buffers in free list, attempt to allocate another rx
39080967ff2SJiri Slaby 		 * buffer unless the maximum count has been reached
39180967ff2SJiri Slaby 		 */
39296fd7ce5SGreg Kroah-Hartman 		if (n_hdlc->rx_buf_list.count < MAX_RX_BUF_COUNT)
39385f4c951SGustavo A. R. Silva 			buf = kmalloc(struct_size(buf, buf, maxframe),
39485f4c951SGustavo A. R. Silva 				      GFP_ATOMIC);
39596fd7ce5SGreg Kroah-Hartman 	}
39696fd7ce5SGreg Kroah-Hartman 
39796fd7ce5SGreg Kroah-Hartman 	if (!buf) {
398b18d1c2eSJiri Slaby 		pr_debug("no more rx buffers, data discarded\n");
39955db4c64SLinus Torvalds 		return;
40096fd7ce5SGreg Kroah-Hartman 	}
40196fd7ce5SGreg Kroah-Hartman 
40296fd7ce5SGreg Kroah-Hartman 	/* copy received data to HDLC buffer */
40396fd7ce5SGreg Kroah-Hartman 	memcpy(buf->buf, data, count);
40496fd7ce5SGreg Kroah-Hartman 	buf->count = count;
40596fd7ce5SGreg Kroah-Hartman 
40696fd7ce5SGreg Kroah-Hartman 	/* add HDLC buffer to list of received frames */
40796fd7ce5SGreg Kroah-Hartman 	n_hdlc_buf_put(&n_hdlc->rx_buf_list, buf);
40896fd7ce5SGreg Kroah-Hartman 
40996fd7ce5SGreg Kroah-Hartman 	/* wake up any blocked reads and perform async signalling */
41096fd7ce5SGreg Kroah-Hartman 	wake_up_interruptible(&tty->read_wait);
411df6de639SJiri Slaby 	if (tty->fasync != NULL)
412df6de639SJiri Slaby 		kill_fasync(&tty->fasync, SIGIO, POLL_IN);
41396fd7ce5SGreg Kroah-Hartman 
41496fd7ce5SGreg Kroah-Hartman }	/* end of n_hdlc_tty_receive() */
41596fd7ce5SGreg Kroah-Hartman 
41696fd7ce5SGreg Kroah-Hartman /**
41796fd7ce5SGreg Kroah-Hartman  * n_hdlc_tty_read - Called to retrieve one frame of data (if available)
418724ac070SJiri Slaby  * @tty: pointer to tty instance data
419724ac070SJiri Slaby  * @file: pointer to open file object
420ef80f77bSLee Jones  * @kbuf: pointer to returned data buffer
421724ac070SJiri Slaby  * @nr: size of returned data buffer
422ef80f77bSLee Jones  * @cookie: stored rbuf from previous run
423ef80f77bSLee Jones  * @offset: offset into the data buffer
42496fd7ce5SGreg Kroah-Hartman  *
42596fd7ce5SGreg Kroah-Hartman  * Returns the number of bytes returned or error code.
42696fd7ce5SGreg Kroah-Hartman  */
n_hdlc_tty_read(struct tty_struct * tty,struct file * file,u8 * kbuf,size_t nr,void ** cookie,unsigned long offset)42796fd7ce5SGreg Kroah-Hartman static ssize_t n_hdlc_tty_read(struct tty_struct *tty, struct file *file,
428*49b8220cSJiri Slaby (SUSE) 			       u8 *kbuf, size_t nr, void **cookie,
429*49b8220cSJiri Slaby (SUSE) 			       unsigned long offset)
43096fd7ce5SGreg Kroah-Hartman {
43175011682SJiri Slaby 	struct n_hdlc *n_hdlc = tty->disc_data;
4321035b63dSPaul Fulghum 	int ret = 0;
43396fd7ce5SGreg Kroah-Hartman 	struct n_hdlc_buf *rbuf;
4341035b63dSPaul Fulghum 	DECLARE_WAITQUEUE(wait, current);
43596fd7ce5SGreg Kroah-Hartman 
4363b830a9cSLinus Torvalds 	/* Is this a repeated call for an rbuf we already found earlier? */
4373b830a9cSLinus Torvalds 	rbuf = *cookie;
4383b830a9cSLinus Torvalds 	if (rbuf)
4393b830a9cSLinus Torvalds 		goto have_rbuf;
4403b830a9cSLinus Torvalds 
4411035b63dSPaul Fulghum 	add_wait_queue(&tty->read_wait, &wait);
44296fd7ce5SGreg Kroah-Hartman 
44396fd7ce5SGreg Kroah-Hartman 	for (;;) {
4440f40fbbcSBrian Bloniarz 		if (test_bit(TTY_OTHER_CLOSED, &tty->flags)) {
4451035b63dSPaul Fulghum 			ret = -EIO;
4461035b63dSPaul Fulghum 			break;
44796fd7ce5SGreg Kroah-Hartman 		}
4481035b63dSPaul Fulghum 		if (tty_hung_up_p(file))
44996fd7ce5SGreg Kroah-Hartman 			break;
45096fd7ce5SGreg Kroah-Hartman 
4511035b63dSPaul Fulghum 		set_current_state(TASK_INTERRUPTIBLE);
45296fd7ce5SGreg Kroah-Hartman 
4531035b63dSPaul Fulghum 		rbuf = n_hdlc_buf_get(&n_hdlc->rx_buf_list);
4543b830a9cSLinus Torvalds 		if (rbuf)
4551035b63dSPaul Fulghum 			break;
4561035b63dSPaul Fulghum 
4571035b63dSPaul Fulghum 		/* no data */
458c96cf923SDmitry Safonov 		if (tty_io_nonblock(tty, file)) {
4591035b63dSPaul Fulghum 			ret = -EAGAIN;
4601035b63dSPaul Fulghum 			break;
4611035b63dSPaul Fulghum 		}
4621035b63dSPaul Fulghum 
4631035b63dSPaul Fulghum 		schedule();
4641035b63dSPaul Fulghum 
4651035b63dSPaul Fulghum 		if (signal_pending(current)) {
4661035b63dSPaul Fulghum 			ret = -EINTR;
4671035b63dSPaul Fulghum 			break;
4681035b63dSPaul Fulghum 		}
4691035b63dSPaul Fulghum 	}
4701035b63dSPaul Fulghum 
4711035b63dSPaul Fulghum 	remove_wait_queue(&tty->read_wait, &wait);
4721035b63dSPaul Fulghum 	__set_current_state(TASK_RUNNING);
4731035b63dSPaul Fulghum 
4743b830a9cSLinus Torvalds 	if (!rbuf)
4753b830a9cSLinus Torvalds 		return ret;
4763b830a9cSLinus Torvalds 	*cookie = rbuf;
4773b830a9cSLinus Torvalds 
4783b830a9cSLinus Torvalds have_rbuf:
4793b830a9cSLinus Torvalds 	/* Have we used it up entirely? */
4803b830a9cSLinus Torvalds 	if (offset >= rbuf->count)
4813b830a9cSLinus Torvalds 		goto done_with_rbuf;
4823b830a9cSLinus Torvalds 
4833b830a9cSLinus Torvalds 	/* More data to go, but can't copy any more? EOVERFLOW */
4843b830a9cSLinus Torvalds 	ret = -EOVERFLOW;
4853b830a9cSLinus Torvalds 	if (!nr)
4863b830a9cSLinus Torvalds 		goto done_with_rbuf;
4873b830a9cSLinus Torvalds 
4883b830a9cSLinus Torvalds 	/* Copy as much data as possible */
4893b830a9cSLinus Torvalds 	ret = rbuf->count - offset;
4903b830a9cSLinus Torvalds 	if (ret > nr)
4913b830a9cSLinus Torvalds 		ret = nr;
4923b830a9cSLinus Torvalds 	memcpy(kbuf, rbuf->buf+offset, ret);
4933b830a9cSLinus Torvalds 	offset += ret;
4943b830a9cSLinus Torvalds 
4953b830a9cSLinus Torvalds 	/* If we still have data left, we leave the rbuf in the cookie */
4963b830a9cSLinus Torvalds 	if (offset < rbuf->count)
4973b830a9cSLinus Torvalds 		return ret;
4983b830a9cSLinus Torvalds 
4993b830a9cSLinus Torvalds done_with_rbuf:
5003b830a9cSLinus Torvalds 	*cookie = NULL;
5013b830a9cSLinus Torvalds 
5023b830a9cSLinus Torvalds 	if (n_hdlc->rx_free_buf_list.count > DEFAULT_RX_BUF_COUNT)
5033b830a9cSLinus Torvalds 		kfree(rbuf);
5043b830a9cSLinus Torvalds 	else
5053b830a9cSLinus Torvalds 		n_hdlc_buf_put(&n_hdlc->rx_free_buf_list, rbuf);
5063b830a9cSLinus Torvalds 
50796fd7ce5SGreg Kroah-Hartman 	return ret;
50896fd7ce5SGreg Kroah-Hartman 
50996fd7ce5SGreg Kroah-Hartman }	/* end of n_hdlc_tty_read() */
51096fd7ce5SGreg Kroah-Hartman 
51196fd7ce5SGreg Kroah-Hartman /**
51296fd7ce5SGreg Kroah-Hartman  * n_hdlc_tty_write - write a single frame of data to device
513724ac070SJiri Slaby  * @tty: pointer to associated tty device instance data
514724ac070SJiri Slaby  * @file: pointer to file object data
515724ac070SJiri Slaby  * @data: pointer to transmit data (one frame)
516724ac070SJiri Slaby  * @count: size of transmit frame in bytes
51796fd7ce5SGreg Kroah-Hartman  *
51896fd7ce5SGreg Kroah-Hartman  * Returns the number of bytes written (or error code).
51996fd7ce5SGreg Kroah-Hartman  */
n_hdlc_tty_write(struct tty_struct * tty,struct file * file,const u8 * data,size_t count)52096fd7ce5SGreg Kroah-Hartman static ssize_t n_hdlc_tty_write(struct tty_struct *tty, struct file *file,
521*49b8220cSJiri Slaby (SUSE) 				const u8 *data, size_t count)
52296fd7ce5SGreg Kroah-Hartman {
52375011682SJiri Slaby 	struct n_hdlc *n_hdlc = tty->disc_data;
52496fd7ce5SGreg Kroah-Hartman 	int error = 0;
52596fd7ce5SGreg Kroah-Hartman 	DECLARE_WAITQUEUE(wait, current);
52696fd7ce5SGreg Kroah-Hartman 	struct n_hdlc_buf *tbuf;
52796fd7ce5SGreg Kroah-Hartman 
528b18d1c2eSJiri Slaby 	pr_debug("%s() called count=%zd\n", __func__, count);
52996fd7ce5SGreg Kroah-Hartman 
53096fd7ce5SGreg Kroah-Hartman 	/* verify frame size */
53196fd7ce5SGreg Kroah-Hartman 	if (count > maxframe) {
532f3c2e277SJiri Slaby 		pr_debug("%s: truncating user packet from %zu to %d\n",
533f3c2e277SJiri Slaby 				__func__, count, maxframe);
53496fd7ce5SGreg Kroah-Hartman 		count = maxframe;
53596fd7ce5SGreg Kroah-Hartman 	}
53696fd7ce5SGreg Kroah-Hartman 
53796fd7ce5SGreg Kroah-Hartman 	add_wait_queue(&tty->write_wait, &wait);
5381035b63dSPaul Fulghum 
5391035b63dSPaul Fulghum 	for (;;) {
54096fd7ce5SGreg Kroah-Hartman 		set_current_state(TASK_INTERRUPTIBLE);
54196fd7ce5SGreg Kroah-Hartman 
5421035b63dSPaul Fulghum 		tbuf = n_hdlc_buf_get(&n_hdlc->tx_free_buf_list);
5431035b63dSPaul Fulghum 		if (tbuf)
5441035b63dSPaul Fulghum 			break;
5451035b63dSPaul Fulghum 
546c96cf923SDmitry Safonov 		if (tty_io_nonblock(tty, file)) {
54796fd7ce5SGreg Kroah-Hartman 			error = -EAGAIN;
54896fd7ce5SGreg Kroah-Hartman 			break;
54996fd7ce5SGreg Kroah-Hartman 		}
55096fd7ce5SGreg Kroah-Hartman 		schedule();
55196fd7ce5SGreg Kroah-Hartman 
55296fd7ce5SGreg Kroah-Hartman 		if (signal_pending(current)) {
55396fd7ce5SGreg Kroah-Hartman 			error = -EINTR;
55496fd7ce5SGreg Kroah-Hartman 			break;
55596fd7ce5SGreg Kroah-Hartman 		}
55696fd7ce5SGreg Kroah-Hartman 	}
55796fd7ce5SGreg Kroah-Hartman 
5581035b63dSPaul Fulghum 	__set_current_state(TASK_RUNNING);
55996fd7ce5SGreg Kroah-Hartman 	remove_wait_queue(&tty->write_wait, &wait);
56096fd7ce5SGreg Kroah-Hartman 
56196fd7ce5SGreg Kroah-Hartman 	if (!error) {
56296fd7ce5SGreg Kroah-Hartman 		/* Retrieve the user's buffer */
56396fd7ce5SGreg Kroah-Hartman 		memcpy(tbuf->buf, data, count);
56496fd7ce5SGreg Kroah-Hartman 
56596fd7ce5SGreg Kroah-Hartman 		/* Send the data */
56696fd7ce5SGreg Kroah-Hartman 		tbuf->count = error = count;
56796fd7ce5SGreg Kroah-Hartman 		n_hdlc_buf_put(&n_hdlc->tx_buf_list, tbuf);
56896fd7ce5SGreg Kroah-Hartman 		n_hdlc_send_frames(n_hdlc, tty);
56996fd7ce5SGreg Kroah-Hartman 	}
5701035b63dSPaul Fulghum 
57196fd7ce5SGreg Kroah-Hartman 	return error;
57296fd7ce5SGreg Kroah-Hartman 
57396fd7ce5SGreg Kroah-Hartman }	/* end of n_hdlc_tty_write() */
57496fd7ce5SGreg Kroah-Hartman 
57596fd7ce5SGreg Kroah-Hartman /**
57696fd7ce5SGreg Kroah-Hartman  * n_hdlc_tty_ioctl - process IOCTL system call for the tty device.
577724ac070SJiri Slaby  * @tty: pointer to tty instance data
578724ac070SJiri Slaby  * @cmd: IOCTL command code
579724ac070SJiri Slaby  * @arg: argument for IOCTL call (cmd dependent)
58096fd7ce5SGreg Kroah-Hartman  *
58196fd7ce5SGreg Kroah-Hartman  * Returns command dependent result.
58296fd7ce5SGreg Kroah-Hartman  */
n_hdlc_tty_ioctl(struct tty_struct * tty,unsigned int cmd,unsigned long arg)583d78328bcSJiri Slaby static int n_hdlc_tty_ioctl(struct tty_struct *tty, unsigned int cmd,
584d78328bcSJiri Slaby 			    unsigned long arg)
58596fd7ce5SGreg Kroah-Hartman {
58675011682SJiri Slaby 	struct n_hdlc *n_hdlc = tty->disc_data;
58796fd7ce5SGreg Kroah-Hartman 	int error = 0;
58896fd7ce5SGreg Kroah-Hartman 	int count;
58996fd7ce5SGreg Kroah-Hartman 	unsigned long flags;
59082f2341cSAlexander Popov 	struct n_hdlc_buf *buf = NULL;
59196fd7ce5SGreg Kroah-Hartman 
592b18d1c2eSJiri Slaby 	pr_debug("%s() called %d\n", __func__, cmd);
59396fd7ce5SGreg Kroah-Hartman 
59496fd7ce5SGreg Kroah-Hartman 	switch (cmd) {
59596fd7ce5SGreg Kroah-Hartman 	case FIONREAD:
59696fd7ce5SGreg Kroah-Hartman 		/* report count of read data available */
59796fd7ce5SGreg Kroah-Hartman 		/* in next available frame (if any) */
59896fd7ce5SGreg Kroah-Hartman 		spin_lock_irqsave(&n_hdlc->rx_buf_list.spinlock, flags);
59982f2341cSAlexander Popov 		buf = list_first_entry_or_null(&n_hdlc->rx_buf_list.list,
60082f2341cSAlexander Popov 						struct n_hdlc_buf, list_item);
60182f2341cSAlexander Popov 		if (buf)
60282f2341cSAlexander Popov 			count = buf->count;
60396fd7ce5SGreg Kroah-Hartman 		else
60496fd7ce5SGreg Kroah-Hartman 			count = 0;
60596fd7ce5SGreg Kroah-Hartman 		spin_unlock_irqrestore(&n_hdlc->rx_buf_list.spinlock, flags);
60696fd7ce5SGreg Kroah-Hartman 		error = put_user(count, (int __user *)arg);
60796fd7ce5SGreg Kroah-Hartman 		break;
60896fd7ce5SGreg Kroah-Hartman 
60996fd7ce5SGreg Kroah-Hartman 	case TIOCOUTQ:
61096fd7ce5SGreg Kroah-Hartman 		/* get the pending tx byte count in the driver */
61196fd7ce5SGreg Kroah-Hartman 		count = tty_chars_in_buffer(tty);
61296fd7ce5SGreg Kroah-Hartman 		/* add size of next output frame in queue */
61396fd7ce5SGreg Kroah-Hartman 		spin_lock_irqsave(&n_hdlc->tx_buf_list.spinlock, flags);
61482f2341cSAlexander Popov 		buf = list_first_entry_or_null(&n_hdlc->tx_buf_list.list,
61582f2341cSAlexander Popov 						struct n_hdlc_buf, list_item);
61682f2341cSAlexander Popov 		if (buf)
61782f2341cSAlexander Popov 			count += buf->count;
61896fd7ce5SGreg Kroah-Hartman 		spin_unlock_irqrestore(&n_hdlc->tx_buf_list.spinlock, flags);
61996fd7ce5SGreg Kroah-Hartman 		error = put_user(count, (int __user *)arg);
62096fd7ce5SGreg Kroah-Hartman 		break;
62196fd7ce5SGreg Kroah-Hartman 
62296fd7ce5SGreg Kroah-Hartman 	case TCFLSH:
62396fd7ce5SGreg Kroah-Hartman 		switch (arg) {
62496fd7ce5SGreg Kroah-Hartman 		case TCIOFLUSH:
62596fd7ce5SGreg Kroah-Hartman 		case TCOFLUSH:
62696fd7ce5SGreg Kroah-Hartman 			flush_tx_queue(tty);
62796fd7ce5SGreg Kroah-Hartman 		}
628df561f66SGustavo A. R. Silva 		fallthrough;	/* to default */
62996fd7ce5SGreg Kroah-Hartman 
63096fd7ce5SGreg Kroah-Hartman 	default:
6317c783601SJiri Slaby 		error = n_tty_ioctl_helper(tty, cmd, arg);
63296fd7ce5SGreg Kroah-Hartman 		break;
63396fd7ce5SGreg Kroah-Hartman 	}
63496fd7ce5SGreg Kroah-Hartman 	return error;
63596fd7ce5SGreg Kroah-Hartman 
63696fd7ce5SGreg Kroah-Hartman }	/* end of n_hdlc_tty_ioctl() */
63796fd7ce5SGreg Kroah-Hartman 
63896fd7ce5SGreg Kroah-Hartman /**
63996fd7ce5SGreg Kroah-Hartman  * n_hdlc_tty_poll - TTY callback for poll system call
640724ac070SJiri Slaby  * @tty: pointer to tty instance data
641724ac070SJiri Slaby  * @filp: pointer to open file object for device
642724ac070SJiri Slaby  * @wait: wait queue for operations
64396fd7ce5SGreg Kroah-Hartman  *
64496fd7ce5SGreg Kroah-Hartman  * Determine which operations (read/write) will not block and return info
64596fd7ce5SGreg Kroah-Hartman  * to caller.
64696fd7ce5SGreg Kroah-Hartman  * Returns a bit mask containing info on which ops will not block.
64796fd7ce5SGreg Kroah-Hartman  */
n_hdlc_tty_poll(struct tty_struct * tty,struct file * filp,poll_table * wait)648afc9a42bSAl Viro static __poll_t n_hdlc_tty_poll(struct tty_struct *tty, struct file *filp,
64996fd7ce5SGreg Kroah-Hartman 				    poll_table *wait)
65096fd7ce5SGreg Kroah-Hartman {
65175011682SJiri Slaby 	struct n_hdlc *n_hdlc = tty->disc_data;
652afc9a42bSAl Viro 	__poll_t mask = 0;
65396fd7ce5SGreg Kroah-Hartman 
6545f289514SJiri Slaby 	/*
6555f289514SJiri Slaby 	 * queue the current process into any wait queue that may awaken in the
6565f289514SJiri Slaby 	 * future (read and write)
6575f289514SJiri Slaby 	 */
65896fd7ce5SGreg Kroah-Hartman 	poll_wait(filp, &tty->read_wait, wait);
65996fd7ce5SGreg Kroah-Hartman 	poll_wait(filp, &tty->write_wait, wait);
66096fd7ce5SGreg Kroah-Hartman 
66196fd7ce5SGreg Kroah-Hartman 	/* set bits for operations that won't block */
66282f2341cSAlexander Popov 	if (!list_empty(&n_hdlc->rx_buf_list.list))
663a9a08845SLinus Torvalds 		mask |= EPOLLIN | EPOLLRDNORM;	/* readable */
6640f40fbbcSBrian Bloniarz 	if (test_bit(TTY_OTHER_CLOSED, &tty->flags))
665a9a08845SLinus Torvalds 		mask |= EPOLLHUP;
66696fd7ce5SGreg Kroah-Hartman 	if (tty_hung_up_p(filp))
667a9a08845SLinus Torvalds 		mask |= EPOLLHUP;
66896fd7ce5SGreg Kroah-Hartman 	if (!tty_is_writelocked(tty) &&
66982f2341cSAlexander Popov 			!list_empty(&n_hdlc->tx_free_buf_list.list))
670a9a08845SLinus Torvalds 		mask |= EPOLLOUT | EPOLLWRNORM;	/* writable */
6715f289514SJiri Slaby 
67296fd7ce5SGreg Kroah-Hartman 	return mask;
67396fd7ce5SGreg Kroah-Hartman }	/* end of n_hdlc_tty_poll() */
67496fd7ce5SGreg Kroah-Hartman 
n_hdlc_alloc_buf(struct n_hdlc_buf_list * list,unsigned int count,const char * name)675740708abSJiri Slaby static void n_hdlc_alloc_buf(struct n_hdlc_buf_list *list, unsigned int count,
676740708abSJiri Slaby 		const char *name)
677740708abSJiri Slaby {
678740708abSJiri Slaby 	struct n_hdlc_buf *buf;
679740708abSJiri Slaby 	unsigned int i;
680740708abSJiri Slaby 
681740708abSJiri Slaby 	for (i = 0; i < count; i++) {
682740708abSJiri Slaby 		buf = kmalloc(struct_size(buf, buf, maxframe), GFP_KERNEL);
683740708abSJiri Slaby 		if (!buf) {
684b18d1c2eSJiri Slaby 			pr_debug("%s(), kmalloc() failed for %s buffer %u\n",
685b18d1c2eSJiri Slaby 					__func__, name, i);
686740708abSJiri Slaby 			return;
687740708abSJiri Slaby 		}
688740708abSJiri Slaby 		n_hdlc_buf_put(list, buf);
689740708abSJiri Slaby 	}
690740708abSJiri Slaby }
691740708abSJiri Slaby 
69296fd7ce5SGreg Kroah-Hartman /**
69396fd7ce5SGreg Kroah-Hartman  * n_hdlc_alloc - allocate an n_hdlc instance data structure
69496fd7ce5SGreg Kroah-Hartman  *
69596fd7ce5SGreg Kroah-Hartman  * Returns a pointer to newly created structure if success, otherwise %NULL
69696fd7ce5SGreg Kroah-Hartman  */
n_hdlc_alloc(void)69796fd7ce5SGreg Kroah-Hartman static struct n_hdlc *n_hdlc_alloc(void)
69896fd7ce5SGreg Kroah-Hartman {
6998e25f8ceSFabian Frederick 	struct n_hdlc *n_hdlc = kzalloc(sizeof(*n_hdlc), GFP_KERNEL);
70096fd7ce5SGreg Kroah-Hartman 
70196fd7ce5SGreg Kroah-Hartman 	if (!n_hdlc)
70296fd7ce5SGreg Kroah-Hartman 		return NULL;
70396fd7ce5SGreg Kroah-Hartman 
704e9b736d8SJiri Slaby 	spin_lock_init(&n_hdlc->rx_free_buf_list.spinlock);
705e9b736d8SJiri Slaby 	spin_lock_init(&n_hdlc->tx_free_buf_list.spinlock);
706e9b736d8SJiri Slaby 	spin_lock_init(&n_hdlc->rx_buf_list.spinlock);
707e9b736d8SJiri Slaby 	spin_lock_init(&n_hdlc->tx_buf_list.spinlock);
70896fd7ce5SGreg Kroah-Hartman 
70982f2341cSAlexander Popov 	INIT_LIST_HEAD(&n_hdlc->rx_free_buf_list.list);
71082f2341cSAlexander Popov 	INIT_LIST_HEAD(&n_hdlc->tx_free_buf_list.list);
71182f2341cSAlexander Popov 	INIT_LIST_HEAD(&n_hdlc->rx_buf_list.list);
71282f2341cSAlexander Popov 	INIT_LIST_HEAD(&n_hdlc->tx_buf_list.list);
71382f2341cSAlexander Popov 
714740708abSJiri Slaby 	n_hdlc_alloc_buf(&n_hdlc->rx_free_buf_list, DEFAULT_RX_BUF_COUNT, "rx");
715740708abSJiri Slaby 	n_hdlc_alloc_buf(&n_hdlc->tx_free_buf_list, DEFAULT_TX_BUF_COUNT, "tx");
71696fd7ce5SGreg Kroah-Hartman 
71796fd7ce5SGreg Kroah-Hartman 	return n_hdlc;
71896fd7ce5SGreg Kroah-Hartman 
71996fd7ce5SGreg Kroah-Hartman }	/* end of n_hdlc_alloc() */
72096fd7ce5SGreg Kroah-Hartman 
72196fd7ce5SGreg Kroah-Hartman /**
72282f2341cSAlexander Popov  * n_hdlc_buf_return - put the HDLC buffer after the head of the specified list
723724ac070SJiri Slaby  * @buf_list: pointer to the buffer list
724724ac070SJiri Slaby  * @buf: pointer to the buffer
72596fd7ce5SGreg Kroah-Hartman  */
n_hdlc_buf_return(struct n_hdlc_buf_list * buf_list,struct n_hdlc_buf * buf)72682f2341cSAlexander Popov static void n_hdlc_buf_return(struct n_hdlc_buf_list *buf_list,
72796fd7ce5SGreg Kroah-Hartman 						struct n_hdlc_buf *buf)
72896fd7ce5SGreg Kroah-Hartman {
72996fd7ce5SGreg Kroah-Hartman 	unsigned long flags;
73096fd7ce5SGreg Kroah-Hartman 
73182f2341cSAlexander Popov 	spin_lock_irqsave(&buf_list->spinlock, flags);
73296fd7ce5SGreg Kroah-Hartman 
73382f2341cSAlexander Popov 	list_add(&buf->list_item, &buf_list->list);
73482f2341cSAlexander Popov 	buf_list->count++;
73596fd7ce5SGreg Kroah-Hartman 
73682f2341cSAlexander Popov 	spin_unlock_irqrestore(&buf_list->spinlock, flags);
73782f2341cSAlexander Popov }
73882f2341cSAlexander Popov 
73982f2341cSAlexander Popov /**
74082f2341cSAlexander Popov  * n_hdlc_buf_put - add specified HDLC buffer to tail of specified list
741724ac070SJiri Slaby  * @buf_list: pointer to buffer list
742724ac070SJiri Slaby  * @buf: pointer to buffer
74382f2341cSAlexander Popov  */
n_hdlc_buf_put(struct n_hdlc_buf_list * buf_list,struct n_hdlc_buf * buf)74482f2341cSAlexander Popov static void n_hdlc_buf_put(struct n_hdlc_buf_list *buf_list,
74582f2341cSAlexander Popov 			   struct n_hdlc_buf *buf)
74682f2341cSAlexander Popov {
74782f2341cSAlexander Popov 	unsigned long flags;
74882f2341cSAlexander Popov 
74982f2341cSAlexander Popov 	spin_lock_irqsave(&buf_list->spinlock, flags);
75082f2341cSAlexander Popov 
75182f2341cSAlexander Popov 	list_add_tail(&buf->list_item, &buf_list->list);
75282f2341cSAlexander Popov 	buf_list->count++;
75382f2341cSAlexander Popov 
75482f2341cSAlexander Popov 	spin_unlock_irqrestore(&buf_list->spinlock, flags);
75596fd7ce5SGreg Kroah-Hartman }	/* end of n_hdlc_buf_put() */
75696fd7ce5SGreg Kroah-Hartman 
75796fd7ce5SGreg Kroah-Hartman /**
75896fd7ce5SGreg Kroah-Hartman  * n_hdlc_buf_get - remove and return an HDLC buffer from list
759724ac070SJiri Slaby  * @buf_list: pointer to HDLC buffer list
76096fd7ce5SGreg Kroah-Hartman  *
76196fd7ce5SGreg Kroah-Hartman  * Remove and return an HDLC buffer from the head of the specified HDLC buffer
76296fd7ce5SGreg Kroah-Hartman  * list.
76396fd7ce5SGreg Kroah-Hartman  * Returns a pointer to HDLC buffer if available, otherwise %NULL.
76496fd7ce5SGreg Kroah-Hartman  */
n_hdlc_buf_get(struct n_hdlc_buf_list * buf_list)76582f2341cSAlexander Popov static struct n_hdlc_buf *n_hdlc_buf_get(struct n_hdlc_buf_list *buf_list)
76696fd7ce5SGreg Kroah-Hartman {
76796fd7ce5SGreg Kroah-Hartman 	unsigned long flags;
76896fd7ce5SGreg Kroah-Hartman 	struct n_hdlc_buf *buf;
76996fd7ce5SGreg Kroah-Hartman 
77082f2341cSAlexander Popov 	spin_lock_irqsave(&buf_list->spinlock, flags);
77182f2341cSAlexander Popov 
77282f2341cSAlexander Popov 	buf = list_first_entry_or_null(&buf_list->list,
77382f2341cSAlexander Popov 						struct n_hdlc_buf, list_item);
77496fd7ce5SGreg Kroah-Hartman 	if (buf) {
77582f2341cSAlexander Popov 		list_del(&buf->list_item);
77682f2341cSAlexander Popov 		buf_list->count--;
77796fd7ce5SGreg Kroah-Hartman 	}
77896fd7ce5SGreg Kroah-Hartman 
77982f2341cSAlexander Popov 	spin_unlock_irqrestore(&buf_list->spinlock, flags);
78096fd7ce5SGreg Kroah-Hartman 	return buf;
78196fd7ce5SGreg Kroah-Hartman }	/* end of n_hdlc_buf_get() */
78296fd7ce5SGreg Kroah-Hartman 
783edee649fSJiri Slaby static struct tty_ldisc_ops n_hdlc_ldisc = {
784edee649fSJiri Slaby 	.owner		= THIS_MODULE,
785fbadf70aSJiri Slaby 	.num		= N_HDLC,
786edee649fSJiri Slaby 	.name		= "hdlc",
787edee649fSJiri Slaby 	.open		= n_hdlc_tty_open,
788edee649fSJiri Slaby 	.close		= n_hdlc_tty_close,
789edee649fSJiri Slaby 	.read		= n_hdlc_tty_read,
790edee649fSJiri Slaby 	.write		= n_hdlc_tty_write,
791edee649fSJiri Slaby 	.ioctl		= n_hdlc_tty_ioctl,
792edee649fSJiri Slaby 	.poll		= n_hdlc_tty_poll,
793edee649fSJiri Slaby 	.receive_buf	= n_hdlc_tty_receive,
794edee649fSJiri Slaby 	.write_wakeup	= n_hdlc_tty_wakeup,
795edee649fSJiri Slaby 	.flush_buffer   = flush_rx_queue,
796edee649fSJiri Slaby };
797edee649fSJiri Slaby 
n_hdlc_init(void)79896fd7ce5SGreg Kroah-Hartman static int __init n_hdlc_init(void)
79996fd7ce5SGreg Kroah-Hartman {
80096fd7ce5SGreg Kroah-Hartman 	int status;
80196fd7ce5SGreg Kroah-Hartman 
80296fd7ce5SGreg Kroah-Hartman 	/* range check maxframe arg */
803c549725fSJiri Slaby 	maxframe = clamp(maxframe, 4096, MAX_HDLC_FRAME_SIZE);
80496fd7ce5SGreg Kroah-Hartman 
805fbadf70aSJiri Slaby 	status = tty_register_ldisc(&n_hdlc_ldisc);
80696fd7ce5SGreg Kroah-Hartman 	if (!status)
807cda3756cSJiri Slaby 		pr_info("N_HDLC line discipline registered with maxframe=%d\n",
808cda3756cSJiri Slaby 				maxframe);
80996fd7ce5SGreg Kroah-Hartman 	else
810cda3756cSJiri Slaby 		pr_err("N_HDLC: error registering line discipline: %d\n",
811cda3756cSJiri Slaby 				status);
81296fd7ce5SGreg Kroah-Hartman 
81396fd7ce5SGreg Kroah-Hartman 	return status;
81496fd7ce5SGreg Kroah-Hartman 
81596fd7ce5SGreg Kroah-Hartman }	/* end of init_module() */
81696fd7ce5SGreg Kroah-Hartman 
n_hdlc_exit(void)81796fd7ce5SGreg Kroah-Hartman static void __exit n_hdlc_exit(void)
81896fd7ce5SGreg Kroah-Hartman {
819357a6a87SJiri Slaby 	tty_unregister_ldisc(&n_hdlc_ldisc);
82096fd7ce5SGreg Kroah-Hartman }
82196fd7ce5SGreg Kroah-Hartman 
82296fd7ce5SGreg Kroah-Hartman module_init(n_hdlc_init);
82396fd7ce5SGreg Kroah-Hartman module_exit(n_hdlc_exit);
82496fd7ce5SGreg Kroah-Hartman 
82596fd7ce5SGreg Kroah-Hartman MODULE_LICENSE("GPL");
82696fd7ce5SGreg Kroah-Hartman MODULE_AUTHOR("Paul Fulghum paulkf@microgate.com");
82796fd7ce5SGreg Kroah-Hartman module_param(maxframe, int, 0);
82896fd7ce5SGreg Kroah-Hartman MODULE_ALIAS_LDISC(N_HDLC);
829