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

PPP驱动程序的基本原理

2013年08月25日 ⁄ 综合 ⁄ 共 15693字 ⁄ 字号 评论关闭

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;
}

 

抱歉!评论已关闭.