打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
linux tty driver

- 1.前言

上世纪,六七十年代,虽然已经有电脑了,但还没有普及,电脑还是极其昂贵的,普通老百姓都买不起这么贵的一台电脑。那时候,UNIX的发明人——汤普森,为了实现多用户能够同时登陆UNIX系统的目的,使用非常廉价的TTY设备连接电脑,通过TTY设备发送和接收来自UNIX系统的信息,实现系统连接多个TTY设备的功能,多个用户可以同时登陆UNIX系统。 Linux继承和发展了TTY功能,不仅仅可以通过串口连接TTY设备,多个用户通过TTY设备登陆linux系统,更支持通过网络连接TTY设备了。接下来,本文会介绍什么为终端,什么为TTY。

- 1.1.何为终端

终端是一种可以同时显示和接收信息的设备,通俗的来讲,终端是一种用于人机交互的设备。用户通过终端发送信息给系统,终端接收来自系统的反馈信息,显示给用户。例如,对于一台电脑来说,显示器和键盘就是一个终端,键盘接收用户输入的信息,发送给系统,系统接收到信息后,处理该信息,把处理结果发送给显示器,显示出来给用户。

目前,终端的种类有很多,包括控制台终端、虚拟终端、串口终端、软件终端、USB或网络终端和图形终端等。其中,软件终端是指,通过串口或者USB等连接到另外一台电脑,该电脑上的一个软件模拟出一个终端,在该电脑可以实现终端的输入和输出。

- 1.2.何为TTY设备

电传打印机是一种基于电报技术的远距离信息传送的设备,通常有键盘、收发报器和打印机构等部件。用户敲击键盘的某一个按键,打印机就会自动把该按键代表的信息发送到信道里面,这就是我们所说的发报;电传打印即接收来自信道的电码信号,打印机构打印该信号所代表的字符,这就是收报。

那时候,电传打印机很便宜,使用一根线就可以连接电传打印。发现电传打印机的优点后,汤普森使用串口连接打印机,敲击打印机的键盘发送信息给UNIX系统,UNIX系统接收到信息后,处理用户的信息,把结果发送给打印机,打印机打印UNIX系统的信息到纸上,查看纸上的打印信息,用户可以获取到系统的状态。

电传打印机的英文名为Teletype,缩写为TTY,由于第一台TTY设备为电传打印机,所以现在称呼所有的这类型设备为TTY设备。

- 1.3.何为控制台终端console

除了外接的TTY设备,电脑自己的显示器和键盘也是一个TTY设备,为了显示电脑自身的TTY设备的高贵身份,美其名曰,控制台终端console。与普通的TTY设备比较,console具有管理员权限,打印系统日志信息。

一般情况下,电脑的控制台终端只有一个输出设备(显示器),任一个时刻仅仅只能被一个应用程序占有。现代系统都支持多任务的操作环境,有时候,将控制台终端切换给另一个应用程序前,往往需要保留上一个应用程序的控制台终端上的输出,下一次切到该应用程序的时候,方便查看上一次输出信息。

因此,在控制台终端的基础上,UNIX/Linux系统虚拟出了6个终端——虚拟终端,分别为tty1~tty6,。各个应用程序可以在虚拟终端上独立输出,使用键盘组合键(CTRL+ALT+F1~F6)在各个虚拟终端间切换。

- 1.4.目的与意义

本文的目的是,介绍linux kernel的TTY驱动框架、tty设备和驱动的注册流程、用户空间open、read和write TTY设备节点的驱动操作过程。本文的意义是,通过了解TTY驱动的框架和使用方法,驱动人员熟悉怎么编写自己的TTY设备驱动,对TTY核心层有更深的了解和认识。

- 2.TTY框架

TTY框架包括三部分,分别是tty driver、discipline和tty core,它们之间的关系如下图所示:


TTY框架

tty core是TTY的核心层,它抽象tty的核心数据结构,例如tty_struct、tty_driver等,提供接口给下面的discipline和tty driver层,discipline和tty driver使用tty core提供的注册接口,把discipline和tty driver注册到tty core。当用户操作设备节点的时候,tty core接收用户的请求,决定下一步是到discipline,还是tty driver层,例如,用户open、read和write tty设备节点的时候,tty core处理完后,交给discipline处理,最后再到tty driver处理,但ioctl的时候,tty core会直接就到tty driver层,没有经过discipline层。

discipline的中文意思是规范,在这里表示线路的规范。discipline接收tty core或者tty driver的数据,进行一些格式的转换,比如换行等等等,处理完后,再上传给tty core或者传给tty driver层。

tty driver是low level driver,它直接操作硬件,完成硬件的初始化,接收硬件的数据,上报数据给discipline或者tty core层;接收来自tty core或者discipline的数据,发送数据给硬件等等。


- 3.tty核心

上一节介绍了tty的整体框架,tty核心定义了tty需要使用的数据结构,提供各种各样的函数接口给下面的层使用,管理注册到tty核心的tty设备和驱动。本节将会详细的介绍tty核心提供的功能,囊括tty core提供的数据结构和接口等等。

- 3.1.tty的数据结构

在代码的世界里,数据结构代表程序里面的一个个抽象出来的实体,就好像代码里面的一个个零件,由这些零件才能组成一个健全的程序,由此可见,数据结构是一个程序的基本,使用抽象出来的数据结构,不仅有利于代码的编写和理解,更有利于代码的维护!

Linux的设备驱动模型有两大部分,分别是驱动和设备,每一个驱动都会有对应描述驱动和设备的数据结构。tty框架也一样,tty框架使用了tty_driver和tty_struct结构描述tty驱动和tty设备。

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;};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

@magic:该tty_driver的幻数;
@kref:该结构的计数,当该计数小于或者等于零的时候,该结构才可以被释放;
@cdevs:该结构注册的字符设备;
@driver_name:tty_driver注册的时候,创建proc文件的名称;
@name:该结构的名称,字符设备的名称也是该名称;
@name_base:和name字段一起构成设备的名称;
@major:该tty_driver的主设备号;
@minor_start:次设备号的开始位;
@num:申请次设备号的数量;
@type:tty_driver的类型;
@subtype:tty_driver的子类型;
@init_termios:终端的初始化参数;
@flags:tty_driver的标志;
以下是指向tty data的结构
@ttys:存放打开的tty_struct;
@ports:存放tty_driver注册的tty_port;
@termios:运行时的终端配置信息;
@driver_state:指向tty_driver的数据,例如,指向uart_driver;
以下是tty_driver的操作函数
@ops:该ops会在打开tty设备节点的时候,赋给tty_struct结构的ops,该ops被discipline层ops调用,最后会打开tty_driver层;
@tty_drivers:链表头,用于挂载到tty_drivers链表;

