linux TTY子系统(3) - tty driver

  • 了解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

猜你喜欢

转载自blog.csdn.net/weixin_41028621/article/details/109331409
tty