meta name='generator' content='cgit v1.2.2'/>
aboutsummaryrefslogblamecommitdiffstats
path: root/drivers/net/ppp_async.c
blob: 6c2e8fa0ca31cd2865ad9f80d2f0fdc99103498b (plain) (tree)































                                                                     
                          
                       
                        
                       


                               
                            























































































                                                                                            

                                                                  





























                                                                       
                  
 


                                    
                      
                                              
                


                                               



















                                                                      

                                       




                                              
                                  
























                                                            
                













                                                                      
                            
                                     
                            








































                                                                 
 







                                                             
                



                              






                                                              





                                                            



                                                                    
                                                  









                                     

                                                          












                                                                              
                                                                            






                                                                      
                



                                                      
                                          

                                           
                            







                                                    
                






                                              
                                         









                                      
















































































































































































                                                                               

                                                                      












































































































                                                                     
                                                                     






                                                                            
                                                       

















                                                                      

                                                               









                                                                 
                       












































                                                                       

                                                              

































                                                                
                                      
                                              
                                                   
























                                                                          

                                                          
                                 

                                                      

















                                                                     
                                                   















                                                                           
                                                          











                                                                             
                                   
                                                                              
                                         
                                                   








                                                                               

                                                                           

































                                                                
                          
































































































                                                                             
                                             




                                                                              
/*
 * PPP async serial channel driver for Linux.
 *
 * Copyright 1999 Paul Mackerras.
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License
 *  as published by the Free Software Foundation; either version
 *  2 of the License, or (at your option) any later version.
 *
 * This driver provides the encapsulation and framing for sending
 * and receiving PPP frames over async serial lines.  It relies on
 * the generic PPP layer to give it frames to send and to process
 * received frames.  It implements the PPP line discipline.
 *
 * Part of the code in this driver was inspired by the old async-only
 * PPP driver, written by Michael Callahan and Al Longyear, and
 * subsequently hacked by Paul Mackerras.
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/skbuff.h>
#include <linux/tty.h>
#include <linux/netdevice.h>
#include <linux/poll.h>
#include <linux/crc-ccitt.h>
#include <linux/ppp_defs.h>
#include <linux/if_ppp.h>
#include <linux/ppp_channel.h>
#include <linux/spinlock.h>
#include <linux/init.h>
#include <linux/jiffies.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include <asm/string.h>

#define PPP_VERSION	"2.4.2"

#define OBUFSIZE	4096

/* Structure for storing local state. */
struct asyncppp {
	struct tty_struct *tty;
	unsigned int	flags;
	unsigned int	state;
	unsigned int	rbits;
	int		mru;
	spinlock_t	xmit_lock;
	spinlock_t	recv_lock;
	unsigned long	xmit_flags;
	u32		xaccm[8];
	u32		raccm;
	unsigned int	bytes_sent;
	unsigned int	bytes_rcvd;

	struct sk_buff	*tpkt;
	int		tpkt_pos;
	u16		tfcs;
	unsigned char	*optr;
	unsigned char	*olim;
	unsigned long	last_xmit;

	struct sk_buff	*rpkt;
	int		lcp_fcs;
	struct sk_buff_head rqueue;

	struct tasklet_struct tsk;

	atomic_t	refcnt;
	struct semaphore dead_sem;
	struct ppp_channel chan;	/* interface to generic ppp layer */
	unsigned char	obuf[OBUFSIZE];
};

/* Bit numbers in xmit_flags */
#define XMIT_WAKEUP	0
#define XMIT_FULL	1
#define XMIT_BUSY	2

/* State bits */
#define SC_TOSS		1
#define SC_ESCAPE	2
#define SC_PREV_ERROR	4

/* Bits in rbits */
#define SC_RCV_BITS	(SC_RCV_B7_1|SC_RCV_B7_0|SC_RCV_ODDP|SC_RCV_EVNP)

static int flag_time = HZ;
module_param(flag_time, int, 0);
MODULE_PARM_DESC(flag_time, "ppp_async: interval between flagged packets (in clock ticks)");
MODULE_LICENSE("GPL");
MODULE_ALIAS_LDISC(N_PPP);

/*
 * Prototypes.
 */
static int ppp_async_encode(struct asyncppp *ap);
static int ppp_async_send(struct ppp_channel *chan, struct sk_buff *skb);
static int ppp_async_push(struct asyncppp *ap);
static void ppp_async_flush_output(struct asyncppp *ap);
static void ppp_async_input(struct asyncppp *ap, const unsigned char *buf,
			    char *flags, int count);