驱动人员注册一个tty_driver的时候,必须初始化tty_driver的一些必要的字段,然后调用tty core提供的接口注册到tty core。

tty core使用tty_struct结构描述一个tty设备。一般情况下,设备和驱动都会有一个匹配的过程,就好像platform_device和platform_driver通过虚拟总线匹配的过程一样,不过,tty_struct和tty_driver并没有通过总线来进行匹配,仿佛tty core弱化了tty device,驱动人员不需要创建一个tty device,而是,tty_driver注册接口注册一个指定范围设备号的字符设备驱动,再创建并注册对应设备号范围的设备。这样,字符设备驱动就会与新建的设备匹配,当用户空间open一个tty设备节点的时候,调用字符设备驱动的ops的open函数,tty_struct就在该open函数创建的,这意味着,一个tty设备对应着一个tty_struct。

如下所示为tty_struct的定义:

struct tty_struct {        int     magic;        struct kref kref;        struct device *dev;        struct tty_driver *driver;        const struct tty_operations *ops;        int index;        /* Protects ldisc changes: Lock tty not pty */        struct ld_semaphore ldisc_sem;        struct tty_ldisc *ldisc;        --cut—        --cut--        struct tty_struct *link;        struct fasync_struct *fasync;        int alt_speed;          /* For magic substitution of 38400 bps */        wait_queue_head_t write_wait;        wait_queue_head_t read_wait;        struct work_struct hangup_work;        void *disc_data;        void *driver_data;        struct list_head tty_files;#define N_TTY_BUF_SIZE 4096        unsigned char closing:1;        unsigned char *write_buf;        int write_cnt;        /* If the tty has a pending do_SAK, queue it here - akpm */        struct work_struct SAK_work;        struct tty_port *port;};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

@magic:该tty_struct的幻数;
@kref:tty_struct的计数;
@driver:tty_struct的tty_driver;
@ops:open设备节点的时候,tty_driver的ops会赋给该ops;
@index:tty_struct在tty_driver中的索引;
@ldisc:tty_struct的discipline;
@write_wait:写等待队列头,N_TTY discipline的ops->write把当前的进程添加到该等待队列头,进入while(1)循环,如果while循环调用了schedule,当前进程进入休眠,设备节点的下一次open,或者reset discipline的时候,会wake_up该等待队列头的等待队列,以继续进行write操作;
@read_wait:与write_wait相似,所不同的是,是在N_TTY discipline的read函数;
@disc_data:保存discipline用到的数据;
@tty_files:链表头,保存file文件;
@write_buf:写缓冲区;
@write_cnt:申请输出的数量;
@port:tty_struct对应的tty_port;

struct tty_operation结构是tty_driver的操作函数集,tty_driver的字符设备操作函数或者discipline的操作函数会调用到 tty_driver的操作函数集,tty_driver的函数集再调用更低一层的操作函数,比如,对于串口来说,tty_driver的函数集会调用uart_port的操作函数。

tty_operation的定义如下:

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);        int  (*write_room)(struct tty_struct *tty);        int  (*ioctl)(struct tty_struct *tty,                    unsigned int cmd, unsigned long arg);        void (*set_termios)(struct tty_struct *tty, struct ktermios * old);        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);};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

@lookup:tty设备节点的open函数使用该回调函数查找tty_struct,如果没有该回调,则直接访问driver->ttys数组,该数组保存着打开的tty_struct;
@install:tty设备节点的open函数新建一个tty_struct的时候,使用该回调把tty_struct安装到tty_driver,如果没有提供该回调,使用tty_standard_insatll函数,把tty_struct存在tty_driver的ttys数组中;
@remove:与install的作用相反,把ttt_struct从tty_driver中去除;
@open:tty设备节点的open函数调用discipline ops的open后,调用该open回调,该回调再调用下一层的回调;
@close:与open相反,close回调在tty设备节点的release函数被调用;
@shutdown:关掉该tty_struct;
@cleanup:tty_struct计数为零,触发queue_release_one_tty被调用,接着release_one_tty被调用,release_one_tty检测tty->ops->cleanup是否存在,如果存在则调用该cleanup回调,该回调释放tty->ops申请的资源;
@write:输出一个buf数据;
@put_char:输出一个字符;
@write_room:返回输出buffer剩下的空间大小;
@ioctl:ioctl接口;
@set_termios:设置tty_struct的配置,在discipline的ioctl会调用该回调;
@stop:暂定tty_struct flow,discipline的ioctl会调用这回调;
@start:开始tty_struct flow,比如,uart driver的start会调用port的start_tx回调,开始发送数据;
@hangup:挂起tty_struct当前flow,包括fulsh buffer,shutdown tty等;

discipline层采用tty_ldisc描述一个discipline,tty_ldisc_ops描述一个discipline的操作函数。tty_ldisc_ops类似于tty_operation,这里不再赘述;tty_ldisc非常简单,作为连接discipline与tty_struct之间的桥梁。

tty_ldisc数据结构定义如下:

struct tty_ldisc {        struct tty_ldisc_ops *ops;        struct tty_struct *tty;};
  • 1
  • 2
  • 3
  • 4

@ops:tty_ldisc_ops操作函数集;
@tty:指向该discipline的tty_struct结构;

控制台终端采用struct console结构描述一个终端,因个人对console还不是很熟悉,所以,这里只给出其定义,定义如下:

struct console {        char    name[16];        void    (*write)(struct console *, const char *, unsigned);        int     (*read)(struct console *, char *, unsigned);        struct tty_driver *(*device)(struct console *, int *);         void    (*unblank)(void);        int     (*setup)(struct console *, char *);         int     (*early_setup)(void);        short   flags;        short   index;        int     cflag;        void    *data;        struct   console *next;};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

uart核心是tty核心的一部分,采用uart_driver结构描述一个uart。uart_driver也是一个tty_driver,是tty_driver更高一层的封装。相对tty_driver,uart_driver封装了tty_driver、console等字段,增加了一些uart需要的字段。

uart_driver定义如下:

struct uart_driver {        struct module           *owner;        const char              *driver_name;        const char              *dev_name;        int                      major;        int                      minor;        int                      nr;         struct console          *cons;        /*           * these are private; the low level driver should not         * touch these; they should be initialised to NULL         */        struct uart_state       *state;        struct tty_driver       *tty_driver;};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

