- 了解linux tty driver
1.TTY device
kernel从设备模型和字符设备两个角度对它进行了抽象:
- 设备模型的角度
为每个“数据通道”注册了一个stuct device,以便可以在sysfs中体现出来,例如:
/sys/class/tty/tty
/sys/class/tty/console
/sys/class/tty/ttyS0
- 字符设备的角度
为每个“数据通道”注册一个struct cdev,以便在用户空间可以访问,例如:
/dev/tty
/dev/console
/dev/ttyS0
2.TTY driver
从当前设备模型的角度看,TTY framework淡化了device的概念,却着重突出driver。由struct tty_driver所代表的TTY driver,几乎大包大揽了TTY device有关的所有内容,如下:
2.1.struct tty_driver
struct tty_driver {
int magic; /* magic number for this structure */
struct kref kref; /* Reference management */
struct cdev **cdevs;
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 */
unsigned int num; /* number of devices allocated */
short type; /* type of tty driver */
short subtype; /* subtype of tty driver */
struct ktermios init_termios; /* Initial termios */
unsigned long 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 tty_port **ports;
struct ktermios **termios;
void *driver_state;
/*
* Driver methods
*/
const struct tty_operations *ops;
struct list_head tty_drivers;
}
原则上来说,在编写TTY driver的时候,只需要定义一个struct tty_driver变量,并根据实际情况正确填充其中的字段后,注册到TTY core中,即可完成驱动的设计。
2.2.TTY struct(struct tty_struct)
TTY struct是TTY设备在TTY core中的内部表示。
-
从TTY driver的角度看,它和文件句柄的功能类似,用于指代某个TTY设备。
-
从TTY core的角度看,它是一个比较复杂的数据结构,保存了TTY设备生命周期中的很多中间变量。
2.3.TTY port(struct tty_port)
TTY port是一个比较难理解的概念,因为它和TTY struct类似,也是TTY device的一种抽象。那么,既然有了TTY struct,为什么还需要TTY port呢?看一下kernel代码注释的解释:
/* include/linux/tty.h */
/*
* Port level information. Each device keeps its own port level information
* so provide a common structure for those ports wanting to use common support
* routines.
*
* The tty port has a different lifetime to the tty so must be kept apart.
* In addition be careful as tty -> port mappings are valid for the life
* of the tty object but in many cases port -> tty mappings are valid only
* until a hangup so don't use the wrong path.
*/
-
TTY struct是TTY设备的“动态抽象”,保存了TTY设备访问过程中的一些临时信息,这些信息是有生命周期的:从打开TTY设备开始,到关闭TTY设备结束;
-
TTY port是TTY设备固有属性的“静态抽象”,保存了该设备的一些固定不变的属性值,例如是否是一个控制台设备(console)、打开关闭时是否需要一些delay操作、等等;
-
另外(这一点很重要),TTY core负责的是逻辑上的抽象,并不关心这些固有属性。因此从层次上看,这些属性完全可以由具体的TTY driver自行维护;
-
不过,由于不同TTY设备的属性有很多共性,如果每个TTY driver都维护一个私有的数据结构,将带来代码的冗余。所以TTY framework就将这些共同的属性抽象出来,保存在struct tty_port数据结构中,同时提供一些通用的操作接口,供具体的TTY driver使用;
-
因此,总结来说:TTY struct是TTY core的一个数据结构,由TTY core提供并使用,必要的时候可以借给具体的TTY driver使用;TTY port是TTY driver的一个数据结构,由TTY core提供,由具体的TTY driver使用,TTY core完全不关心。
2.4.tty driver flags
TTY driver在注册struct tty_driver变量的时候,可以提供一些flags,以告知TTY core一些额外的信息,例如(具体可参考include/linux/tty_driver.h中的定义和注释,写的很清楚):
TTY_DRIVER_DYNAMIC_DEV:如果设置了该flag,则表示TTY driver会在需要的时候,自行调用tty_register_device接口注册TTY设备(相应地回体现在字符设备以及sysfs中);如果没有设置,TTY core会在tty_register_driver时根据driver->num信息,自行创建对应的TTY设备。
2.5.TTY操作函数集
TTY core将和硬件有关的操作,抽象、封装出来,形成名称为struct tty_operations的数据结构,具体的TTY driver不需要关心具体的业务逻辑,只需要根据实际的硬件情况,实现这些操作接口即可。
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,
unsigned int cmd, unsigned long arg);
long (*compat_ioctl)(struct tty_struct *tty,
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);
int (*tiocmset)(struct tty_struct *tty,
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);
int (*get_icount)(struct tty_struct *tty,
struct serial_icounter_struct *icount);
#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设备没有read函数,是因为大部分tty的输入设备和输出设备不一样。例如虚拟终端设备,它的输入是键盘,输出是显示器。由于这样的原因,tty的驱动层和tty的线路规程层都有一个缓冲区。
- tty驱动层的缓冲区用来保存硬件发过来的数据。在驱动程序里使用 tty_insert_flip_string 函数可以实现将硬件的数据存入到驱动层的缓冲区。
- 为什么线路规程层还是有一个缓冲区呢?
因为tty核心无法直接读取驱动层的缓冲区的数据。tty核心读不到数据,用户也就无法获取数据。用户的read函数只能从tty核心读取数据。而tty核心只能从tty线路规程层的缓冲区读取数据。因为是层层读写的关系,所以tty线路规程也是需要一个缓冲区的。
在驱动程序里使用 tty_flip_buffer_push() 函数将tty驱动层缓冲区的数据推到tty线路规程层的缓冲区。这样就完成了数据的流通。因为全是缓冲区操作,所以需要两个进程:写数据进程和读数据进程。如果缓冲区内没有数据,运行读进程的话,tty核心就会把读进程加入到等待队列。
3.TTY driver的API
3.1 TTY driver有关的API
用于struct tty_driver数据结构的分配、初始化、注册等:
/* include/linux/tty_driver.h */
extern struct tty_driver *__tty_alloc_driver(unsigned int lines,
struct module *owner, unsigned long flags);
extern void put_tty_driver(struct tty_driver *driver);
extern void tty_set_operations(struct tty_driver *driver,
const struct tty_operations *op);
extern struct tty_driver *tty_find_polling_driver(char *name, int *line);
extern void tty_driver_kref_put(struct tty_driver *driver);
/* Use TTY_DRIVER_* flags below */
#define tty_alloc_driver(lines, flags) \
__tty_alloc_driver(lines, THIS_MODULE, flags)
/* include/linux/tty.h */
extern int tty_register_driver(struct tty_driver *driver);
extern int tty_unregister_driver(struct tty_driver *driver);
3.2.TTY device有关的API
如果TTY driver设置了TTY_DRIVER_DYNAMIC_DEV flag,就需要自行注册TTY device,相应的API包括:
/* include/linux/tty.h */
extern struct device *tty_register_device(struct tty_driver *driver,
unsigned index, struct device *dev);
extern struct device *tty_register_device_attr(struct tty_driver *driver,
unsigned index, struct device *device,
void *drvdata,
const struct attribute_group **attr_grp);
extern void tty_unregister_device(struct tty_driver *driver, unsigned index);
- tty_register_device,分配并注册一个TTY device,最后将新分配的设备指针返回给调用者。
- tty_register_device_attr,和tty_register_device类似,只不过可以额外指定设备的attribute。
3.3 数据传输有关的API
当TTY core有数据需要发送给TTY设备时,会调用TTY driver提供的.write或者.put_char回调函数,TTY driver在这些回调函数中操作硬件即可。
当TTY driver从TTY设备收到数据并需要转交给TTY core的时候,需要调用TTY buffer有关的接口,将数据保存在缓冲区中,并等待Application读取,相关的API有:
/* include/linux/tty_flip.h */
static inline int tty_insert_flip_char(struct tty_port *port,
unsigned char ch, char flag)
{
….
}
static inline int tty_insert_flip_string(struct tty_port *port,
const unsigned char *chars, size_t size)
{
return tty_insert_flip_string_fixed_flag(port, chars, TTY_NORMAL, size);
}
参考:drivers/s390/char/tty3270.c
4.Example:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/tty.h> /*注意:tty.h和tty_driver.h顺序不能颠倒,必须是tty.h在tty_driver.h前面。一旦顺序颠倒,就会提示有错误。*/
#include <linux/tty_driver.h>
#include <linux/fs.h>
#include <linux/ioport.h>
#include <linux/serial_reg.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("lan");
#define TTY_LAN_MINORS_NUM 5
#define TTY_LAN_MAJOR 202
static struct tty_driver *tty_lan_driver;
static int tty_lan_open(struct tty_struct *tty, struct file *filp);
static struct tty_operations tty_lan_ops = {
.open = tty_lan_open,
};
static int __init tty_lan_init(void)
{
int i;
int retval;
tty_lan_driver = alloc_tty_driver(TTY_LAN_MINORS_NUM);
if(!tty_lan_driver)
return -ENOMEM;
tty_lan_driver->owner = THIS_MODULE;
tty_lan_driver->driver_name = "tty_lan";
tty_lan_driver->name = "ttty_lan";
tty_lan_driver->major = TTY_LAN_MAJOR,
tty_lan_driver->minor_start = 0;
tty_lan_driver->type = TTY_DRIVER_TYPE_SERIAL;
tty_lan_driver->subtype = SERIAL_TYPE_NORMAL;
tty_lan_driver->flags = TTY_DRIVER_REAL_RAW;
tty_lan_driver->init_termios = tty_std_termios;
tty_lan_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
retval = tty_register_driver(tty_lan_driver);
if(retval){
printk(KERN_ERR"Failed to register tty_lan_driver!\n");
put_tty_driver(tty_lan_driver);
return retval;
}
for(i = 0; i < TTY_LAN_MINORS_NUM; i++)
tty_register_device(tty_lan_driver, i, NULL);
return 0;
}
static int tty_lan_open(struct tty_struct *tty, struct file *filp)
{
return 0;
}
static void __exit tty_lan_exit(void)
{
int i;
for(i = 0; i < TTY_LAN_MINORS_NUM; i++)
tty_unregister_device(tty_lan_driver, i);
tty_unregister_driver(tty_lan_driver);
}
module_init(tty_lan_init);
module_exit(tty_lan_exit);
Makefile:
UNAME = $(shell uname -r)
LINUX_PATH = /lib/modules/$(UNAME)/build
obj-m = tty_lan.o
all:
$(MAKE) -C $(LINUX_PATH) M=$(PWD) modules
clean:
$(MAKE) -C $(LINUX_PATH) M=$(PWD) clean
编译并加载此模块后,观察如下文件:
$ cat /proc/tty/drivers
/dev/tty /dev/tty 5 0 system:/dev/tty
/dev/console /dev/console 5 1 system:console
/dev/ptmx /dev/ptmx 5 2 system
/dev/vc/0 /dev/vc/0 4 0 system:vtmaster
tty_lan /dev/ttty_lan 202 0-4 serial
rfcomm /dev/rfcomm 216 0-255 serial
serial /dev/ttyS 4 64-111 serial
pty_slave /dev/pts 136 0-1048575 pty:slave
pty_master /dev/ptm 128 0-1048575 pty:master
unknown /dev/tty 4 1-63 console
tty_lan /dev/ttty_lan 202 0-4 serial
创建了5个设备。到/dev目录下看看:
$ ls -l /dev/ttty_lan*
crw-rw---- 1 root root 202, 0 2010-07-26 16:40 /dev/ttty_lan0
crw-rw---- 1 root root 202, 1 2010-07-26 16:40 /dev/ttty_lan1
crw-rw---- 1 root root 202, 2 2010-07-26 16:40 /dev/ttty_lan2
crw-rw---- 1 root root 202, 3 2010-07-26 16:40 /dev/ttty_lan3
crw-rw---- 1 root root 202, 4 2010-07-26 16:40 /dev/ttty_lan4
5./proc/tty/drivers
通过查看/proc/tty/drivers文件可以获知什么类型的tty设备文件存在以及什么驱动被加载到内核。这个文件包括一个当前存在的不同tty驱动的列表,包括驱动名、默认的节点名、驱动的主编号、这个驱动使用的次编号范围以及tty驱动的类型。看下面一个例子:
$ cat /proc/tty/drivers
/dev/tty /dev/tty 5 0 system:/dev/tty
/dev/console /dev/console 5 1 system:console
/dev/ptmx /dev/ptmx 5 2 system
/dev/vc/0 /dev/vc/0 4 0 system:vtmaster
rfcomm /dev/rfcomm 216 0-255 serial
serial /dev/ttyS 4 64-111 serial
pty_slave /dev/pts 136 0-1048575 pty:slave
pty_master /dev/ptm 128 0-1048575 pty:master
unknown /dev/tty 4 1-63 console
refer to
- http://www.wowotech.net/tty_framework/tty_driver.html
- http://www.uml.org.cn/embeded/201209071.asp