📄 n_hdlc.c
字号:
/* 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 * $Id: n_hdlc.c,v 3.2 2000/11/06 22:34:38 paul Exp $ * * This code is released under the GNU General Public License (GPL) * * 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 seperate * 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. */#define HDLC_MAGIC 0x239e#define HDLC_VERSION "3.2"#include <linux/version.h>#include <linux/config.h>#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>#undef VERSION#define VERSION(major,minor,patch) (((((major)<<8)+(minor))<<8)+(patch))#include <linux/poll.h>#include <linux/in.h>#include <linux/malloc.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 <asm/system.h>#include <asm/bitops.h>#include <asm/termios.h>#include <linux/if.h>#include <linux/ioctl.h>#ifdef CONFIG_KERNELD#include <linux/kerneld.h>#endif#include <asm/segment.h>#define GET_USER(error,value,addr) error = get_user(value,addr)#define COPY_FROM_USER(error,dest,src,size) error = copy_from_user(dest,src,size) ? -EFAULT : 0#define PUT_USER(error,value,addr) error = put_user(value,addr)#define COPY_TO_USER(error,dest,src,size) error = copy_to_user(dest,src,size) ? -EFAULT : 0#include <asm/uaccess.h>typedef ssize_t rw_ret_t;typedef size_t rw_count_t;/* * 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 1typedef struct _n_hdlc_buf{ struct _n_hdlc_buf *link; int count; char buf[1];} N_HDLC_BUF;#define N_HDLC_BUF_SIZE (sizeof(N_HDLC_BUF)+maxframe)typedef struct _n_hdlc_buf_list{ N_HDLC_BUF *head; N_HDLC_BUF *tail; int count; spinlock_t spinlock; } N_HDLC_BUF_LIST;/* * Per device instance data structure */struct n_hdlc { int magic; /* magic value for structure */ __u32 flags; /* miscellaneous control flags */ struct tty_struct *tty; /* ptr to TTY structure */ struct tty_struct *backup_tty; /* TTY to use if tty gets closed */ /* Queues for select() functionality */ wait_queue_head_t read_wait; wait_queue_head_t write_wait; wait_queue_head_t poll_wait; int tbusy; /* reentrancy flag for tx wakeup code */ int woke_up; N_HDLC_BUF *tbuf; /* currently transmitting tx buffer */ N_HDLC_BUF_LIST tx_buf_list; /* list of pending transmit frame buffers */ N_HDLC_BUF_LIST rx_buf_list; /* list of received frame buffers */ N_HDLC_BUF_LIST tx_free_buf_list; /* list unused transmit frame buffers */ N_HDLC_BUF_LIST rx_free_buf_list; /* list unused received frame buffers */};/* * HDLC buffer list manipulation functions */void n_hdlc_buf_list_init(N_HDLC_BUF_LIST *list);void n_hdlc_buf_put(N_HDLC_BUF_LIST *list,N_HDLC_BUF *buf);N_HDLC_BUF* n_hdlc_buf_get(N_HDLC_BUF_LIST *list);/* Local functions */static struct n_hdlc *n_hdlc_alloc (void);MODULE_PARM(debuglevel, "i");MODULE_PARM(maxframe, "i");/* debug level can be set by insmod for debugging purposes */#define DEBUG_LEVEL_INFO 1int debuglevel=0;/* max frame size for memory allocations */ssize_t maxframe=4096;/* TTY callbacks */static rw_ret_t n_hdlc_tty_read(struct tty_struct *, struct file *, __u8 *, rw_count_t);static rw_ret_t n_hdlc_tty_write(struct tty_struct *, struct file *, const __u8 *, rw_count_t);static int n_hdlc_tty_ioctl(struct tty_struct *, struct file *, unsigned int, unsigned long);static unsigned int n_hdlc_tty_poll (struct tty_struct *tty, struct file *filp, poll_table * wait);static int n_hdlc_tty_open (struct tty_struct *);static void n_hdlc_tty_close (struct tty_struct *);static int n_hdlc_tty_room (struct tty_struct *tty);static void n_hdlc_tty_receive (struct tty_struct *tty, const __u8 * cp, char *fp, int count);static void n_hdlc_tty_wakeup (struct tty_struct *tty);#define bset(p,b) ((p)[(b) >> 5] |= (1 << ((b) & 0x1f)))#define tty2n_hdlc(tty) ((struct n_hdlc *) ((tty)->disc_data))#define n_hdlc2tty(n_hdlc) ((n_hdlc)->tty)/* Define this string only once for all macro invocations */static char szVersion[] = HDLC_VERSION;/* n_hdlc_release() * * release an n_hdlc per device line discipline info structure * */static void n_hdlc_release (struct n_hdlc *n_hdlc){ struct tty_struct *tty = n_hdlc2tty (n_hdlc); N_HDLC_BUF *buf; if (debuglevel >= DEBUG_LEVEL_INFO) printk("%s(%d)n_hdlc_release() called\n",__FILE__,__LINE__); /* Ensure that the n_hdlcd process is not hanging on select()/poll() */ wake_up_interruptible (&n_hdlc->read_wait); wake_up_interruptible (&n_hdlc->poll_wait); wake_up_interruptible (&n_hdlc->write_wait); if (tty != NULL && tty->disc_data == n_hdlc) tty->disc_data = NULL; /* Break the tty->n_hdlc link */ /* Release transmit and receive buffers */ for(;;) { buf = n_hdlc_buf_get(&n_hdlc->rx_free_buf_list); if (buf) { kfree(buf); } else break; } for(;;) { buf = n_hdlc_buf_get(&n_hdlc->tx_free_buf_list); if (buf) { kfree(buf); } else break; } for(;;) { buf = n_hdlc_buf_get(&n_hdlc->rx_buf_list); if (buf) { kfree(buf); } else break; } for(;;) { buf = n_hdlc_buf_get(&n_hdlc->tx_buf_list); if (buf) { kfree(buf); } else break; } kfree(n_hdlc); } /* end of n_hdlc_release() *//* n_hdlc_tty_close() * * 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 = tty2n_hdlc (tty); if (debuglevel >= DEBUG_LEVEL_INFO) printk("%s(%d)n_hdlc_tty_close() called\n",__FILE__,__LINE__); if (n_hdlc != NULL) { if (n_hdlc->magic != HDLC_MAGIC) { printk (KERN_WARNING"n_hdlc: trying to close unopened tty!\n"); return; }#if defined(TTY_NO_WRITE_SPLIT) clear_bit(TTY_NO_WRITE_SPLIT,&tty->flags);#endif tty->disc_data = NULL; if (tty == n_hdlc->backup_tty) n_hdlc->backup_tty = 0; if (tty != n_hdlc->tty) return; if (n_hdlc->backup_tty) { n_hdlc->tty = n_hdlc->backup_tty; } else { n_hdlc_release (n_hdlc); MOD_DEC_USE_COUNT; } } if (debuglevel >= DEBUG_LEVEL_INFO) printk("%s(%d)n_hdlc_tty_close() success\n",__FILE__,__LINE__); } /* end of n_hdlc_tty_close() *//* n_hdlc_tty_open * * called when line discipline changed to n_hdlc * * Arguments: tty pointer to tty info structure * Return Value: 0 if success, otherwise error code */static int n_hdlc_tty_open (struct tty_struct *tty){ struct n_hdlc *n_hdlc = tty2n_hdlc (tty); if (debuglevel >= DEBUG_LEVEL_INFO) printk("%s(%d)n_hdlc_tty_open() called (major=%u,minor=%u)\n", __FILE__,__LINE__, MAJOR(tty->device), MINOR(tty->device)); /* There should not be an existing table for this slot. */ if (n_hdlc) { printk (KERN_ERR"n_hdlc_tty_open:tty already associated!\n" ); return -EEXIST; } n_hdlc = n_hdlc_alloc(); if (!n_hdlc) { printk (KERN_ERR "n_hdlc_alloc failed\n"); return -ENFILE; } tty->disc_data = n_hdlc; n_hdlc->tty = tty; MOD_INC_USE_COUNT; #if defined(TTY_NO_WRITE_SPLIT) /* change tty_io write() to not split large writes into 8K chunks */ set_bit(TTY_NO_WRITE_SPLIT,&tty->flags);#endif /* Flush any pending characters in the driver and discipline. */ if (tty->ldisc.flush_buffer) tty->ldisc.flush_buffer (tty); if (tty->driver.flush_buffer) tty->driver.flush_buffer (tty); if (debuglevel >= DEBUG_LEVEL_INFO) printk("%s(%d)n_hdlc_tty_open() success\n",__FILE__,__LINE__); return 0; } /* end of n_tty_hdlc_open() *//* n_hdlc_send_frames() * * 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 * * Arguments: n_hdlc pointer to ldisc instance data * tty pointer to tty instance data * Return Value: None */static void n_hdlc_send_frames (struct n_hdlc *n_hdlc, struct tty_struct *tty){ register int actual; unsigned long flags; N_HDLC_BUF *tbuf; if (debuglevel >= DEBUG_LEVEL_INFO) printk("%s(%d)n_hdlc_send_frames() called\n",__FILE__,__LINE__); check_again: save_flags(flags); cli (); if (n_hdlc->tbusy) { n_hdlc->woke_up = 1; restore_flags(flags); return; } n_hdlc->tbusy = 1; n_hdlc->woke_up = 0; restore_flags(flags); /* get current transmit buffer or get new transmit */ /* buffer from list of pending transmit buffers */ tbuf = n_hdlc->tbuf; if (!tbuf) tbuf = n_hdlc_buf_get(&n_hdlc->tx_buf_list); while (tbuf) { if (debuglevel >= DEBUG_LEVEL_INFO) printk("%s(%d)sending frame %p, count=%d\n", __FILE__,__LINE__,tbuf,tbuf->count); /* Send the next block of data to device */ tty->flags |= (1 << TTY_DO_WRITE_WAKEUP); actual = tty->driver.write(tty, 0, tbuf->buf, tbuf->count); /* if transmit error, throw frame away by */ /* pretending it was accepted by driver */ if (actual < 0) actual = tbuf->count; if (actual == tbuf->count) { if (debuglevel >= DEBUG_LEVEL_INFO) printk("%s(%d)frame %p completed\n", __FILE__,__LINE__,tbuf); /* free current transmit buffer */ n_hdlc_buf_put(&n_hdlc->tx_free_buf_list,tbuf); /* this tx buffer is done */ n_hdlc->tbuf = NULL; /* wait up sleeping writers */ wake_up_interruptible(&n_hdlc->write_wait); wake_up_interruptible(&n_hdlc->poll_wait); /* get next pending transmit buffer */ tbuf = n_hdlc_buf_get(&n_hdlc->tx_buf_list); } else { if (debuglevel >= DEBUG_LEVEL_INFO) printk("%s(%d)frame %p pending\n", __FILE__,__LINE__,tbuf); /* buffer not accepted by driver */ /* set this buffer as pending buffer */ n_hdlc->tbuf = tbuf; break; } } if (!tbuf) tty->flags &= ~(1 << TTY_DO_WRITE_WAKEUP); /* Clear the re-entry flag */ save_flags(flags); cli (); n_hdlc->tbusy = 0; restore_flags(flags); if (n_hdlc->woke_up) goto check_again; if (debuglevel >= DEBUG_LEVEL_INFO) printk("%s(%d)n_hdlc_send_frames() exit\n",__FILE__,__LINE__); } /* end of n_hdlc_send_frames() *//* n_hdlc_tty_wakeup() * * Callback for transmit wakeup. Called when low level * device driver can accept more send data. * * Arguments: tty pointer to associated tty instance data * Return Value: None */static void n_hdlc_tty_wakeup (struct tty_struct *tty){ struct n_hdlc *n_hdlc = tty2n_hdlc (tty); if (debuglevel >= DEBUG_LEVEL_INFO) printk("%s(%d)n_hdlc_tty_wakeup() called\n",__FILE__,__LINE__); if (!n_hdlc) return; if (tty != n_hdlc->tty) { tty->flags &= ~(1 << TTY_DO_WRITE_WAKEUP); return; } n_hdlc_send_frames (n_hdlc, tty); } /* end of n_hdlc_tty_wakeup() *//* n_hdlc_tty_room() * * Callback function from tty driver. Return the amount of * space left in the receiver's buffer to decide if remote * transmitter is to be throttled. * * Arguments: tty pointer to associated tty instance data * Return Value: number of bytes left in receive buffer */static int n_hdlc_tty_room (struct tty_struct *tty){ if (debuglevel >= DEBUG_LEVEL_INFO) printk("%s(%d)n_hdlc_tty_room() called\n",__FILE__,__LINE__); /* always return a larger number to prevent */ /* throttling of remote transmitter. */ return 65536;} /* end of n_hdlc_tty_root() *//* n_hdlc_tty_receive() * * Called by tty low level driver when receive data is * available. Data is interpreted as one HDLC frame.
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -