Chinaunix首页 | 论坛 | 博客
  • 博客访问: 203603
  • 博文数量: 55
  • 博客积分: 1305
  • 博客等级: 中尉
  • 技术积分: 350
  • 用 户 组: 普通用户
  • 注册时间: 2010-12-11 15:18
文章分类

全部博文(55)

文章存档

2012年(1)

2011年(54)

分类: LINUX

2011-10-27 16:35:40

PPP驱动程序的基本原理
=====================
1) ppp设备是指在点对点的物理链路之间使用PPP帧进行分组交换的内核网络接口设备,
由于Linux内核将串行设备作为终端设备来驱动,
于是引入PPP终端规程来实现终端设备与PPP设备的接口. 根据终端设备的物理传输特性的不同,
PPP规程分为异步规程(N_PPP)和同步规程(N_SYNC_PPP)两种, 对于普通串口设备使用异步PPP规程.


2) 在PPP驱动程序中, 每一tty终端设备对应于一条PPP传输通道(chanell),
每一ppp网络设备对应于一个PPP接口单元(unit).
从终端设备上接收到的数据流通过PPP传输通道解码后转换成PPP帧传递到PPP网络接口单元,
PPP接口单元再将PPP帧转换为PPP设备的接收帧. 反之, 当PPP设备发射数据帧时,
发射帧通过PPP接口单元转换成PPP帧传递给PPP通道, PPP通道负责将PPP帧编码后写入终端设备.
在配置了多链路PPP时(CONFIG_PPP_MULTILINK), 多个PPP传输通道可连接到同一PPP接口单元.
PPP接口单元将PPP帧分割成若干个片段传递给不同的PPP传输通道, 反之,
PPP传输通道接收到的PPP帧片段被PPP接口单元重组成完整的PPP帧.

3) 在Linux-2.4中, 应用程序可通过字符设备/dev/ppp监控内核PPP驱动程序.
用户可以用ioctl(PPPIOCATTACH)将文件绑定到PPP接口单元上, 来读写PPP接口单元的输出帧,
也可以用ioctl(PPPIOCATTCHAN)将文件绑定到PPP传输通道上, 来读写PPP传输通道的输入帧.

4) PPP传输通道用channel结构描述, 系统中所有打开的传输通道在all_channels链表中.
PPP接口单元用ppp结构描述, 系统中所有建立的接口单元在all_ppp_units链表中.
当终端设备的物理链路连接成功后, 用户使用ioctl(TIOCSETD)将终端切换到PPP规程.
PPP规程初始化时, 将建立终端设备的传输通道和通道驱动结构. 对于异步PPP规程来说,
通道驱动结构为asyncppp, 它包含通道操作表async_ops.
传输通道和接口单元各自包含自已的设备文件(/dev/ppp)参数结构(ppp_file).

; drivers/char/tty_io.c:

int tty_register_ldisc(int disc, struct tty_ldisc *new_ldisc)
{
if (disc < N_TTY || disc >= NR_LDISCS)
return -EINVAL;

if (new_ldisc) {
ldiscs[disc] = *new_ldisc;
ldiscs[disc].flags |= LDISC_FLAG_DEFINED;
ldiscs[disc].num = disc;
} else
memset(&ldiscs[disc], 0, sizeof(struct tty_ldisc));

return 0;
}

