Linux tty驱动学习 - LDD3的tty驱动

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


tty_ldisc用来表示一个具体的线路规程,里面只有两个成员,一个是原子计数变量,另外一个对应一个具体的线路规程操作函数集tty_ldisc_ops。用户空间的操作先提交到tty核心层,然后核心层会调用线路规则的操作方法。

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目录下创建对应的设备节点。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值