/* 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 , * Al Longyear , Paul Mackerras * * Original release 01/11/99 * ==FILEDATE 19990524== * * 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-4096 bytes (2 is min HDLC frame * with 1 addr byte and 1 ctrl byte). * * 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 "1.2" #include #include #include #include #include #include #include #include #include #undef VERSION #define VERSION(major,minor,patch) (((((major)<<8)+(minor))<<8)+(patch)) #if LINUX_VERSION_CODE < VERSION(2,1,14) #include #endif #if LINUX_VERSION_CODE >= VERSION(2,1,23) #include #endif #include #include #include #include #include /* used in new tty drivers */ #include /* used in new tty drivers */ #include #include #include #include #include #ifdef CONFIG_KERNELD #include #endif #if LINUX_VERSION_CODE >= VERSION(2,1,4) #include #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 #if LINUX_VERSION_CODE >= VERSION(2,1,5) #include #endif #else /* 2.0.x and 2.1.x before 2.1.4 */ #define GET_USER(error,value,addr) \ do { \ error = verify_area (VERIFY_READ, (void *) addr, sizeof (value)); \ if (error == 0) \ value = get_user(addr); \ } while (0) #define COPY_FROM_USER(error,dest,src,size) \ do { \ error = verify_area (VERIFY_READ, (void *) src, size); \ if (error == 0) \ memcpy_fromfs (dest, src, size); \ } while (0) #define PUT_USER(error,value,addr) \ do { \ error = verify_area (VERIFY_WRITE, (void *) addr, sizeof (value)); \ if (error == 0) \ put_user (value, addr); \ } while (0) #define COPY_TO_USER(error,dest,src,size) \ do { \ error = verify_area (VERIFY_WRITE, (void *) dest, size); \ if (error == 0) \ memcpy_tofs (dest, src, size); \ } while (0) #endif #if LINUX_VERSION_CODE < VERSION(2,1,0) #define __init typedef int spinlock_t; #define spin_lock_irqsave(a,b) {save_flags((b));cli();} #define spin_unlock_irqrestore(a,b) {restore_flags((b));} #define spin_lock(a) #define spin_unlock(a) #define schedule_timeout(a){current->timeout = jiffies + (a); schedule();} #endif #if LINUX_VERSION_CODE < VERSION(2,1,37) #define test_and_set_bit(nr, addr) set_bit(nr, addr) #endif #if LINUX_VERSION_CODE < VERSION(2,1,57) #define signal_pending(p) ((p)->signal & ~(p)->blocked) #endif #if LINUX_VERSION_CODE < VERSION(2,1,25) #define net_device_stats enet_statistics #endif #if LINUX_VERSION_CODE < VERSION(2,1,60) typedef int rw_ret_t; typedef unsigned int rw_count_t; #else typedef ssize_t rw_ret_t; typedef size_t rw_count_t; #endif /* * Buffers for individual HDLC frames */ #define MAX_HDLC_FRAME_SIZE 4096 #define DEFAULT_RX_BUF_COUNT 10 #define MAX_RX_BUF_COUNT 30 #define DEFAULT_TX_BUF_COUNT 1 typedef struct _n_hdlc_buf { struct _n_hdlc_buf *link; int count; char buf[MAX_HDLC_FRAME_SIZE]; } N_HDLC_BUF; 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; 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); #if LINUX_VERSION_CODE >= VERSION(2,1,19) MODULE_PARM(debuglevel, "i"); #endif /* debug level can be set by insmod for debugging purposes */ #define DEBUG_LEVEL_INFO 1 int debuglevel=0; /* 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); #if LINUX_VERSION_CODE < VERSION(2,1,23) static int n_hdlc_tty_select (struct tty_struct *tty, struct inode *inode, struct file *filp, int sel_type, select_table * wait); #else static unsigned int n_hdlc_tty_poll (struct tty_struct *tty, struct file *filp, poll_table * wait); #endif 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->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; } 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\n",__FILE__,__LINE__); /* 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; /* 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__); save_flags(flags); cli (); if (n_hdlc->tbusy) { n_hdlc->woke_up = 1; restore_flags(flags); return; } n_hdlc->tbusy = 1; 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 */ n_hdlc->woke_up = 0; 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); /* 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 */ /* check if wake up code called since last write call */ if (n_hdlc->woke_up) continue; /* 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 (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; } if (!n_hdlc->tbuf) tty->flags &= ~(1 << TTY_DO_WRITE_WAKEUP); else 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. * * Arguments: tty pointer to tty isntance data * data pointer to received data * flags pointer to flags for data * count count of received data in bytes * * Return Value: None */ static void n_hdlc_tty_receive(struct tty_struct *tty, const __u8 * data, char *flags, int count) { register struct n_hdlc *n_hdlc = tty2n_hdlc (tty); register N_HDLC_BUF *buf; if (debuglevel >= DEBUG_LEVEL_INFO) printk("%s(%d)n_hdlc_tty_receive() called count=%d\n", __FILE__,__LINE__, count); /* This can happen if stuff comes in on the backup tty */ if (n_hdlc == 0 || tty != n_hdlc->tty) return; /* verify line is using HDLC discipline */ if (n_hdlc->magic != HDLC_MAGIC) { printk("%s(%d) line not using HDLC discipline\n", __FILE__,__LINE__); 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 = (N_HDLC_BUF*)kmalloc(sizeof(N_HDLC_BUF),GFP_ATOMIC); } if (!buf) { printk("%s(%d) no more rx buffers, data discarded\n", __FILE__,__LINE__); 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 (&n_hdlc->read_wait); if (n_hdlc->tty->fasync != NULL) kill_fasync (n_hdlc->tty->fasync, SIGIO); } /* end of n_hdlc_tty_receive() */ /* n_hdlc_tty_read() * * Called to retreive one frame of data (if available) * * Arguments: * * tty pointer to tty instance data * file pointer to open file object * buf pointer to returned data buffer * nr size of returned data buffer * * Return Value: * * Number of bytes returned or error code */ static rw_ret_t n_hdlc_tty_read (struct tty_struct *tty, struct file *file, __u8 * buf, rw_count_t nr) { struct n_hdlc *n_hdlc = tty2n_hdlc(tty); int error; rw_ret_t ret; N_HDLC_BUF *rbuf; if (debuglevel >= DEBUG_LEVEL_INFO) printk("%s(%d)n_hdlc_tty_read() called\n",__FILE__,__LINE__); /* Validate the pointers */ if (!n_hdlc) return -EIO; /* verify user access to buffer */ error = verify_area (VERIFY_WRITE, buf, nr); if (error != 0) { printk(KERN_WARNING"%s(%d) n_hdlc_tty_read() can't verify user " "buffer\n",__FILE__,__LINE__); return (error); } for (;;) { n_hdlc = tty2n_hdlc (tty); if (!n_hdlc || n_hdlc->magic != HDLC_MAGIC || tty != n_hdlc->tty) return 0; rbuf = n_hdlc_buf_get(&n_hdlc->rx_buf_list); if (rbuf) break; /* no data */ if (file->f_flags & O_NONBLOCK) return -EAGAIN; /* TODO: no timeout? current->timeout = 0;*/ interruptible_sleep_on (&n_hdlc->read_wait); if (signal_pending(current)) return -EINTR; } if (rbuf->count > nr) { /* frame too large for caller's buffer (discard frame) */ ret = (rw_ret_t)-EOVERFLOW; } else { /* Copy the data to the caller's buffer */ COPY_TO_USER(error,buf,rbuf->buf,rbuf->count); if (error) ret = (rw_ret_t)error; else ret = (rw_ret_t)rbuf->count; } /* return HDLC buffer to free list unless the free list */ /* count has exceeded the default value, in which case the */ /* buffer is freed back to the OS to conserve memory */ 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 * * Arguments: 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 * * Return Value: number of bytes written (or error code) */ static rw_ret_t n_hdlc_tty_write (struct tty_struct *tty, struct file *file, const __u8 * data, rw_count_t count) { struct n_hdlc *n_hdlc = tty2n_hdlc (tty); int error = 0; DECLARE_WAITQUEUE(wait, current); N_HDLC_BUF *tbuf; if (debuglevel >= DEBUG_LEVEL_INFO) printk("%s(%d)n_hdlc_tty_write() called count=%d\n", __FILE__,__LINE__,count); /* Verify pointers */ if (!n_hdlc) return -EIO; if (n_hdlc->magic != HDLC_MAGIC) return -EIO; /* verify frame size */ if (count > MAX_HDLC_FRAME_SIZE) { if (debuglevel & DEBUG_LEVEL_INFO) printk (KERN_WARNING "n_hdlc_tty_write: truncating user packet " "from %lu to %d\n", (unsigned long) count, MAX_HDLC_FRAME_SIZE); count = MAX_HDLC_FRAME_SIZE; } /* Allocate transmit buffer */ tbuf = n_hdlc_buf_get(&n_hdlc->tx_free_buf_list); if (!tbuf) { /* sleep until transmit buffer available */ add_wait_queue(&n_hdlc->write_wait, &wait); while (!tbuf) { /* TODO: no timeout? current->timeout = 0;*/ current->state = TASK_INTERRUPTIBLE; schedule(); n_hdlc = tty2n_hdlc (tty); if (!n_hdlc || n_hdlc->magic != HDLC_MAGIC || tty != n_hdlc->tty) { printk("n_hdlc_tty_write: %p invalid after wait!\n", n_hdlc); error = -EIO; break; } if (signal_pending(current)) { error = -EINTR; break; } tbuf = n_hdlc_buf_get(&n_hdlc->tx_free_buf_list); } current->state = TASK_RUNNING; remove_wait_queue(&n_hdlc->write_wait, &wait); } if (!error) { /* Retrieve the user's buffer */ COPY_FROM_USER (error, tbuf->buf, data, count); if (error) { /* return tx buffer to free list */ n_hdlc_buf_put(&n_hdlc->tx_free_buf_list,tbuf); } else { /* 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. * * Arguments: * * tty pointer to tty instance data * file pointer to open file object for device * cmd IOCTL command code * arg argument for IOCTL call (cmd dependent) * * Return Value: Command dependent */ static int n_hdlc_tty_ioctl (struct tty_struct *tty, struct file * file, unsigned int cmd, unsigned long arg) { struct n_hdlc *n_hdlc = tty2n_hdlc (tty); int error = 0; int count; unsigned long flags; if (debuglevel >= DEBUG_LEVEL_INFO) printk("%s(%d)n_hdlc_tty_ioctl() called %d\n", __FILE__,__LINE__,cmd); /* Verify the status of the device */ if (!n_hdlc || n_hdlc->magic != HDLC_MAGIC) return -EBADF; 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); if (n_hdlc->rx_buf_list.head) count = n_hdlc->rx_buf_list.head->count; else count = 0; spin_unlock_irqrestore(&n_hdlc->rx_buf_list.spinlock,flags); PUT_USER (error, count, (int *) arg); break; case TIOCOUTQ: /* get the pending tx byte count in the driver */ count = tty->driver.chars_in_buffer ? tty->driver.chars_in_buffer(tty) : 0; /* add size of next output frame in queue */ spin_lock_irqsave(&n_hdlc->tx_buf_list.spinlock,flags); if (n_hdlc->tx_buf_list.head) count += n_hdlc->tx_buf_list.head->count; spin_unlock_irqrestore(&n_hdlc->tx_buf_list.spinlock,flags); PUT_USER (error, count, (int*)arg); break; default: error = n_tty_ioctl (tty, file, cmd, arg); break; } return error; } /* end of n_hdlc_tty_ioctl() */ #if LINUX_VERSION_CODE < VERSION(2,1,23) /* n_hdlc_tty_select() * * Device select method. Determine if operation requires * blocking and if so put appropriate wait queue in select * table and return 0, otherwise return 1. * * Arguments: * * tty pointer to tty device instance data * inode pointer to inode for device * filp pointer to file object * sel_type identified the select type (read/write/exception) * wait select table for adding wait queue if appropriate * * Return Value: * * 1 if no need to block on operation * 0 if must block and wait queue added to select table */ static int n_hdlc_tty_select (struct tty_struct *tty, struct inode *inode, struct file *filp, int sel_type, select_table * wait) { struct n_hdlc *n_hdlc = tty2n_hdlc(tty); int result = 1; if (debuglevel >= DEBUG_LEVEL_INFO) printk("%s(%d)n_hdlc_tty_select() called\n",__FILE__,__LINE__); /* Verify the status of the device */ if (!n_hdlc) return -EBADF; if (n_hdlc->magic != HDLC_MAGIC || tty != n_hdlc->tty) return -EBADF; switch (sel_type) { case SEL_IN: if (n_hdlc->rx_buf_list.head) break; case SEL_EX: /* Exceptions or read errors */ /* Is this a pty link and the remote disconnected? */ if (tty->flags & (1 << TTY_OTHER_CLOSED)) break; /* Is this a local link and the modem disconnected? */ if (tty_hung_up_p (filp)) break; select_wait (&n_hdlc->read_wait, wait); result = 0; break; /* Write mode. A write is allowed if there is no current transmission */ case SEL_OUT: if (!n_hdlc->tx_free_buf_list.head) { select_wait (&n_hdlc->write_wait, wait); result = 0; } break; } return result; } /* end of n_hdlc_tty_select() */ #else /* 2.1.23 or later */ /* n_hdlc_tty_poll() * * TTY callback for poll system call. Determine which * operations (read/write) will not block and return * info to caller. * * Arguments: * * tty pointer to tty instance data * filp pointer to open file object for device * poll_table wait queue for operations * * Return Value: * * bit mask containing info on which ops will not block */ static unsigned int n_hdlc_tty_poll (struct tty_struct *tty, struct file *filp, poll_table * wait) { struct n_hdlc *n_hdlc = tty2n_hdlc (tty); unsigned int mask = 0; if (debuglevel >= DEBUG_LEVEL_INFO) printk("%s(%d)n_hdlc_tty_poll() called\n",__FILE__,__LINE__); if (n_hdlc && n_hdlc->magic == HDLC_MAGIC && tty == n_hdlc->tty) { /* queue current process into any wait queue that */ /* may awaken in the future (read and write) */ #if LINUX_VERSION_CODE < VERSION(2,1,89) poll_wait(&n_hdlc->read_wait, wait); poll_wait(&n_hdlc->write_wait, wait); #else poll_wait(filp, &n_hdlc->read_wait, wait); poll_wait(filp, &n_hdlc->write_wait, wait); #endif /* set bits for operations that wont block */ if(n_hdlc->rx_buf_list.head) mask |= POLLIN | POLLRDNORM; /* readable */ if(tty->flags & (1 << TTY_OTHER_CLOSED)) mask |= POLLHUP; if(tty_hung_up_p(filp)) mask |= POLLHUP; if(n_hdlc->tx_free_buf_list.head) mask |= POLLOUT | POLLWRNORM; /* writable */ } return mask; } /* end of n_hdlc_tty_poll() */ #endif /* n_hdlc_alloc() * * Allocate an n_hdlc instance data structure * * Arguments: None * Return Value: pointer to structure if success, otherwise 0 */ static struct n_hdlc *n_hdlc_alloc (void) { struct n_hdlc *n_hdlc; N_HDLC_BUF *buf; int i; n_hdlc = (struct n_hdlc *)kmalloc(sizeof(struct n_hdlc), GFP_KERNEL); if (!n_hdlc) return 0; memset(n_hdlc, 0, sizeof(*n_hdlc)); n_hdlc_buf_list_init(&n_hdlc->rx_free_buf_list); n_hdlc_buf_list_init(&n_hdlc->tx_free_buf_list); n_hdlc_buf_list_init(&n_hdlc->rx_buf_list); n_hdlc_buf_list_init(&n_hdlc->tx_buf_list); /* allocate free rx buffer list */ for(i=0;irx_free_buf_list,buf); } /* allocate free rx buffer list */ for(i=0;itx_free_buf_list,buf); } /* Initialize the control block */ n_hdlc->magic = HDLC_MAGIC; n_hdlc->flags = 0; init_waitqueue_head(&n_hdlc->read_wait); init_waitqueue_head(&n_hdlc->write_wait); return n_hdlc; } /* end of n_hdlc_alloc() */ /* n_hdlc_buf_list_init() * * initialize specified HDLC buffer list * * Arguments: list pointer to buffer list * Return Value: None */ void n_hdlc_buf_list_init(N_HDLC_BUF_LIST *list) { memset(list,0,sizeof(N_HDLC_BUF_LIST)); } /* end of n_hdlc_buf_list_init() */ /* n_hdlc_buf_put() * * add specified HDLC buffer to tail of specified list * * Arguments: * * list pointer to buffer list * buf pointer to buffer * * Return Value: None */ void n_hdlc_buf_put(N_HDLC_BUF_LIST *list,N_HDLC_BUF *buf) { unsigned long flags; spin_lock_irqsave(&list->spinlock,flags); buf->link=NULL; if(list->tail) list->tail->link = buf; else list->head = buf; list->tail = buf; (list->count)++; spin_unlock_irqrestore(&list->spinlock,flags); } /* end of n_hdlc_buf_put() */ /* n_hdlc_buf_get() * * remove and return an HDLC buffer from the * head of the specified HDLC buffer list * * Arguments: * * list pointer to HDLC buffer list * * Return Value: * * pointer to HDLC buffer if available, otherwise NULL */ N_HDLC_BUF* n_hdlc_buf_get(N_HDLC_BUF_LIST *list) { unsigned long flags; N_HDLC_BUF *buf; spin_lock_irqsave(&list->spinlock,flags); buf = list->head; if (buf) { list->head = buf->link; (list->count)--; } if (!list->head) list->tail = NULL; spin_unlock_irqrestore(&list->spinlock,flags); return buf; } /* end of n_hdlc_buf_get() */ /* init_module() * * called when module is loading to register line discipline * * Arguments: None * Return Value: 0 if success, otherwise error code */ int init_module(void) { static struct tty_ldisc n_hdlc_ldisc; int status; printk("HDLC line discipline: version %s\n", szVersion); /* Register the tty discipline */ memset(&n_hdlc_ldisc, 0, sizeof (n_hdlc_ldisc)); n_hdlc_ldisc.magic = TTY_LDISC_MAGIC; #if LINUX_VERSION_CODE >= VERSION(2,1,28) n_hdlc_ldisc.name = "hdlc"; #endif n_hdlc_ldisc.open = n_hdlc_tty_open; n_hdlc_ldisc.close = n_hdlc_tty_close; n_hdlc_ldisc.read = n_hdlc_tty_read; n_hdlc_ldisc.write = n_hdlc_tty_write; n_hdlc_ldisc.ioctl = n_hdlc_tty_ioctl; #if LINUX_VERSION_CODE < VERSION(2,1,23) n_hdlc_ldisc.select = n_hdlc_tty_select; #else n_hdlc_ldisc.poll = n_hdlc_tty_poll; #endif n_hdlc_ldisc.receive_room = n_hdlc_tty_room; n_hdlc_ldisc.receive_buf = n_hdlc_tty_receive; n_hdlc_ldisc.write_wakeup = n_hdlc_tty_wakeup; status = tty_register_ldisc(N_HDLC, &n_hdlc_ldisc); if (!status) printk (KERN_INFO"N_HDLC line discipline registered.\n"); else printk (KERN_ERR"error registering line discipline: %d\n",status); if (status) printk(KERN_INFO"N_HDLC: init failure %d\n", status); return (status); } /* end of init_module() */ /* cleanup_module() * * called when module is unloading to unregister line discipline * * Arguments: None * Return Value: None */ void cleanup_module(void) { int status; /* Release tty registration of line discipline */ if ((status = tty_register_ldisc(N_HDLC, NULL))) printk("N_HDLC: can't unregister line discipline (err = %d)\n", status); else printk("N_HDLC: line discipline unregistered\n"); }