int tty_ioctl(struct inode * inode, struct file * file,
unsigned int cmd, unsigned long arg)
{
struct tty_struct *tty, *real_tty;
int retval;

tty = (struct tty_struct *)file->private_data;
if (tty_paranoia_check(tty, inode->i_rdev, "tty_ioctl"))
return -EINVAL;

real_tty = tty;
if (tty->driver.type == TTY_DRIVER_TYPE_PTY &&
tty->driver.subtype == PTY_TYPE_MASTER)
real_tty = tty->link;

...

switch (cmd) {
...
case TIOCGETD:
return put_user(tty->ldisc.num, (int *) arg);
case TIOCSETD:
return tiocsetd(tty, (int *) arg);
...
}
if (tty->driver.ioctl) {
int retval = (tty->driver.ioctl)(tty, file, cmd, arg);
if (retval != -ENOIOCTLCMD)
return retval;
}
if (tty->ldisc.ioctl) {
int retval = (tty->ldisc.ioctl)(tty, file, cmd, arg);
if (retval != -ENOIOCTLCMD)
return retval;
}
return -EINVAL;
}
static int tiocsetd(struct tty_struct *tty, int *arg)
{
int retval, ldisc;

retval = get_user(ldisc, arg);
if (retval)
return retval;
return tty_set_ldisc(tty, ldisc);
}
/* Set the discipline of a tty line. */
static int tty_set_ldisc(struct tty_struct *tty, int ldisc)
{
int retval = 0;
struct tty_ldisc o_ldisc;
char buf[64];

if ((ldisc < N_TTY) || (ldisc >= NR_LDISCS))
return -EINVAL;
/* Eduardo Blanco */
/* Cyrus Durgin */
if (!(ldiscs[ldisc].flags & LDISC_FLAG_DEFINED)) { 如果设定的规程不存在
char modname [20];
sprintf(modname, "tty-ldisc-%d", ldisc);
request_module (modname); 尝试加载模块
}
if (!(ldiscs[ldisc].flags & LDISC_FLAG_DEFINED))
return -EINVAL;

if (tty->ldisc.num == ldisc)
return 0; /* We are already in the desired discipline */
o_ldisc = tty->ldisc;

tty_wait_until_sent(tty, 0); 等待终端输出设备的数据发送完

/* Shutdown the current discipline. */
if (tty->ldisc.close)
(tty->ldisc.close)(tty); 关闭原来的规程

/* Now set up the new line discipline. */
tty->ldisc = ldiscs[ldisc];
tty->termios->c_line = ldisc;
if (tty->ldisc.open)
retval = (tty->ldisc.open)(tty); 打开新规程
if (retval < 0) { 如果打开失败, 恢复原来的规程
tty->ldisc = o_ldisc;
tty->termios->c_line = tty->ldisc.num;
if (tty->ldisc.open && (tty->ldisc.open(tty) < 0)) {
tty->ldisc = ldiscs[N_TTY];
tty->termios->c_line = N_TTY;
if (tty->ldisc.open) {
int r = tty->ldisc.open(tty);

if (r < 0)
panic("Couldn't open N_TTY ldisc for "
"%s --- error %d.",
tty_name(tty, buf), r);
}
}
}
if (tty->ldisc.num != o_ldisc.num && tty->driver.set_ldisc)
tty->driver.set_ldisc(tty);
return retval;
}

; drivers/char/tty_ioctl.c:

/*
* Internal flag options for termios setting behavior
*/
#define TERMIOS_FLUSH 1
#define TERMIOS_WAIT 2
#define TERMIOS_TERMIO 4

void tty_wait_until_sent(struct tty_struct * tty, long timeout)
{
DECLARE_WAITQUEUE(wait, current);

#ifdef TTY_DEBUG_WAIT_UNTIL_SENT
char buf[64];

printk("%s wait until sent...\n", tty_name(tty, buf));
#endif
if (!tty->driver.chars_in_buffer)
return;
add_wait_queue(&tty->write_wait, &wait);
if (!timeout)
timeout = MAX_SCHEDULE_TIMEOUT;
do {
#ifdef TTY_DEBUG_WAIT_UNTIL_SENT
printk("waiting %s...(%d)\n", tty_name(tty, buf),
tty->driver.chars_in_buffer(tty));
#endif
set_current_state(TASK_INTERRUPTIBLE);
if (signal_pending(current))
goto stop_waiting;
if (!tty->driver.chars_in_buffer(tty))
break;
timeout = schedule_timeout(timeout);
} while (timeout);
if (tty->driver.wait_until_sent)
tty->driver.wait_until_sent(tty, timeout);
stop_waiting:
current->state = TASK_RUNNING;
remove_wait_queue(&tty->write_wait, &wait);
}

; drivers/net/ppp_async.c:

/*
* The basic PPP frame.
*/
#define PPP_HDRLEN 4 /* octets for standard ppp header */
#define PPP_FCSLEN 2 /* octets for FCS */
#define PPP_MRU 1500 /* default MRU = max length of info field */

#define OBUFSIZE 256

/* Structure for storing local state. */
struct asyncppp { 异步PPP通道的驱动结构
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 ppp_channel chan; /* interface to generic ppp layer */
unsigned char obuf[OBUFSIZE];
};

static struct tty_ldisc ppp_ldisc = { 异步PPP规程操作表
magic: TTY_LDISC_MAGIC,
name: "ppp",
open: ppp_asynctty_open,
close: ppp_asynctty_close,
read: ppp_asynctty_read,
write: ppp_asynctty_write,
ioctl: ppp_asynctty_ioctl,
poll: ppp_asynctty_poll,
receive_room: ppp_asynctty_room,
receive_buf: ppp_asynctty_receive,
write_wakeup: ppp_asynctty_wakeup,
};