static int ppp_async_ioctl(struct ppp_channel *chan, unsigned int cmd,
			   unsigned long arg);
static void ppp_async_process(unsigned long arg);

static void async_lcp_peek(struct asyncppp *ap, unsigned char *data,
			   int len, int inbound);

static struct ppp_channel_ops async_ops = {
	ppp_async_send,
	ppp_async_ioctl
};

/*
 * Routines implementing the PPP line discipline.
 */

/*
 * We have a potential race on dereferencing tty->disc_data,
 * because the tty layer provides no locking at all - thus one
 * cpu could be running ppp_asynctty_receive while another
 * calls ppp_asynctty_close, which zeroes tty->disc_data and
 * frees the memory that ppp_asynctty_receive is using.  The best
 * way to fix this is to use a rwlock in the tty struct, but for now
 * we use a single global rwlock for all ttys in ppp line discipline.
 *
 * FIXME: this is no longer true. The _close path for the ldisc is
 * now guaranteed to be sane.
 */
static DEFINE_RWLOCK(disc_data_lock);

static struct asyncppp *ap_get(struct tty_struct *tty)
{
	struct asyncppp *ap;

	read_lock(&disc_data_lock);
	ap = tty->disc_data;
	if (ap != NULL)
		atomic_inc(&ap->refcnt);
	read_unlock(&disc_data_lock);
	return ap;
}

static void ap_put(struct asyncppp *ap)
{
	if (atomic_dec_and_test(&ap->refcnt))
		up(&ap->dead_sem);
}

/*
 * Called when a tty is put into PPP line discipline. Called in process
 * context.
 */
static int
ppp_asynctty_open(struct tty_struct *tty)
{
	struct asyncppp *ap;
	int err;
	int speed;

	if (tty->ops->write == NULL)
		return -EOPNOTSUPP;

	err = -ENOMEM;
	ap = kzalloc(sizeof(*ap), GFP_KERNEL);
	if (!ap)
		goto out;

	/* initialize the asyncppp structure */
	ap->tty = tty;
	ap->mru = PPP_MRU;
	spin_lock_init(&ap->xmit_lock);
	spin_lock_init(&ap->recv_lock);
	ap->xaccm[0] = ~0U;
	ap->xaccm[3] = 0x60000000U;
	ap->raccm = ~0U;
	ap->optr = ap->obuf;
	ap->olim = ap->obuf;
	ap->lcp_fcs = -1;

	skb_queue_head_init(&ap->rqueue);
	tasklet_init(&ap->tsk, ppp_async_process, (unsigned long) ap);

	atomic_set(&ap->refcnt, 1);
	init_MUTEX_LOCKED(&ap->dead_sem);

	ap->chan.private = ap;
	ap->chan.ops = &async_ops;
	ap->chan.mtu = PPP_MRU;
	speed = tty_get_baud_rate(tty);
	ap->chan.speed = speed;
	err = ppp_register_channel(&ap->chan);
	if (err)
		goto out_free;

	tty->disc_data = ap;
	tty->receive_room = 65536;
	return 0;

 out_free:
	kfree(ap);
 out:
	return err;
}

/*
 * Called when the tty is put into another line discipline
 * or it hangs up.  We have to wait for any cpu currently
 * executing in any of the other ppp_asynctty_* routines to
 * finish before we can call ppp_unregister_channel and free
 * the asyncppp struct.  This routine must be called from
 * process context, not interrupt or softirq context.
 */
static void
ppp_asynctty_close(struct tty_struct *tty)
{
	struct asyncppp *ap;

	write_lock_irq(&disc_data_lock);
	ap = tty->disc_data;
	tty->disc_data = NULL;
	write_unlock_irq(&disc_data_lock);
	if (!ap)
		return;

	/*
	 * We have now ensured that nobody can start using ap from now
	 * on, but we have to wait for all existing users to finish.
	 * Note that ppp_unregister_channel ensures that no calls to
	 * our channel ops (i.e. ppp_async_send/ioctl) are in progress
	 * by the time it returns.
	 */
	if (!atomic_dec_and_test(&ap->refcnt))
		down(&ap->dead_sem);
	tasklet_kill(&ap->tsk);

	ppp_unregister_channel(&ap->chan);
	kfree_skb(ap->rpkt);
	skb_queue_purge(&ap->rqueue);
	kfree_skb(ap->tpkt);
	kfree(ap);
}

