现在的位置: 首页 > 综合 > 正文

PPP驱动程序的基本原理

2014年02月01日 ⁄ 综合 ⁄ 共 6363字 ⁄ 字号 评论关闭

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)
{

【上篇】
【下篇】

抱歉!评论已关闭.