@owner:uart_driver的MODULE;
@driver_name:driver名称;
@dev_name:对应于tty_driver的name;
@major:主设备号,用于初始化tty_driver;
@minor:次设备号,初始化tty_driver;
@nr:uart_port的数量;
@cons:指向console;
@state:包含环形buf、uart_port和tty_port指针,是uart_driver与uart_port的桥梁;
@tty_driver:指向tty_driver的指针;

- 3.2.tty的接口

上一节介绍了tty核心的主要数据结构,这一节将会介绍tty的接口。tty核心提供了很多方便的接口。其中,alloc_tty_driver接口申请一个tty_driver;tty_register_driver注册一个tty_driver;tty_register_ldisc注册一个dicsipline;register_console注册一个console;uart_register_driver注册一个uart_driver等等。接下来,将会主要介绍这几个接口。

alloc_tty_driver(unsigned int lines)
参数lines表示tty_driver有几条line。一条line分别占有一个tty_struct、tty_port和termios,所以,该接口根据lines初始化tty_driver->num,申请lines个ttys、terminos和ports内存空间。

tty_register_driver(struct tty_driver *driver)
tty_register_ldisc(int disc, struct tty_ldisc_ops *new_ldisc)
参数disc表示discipline的序号,new_ldisc表示该discipline的操作函数集。该接口接收这两个参数,把new_ldisc放到tty_ldiscs全局数组中。

register_console(struct console *newcon)

uart_register_driver(struct uart_driver *drv)
该接口注册一个uart_driver到uart core层。该接口为uart_driver->state申请空间,调用alloc_tty_driver申请一个tty_driver,初始化uart需要的一些tty_driver字段,把tty_driver的ops设置为uart使用的uart_ops操作函数,最后调用tty_register_driver注册初始化后的tty_driver。


- 4.tty_driver的注册

tty_driver的注册由tty_registeer_driver完成,注册之前,需要使用alloc_tty_driver申请一个tty_driver,然后对其初始化,再传给tty_register_driver接口,剩下的工作就交给它完成就行了。
tty_register_driver接口非常简单,其定义如下:

int tty_register_driver(struct tty_driver *driver){        --cut--        if (!driver->major) {                error = alloc_chrdev_region(&dev, driver->minor_start,                                                driver->num, driver->name);                if (!error) {                        driver->major = MAJOR(dev);                        driver->minor_start = MINOR(dev);                }        } else {                dev = MKDEV(driver->major, driver->minor_start);                error = register_chrdev_region(dev, driver->num, driver->name);        }        if (error < 0)                 goto err;        if (driver->flags & TTY_DRIVER_DYNAMIC_ALLOC) {                error = tty_cdev_add(driver, dev, 0, driver->num);                if (error)                        goto err_unreg_char;        }            mutex_lock(&tty_mutex);        list_add(&driver->tty_drivers, &tty_drivers);        mutex_unlock(&tty_mutex);        if (!(driver->flags & TTY_DRIVER_DYNAMIC_DEV)) {                for (i = 0; i < driver->num; i++) {                        d = tty_register_device(driver, i, NULL);                        if (IS_ERR(d)) {                                error = PTR_ERR(d);                                goto err_unreg_devs;                        }                }        }        proc_tty_register_driver(driver);        driver->flags |= TTY_DRIVER_INSTALLED;        return 0;        --cut--}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

1.如果driver没有指定major,调用alloc_chrdev_region,动态申请主设备号;
2.否则,调用register_chrdev_region,申请driver指定的设备号;
3.判断driver->flags是否设置了TTY_DRIVER_DYNAMIC_ALLOC标志位,如果设置了,调用tty_cdev_add注册一个字符设备驱动,该字符设备驱动负责处理对应于第1或第2部申请的设备号的设备,tty_cdev_add函数下面再作展开;
4.如果driver->flags设置了TTY_DRIVER_DYNAMIC_DEV标志,调用tty_register_device,注册driver->num个设备;
5.调用proc_tty_register_driver,注册tty_driver的proc文件系统文件;
6.设置driver->flags的TTY_DRIVER_INSTALLED标志位,表示driver已经成功注册到tty core。

tty_cdev_add接口也非常简单,调用cdev_init初始化一个cdev,指明cdev的owner,最后调用cdev_add注册该cdev。该函数的定义如下:

static int tty_cdev_add(struct tty_driver *driver, dev_t dev,                unsigned int index, unsigned int count){        /* init here, since reused cdevs cause crashes */        cdev_init(&driver->cdevs[index], &tty_fops);        driver->cdevs[index].owner = driver->owner;        return cdev_add(&driver->cdevs[index], dev, count);}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

- 5.discipline的注册

discipline层需要注册disc到tty核心,这样打开tty设备的时候,通过tty核心才可以找到该disc。不同的tty设备可能需要不同的disc,tty核心管理着一个tty_ldiscs数组,不同类型的disc以disc类型号为数组的下标存到该数组,获取该disc的时候,以disc类型号找到目标disc。disc的注册接口为tty_register_ldisc,注册一个disc之前,需要初始化一个tty_ldisc_ops,指定该tty_ldisc_ops的disc号,准备好后,调用tty_register_ldisc完成注册。

tty_register_ldisc接口也非常简单,其定义如下:

int tty_register_ldisc(int disc, struct tty_ldisc_ops *new_ldisc){        unsigned long flags;        int ret = 0;        if (disc < N_TTY || disc >= NR_LDISCS)                return -EINVAL;        raw_spin_lock_irqsave(&tty_ldiscs_lock, flags);        tty_ldiscs[disc] = new_ldisc;        new_ldisc->num = disc;        new_ldisc->refcount = 0;        raw_spin_unlock_irqrestore(&tty_ldiscs_lock, flags);        return ret;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

tty_register_ldisc检查disc类型号disc是否在正确的范围,如果不在正确的范围,返回-EINVAL,如果在,以disc类型号为下标,存放到tty_ldiscs数组中。


- 6.console的注册

内核启动的打印信息通过printk函数打印出来的, printk找到合适的console,console最后依赖tty_driver输出打印信息。console分为两种类型,一种为bootconsole,另外一种为”real”console。bootconsole可以在任何时候注册,但是,当real console注册后,bootconsole将会被自动注销,而且,real console注册后,再注册bootconsole将会失败。

注册console的接口函数为register_console,参数为struct console类型的变量,注册之前,需要初始化好struct console的变量。register_console的定义如下:

void register_console(struct console *newcon){        int i;        unsigned long flags;        struct console *bcon = NULL;        struct console_cmdline *c;        if (console_drivers)                for_each_console(bcon)                        if (WARN(bcon == newcon,                                        "console '%s%d' already registered\n",                                        bcon->name, bcon->index))                                return;        /*         * before we register a new CON_BOOT console, make sure we don't         * already have a valid console         */        if (console_drivers && newcon->flags & CON_BOOT) {                /* find the last or real console */                for_each_console(bcon) {                        if (!(bcon->flags & CON_BOOT)) {                                pr_info("Too late to register bootconsole %s%d\n",                                        newcon->name, newcon->index);                                return;                        }                }        }        if (console_drivers && console_drivers->flags & CON_BOOT)                bcon = console_drivers;        if (preferred_console < 0 || bcon || !console_drivers)                preferred_console = selected_console;        if (newcon->early_setup)                newcon->early_setup();        /*         *      See if we want to use this console driver. If we         *      didn't select a console we take the first one         *      that registers here.         */        if (preferred_console < 0) {                if (newcon->index < 0)                        newcon->index = 0;                if (newcon->setup == NULL ||                    newcon->setup(newcon, NULL) == 0) {                        newcon->flags |= CON_ENABLED;                        if (newcon->device) {                                newcon->flags |= CON_CONSDEV;                                preferred_console = 0;                        }                }        }        /*         *      See if this console matches one we selected on         *      the command line.         */        for (i = 0, c = console_cmdline;             i < MAX_CMDLINECONSOLES && c->name[0];             i++, c++) {                BUILD_BUG_ON(sizeof(c->name) != sizeof(newcon->name));                if (strcmp(c->name, newcon->name) != 0)                        continue;                if (newcon->index >= 0 &&                    newcon->index != c->index)                        continue;                if (newcon->index < 0)                        newcon->index = c->index;                if (_braille_register_console(newcon, c))                        return;                if (newcon->setup &&                    newcon->setup(newcon, console_cmdline[i].options) != 0)                        break;                newcon->flags |= CON_ENABLED;                newcon->index = c->index;                if (i == selected_console) {                        newcon->flags |= CON_CONSDEV;                        preferred_console = selected_console;                }                break;        }        if (!(newcon->flags & CON_ENABLED))                return;        /*         * If we have a bootconsole, and are switching to a real console,         * don't print everything out again, since when the boot console, and         * the real console are the same physical device, it's annoying to         * see the beginning boot messages twice         */        if (bcon && ((newcon->flags & (CON_CONSDEV | CON_BOOT)) == CON_CONSDEV))                newcon->flags &= ~CON_PRINTBUFFER;        /*         *      Put this console in the list - keep the         *      preferred driver at the head of the list.         */        console_lock();        if ((newcon->flags & CON_CONSDEV) || console_drivers == NULL) {                newcon->next = console_drivers;                console_drivers = newcon;                if (newcon->next)                        newcon->next->flags &= ~CON_CONSDEV;        } else {                newcon->next = console_drivers->next;                console_drivers->next = newcon;        }        if (newcon->flags & CON_PRINTBUFFER) {                /*                 * console_unlock(); will print out the buffered messages                 * for us.                 */                raw_spin_lock_irqsave(&logbuf_lock, flags);                console_seq = syslog_seq;                console_idx = syslog_idx;                console_prev = syslog_prev;                raw_spin_unlock_irqrestore(&logbuf_lock, flags);                /*                 * We're about to replay the log buffer.  Only do this to the                 * just-registered console to avoid excessive message spam to                 * the already-registered consoles.                 */                exclusive_console = newcon;        }        console_unlock();        console_sysfs_notify();        /*         * By unregistering the bootconsoles after we enable the real console         * we get the "console xxx enabled" message on all the consoles -         * boot consoles, real consoles, etc - this is to ensure that end         * users know there might be something in the kernel's log buffer that         * went to the bootconsole (that they do not see on the real console)         */        pr_info("%sconsole [%s%d] enabled\n",                (newcon->flags & CON_BOOT) ? "boot" : "" ,                newcon->name, newcon->index);        if (bcon &&            ((newcon->flags & (CON_CONSDEV | CON_BOOT)) == CON_CONSDEV) &&            !keep_bootcon) {                /* We need to iterate through all boot consoles, to make                 * sure we print everything out, before we unregister them.                 */                for_each_console(bcon)                        if (bcon->flags & CON_BOOT)                                unregister_console(bcon);        }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153

这个函数非常长,也比较复杂,下面一步一步分析这个函数做了哪些工作。

1.扫描console_drivers链表,检查newcon是否已经注册,如果已经被注册,直接返回,否则,继续;

2.如果console_drivers不为空、newcon->flags标志newcon位bootconsole,扫描console_drivers链表,检查是否有”real”console已经注册,如果有,提示已经注册了”real”console,不能再注册bootconsole,然后返回;

3.函数局部变量bcon表示上一个注册的bootconsole,函数最后判断bcon是否不为空,如果不为空,且新注册的console不为bootconsole,将注销所有的bootconsole。如果console_drivers不为空,且console_drivers->flags标志了CON_BOOT,bcon的值为该console_drivers;

4.全局变量preferred_console表示作为printk输出的console,全局变量selected_console表示uboot传给kernel的命令行参数选择输出的console。如果preferred_console小于0,或者bcon不为空,或者console_drivers为空(表示还没有console已经注册),preferred_console的值为selected_console(selected_console为解释uboot命令行选择的console);

5.如果uboot传给kernel的命令行参数没有选择console,即preferred_console和selected_console都为-1,选择第一个注册的console为输出打印信息的console;

6.newcon与命令行console_cmdline选择的console比较,判断newcon是否为console_cmdline选择的console,如果是,且newcon->setup接口存在,调用newcon->setup,进行console自定义的初始化工作,标识newcon为使能状态;如果console_cmdline的第i个与newcon匹配,且i等于selected_console,标志newcon->flags的CON_CONSDEV,表示newcon为uboot命令行选择的最后一个的console;

7.经过前面的处理后,如果newcon->flags没有包含CON_ENABLED标志位,直接返回,表示只有选择了的console才可以注册;

8.如果bcon不为空,且newcon->flags标志了CON_SONSDEV,设置newcon->flags不包含CON_PRINTBUFFER标志位,这样可以防止重复打印内核启动信息;

9.如果newcon->flags包含CON_CONSDEV,把newcon插入链表的表头位置,插入链表后,如果newcon->next存在(即链表不为空),设置链表倒数第二个console的flags不包含CON_CONSDEV。或者,如果console_drivers为空,表示newcon为第一个注册的console,设置console_drivers指向newcon;如果newcon->flags既不包含CON_CONSDEV,而且console_drivers不为空,则把newcon添加到链表表头的位置;

10.全局变量exclusive_console表示打印信息将从该console输出。如果newcon->flags设置了CON_PRINTBUFFER,exclusive_console为newcon;

11.如果bcon不为空,newcon->flags设置了CON_CONSDEV,并且没有设置keep_bootcon,那么,注销所有的bootconsole。


- 7.uart_driver的注册

uart是tty设备连接系统的典型工具。uart_driver是tty_driver中的一种,封装了更加多的成员,以适应uart设备。为了简化uart driver的开发, kernel封装了描述uart设备驱动的uart_driver结构体,提供用于注册uart_driver和操作uart设备的接口,管理一些不需要驱动人员关心的信息,负责管理已经注册的uart_driver,这就是uart core,这样大大减少了驱动人员开发uart driver的工作。下图是tty core与uart core之间的关系:

uart core位于tty driver的下面,uart_driver在uart core的下面,uart driver的下面就是硬件了。uart driver注册到uart core,uart core根据uart driver申请一个tty driver,初始化tty driver,把tty driver注册到tty core,因此,uart core是一个中间层,在tty core与uart_driver之间封装一个接口,管理tty_driver的注册。除了管理tty_driver的注册外,uart_core也提供了tty_driver的ops,这表示,uart core提供一套公用的接口,这些接口包含uart一些特有的处理,再调用注册到uart core中的uart_driver的接口,交给uart_driver处理。
uart_driver的注册由uart_register_driver接口完成,该接口接收一个uart_driver类型的参数,注册uart_driver之前,需要初始化好一个uart_driver类型的变量。

uart_register_driver的定义如下:

int uart_register_driver(struct uart_driver *drv){        struct tty_driver *normal;        int i, retval;        BUG_ON(drv->state);        /*            * Maybe we should be using a slab cache for this, especially if         * we have a large number of ports to handle.         */        drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL);        if (!drv->state)                goto out;         normal = alloc_tty_driver(drv->nr);        if (!normal)                goto out_kfree;        drv->tty_driver = normal;        normal->driver_name     = drv->driver_name;        normal->name            = drv->dev_name;        normal->major           = drv->major;        normal->minor_start     = drv->minor;        normal->type            = TTY_DRIVER_TYPE_SERIAL;        normal->subtype         = SERIAL_TYPE_NORMAL;        normal->init_termios    = tty_std_termios;        normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;        normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = 9600;        normal->flags           = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;        normal->driver_state    = drv;         tty_set_operations(normal, &uart_ops);        /*         * Initialise the UART state(s).         */        for (i = 0; i < drv->nr; i++) {                struct uart_state *state = drv->state + i;                struct tty_port *port = &state->port;                tty_port_init(port);                port->ops = &uart_port_ops;                port->close_delay     = HZ / 2; /* .5 seconds */                port->closing_wait    = 30 * HZ;/* 30 seconds */        }        retval = tty_register_driver(normal);        if (retval >= 0)                return retval;        --cut--}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52

1.uart_state的作用后面再说明。这里为uart_driver->state申请空间,调用alloc_tty_driver申请一个tty_driver——normal。alloc_tty_driver接口前面章节已经讲述,主要为tty_driver申请空间,初始化一些字段;

2.初始化normal的名称、设备号、init_termios和ops等。设置init_termios一些串口控制cflag和输入、输出速度,设置normal->ops为uart core提供的uart_ops;

3.每个uart_state都包含着一个tty_port,这里初始化每个uart_state的tty_port,设置tty_port的ops为uart_port_ops,uart_port_ops与uart_ops一样,都为uart core提供的公用操作函数集,这些ops再调用下面uart_driver的ops;设置tty_port的close_delay和closing_wait;

4.现在,已经初始化完normal了,调用我们前面章节已经讨论过的tty_register_driver函数,注册normal到tty core。


- 8.uart添加port

上一节,uart_register_driver注册了一个uart_driver,这一节,将介绍如何注册一个uart port。硬件上,串口的端口依附在串口控制器上,软件采用了相似的结构描述串口的结构,uart_driver描述一个串口控制器,uart_port描述一个串口端口。与串口的硬件一样,uart_port同样也是依附在uart_driver上,串口端口正常工作,需要添加该端口到uart_driver上,本节将会介绍如果添加端口uart_port到uart_driver。
添加uart_port的接口为uart_add_one_port,该接口负责把一个uart_port添加到uart_driver,它有两个参数,一个为已经注册的uart_driver,另一个为需要添加的uart_port。

下面为uart_add_one_port的定义:

int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport){        struct uart_state *state;        struct tty_port *port;        int ret = 0;        struct device *tty_dev;        BUG_ON(in_interrupt());        if (uport->line >= drv->nr)                return -EINVAL;        state = drv->state + uport->line;        port = &state->port;        mutex_lock(&port_mutex);        mutex_lock(&port->mutex);        if (state->uart_port) {                ret = -EINVAL;                goto out;        }        state->uart_port = uport;        state->pm_state = UART_PM_STATE_UNDEFINED;        uport->cons = drv->cons;        uport->state = state;        /*         * If this port is a console, then the spinlock is already                                                                                                                      * initialised.         */        if (!(uart_console(uport) && (uport->cons->flags & CON_ENABLED))) {                spin_lock_init(&uport->lock);                lockdep_set_class(&uport->lock, &port_lock_key);        }        uart_configure_port(drv, state, uport);        /*         * Register the port whether it's detected or not.  This allows         * setserial to be used to alter this ports parameters.         */        tty_dev = tty_port_register_device_attr(port, drv->tty_driver,                        uport->line, uport->dev, port, tty_dev_attr_groups);        if (likely(!IS_ERR(tty_dev))) {                device_set_wakeup_capable(tty_dev, 1);        } else {                printk(KERN_ERR "Cannot register tty device on line %d\n",                       uport->line);        }        /*         * Ensure UPF_DEAD is not set.         */        uport->flags &= ~UPF_DEAD; out:        mutex_unlock(&port->mutex);        mutex_unlock(&port_mutex);        return ret;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63

1.uart_port以line来区分不同的uart_port,uart_driver的nr表示uart_driver可以处理的uart_port的数量,line必须小于nr。检查uport->line有没有超出drv->nr,如果超出,表示超出了uart_driver的处理范围,返回-EINVAL;

2.uart_driver为每一个uart_port准备了一个uart_state,uart_state是tty_port与uart_port之间的桥梁。根据uport->line,获取uart_state(该uart_state在uart_driver注册的时候已经初始化,初始化了uart_state的tty_port等),把uart_state的uart_port指针指向添加的uart_port,这样,以后就可以通过uart_state找到该uart_port了;

3.调用uart_configure_port。uart_configure_port根据uport的flags,判断是否需要调用uport->ops->config_port;如果uport->type不是PORT_UNKNOWN,调用uart_change_pm,设置uport为上电的状态,调用uport->ops->set_mctrl,确保modem control line是非激活状态;如果uport带有CON_ENABLED标志,调用register_console接口,注册console;

4.调用tty_port_register_device_attr。tty_port_link_device把tty_port存放到tty_driver->ports中;tty_register_device_attr注册一个以tty_driver->major为主设备号,以uport->line + base为次设备号的设备,该设备将会与tty_driver注册的字符设备驱动匹配,/dev下的tty设备节点的名字为该设备的名称,open该设备节点,以次设备号,最终找到该uport;

5.设置uport->flags不包含UPF_DEAD,表示该uport状态不是dead的状态;


- 9.tty的open

open tty设备节点的时候,会调用设备节点对应的字符设备驱动的open函数。tty_register_driver注册tty_driver的时候,如果tty_driver的flags带有TTY_DRIVER_DYNAMIC_ALLOC标志,tty_register_driver调用tty_cdev_add函数,tty_cdev_add函数初始化字符设备驱动cdev,注册字符设备驱动cdev; tty_driver支持的所有的tty 设备的设备号范围都在cdev注册的设备号范围内,对tty设备的操作都有cdev的操作函数集进行处理。

open tty设备节点调用的是cdev的tty_open函数,该函数的定义如下:

static int tty_open(struct inode *inode, struct file *filp){        struct tty_struct *tty;        int noctty, retval;        struct tty_driver *driver = NULL;        int index;        dev_t device = inode->i_rdev;        unsigned saved_flags = filp->f_flags;        nonseekable_open(inode, filp);retry_open:        retval = tty_alloc_file(filp);        if (retval)                return -ENOMEM;        noctty = filp->f_flags & O_NOCTTY;        index  = -1;        retval = 0;         mutex_lock(&tty_mutex);        /* This is protected by the tty_mutex */        tty = tty_open_current_tty(device, filp);        if (IS_ERR(tty)) {                retval = PTR_ERR(tty);                goto err_unlock;        } else if (!tty) {                driver = tty_lookup_driver(device, filp, &noctty, &index);                if (IS_ERR(driver)) {                        retval = PTR_ERR(driver);                        goto err_unlock;                }                /* check whether we're reopening an existing tty */                tty = tty_driver_lookup_tty(driver, inode, index);                if (IS_ERR(tty)) {                        retval = PTR_ERR(tty);                        goto err_unlock;                }        }        if (tty) {                tty_lock(tty);                retval = tty_reopen(tty);                if (retval < 0) {                        tty_unlock(tty);                        tty = ERR_PTR(retval);                }        } else  /* Returns with the tty_lock held for now */                tty = tty_init_dev(driver, index);        mutex_unlock(&tty_mutex);        if (driver)                tty_driver_kref_put(driver);        if (IS_ERR(tty)) {                retval = PTR_ERR(tty);                goto err_file;        }        tty_add_file(tty, filp);        check_tty_count(tty, __func__);        if (tty->driver->type == TTY_DRIVER_TYPE_PTY &&            tty->driver->subtype == PTY_TYPE_MASTER)                noctty = 1;#ifdef TTY_DEBUG_HANGUP        printk(KERN_DEBUG "%s: opening %s...\n", __func__, tty->name);                                                                                                         #endif        if (tty->ops->open)                retval = tty->ops->open(tty, filp);        else                retval = -ENODEV;        filp->f_flags = saved_flags;        --cut—        mutex_lock(&tty_mutex);        tty_lock(tty);        spin_lock_irq(&current->sighand->siglock);        if (!noctty &&                                                                                                                                                                     current->signal->leader &&            !current->signal->tty &&            tty->session == NULL)                __proc_set_tty(current, tty);        spin_unlock_irq(&current->sighand->siglock);        tty_unlock(tty);        mutex_unlock(&tty_mutex);        --cut--}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87

1.tty_alloc_file为file的private_data字段申请内存,private_data将会保存一些私有数据等,例如,tty_struct等;

2.TTYAUX_MAJOR主设备号的设备为控制台终端设备,open主设备号为TTYAUX_MAJOR、次设备号为0的设备(/dev/console),打开的为当前进程的控制台终端设备,即次设备号为0的控制台终端设备指向的是当前进程的控制台终端设备;主设备号为TTY_MAJOR的设备为虚拟终端设备,open这设备号为TTY_MAJOR、次设备号为0的设备(/dev/tty0),打开的为fg_console(虚拟终端,应该是当前活动的虚拟终端),即次设备号为0的虚拟终端设备指向的是当前活动的虚拟终端。tty_open_current_tty检测打开的tty设备节点的设备号device是否为MKDEV(TTYAUX_MAJOR, 0),如果不是,返回NULL,否则,返回当前进程的tty,如果当前进程没有tty,打开就会失败;

3.第2步返回NULL,!tty为真,调用tty_lookup_driver。tty_lookup_driver检测tty设备的类型(虚拟终端设备、控制台终端设备、其它tty设备),默认是通过get_tty_driver获取tty_driver。本文通过get_tty_driver获取tty_driver,get_tty_driver扫描已经注册的tty_driver的链表tty_drivers,找到包含设备号device的tty_driver,返回该tty_driver,并返回该设备号device在该tty_driver设备号中的偏移index,如果没有找到,返回NULL;

4.第3步返回driver和index后,以driver、index和inode为参数,调用tty_driver_lookup_tty,查找driver是否已经打开了该index的tty。tty_driver_lookup_tty检测tty_driver是否有lookup回调函数,如果有返回回调函数lookup的结果,如果没有lookup回调函数,以index为索引,返回数组tty_driver->ttys数组的第index个成员,该数组存放的是tty_driver已经打开的tty_struct。本节讨论的是第一次open tty设备节点的情况,因此,tty_driver_lookup_tty返回NULL;

5.第4步,tty_driver_lookup_tty返回NULL,即tty为NULL,这样,下一步将会调用tty_init_dev,新建一个tty_struct。tty_init_dev申请一个tty_struct,初始化该tty_struct,安装tty_struct到tty_driver,调用ldisc的open函数。下面再具体展开tty_init_dev;

6.tty_add_file把tty_struct添加到file的private_data中,这样,字符设备驱动的其他函数就可以通过file获取到tty_struct;

7.tty->ops在tty_init_dev函数被初始化为tty_driver的ops。判断tty->ops->open是否存在,即判断tty_driver的ops-open是否存在,如果存在,调用tty->ops->open,否则,返回-ENODEV。前面uart_driver注册过程一节,uart_driver注册的时候,初始化tty_driver的ops为uart_ops,因此,在此处,如果是open的是串口类型的tty设备,tty->ops->open存在。下面再详细展开uart_driver的uart_ops open函数;

8.noctty表示该tty是否为控制终端,noctty为1,表示不是控制终端,noctty为0,表示是控制终端。前面的tty_lookup_driver函数,如果打开主设备号为TTY_MAJOR,次设备号为0、或者主设备号为TTYAUX_MAJOR、次设备号为0的设备,设noctty为1。如果noctty不为1、current->signal->leader为真、current->signal->tty不存在和tty->session为NULL,调用__proc_set_tty,设置当前进程的tty为tty_struct。

- 9.1.tty_init_dev

tty_open没有找到tty的时候,调用tty_init_dev,初始化一个tty。tty_init_dev的定义如下:

struct tty_struct *tty_init_dev(struct tty_driver *driver, int idx) {        struct tty_struct *tty;        --cut--        tty = alloc_tty_struct();        if (!tty) {                retval = -ENOMEM;                goto err_module_put;        }        initialize_tty_struct(tty, driver, idx);        tty_lock(tty);        retval = tty_driver_install_tty(driver, tty);        if (retval < 0)                goto err_deinit_tty;        if (!tty->port)                tty->port = driver->ports[idx];        --cut--        tty->port->itty = tty;        retval = tty_ldisc_setup(tty, tty->link);        if (retval)                goto err_release_tty;        --cut—}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

1.调用alloc_tty_struct,alloc_tty_struct申请一个tty_struct的内存空间;

2.initialize_tty_struct初始化tty_struct,初始化各个锁。initialize_tty_struct调用tty_ldisc_init,初始化tty的discipline为N_TTY类型。tty_ldisc_init以tty和N_TTY为参数,调用tty_ldisc_get,tty_ldisc_get以N_TTY为索引,在tty_ldiscs数组(已经注册的ldops都存放在该全局数组)中找到目的ldops,新建一个tty_ldisc,为其申请空间,初始化tty_ldisc的ops为刚刚获取到的ldops,初始化tty字段指向参数tty,即我们在initialize_tty_struct新建的tty_struct;tty_ldisc_get返回新建的tty_ldisc后,tty_ldisc_init初始化tty的ldisc指向返回的ldisc。initialize_tty_struct初始化完discipline、各个链表头和锁后,初始化tty的ops为tty_driver的ops,所以以后调用tty的ops,实际是调用tty_driver的ops;

3.tty_driver_install_tty判断tty_driver是否提供intstall回调函数,如果有,调用install回调,让tty_driver自己处理tty的安装,否则,调用tty_standard_install,使用tty_driver->init_termios或者tty_driver->termios初始化tty的termios,以tty->index(设备接待的设备号在tty_driver中的偏移),把tty保存在tty_driver的ttys数组中,之后,可以在该数组找到tty;

4.如果tty->port不存在,以index为索引,获取tty_driver的ports数组中的index成员,tty的port指向该成员,这意味着,打开这个tty最后打开的是该port;tty->port->itty指向tty;

5.tty初始化完后,tty_ldisc_setup调用ldisc ops的open回调函数,交给ldisc做一些初始化工作;

- 9.2.tty->ops->open

上一节,tty->ops初始化为tty_driver的ops,调用tty->ops->open,最后调用的是tty_driver->ops->open。本节将会基于uart_driver展开tty_driver->ops->open流程。由uart_driver注册过程一节得知,tty_driver的ops为uart_ops,tty_driver->ops->open为uart_open函数。下面为uart_open函数的定义:

位置:drivers/tty/serial/serial_core.c

static int uart_open(struct tty_struct *tty, struct file *filp){        struct uart_driver *drv = (struct uart_driver *)tty->driver->driver_state;        int retval, line = tty->index;        struct uart_state *state = drv->state + line;        struct tty_port *port = &state->port;        --cut--        port->count++;        --cut--        tty->driver_data = state;        state->uart_port->state = state;        state->port.low_latency =                (state->uart_port->flags & UPF_LOW_LATENCY) ? 1 : 0;        tty_port_tty_set(port, tty);        --cut--        /*         * Make sure the device is in D0 state.         */        if (port->count == 1)                uart_change_pm(state, UART_PM_STATE_ON);        /*         * Start up the serial port.         */        retval = uart_startup(tty, state, 0);        --cut--}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

1.uart_driver注册的时候,tty_driver的driver_state指向uart_driver,所以,uart_open通过tty_drvier获取到uart_driver,通过uart_driver获取到uart_state,再从uart_state得到tty_port和uart_port等;

2.增加port的count计数,这样,下面可以通过判断 count是否为1,再决定uart pm state为UART_PM_STATE_ON。设定tty的driver_data指向state,uart_port的state指向state。tty_port_tty_set设置tty_port的tty指向tty;

3.如果port->count为1,调用uart_change_pm,设置uart的电源状态为UART_PM_STATE_ON状态,uart_change_pm判断uart_state的pm_state是否与目的state一样,如果不一样、uart_port->ops->pm回调函数存在,调用该回调函数设置目的电源状态,并把成功设置后的state保存在uart_state的state字段里;

4.uart_startup调用uart_port_startup,为uart_state申请环形buf空间,调用uart_port->ops->startup,交给更加底层的upart_port完成硬件的初始化,调用uart_change_speed设置uart_port的termios;下面将会详细展开uart_port_startup函数;

uart_startup的主体工作是由uart_port_startup完成,uart_port_startup有三个参数,分别为tty_struct、uart_state和init_hw。uart_port_startup的定义如下:

位置:drivers/tty/serial/serial_core.c。

static int uart_port_startup(struct tty_struct *tty, struct uart_state *state,                int init_hw){        --cut--        if (!state->xmit.buf) {                /* This is protected by the per port mutex */                page = get_zeroed_page(GFP_KERNEL);                if (!page)                        return -ENOMEM;                state->xmit.buf = (unsigned char *) page;                uart_circ_clear(&state->xmit);        }        retval = uport->ops->startup(uport);        if (retval == 0) {                if (uart_console(uport) && uport->cons->cflag) {                        tty->termios.c_cflag = uport->cons->cflag;                        uport->cons->cflag = 0;                }                /*                 * Initialise the hardware port settings.                 */                uart_change_speed(tty, state, NULL);                if (init_hw) {                        /*                         * Setup the RTS and DTR signals once the                         * port is open and ready to respond.                         */                        if (tty->termios.c_cflag & CBAUD)                                uart_set_mctrl(uport, TIOCM_RTS | TIOCM_DTR);                }        --cut--        }        --cut--}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

1.get_zeroed_page申请一页初始化为0的内存页,state->xmit.buf指向该内存页;uart_circ_clear初始化state->xmit的head和tail指针,head和tail指针构成了一个环形buffer的功能;

2.调用uart_port->ops->startup函数,交给具体的、更底层的uart_port完成硬件的初始化;

3.uart_change_speed根据tty->termios的c_cflag,清除或者设置tty_port的flag,最后调用uart_port->ops->set_termios,最终设置硬件;

4.如果指定了init_hw,设置RTS和DTR信号;

- 9.3.tty open流程图

tty open流程图如下:

- 10.tty的write

tty write接收用户的输入,把数据发送出去。由上一节的tty_open,可以知道,处理tty设备节点操作由注册的字符设备驱动cdev完成。tty write对应的操作函数为tty_write,tty_write先经过discipline,discipline处理一些特殊字符的处理、输出,接着接着调用tty_driver ops的write函数,tty_driver->ops->write最后调用到具体的port的函数。

首先,tty_write的定义如下:

static ssize_t tty_write(struct file *file, const char __user *buf,                                                size_t count, loff_t *ppos){        struct tty_struct *tty = file_tty(file);        struct tty_ldisc *ld;         ssize_t ret;        --cut--        if (tty->ops->write_room == NULL)                printk(KERN_ERR "tty driver %s lacks a write_room method.\n",                        tty->driver->name);        ld = tty_ldisc_ref_wait(tty);        if (!ld->ops->write)                ret = -EIO;        else                ret = do_tty_write(ld->ops->write, tty, file, buf, count);        tty_ldisc_deref(ld);        return ret;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

1.tty_write检测tty ops是否存在write_room回调函数,write_room函数返回的是当前可以支持的写的空间大小,使用tty_write一般需要实现write_room回调函数,如果没有实现,输出警告;

2.检测discipline有没有实现write回调函数,如果没有,直接返回错误,实现tty_write功能,必须提供健全的discipline的write回调函数;

3.do_tty_write检测tty->write_cnt的大小,如果write_cnt小于chunk,从新申请一块内存给tty->write_buf,write_buf是作为临时存放用户数据的buf;接着,do_tty_write拷贝用户空间数据到write_buf,调用参数write函数,该write参数就是discipline的write回调函数,本节使用的discipline为N_TTY类型的discipline,N_TTY类型的discipline的write回调函数为n_tty_write;

4.n_tty_write首先调用process_echoes输出一些特殊字符,然后进入while循环,本节加入O_OPOS(tty)成立,while循环进入process_output_block函数,process_out_block检测用户数据是否包含特殊字符,如果包含了特殊字符,不再发送特殊字符以下的数据(包括特殊字符),留给process_output处理,否则,调用tty->ops->write函数;

5.process_output检测到特殊字符,直接调用tty->ops->write输出特殊字符,如果不是,调用 tty_put_char,tty_put_char检测是否存在put_char回调,如果有,使用该回调输出单个字符;

6.与上一节tty_open一样,本节讨论的是uart类型的tty_driver。第四步最后提到的tty->ops->write回调函数为uart_write。uart_write把write_buf的数据拷贝到circ环形buf里面。最后调用uart_start,启动传输,这样,中断的时候,自动从该环形buf拿数据发送。

- 10.1.tty_write流程图

tty_write的流程图如下所示:

- 11.tty的read

tty read与tty write不一样,tty read的数据是,硬件检测到数据的到来,产生中断,通知软件,软件从寄存器里面读取数据出来,再一层一层上报,最终送到tty read,tty read返回数据给用户空间。

与前面的章节一样,这里以uart为例,讨论数据的接收流程。硬件产生中断后,软件从寄存器读取接收到的数据 ,调用tty_insert_flip_char,把数据往上一层提报。tty_insert_flip_char进行tty_port的flag的一些检测,检测完后,调用tty_insert_flip_string_flags,主体工作由该函数完成。下面进入该函数看看。

tty_insert_flip_string_flags函数的定义如下:

int tty_insert_flip_string_flags(struct tty_port *port,                const unsigned char *chars, const char *flags, size_t size){        int copied = 0;        do {                int goal = min_t(size_t, size - copied, TTY_BUFFER_PAGE);                int space = tty_buffer_request_room(port, goal);                struct tty_buffer *tb = port->buf.tail;                if (unlikely(space == 0))                         break;                memcpy(char_buf_ptr(tb, tb->used), chars, space);                memcpy(flag_buf_ptr(tb, tb->used), flags, space);                tb->used += space;                copied += space;                chars += space;                flags += space;                /* There is a small chance that we need to split the data over                   several buffers. If this is the case we must loop */        } while (unlikely(size > copied));        return copied;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

该函数非常简单,只有寥寥几行代码。函数把接收到的字符数据chars和标志flags拷贝到tty buf。

软件调用完tty_insert_flip_char后,调用tty_flip_buffer_push,tty_flip_buffer_push调度tty buf的工作队列,工作队列调用flush_to_ldisc处理函数,把数据往discipline层上报。flush_to_ldisc调用receive_buf,receive_buf检测discipline是否存在receive_buf2函数,如果存在调用该回调函数,否则调用discipline的receive_buf回调函数。N_TTY类型的discipline存在receive_buf2回调函数,所以,下一步进入的是receive_buf2回调函数。N_TTY discipline的receive_buf2回调函数为n_tty_receive_buf函数,该函数只是调用n_tty_receive_buf_common函数,n_tty_receive_buf_common再调用__receive_buf函数,__receive_buf再调用n_tty_receive_buf_standard或者n_tty_receive_buf_fast,无论调用哪个函数,最终都是调用put_tty_queue,把tty buf的数据放到ldata buf。

经过上面那么多层的函数调用,数据最终的位置为ldata buf。这样,用户空间调用tty_read的时候,tty_read函数调用的是discipline的read回调,该read回调从ldata buf获取数据,把数据拷贝到用户空间,实现数据的读取。

本节涉及太多的函数了,因此,本节只列出了一小部分的函数,具体的函数调用流程,可以参考下一节的流程图。

- 11.1.tty read流程图

tty read流程图如下所示:

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
linux console驱动详解
linux内核中串口驱动注册过程(tty驱动)[转]
tty初探
linux UART串口驱动开发文档
linux kernel下输入输出console如何实现
linux下/dev/tty, /dev/tty0, /dev/console区别
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服