/*
 * Called on tty hangup in process context.
 *
 * Wait for I/O to driver to complete and unregister PPP channel.
 * This is already done by the close routine, so just call that.
 */
static int ppp_asynctty_hangup(struct tty_struct *tty)
{
	ppp_asynctty_close(tty);
	return 0;
}

/*
 * Read does nothing - no data is ever available this way.
 * Pppd reads and writes packets via /dev/ppp instead.
 */
static ssize_t
ppp_asynctty_read(struct tty_struct *tty, struct file *file,
		  unsigned char __user *buf, size_t count)
{
	return -EAGAIN;
}

/*
 * Write on the tty does nothing, the packets all come in
 * from the ppp generic stuff.
 */
static ssize_t
ppp_asynctty_write(struct tty_struct *tty, struct file *file,
		   const unsigned char *buf, size_t count)
{
	return -EAGAIN;
}

/*
 * Called in process context only. May be re-entered by multiple
 * ioctl calling threads.
 */

static int
ppp_asynctty_ioctl(struct tty_struct *tty, struct file *file,
		   unsigned int cmd, unsigned long arg)
{
	struct asyncppp *ap = ap_get(tty);
	int err, val;
	int __user *p = (int __user *)arg;

	if (!ap)
		return -ENXIO;
	err = -EFAULT;
	switch (cmd) {
	case PPPIOCGCHAN:
		err = -EFAULT;
		if (put_user(ppp_channel_index(&ap->chan), p))
			break;
		err = 0;
		break;

	case PPPIOCGUNIT:
		err = -EFAULT;
		if (put_user(ppp_unit_number(&ap->chan), p))
			break;
		err = 0;
		break;

	case TCFLSH:
		/* flush our buffers and the serial port's buffer */
		if (arg == TCIOFLUSH || arg == TCOFLUSH)
			ppp_async_flush_output(ap);
		err = tty_perform_flush(tty, arg);
		break;

	case FIONREAD:
		val = 0;
		if (put_user(val, p))
			break;
		err = 0;
		break;

	default:
		/* Try the various mode ioctls */
		err = tty_mode_ioctl(tty, file, cmd, arg);
	}

	ap_put(ap);
	return err;
}

/* No kernel lock - fine */
static unsigned int
ppp_asynctty_poll(struct tty_struct *tty, struct file *file, poll_table *wait)
{
	return 0;
}

/* May sleep, don't call from interrupt level or with interrupts disabled */
static void
ppp_asynctty_receive(struct tty_struct *tty, const unsigned char *buf,
		  char *cflags, int count)
{
	struct asyncppp *ap = ap_get(tty);
	unsigned long flags;

	if (!ap)
		return;
	spin_lock_irqsave(&ap->recv_lock, flags);
	ppp_async_input(ap, buf, cflags, count);
	spin_unlock_irqrestore(&ap->recv_lock, flags);
	if (!skb_queue_empty(&ap->rqueue))
		tasklet_schedule(&ap->tsk);
	ap_put(ap);
	tty_unthrottle(tty);
}

static void
ppp_asynctty_wakeup(struct tty_struct *tty)
{
	struct asyncppp *ap = ap_get(tty);

	clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
	if (!ap)
		return;
	set_bit(XMIT_WAKEUP, &ap->xmit_flags);
	tasklet_schedule(&ap->tsk);
	ap_put(ap);
}


static struct tty_ldisc_ops ppp_ldisc = {
	.owner  = THIS_MODULE,
	.magic	= TTY_LDISC_MAGIC,
	.name	= "ppp",
	.open	= ppp_asynctty_open,
	.close	= ppp_asynctty_close,
	.hangup	= ppp_asynctty_hangup,
	.read	= ppp_asynctty_read,
	.write	= ppp_asynctty_write,
	.ioctl	= ppp_asynctty_ioctl,
	.poll	= ppp_asynctty_poll,
	.receive_buf = ppp_asynctty_receive,
	.write_wakeup = ppp_asynctty_wakeup,
};

static int __init
ppp_async_init(void)
{
	int err;

	err = tty_register_ldisc(N_PPP, &ppp_ldisc);
	if (err != 0)
		printk(KERN_ERR "PPP_async: error %d registering line disc.\n",
		       err);
	return err;
}

/*
 * The following routines provide the PPP channel interface.
 */
static int
ppp_async_ioctl(struct ppp_channel *chan, unsigned int cmd, unsigned long arg)
{
	struct asyncppp *ap = chan->private;
	void __user *argp = (void __user *)arg;
	int __user *p = argp;