// 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.
*/
/** * 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;
};
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.
*/ staticvoid n_hdlc_tty_close(struct tty_struct *tty)
{ struct n_hdlc *n_hdlc = tty->disc_data;
/* 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
*/ staticint 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;
}
/* 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.
*/ staticvoid n_hdlc_send_frames(struct n_hdlc *n_hdlc, struct tty_struct *tty)
{ unsignedlong flags; struct n_hdlc_buf *tbuf;
ssize_t actual;
/* 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.
*/ staticvoid 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.
*/ staticvoid 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.
*/ staticvoid n_hdlc_tty_receive(struct tty_struct *tty, const u8 *data, const u8 *flags, size_t count)
{ registerstruct n_hdlc *n_hdlc = tty->disc_data; registerstruct 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, unsignedlong 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;
}
}
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;
}
}
/** * 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 */
/** * 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
*/ staticvoid n_hdlc_buf_return(struct n_hdlc_buf_list *buf_list, struct n_hdlc_buf *buf)
{ unsignedlong flags;
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.
*/ staticstruct n_hdlc_buf *n_hdlc_buf_get(struct n_hdlc_buf_list *buf_list)
{ unsignedlong flags; struct n_hdlc_buf *buf;
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);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.