struct ppp_channel_ops async_ops = { PPP通道驱动操作表
ppp_async_send, 发送PPP帧到终端设备
ppp_async_ioctl
};

int
ppp_async_init(void) 模块初始化
{
int err;

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

/*
* Called when a tty is put into PPP line discipline.
*/
static int
ppp_asynctty_open(struct tty_struct *tty) 打开异步PPP规程
{
struct asyncppp *ap;
int err;

MOD_INC_USE_COUNT;
err = -ENOMEM;
ap = kmalloc(sizeof(*ap), GFP_KERNEL);
if (ap == 0)
goto out;

/* initialize the asyncppp structure */
memset(ap, 0, sizeof(*ap));
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;

ap->chan.private = ap; 在一般的PPP驱动结构上设置异步驱动结构指针
ap->chan.ops = &async_ops; 异步通道操作表
ap->chan.mtu = PPP_MRU;
err = ppp_register_channel(&ap->chan); 建立通道驱动程序的传输通道结构
if (err)
goto out_free;

tty->disc_data = ap; 在打开终端结构上设置驱动结构指针

return 0;

out_free:
kfree(ap);
out:
MOD_DEC_USE_COUNT;
return err;
}
/*
* Called when the tty is put into another line discipline
* or it hangs up.
* We assume that while we are in this routine, the tty layer
* won't call any of the other line discipline entries for the
* same tty.
*/
static void
ppp_asynctty_close(struct tty_struct *tty)
{
struct asyncppp *ap = tty->disc_data;

if (ap == 0)
return;
tty->disc_data = 0;
ppp_unregister_channel(&ap->chan);
if (ap->rpkt != 0)
kfree_skb(ap->rpkt);
if (ap->tpkt != 0)
kfree_skb(ap->tpkt);
kfree(ap);
MOD_DEC_USE_COUNT;
}
/*
* 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 *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;
}

static int
ppp_asynctty_ioctl(struct tty_struct *tty, struct file *file,
unsigned int cmd, unsigned long arg)
{
struct asyncppp *ap = tty->disc_data;
int err, val;

err = -EFAULT;
switch (cmd) {
case PPPIOCGCHAN: 取通道号
err = -ENXIO;
if (ap == 0)
break;
err = -EFAULT;
if (put_user(ppp_channel_index(&ap->chan), (int *) arg))
break;
err = 0;
break;

case PPPIOCGUNIT: 取接口单元号
err = -ENXIO;
if (ap == 0)
break;
err = -EFAULT;
if (put_user(ppp_unit_number(&ap->chan), (int *) arg))
break;
err = 0;
break;

case TCGETS:
case TCGETA:
err = n_tty_ioctl(tty, file, cmd, arg);
break;

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

case FIONREAD:
val = 0;
if (put_user(val, (int *) arg))
break;
err = 0;
break;

case PPPIOCATTACH: 将传输通道连接到接口单元
case PPPIOCDETACH: 将传输通道与接口单元脱离
err = ppp_channel_ioctl(&ap->chan, cmd, arg);
break;

default:
err = -ENOIOCTLCMD;
}

return err;
}
/*
* Flush output from our internal buffers.
* Called for the TCFLSH ioctl.
*/
static void
ppp_async_flush_output(struct asyncppp *ap)
{
int done = 0;

spin_lock_bh(&ap->xmit_lock);
ap->optr = ap->olim;
if (ap->tpkt != NULL) {
kfree_skb(ap->tpkt);
ap->tpkt = 0;
clear_bit(XMIT_FULL, &ap->xmit_flags);
done = 1;
}
spin_unlock_bh(&ap->xmit_lock);
if (done)
ppp_output_wakeup(&ap->chan);
}

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

static int
ppp_asynctty_room(struct tty_struct *tty)
{
return 65535;
}

; drivers/net/ppp_generic.c:

/*
* Private data structure for each channel.
* This includes the data structure used for multilink.
*/
struct channel { 传输通道结构
struct ppp_file file; /* stuff for read/write/poll */
struct ppp_channel *chan; /* public channel data structure */
spinlock_t downl; /* protects `chan', file.xq dequeue */
struct ppp *ppp; /* ppp unit we're connected to */
struct list_head clist; /* link in list of channels per unit */
rwlock_t upl; /* protects `ppp' and `ulist' */
#ifdef CONFIG_PPP_MULTILINK
u8 avail; /* flag used in multilink stuff */
u8 had_frag; /* >= 1 fragments have been sent */
u32 lastseq; /* MP: last sequence # received */
#endif /* CONFIG_PPP_MULTILINK */
};
struct ppp_channel { 传输通道的通用驱动结构
void *private; /* channel private data */
struct ppp_channel_ops *ops; /* operations for this channel */
int mtu; /* max transmit packet size */
int hdrlen; /* amount of headroom channel needs */
void *ppp; /* opaque to channel */
/* the following are not used at present */
int speed; /* transfer rate (bytes/second) */
int latency; /* overhead time in milliseconds */
};
struct ppp_channel_ops { 驱动操作表
/* Send a packet (or multilink fragment) on this channel.
Returns 1 if it was accepted, 0 if not. */
int (*start_xmit)(struct ppp_channel *, struct sk_buff *);
/* Handle an ioctl call that has come in via /dev/ppp. */
int (*ioctl)(struct ppp_channel *, unsigned int, unsigned long);

};

/*
* Data structure describing one ppp unit.
* A ppp unit corresponds to a ppp network interface device
* and represents a multilink bundle.
* It can have 0 or more ppp channels connected to it.
*/
struct ppp {
struct ppp_file file; /* stuff for read/write/poll */
struct list_head channels; /* list of attached channels */
int n_channels; /* how many channels are attached */
spinlock_t rlock; /* lock for receive side */
spinlock_t wlock; /* lock for transmit side */
int mru; /* max receive unit */
unsigned int flags; /* control bits */
unsigned int xstate; /* transmit state bits */
unsigned int rstate; /* receive state bits */
int debug; /* debug flags */
struct slcompress *vj; /* state for VJ header compression */
enum NPmode npmode[NUM_NP]; /* what to do with each net proto */
struct sk_buff *xmit_pending; /* a packet ready to go out */
struct compressor *xcomp; /* transmit packet compressor */
void *xc_state; /* its internal state */
struct compressor *rcomp; /* receive decompressor */
void *rc_state; /* its internal state */
unsigned long last_xmit; /* jiffies when last pkt sent */
unsigned long last_recv; /* jiffies when last pkt rcvd */
struct net_device *dev; /* network interface device */
#ifdef CONFIG_PPP_MULTILINK
int nxchan; /* next channel to send something on */
u32 nxseq; /* next sequence number to send */
int mrru; /* MP: max reconst. receive unit */
u32 nextseq; /* MP: seq no of next packet */
u32 minseq; /* MP: min of most recent seqnos */
struct sk_buff_head mrq; /* MP: receive reconstruction queue */
#endif /* CONFIG_PPP_MULTILINK */
struct net_device_stats stats; /* statistics */
};

/*
* An instance of /dev/ppp can be associated with either a ppp
* interface unit or a ppp channel. In both cases, file->private_data
* points to one of these.
*/
struct ppp_file { 监控文件结构参数
enum {
INTERFACE=1, CHANNEL
} kind;
struct sk_buff_head xq; /* pppd transmit queue */
struct sk_buff_head rq; /* receive queue for pppd */
wait_queue_head_t rwait; /* for poll on reading /dev/ppp */
atomic_t refcnt; /* # refs (incl /dev/ppp attached) */
int hdrlen; /* space to leave for headers */
struct list_head list; /* link in all_* list */
int index; /* interface unit / channel number */
};

/*
* all_ppp_lock protects the all_ppp_units.
* It also ensures that finding a ppp unit in the all_ppp_units list
* and updating its file.refcnt field is atomic.
*/
static spinlock_t all_ppp_lock = SPIN_LOCK_UNLOCKED;
static LIST_HEAD(all_ppp_units);

/*
* all_channels_lock protects all_channels and last_channel_index,
* and the atomicity of find a channel and updating its file.refcnt
* field.
*/
static spinlock_t all_channels_lock = SPIN_LOCK_UNLOCKED;
static LIST_HEAD(all_channels);
static int last_channel_index;

/*
* Create a new, unattached ppp channel.
*/
int
ppp_register_channel(struct ppp_channel *chan)
{
struct channel *pch;

pch = kmalloc(sizeof(struct channel), GFP_ATOMIC);
if (pch == 0)
return -ENOMEM;
memset(pch, 0, sizeof(struct channel));
pch->ppp = NULL;
pch->chan = chan;
chan->ppp = pch;
init_ppp_file(&pch->file, CHANNEL);
pch->file.hdrlen = chan->hdrlen;
#ifdef CONFIG_PPP_MULTILINK
pch->lastseq = -1;
#endif /* CONFIG_PPP_MULTILINK */
spin_lock_init(&pch->downl);
pch->upl = RW_LOCK_UNLOCKED;
spin_lock_bh(&all_channels_lock);
pch->file.index = ++last_channel_index;
list_add(&pch->file.list, &all_channels);
spin_unlock_bh(&all_channels_lock);
MOD_INC_USE_COUNT;
return 0;
}
/*
* Initialize a ppp_file structure.
*/
static void
init_ppp_file(struct ppp_file *pf, int kind)
{
pf->kind = kind;
skb_queue_head_init(&pf->xq);
skb_queue_head_init(&pf->rq);
atomic_set(&pf->refcnt, 1);
init_waitqueue_head(&pf->rwait);
}

void
int ppp_channel_ioctl(struct ppp_channel *chan, unsigned int cmd,
unsigned long arg)
{
struct channel *pch = chan->ppp;
int err = -ENOTTY;
int unit;

if (!capable(CAP_NET_ADMIN))
return -EPERM;
if (pch == 0)
return -EINVAL;
switch (cmd) {
case PPPIOCATTACH:
if (get_user(unit, (int *) arg))
break;
err = ppp_connect_channel(pch, unit);
break;
case PPPIOCDETACH:
err = ppp_disconnect_channel(pch);
break;
}
return err;
}
/*
* Connect a PPP channel to a PPP interface unit.
*/
static int
ppp_connect_channel(struct channel *pch, int unit)
{
struct ppp *ppp;
int ret = -ENXIO;
int hdrlen;

spin_lock(&all_ppp_lock);
ppp = ppp_find_unit(unit);
if (ppp == 0)
goto out;
write_lock_bh(&pch->upl);
ret = -EINVAL;
if (pch->ppp != 0)
goto outw;
ppp_lock(ppp);
spin_lock_bh(&pch->downl);
if (pch->chan == 0) /* need to check this?? */
goto outr;

if (pch->file.hdrlen > ppp->file.hdrlen)
ppp->file.hdrlen = pch->file.hdrlen;
hdrlen = pch->file.hdrlen + 2; /* for protocol bytes */
if (ppp->dev && hdrlen > ppp->dev->hard_header_len)
ppp->dev->hard_header_len = hdrlen;
list_add_tail(&pch->clist, &ppp->channels);
++ppp->n_channels;
pch->ppp = ppp;
ret = 0;

outr:
spin_unlock_bh(&pch->downl);
ppp_unlock(ppp);
outw:
write_unlock_bh(&pch->upl);
out:
spin_unlock(&all_ppp_lock);
return ret;
}

/*
* Disconnect a channel from its ppp unit.
*/
static int
ppp_disconnect_channel(struct channel *pch)
{
struct ppp *ppp;
int err = -EINVAL;
int dead;

write_lock_bh(&pch->upl);
ppp = pch->ppp;
if (ppp != 0) {
/* remove it from the ppp unit's list */
pch->ppp = NULL;
ppp_lock(ppp);
list_del(&pch->clist);
--ppp->n_channels;
dead = ppp->dev == 0 && ppp->n_channels == 0;
ppp_unlock(ppp);
if (dead)
/* Last disconnect from a ppp unit
that is already dead: free it. */
kfree(ppp);
err = 0;
}
write_unlock_bh(&pch->upl);
return err;
}
/*
* Locate an existing ppp unit.
* The caller should have locked the all_ppp_lock.
*/
static struct ppp *
ppp_find_unit(int unit)
{
struct ppp *ppp;
struct list_head *list;

list = &all_ppp_units;
while ((list = list->next) != &all_ppp_units) {
ppp = list_entry(list, struct ppp, file.list);
if (ppp->file.index == unit)
return ppp;
}
return 0;
}
/*
* Locate an existing ppp channel.
* The caller should have locked the all_channels_lock.
*/
static struct channel *
ppp_find_channel(int unit)
{
struct channel *pch;
struct list_head *list;

list = &all_channels;
while ((list = list->next) != &all_channels) {
pch = list_entry(list, struct channel, file.list);
if (pch->file.index == unit)
return pch;
}
return 0;
}

阅读(3658) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~