// SPDX-License-Identifier: GPL-1.0+
/* generic HDLC line discipline for Linux
*
* Written by Paul Fulghum paulkf @ microgate . com
* for Microgate Corporation
*
* Microgate and SyncLink are registered trademarks of Microgate Corporation
*
* Adapted from ppp . c , written by Michael Callahan < callahan @ maths . ox . ac . uk > ,
* Al Longyear < longyear @ netcom . com > ,
* Paul Mackerras < Paul . Mackerras @ cs . anu . edu . au >
*
* Original release 01 / 11 / 99
*
* This module implements the tty line discipline N_HDLC for use with
* tty device drivers that support bit - synchronous HDLC communications .
*
* All HDLC data is frame oriented which means :
*
* 1 . tty write calls represent one complete transmit frame of data
* The device driver should accept the complete frame or none of
* the frame ( busy ) in the write method . Each write call should have
* a byte count in the range of 2 - 65535 bytes ( 2 is min HDLC frame
* with 1 addr byte and 1 ctrl byte ) . The max byte count of 65535
* should include any crc bytes required . For example , when using
* CCITT CRC32 , 4 crc bytes are required , so the maximum size frame
* the application may transmit is limited to 65531 bytes . For CCITT
* CRC16 , the maximum application frame size would be 65533 .
*
*
* 2 . receive callbacks from the device driver represents
* one received frame . The device driver should bypass
* the tty flip buffer and call the line discipline receive
* callback directly to avoid fragmenting or concatenating
* multiple frames into a single receive callback .
*
* The HDLC line discipline queues the receive frames in separate
* buffers so complete receive frames can be returned by the
* tty read calls .
*
* 3 . tty read calls returns an entire frame of data or nothing .
*
* 4 . all send and receive data is considered raw . No processing
* or translation is performed by the line discipline , regardless
* of the tty flags
*
* 5 . When line discipline is queried for the amount of receive
* data available ( FIOC ) , 0 is returned if no data available ,
* otherwise the count of the next available frame is returned .
* ( instead of the sum of all received frame counts ) .
*
* These conventions allow the standard tty programming interface
* to be used for synchronous HDLC applications when used with
* this line discipline ( or another line discipline that is frame
* oriented such as N_PPP ) .
*
* The SyncLink driver ( synclink . c ) implements both asynchronous
* ( using standard line discipline N_TTY ) and synchronous HDLC
* ( using N_HDLC ) communications , with the latter using the above
* conventions .
*
* This implementation is very basic and does not maintain
* any statistics . The main point is to enforce the raw data
* and frame orientation of HDLC communications .
*
* THIS SOFTWARE IS PROVIDED ` ` AS IS ' ' AND ANY EXPRESS OR IMPLIED
* WARRANTIES , INCLUDING , BUT NOT LIMITED TO , THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED . IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT ,
* INDIRECT , INCIDENTAL , SPECIAL , EXEMPLARY , OR CONSEQUENTIAL DAMAGES
* ( INCLUDING , BUT NOT LIMITED TO , PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES ; LOSS OF USE , DATA , OR PROFITS ; OR BUSINESS INTERRUPTION )
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY , WHETHER IN CONTRACT ,
* STRICT LIABILITY , OR TORT ( INCLUDING NEGLIGENCE OR OTHERWISE )
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE , EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE .
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/interrupt.h>
#include <linux/ptrace.h>
#include <linux/poll.h>
#include <linux/in.h>
#include <linux/ioctl.h>
#include <linux/slab.h>
#include <linux/tty.h>
#include <linux/errno.h>
#include <linux/string.h> /* used in new tty drivers */
#include <linux/signal.h> /* used in new tty drivers */
#include <linux/if .h>
#include <linux/bitops.h>
#include <linux/uaccess.h>
#include "tty.h"
/*
* Buffers for individual HDLC frames
*/
#define MAX_HDLC_FRAME_SIZE 65535
#define DEFAULT_RX_BUF_COUNT 10
#define MAX_RX_BUF_COUNT 60
#define DEFAULT_TX_BUF_COUNT 3
struct n_hdlc_buf {
struct list_head list_item;
size_t count;
u8 buf[];
};
struct n_hdlc_buf_list {
struct list_head list;
int count;
spinlock_t spinlock;
};
/**
* struct n_hdlc - per device instance data structure
* @ tbusy : reentrancy flag for tx wakeup code
* @ woke_up : tx wakeup needs to be run again as it was called while @ tbusy
* @ tx_buf_list : list of pending transmit frame buffers
* @ rx_buf_list : list of received frame buffers
* @ tx_free_buf_list : list unused transmit frame buffers
* @ rx_free_buf_list : list unused received frame buffers
*/
struct n_hdlc {
bool tbusy;
bool woke_up;
struct n_hdlc_buf_list tx_buf_list;
struct n_hdlc_buf_list rx_buf_list;
struct n_hdlc_buf_list tx_free_buf_list;
struct n_hdlc_buf_list rx_free_buf_list;
struct work_struct write_work;
struct tty_struct *tty_for_write_work;
};
/*
* HDLC buffer list manipulation functions
*/
static void n_hdlc_buf_return(struct n_hdlc_buf_list *buf_list,
struct n_hdlc_buf *buf);
static void n_hdlc_buf_put(struct n_hdlc_buf_list *list,
struct n_hdlc_buf *buf);
static struct n_hdlc_buf *n_hdlc_buf_get(struct n_hdlc_buf_list *list);
/* Local functions */
static struct n_hdlc *n_hdlc_alloc(void );
static void n_hdlc_tty_write_work(struct work_struct *work);
/* max frame size for memory allocations */
static int maxframe = 4096 ;
static void flush_rx_queue(struct tty_struct *tty)
{
struct n_hdlc *n_hdlc = tty->disc_data;
struct n_hdlc_buf *buf;
while ((buf = n_hdlc_buf_get(&n_hdlc->rx_buf_list)))
n_hdlc_buf_put(&n_hdlc->rx_free_buf_list, buf);
}
static void flush_tx_queue(struct tty_struct *tty)
{
struct n_hdlc *n_hdlc = tty->disc_data;
struct n_hdlc_buf *buf;
while ((buf = n_hdlc_buf_get(&n_hdlc->tx_buf_list)))
n_hdlc_buf_put(&n_hdlc->tx_free_buf_list, buf);
}
static void n_hdlc_free_buf_list(struct n_hdlc_buf_list *list)
{
struct n_hdlc_buf *buf;
do {
buf = n_hdlc_buf_get(list);
kfree(buf);
} while (buf);
}
/**
* n_hdlc_tty_close - line discipline close
* @ tty : pointer to tty info structure
*
* Called when the line discipline is changed to something
* else , the tty is closed , or the tty detects a hangup .
*/
static void n_hdlc_tty_close(struct tty_struct *tty)
{
struct n_hdlc *n_hdlc = tty->disc_data;
#if defined (TTY_NO_WRITE_SPLIT)
clear_bit(TTY_NO_WRITE_SPLIT, &tty->flags);
#endif
tty->disc_data = NULL;
/* Ensure that the n_hdlcd process is not hanging on select()/poll() */
wake_up_interruptible(&tty->read_wait);
wake_up_interruptible(&tty->write_wait);
cancel_work_sync(&n_hdlc->write_work);
n_hdlc_free_buf_list(&n_hdlc->rx_free_buf_list);
n_hdlc_free_buf_list(&n_hdlc->tx_free_buf_list);
n_hdlc_free_buf_list(&n_hdlc->rx_buf_list);
n_hdlc_free_buf_list(&n_hdlc->tx_buf_list);
kfree(n_hdlc);
} /* end of n_hdlc_tty_close() */
/**
* n_hdlc_tty_open - called when line discipline changed to n_hdlc
* @ tty : pointer to tty info structure
*
* Returns 0 if success , otherwise error code
*/
static int n_hdlc_tty_open(struct tty_struct *tty)
{
struct n_hdlc *n_hdlc = tty->disc_data;
pr_debug("%s() called (device=%s)\n" , __func__, tty->name);
/* There should not be an existing table for this slot. */
if (n_hdlc) {
pr_err("%s: tty already associated!\n" , __func__);
return -EEXIST;
}
n_hdlc = n_hdlc_alloc();
if (!n_hdlc) {
pr_err("%s: n_hdlc_alloc failed\n" , __func__);
return -ENFILE;
}
INIT_WORK(&n_hdlc->write_work, n_hdlc_tty_write_work);
n_hdlc->tty_for_write_work = tty;
tty->disc_data = n_hdlc;
tty->receive_room = 65536 ;
/* change tty_io write() to not split large writes into 8K chunks */
set_bit(TTY_NO_WRITE_SPLIT, &tty->flags);
/* flush receive data from driver */
tty_driver_flush_buffer(tty);
return 0 ;
} /* end of n_tty_hdlc_open() */
/**
* n_hdlc_send_frames - send frames on pending send buffer list
* @ n_hdlc : pointer to ldisc instance data
* @ tty : pointer to tty instance data
*
* Send frames on pending send buffer list until the driver does not accept a
* frame ( busy ) this function is called after adding a frame to the send buffer
* list and by the tty wakeup callback .
*/
static void n_hdlc_send_frames(struct n_hdlc *n_hdlc, struct tty_struct *tty)
{
unsigned long flags;
struct n_hdlc_buf *tbuf;
ssize_t actual;
check_again:
spin_lock_irqsave(&n_hdlc->tx_buf_list.spinlock, flags);
if (n_hdlc->tbusy) {
n_hdlc->woke_up = true ;
spin_unlock_irqrestore(&n_hdlc->tx_buf_list.spinlock, flags);
return ;
}
n_hdlc->tbusy = true ;
n_hdlc->woke_up = false ;
spin_unlock_irqrestore(&n_hdlc->tx_buf_list.spinlock, flags);
tbuf = n_hdlc_buf_get(&n_hdlc->tx_buf_list);
while (tbuf) {
pr_debug("sending frame %p, count=%zu\n" , tbuf, tbuf->count);
/* Send the next block of data to device */
set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
actual = tty->ops->write(tty, tbuf->buf, tbuf->count);
/* rollback was possible and has been done */
if (actual == -ERESTARTSYS) {
n_hdlc_buf_return(&n_hdlc->tx_buf_list, tbuf);
break ;
}
/* if transmit error, throw frame away by */
/* pretending it was accepted by driver */
if (actual < 0 )
actual = tbuf->count;
if (actual == tbuf->count) {
pr_debug("frame %p completed\n" , tbuf);
/* free current transmit buffer */
n_hdlc_buf_put(&n_hdlc->tx_free_buf_list, tbuf);
/* wait up sleeping writers */
wake_up_interruptible(&tty->write_wait);
/* get next pending transmit buffer */
tbuf = n_hdlc_buf_get(&n_hdlc->tx_buf_list);
} else {
pr_debug("frame %p pending\n" , tbuf);
/*
* the buffer was not accepted by driver ,
* return it back into tx queue
*/
n_hdlc_buf_return(&n_hdlc->tx_buf_list, tbuf);
break ;
}
}
if (!tbuf)
clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
/* Clear the re-entry flag */
spin_lock_irqsave(&n_hdlc->tx_buf_list.spinlock, flags);
n_hdlc->tbusy = false ;
spin_unlock_irqrestore(&n_hdlc->tx_buf_list.spinlock, flags);
if (n_hdlc->woke_up)
goto check_again;
} /* end of n_hdlc_send_frames() */
/**
* n_hdlc_tty_write_work - Asynchronous callback for transmit wakeup
* @ work : pointer to work_struct
*
* Called when low level device driver can accept more send data .
*/
static void n_hdlc_tty_write_work(struct work_struct *work)
{
struct n_hdlc *n_hdlc = container_of(work, struct n_hdlc, write_work);
struct tty_struct *tty = n_hdlc->tty_for_write_work;
n_hdlc_send_frames(n_hdlc, tty);
} /* end of n_hdlc_tty_write_work() */
/**
* n_hdlc_tty_wakeup - Callback for transmit wakeup
* @ tty : pointer to associated tty instance data
*
* Called when low level device driver can accept more send data .
*/
static void n_hdlc_tty_wakeup(struct tty_struct *tty)
{
struct n_hdlc *n_hdlc = tty->disc_data;
schedule_work(&n_hdlc->write_work);
} /* end of n_hdlc_tty_wakeup() */
/**
* n_hdlc_tty_receive - Called by tty driver when receive data is available
* @ tty : pointer to tty instance data
* @ data : pointer to received data
* @ flags : pointer to flags for data
* @ count : count of received data in bytes
*
* Called by tty low level driver when receive data is available . Data is
* interpreted as one HDLC frame .
*/
static void n_hdlc_tty_receive(struct tty_struct *tty, const u8 *data,
const u8 *flags, size_t count)
{
register struct n_hdlc *n_hdlc = tty->disc_data;
register struct n_hdlc_buf *buf;
pr_debug("%s() called count=%zu\n" , __func__, count);
if (count > maxframe) {
pr_debug("rx count>maxframesize, data discarded\n" );
return ;
}
/* get a free HDLC buffer */
buf = n_hdlc_buf_get(&n_hdlc->rx_free_buf_list);
if (!buf) {
/*
* no buffers in free list , attempt to allocate another rx
* buffer unless the maximum count has been reached
*/
if (n_hdlc->rx_buf_list.count < MAX_RX_BUF_COUNT)
buf = kmalloc(struct_size(buf, buf, maxframe),
GFP_ATOMIC);
}
if (!buf) {
pr_debug("no more rx buffers, data discarded\n" );
return ;
}
/* copy received data to HDLC buffer */
memcpy(buf->buf, data, count);
buf->count = count;
/* add HDLC buffer to list of received frames */
n_hdlc_buf_put(&n_hdlc->rx_buf_list, buf);
/* wake up any blocked reads and perform async signalling */
wake_up_interruptible(&tty->read_wait);
if (tty->fasync != NULL)
kill_fasync(&tty->fasync, SIGIO, POLL_IN);
} /* end of n_hdlc_tty_receive() */
/**
* n_hdlc_tty_read - Called to retrieve one frame of data ( if available )
* @ tty : pointer to tty instance data
* @ file : pointer to open file object
* @ kbuf : pointer to returned data buffer
* @ nr : size of returned data buffer
* @ cookie : stored rbuf from previous run
* @ offset : offset into the data buffer
*
* Returns the number of bytes returned or error code .
*/
static ssize_t n_hdlc_tty_read(struct tty_struct *tty, struct file *file,
u8 *kbuf, size_t nr, void **cookie,
unsigned long offset)
{
struct n_hdlc *n_hdlc = tty->disc_data;
int ret = 0 ;
struct n_hdlc_buf *rbuf;
DECLARE_WAITQUEUE(wait, current);
/* Is this a repeated call for an rbuf we already found earlier? */
rbuf = *cookie;
if (rbuf)
goto have_rbuf;
add_wait_queue(&tty->read_wait, &wait);
for (;;) {
if (test_bit(TTY_OTHER_CLOSED, &tty->flags)) {
ret = -EIO;
break ;
}
if (tty_hung_up_p(file))
break ;
set_current_state(TASK_INTERRUPTIBLE);
rbuf = n_hdlc_buf_get(&n_hdlc->rx_buf_list);
if (rbuf)
break ;
/* no data */
if (tty_io_nonblock(tty, file)) {
ret = -EAGAIN;
break ;
}
schedule();
if (signal_pending(current)) {
ret = -EINTR;
break ;
}
}
remove_wait_queue(&tty->read_wait, &wait);
__set_current_state(TASK_RUNNING);
if (!rbuf)
return ret;
*cookie = rbuf;
have_rbuf:
/* Have we used it up entirely? */
if (offset >= rbuf->count)
goto done_with_rbuf;
/* More data to go, but can't copy any more? EOVERFLOW */
ret = -EOVERFLOW;
if (!nr)
goto done_with_rbuf;
/* Copy as much data as possible */
ret = rbuf->count - offset;
if (ret > nr)
ret = nr;
memcpy(kbuf, rbuf->buf+offset, ret);
offset += ret;
/* If we still have data left, we leave the rbuf in the cookie */
if (offset < rbuf->count)
return ret;
done_with_rbuf:
*cookie = NULL;
if (n_hdlc->rx_free_buf_list.count > DEFAULT_RX_BUF_COUNT)
kfree(rbuf);
else
n_hdlc_buf_put(&n_hdlc->rx_free_buf_list, rbuf);
return ret;
} /* end of n_hdlc_tty_read() */
/**
* n_hdlc_tty_write - write a single frame of data to device
* @ tty : pointer to associated tty device instance data
* @ file : pointer to file object data
* @ data : pointer to transmit data ( one frame )
* @ count : size of transmit frame in bytes
*
* Returns the number of bytes written ( or error code ) .
*/
static ssize_t n_hdlc_tty_write(struct tty_struct *tty, struct file *file,
const u8 *data, size_t count)
{
struct n_hdlc *n_hdlc = tty->disc_data;
DECLARE_WAITQUEUE(wait, current);
struct n_hdlc_buf *tbuf;
ssize_t error = 0 ;
pr_debug("%s() called count=%zd\n" , __func__, count);
/* verify frame size */
if (count > maxframe) {
pr_debug("%s: truncating user packet from %zu to %d\n" ,
__func__, count, maxframe);
count = maxframe;
}
add_wait_queue(&tty->write_wait, &wait);
for (;;) {
set_current_state(TASK_INTERRUPTIBLE);
tbuf = n_hdlc_buf_get(&n_hdlc->tx_free_buf_list);
if (tbuf)
break ;
if (tty_io_nonblock(tty, file)) {
error = -EAGAIN;
break ;
}
schedule();
if (signal_pending(current)) {
error = -EINTR;
break ;
}
}
__set_current_state(TASK_RUNNING);
remove_wait_queue(&tty->write_wait, &wait);
if (!error) {
/* Retrieve the user's buffer */
memcpy(tbuf->buf, data, count);
/* Send the data */
tbuf->count = error = count;
n_hdlc_buf_put(&n_hdlc->tx_buf_list, tbuf);
n_hdlc_send_frames(n_hdlc, tty);
}
return error;
} /* end of n_hdlc_tty_write() */
/**
* n_hdlc_tty_ioctl - process IOCTL system call for the tty device .
* @ tty : pointer to tty instance data
* @ cmd : IOCTL command code
* @ arg : argument for IOCTL call ( cmd dependent )
*
* Returns command dependent result .
*/
static int n_hdlc_tty_ioctl(struct tty_struct *tty, unsigned int cmd,
unsigned long arg)
{
struct n_hdlc *n_hdlc = tty->disc_data;
int error = 0 ;
int count;
unsigned long flags;
struct n_hdlc_buf *buf = NULL;
pr_debug("%s() called %d\n" , __func__, cmd);
switch (cmd) {
case FIONREAD:
/* report count of read data available */
/* in next available frame (if any) */
spin_lock_irqsave(&n_hdlc->rx_buf_list.spinlock, flags);
buf = list_first_entry_or_null(&n_hdlc->rx_buf_list.list,
struct n_hdlc_buf, list_item);
if (buf)
count = buf->count;
else
count = 0 ;
spin_unlock_irqrestore(&n_hdlc->rx_buf_list.spinlock, flags);
error = put_user(count, (int __user *)arg);
break ;
case TIOCOUTQ:
/* get the pending tx byte count in the driver */
count = tty_chars_in_buffer(tty);
/* add size of next output frame in queue */
spin_lock_irqsave(&n_hdlc->tx_buf_list.spinlock, flags);
buf = list_first_entry_or_null(&n_hdlc->tx_buf_list.list,
struct n_hdlc_buf, list_item);
if (buf)
count += buf->count;
spin_unlock_irqrestore(&n_hdlc->tx_buf_list.spinlock, flags);
error = put_user(count, (int __user *)arg);
break ;
case TCFLSH:
switch (arg) {
case TCIOFLUSH:
case TCOFLUSH:
flush_tx_queue(tty);
}
fallthrough; /* to default */
default :
error = n_tty_ioctl_helper(tty, cmd, arg);
break ;
}
return error;
} /* end of n_hdlc_tty_ioctl() */
/**
* n_hdlc_tty_poll - TTY callback for poll system call
* @ tty : pointer to tty instance data
* @ filp : pointer to open file object for device
* @ wait : wait queue for operations
*
* Determine which operations ( read / write ) will not block and return info
* to caller .
* Returns a bit mask containing info on which ops will not block .
*/
static __poll_t n_hdlc_tty_poll(struct tty_struct *tty, struct file *filp,
poll_table *wait)
{
struct n_hdlc *n_hdlc = tty->disc_data;
__poll_t mask = 0 ;
/*
* queue the current process into any wait queue that may awaken in the
* future ( read and write )
*/
poll_wait(filp, &tty->read_wait, wait);
poll_wait(filp, &tty->write_wait, wait);
/* set bits for operations that won't block */
if (!list_empty(&n_hdlc->rx_buf_list.list))
mask |= EPOLLIN | EPOLLRDNORM; /* readable */
if (test_bit(TTY_OTHER_CLOSED, &tty->flags))
mask |= EPOLLHUP;
if (tty_hung_up_p(filp))
mask |= EPOLLHUP;
if (!tty_is_writelocked(tty) &&
!list_empty(&n_hdlc->tx_free_buf_list.list))
mask |= EPOLLOUT | EPOLLWRNORM; /* writable */
return mask;
} /* end of n_hdlc_tty_poll() */
static void n_hdlc_alloc_buf(struct n_hdlc_buf_list *list, unsigned int count,
const char *name)
{
struct n_hdlc_buf *buf;
unsigned int i;
for (i = 0 ; i < count; i++) {
buf = kmalloc(struct_size(buf, buf, maxframe), GFP_KERNEL);
if (!buf) {
pr_debug("%s(), kmalloc() failed for %s buffer %u\n" ,
__func__, name, i);
return ;
}
n_hdlc_buf_put(list, buf);
}
}
/**
* n_hdlc_alloc - allocate an n_hdlc instance data structure
*
* Returns a pointer to newly created structure if success , otherwise % NULL
*/
static struct n_hdlc *n_hdlc_alloc(void )
{
struct n_hdlc *n_hdlc = kzalloc(sizeof (*n_hdlc), GFP_KERNEL);
if (!n_hdlc)
return NULL;
spin_lock_init(&n_hdlc->rx_free_buf_list.spinlock);
spin_lock_init(&n_hdlc->tx_free_buf_list.spinlock);
spin_lock_init(&n_hdlc->rx_buf_list.spinlock);
spin_lock_init(&n_hdlc->tx_buf_list.spinlock);
INIT_LIST_HEAD(&n_hdlc->rx_free_buf_list.list);
INIT_LIST_HEAD(&n_hdlc->tx_free_buf_list.list);
INIT_LIST_HEAD(&n_hdlc->rx_buf_list.list);
INIT_LIST_HEAD(&n_hdlc->tx_buf_list.list);
n_hdlc_alloc_buf(&n_hdlc->rx_free_buf_list, DEFAULT_RX_BUF_COUNT, "rx" );
n_hdlc_alloc_buf(&n_hdlc->tx_free_buf_list, DEFAULT_TX_BUF_COUNT, "tx" );
return n_hdlc;
} /* end of n_hdlc_alloc() */
/**
* n_hdlc_buf_return - put the HDLC buffer after the head of the specified list
* @ buf_list : pointer to the buffer list
* @ buf : pointer to the buffer
*/
static void n_hdlc_buf_return(struct n_hdlc_buf_list *buf_list,
struct n_hdlc_buf *buf)
{
unsigned long flags;
spin_lock_irqsave(&buf_list->spinlock, flags);
list_add(&buf->list_item, &buf_list->list);
buf_list->count++;
spin_unlock_irqrestore(&buf_list->spinlock, flags);
}
/**
* n_hdlc_buf_put - add specified HDLC buffer to tail of specified list
* @ buf_list : pointer to buffer list
* @ buf : pointer to buffer
*/
static void n_hdlc_buf_put(struct n_hdlc_buf_list *buf_list,
struct n_hdlc_buf *buf)
{
unsigned long flags;
spin_lock_irqsave(&buf_list->spinlock, flags);
list_add_tail(&buf->list_item, &buf_list->list);
buf_list->count++;
spin_unlock_irqrestore(&buf_list->spinlock, flags);
} /* end of n_hdlc_buf_put() */
/**
* n_hdlc_buf_get - remove and return an HDLC buffer from list
* @ buf_list : pointer to HDLC buffer list
*
* Remove and return an HDLC buffer from the head of the specified HDLC buffer
* list .
* Returns a pointer to HDLC buffer if available , otherwise % NULL .
*/
static struct n_hdlc_buf *n_hdlc_buf_get(struct n_hdlc_buf_list *buf_list)
{
unsigned long flags;
struct n_hdlc_buf *buf;
spin_lock_irqsave(&buf_list->spinlock, flags);
buf = list_first_entry_or_null(&buf_list->list,
struct n_hdlc_buf, list_item);
if (buf) {
list_del(&buf->list_item);
buf_list->count--;
}
spin_unlock_irqrestore(&buf_list->spinlock, flags);
return buf;
} /* end of n_hdlc_buf_get() */
static struct tty_ldisc_ops n_hdlc_ldisc = {
.owner = THIS_MODULE,
.num = N_HDLC,
.name = "hdlc" ,
.open = n_hdlc_tty_open,
.close = n_hdlc_tty_close,
.read = n_hdlc_tty_read,
.write = n_hdlc_tty_write,
.ioctl = n_hdlc_tty_ioctl,
.poll = n_hdlc_tty_poll,
.receive_buf = n_hdlc_tty_receive,
.write_wakeup = n_hdlc_tty_wakeup,
.flush_buffer = flush_rx_queue,
};
static int __init n_hdlc_init(void )
{
int status;
/* range check maxframe arg */
maxframe = clamp(maxframe, 4096 , MAX_HDLC_FRAME_SIZE);
status = tty_register_ldisc(&n_hdlc_ldisc);
if (!status)
pr_info("N_HDLC line discipline registered with maxframe=%d\n" ,
maxframe);
else
pr_err("N_HDLC: error registering line discipline: %d\n" ,
status);
return status;
} /* end of init_module() */
static void __exit n_hdlc_exit(void )
{
tty_unregister_ldisc(&n_hdlc_ldisc);
}
module_init(n_hdlc_init);
module_exit(n_hdlc_exit);
MODULE_DESCRIPTION("HDLC line discipline support" );
MODULE_LICENSE("GPL" );
MODULE_AUTHOR("Paul Fulghum paulkf@microgate.com" );
module_param(maxframe, int , 0 );
MODULE_ALIAS_LDISC(N_HDLC);
Messung V0.5 in Prozent C=92 H=91 G=91
¤ Dauer der Verarbeitung: 0.16 Sekunden
(vorverarbeitet am 2026-09-29)
¤
*© Formatika GbR, Deutschland