Linux中的tty驱动分三大类:串口,终端,PTY伪终端,控制台和PTY驱动内核已经编写好,所以在开发移植过程中接触到的通常是串口驱动。linux的tty驱动框架如下图所示,最上层的是tty核心层,该层负责把tty驱动注册成字符设备驱动,并提供使用接口给用户空间。tty线路规程用来实现用户空间和设备驱动之间的数据格式转换,比如采用UART接口的3G模块就会用到线路规程里面的ppp_ldisc设置,而普通的UART串口驱动会用到tty_ldisc_N_TTY设置。最下层的tty驱动部分负责跟硬件沟通,它把从线路规程驱动得到的数据转换成硬件能够发送的数据,另外还负责对硬件的控制。
Linux用tty_driver这个结构体来注册tty驱动到tty核心。因为tty核心会把tty驱动注册成一个字符设备,所以cdev用来表示一个字符设备。driver_name和name分别是驱动和设备节点的名字。major是该驱动创建设备的主设备节点号,minor_start和minor_num表示起始次设备号和次设备号的数目。type用来表示该tty驱动的类型,对应于前面讲的三种类型。init_termios是用来对设备做初始化设置的配置值。最关键的两个成员是tty_struct和tty_operations。
struct tty_driver {
int magic; /* magic number for this structure */
struct kref kref; /* Reference management */
struct cdev cdev;
struct module *owner;
const char *driver_name;
const char *name;
int name_base; /* offset of printed name */
int major; /* major device number */
int minor_start; /* start of minor device number */
int minor_num; /* number of *possible* devices */
int num; /* number of devices allocated */
short type; /* type of tty driver */
short subtype; /* subtype of tty driver */
struct ktermios init_termios; /* Initial termios */
int flags; /* tty driver flags */
struct proc_dir_entry *proc_entry; /* /proc fs entry */
struct tty_driver *other; /* only used for the PTY driver */
/*
* Pointer to the tty data structures
*/
struct tty_struct **ttys;
struct ktermios **termios;
struct ktermios **termios_locked;
void *driver_state;
/*
* Driver methods
*/
const struct tty_operations *ops;
struct list_head tty_drivers;
};
tty_struct是tty核心用来表示当前特定tty端口的状态,它的成员基本上都只被tty核心使用。tty_operations包含了所有的tty操作函数,它可以被tty驱动和tty核心一起使用。
struct tty_operations {
struct tty_struct * (*lookup)(struct tty_driver *driver,
struct inode *inode, int idx);
int (*install)(struct tty_driver *driver, struct tty_struct *tty);
void (*remove)(struct tty_driver *driver, struct tty_struct *tty);
int (*open)(struct tty_struct * tty, struct file * filp);
void (*close)(struct tty_struct * tty, struct file * filp);
void (*shutdown)(struct tty_struct *tty);
void (*cleanup)(struct tty_struct *tty);
int (*write)(struct tty_struct * tty,
const unsigned char *buf, int count);
int (*put_char)(struct tty_struct *tty, unsigned char ch);
void (*flush_chars)(struct tty_struct *tty);
int (*write_room)(struct tty_struct *tty);
int (*chars_in_buffer)(struct tty_struct *tty);
int (*ioctl)(struct tty_struct *tty, struct file * file,
unsigned int cmd, unsigned long arg);
long (*compat_ioctl)(struct tty_struct *tty, struct file * file,
unsigned int cmd, unsigned long arg);
void (*set_termios)(struct tty_struct *tty, struct ktermios * old);
void (*throttle)(struct tty_struct * tty);
void (*unthrottle)(struct tty_struct * tty);
void (*stop)(struct tty_struct *tty);
void (*start)(struct tty_struct *tty);
void (*hangup)(struct tty_struct *tty);
int (*break_ctl)(struct tty_struct *tty, int state);
void (*flush_buffer)(struct tty_struct *tty);
void (*set_ldisc)(struct tty_struct *tty);
void (*wait_until_sent)(struct tty_struct *tty, int timeout);
void (*send_xchar)(struct tty_struct *tty, char ch);
int (*tiocmget)(struct tty_struct *tty, struct file *file);
int (*tiocmset)(struct tty_struct *tty, struct file *file,
unsigned int set, unsigned int clear);
int (*resize)(struct tty_struct *tty, struct winsize *ws);
int (*set_termiox)(struct tty_struct *tty, struct termiox *tnew);
#ifdef CONFIG_CONSOLE_POLL
int (*poll_init)(struct tty_driver *driver, int line, char *options);
int (*poll_get_char)(struct tty_driver *driver, int line);
void (*poll_put_char)(struct tty_driver *driver, int line, char ch);
#endif
const struct file_operations *proc_fops;
};
struct tty_ldisc_ops {
int magic;
char *name;
int num;
int flags;
/*
* The following routines are called from above.
*/
int (*open)(struct tty_struct *);
void (*close)(struct tty_struct *);
void (*flush_buffer)(struct tty_struct *tty);
ssize_t (*chars_in_buffer)(struct tty_struct *tty);
ssize_t (*read)(struct tty_struct * tty, struct file * file,
unsigned char __user * buf, size_t nr);
ssize_t (*write)(struct tty_struct * tty, struct file * file,
const unsigned char * buf, size_t nr);
int (*ioctl)(struct tty_struct * tty, struct file * file,
unsigned int cmd, unsigned long arg);
long (*compat_ioctl)(struct tty_struct * tty, struct file * file,
unsigned int cmd, unsigned long arg);
void (*set_termios)(struct tty_struct *tty, struct ktermios * old);
unsigned int (*poll)(struct tty_struct *, struct file *,
struct poll_table_struct *);
int (*hangup)(struct tty_struct *tty);
/*
* The following routines are called from below.
*/
void (*receive_buf)(struct tty_struct *, const unsigned char *cp,
char *fp, int count);
void (*write_wakeup)(struct tty_struct *);
void (*dcd_change)(struct tty_struct *, unsigned int,
struct timespec *);
struct module *owner;
int refcount;
};
termios结构体是用来对tty线路进行设置的集合。它用来对tty的输入,输出,控制模式进行设置。比如经常用到的波特率,数据位,停止位和校验位就是通过改变该结构体的c_cflag的值去设置的。
struct ktermios {
tcflag_t c_iflag; /* input mode flags */
tcflag_t c_oflag; /* output mode flags */
tcflag_t c_cflag; /* control mode flags */
tcflag_t c_lflag; /* local mode flags */
cc_t c_line; /* line discipline */
cc_t c_cc[NCCS]; /* control characters */
speed_t c_ispeed; /* input speed */
speed_t c_ospeed; /* output speed */
};
前面讲到用tty_driver这个结构体把tty驱动注册到tty核心,所以在注册tty驱动的时候,首先要调用内核提供的alloc_tty_driver(int lines)函数来为tty_driver分配内存,然后对该机构体进行初始化,其中参数lines表示可注册的最大次设备号。初始化好了tty_driver之后,调用tty_register_driver(struct tty_driver *driver)把它注册到tty核心。因为tty驱动注册到tty核心后,会表示成一个字符设备,所以在tty_register_driver()里面,首先会申请一个字符设备号,然后对tty_driver->cdev成员进行初始化操作,主要是对字符设备的操作函数集进行赋值。最后会调用cdev_add()把tyy_dirver->cdev注册到字符设备模型里面,它根据设备号把cdev结构添加到cdev_map的对应位置,这样我们在用户空间打开对应设备号的设备时,就可以根据设备号从cdev_map里面找到对应的cdev结构体,也就是找到对应的字符设备。注册完驱动后,如果tty_driver里面没有定义TTY_DRIVER_DYNAMIC_DEV 标志,那么会根据driver->num的数目调用tty_register_device()注册该驱动所控制的设备。tty设备注册函数有三个参数,第一个参数表示该设备所属的tty_driver,这二个参数表示设备的次设备号,最后一个参数指向该tty设备所对应的device结构,如果这个tty设备没有绑定的device,那么这个参数可以被设为NULL。tty_reigster_device()最后会调用device_create()把tty设备注册到Linux驱动的设备列表里面,并通过udev机制在用户空间的/dev目录下创建对应的设备节点。