tty驱动框架分析

版权声明:本文为博主原创文章,未经博主允许不得转载 https://blog.csdn.net/qq_37600027/article/details/84504042

tty框架如下图所示:

整个 uart 框架大概的样子如上图所示,大致可以分为四层,一层是下层我们的串口驱动层,它直接与硬件相接触,我们需要填充一个 struct uart_ops 的结构体,再向上是tty核心层,在向上是线路规程,再向上是是直接和用户空间对接的,它们每一层都有一个 Ops 结构,用户空通过tty注册的字符设备节点来访问,这么说来如上图所示涉及到了4个 ops 结构了,层层跳转。其中我们想要添加一个驱动时,主要完成的工作就是底层驱动,其他三层内核已经实现。下面,就来分析分析它们的层次结构。以amba_pl011驱动为例。

用户空间通过函数open、write等首先会调用到tty用户空间的tty_open函数,然后tty_open函数会调用线路规程函数,然后线路规程的函数会调用tty核心层的函数,tty核心层的函数也可以直接调用tty核心层的函数(不经过线路规程),最终tty驱动层的函数在调用tty设备驱动。即:

tty_open->n_tty_open->uart_open->pl011_startup或者是

tty_open->uart_open->pl011_startup

    在amba_pl011中,它是这样来注册串口驱动的,分配一个struct uart_driver 简单填充,并调用uart_register_driver 注册到内核中去。

static struct uart_driver amba_reg = {
    .owner = THIS_MODULE,
    .driver_name = "ttyAMA",
    .dev_name = "ttyAMA",
    .major = SERIAL_AMBA_MAJOR,
    .minor = SERIAL_AMBA_MINOR,
    .nr = UART_NR,
    .cons = AMBA_CONSOLE,
};
static int __init pl011_init(void)
{
    int ret;

    printk(KERN_INFO "Serial: AMBA PL011 UART driver\n");
    ret = uart_register_driver(&amba_reg);

    if (ret == 0) {
        ret = amba_driver_register(&pl011_driver);
        if (ret)
            uart_unregister_driver(&amba_reg);
    }

    return ret;

}

    uart_driver 中,我们只是填充了一些名字、设备号等信息,这些都是不涉及底层硬件访问的,那是怎么回事呢?来看一下完整的 uart_driver 结构或许就明白了。

struct uart_driver {
    struct module *owner; /* 拥有该uart_driver的模块,一般为THIS_MODULE */
    const char *driver_name; /* 串口驱动名,串口设备文件名以驱动名为基础 */
    const char *dev_name; /* 串口设备名 */
    int  major; /* 主设备号 */
    int  minor; /* 次设备号 */
    int  nr; /* 该uart_driver支持的串口个数(最大) */
    struct console *cons; /* 其对应的console.若该uart_driver支持serial console,否则为NULL */
    /* 下面这俩,它们应该被初始化为NULL */
    struct uart_state *state;//下层,串口驱动层
    struct tty_driver *tty_driver; /* tty相关 */
};

    在我们上边填充的结构体中,有两个成员未被赋值,他们分别代表底层和上层,tty_driver 代表的是上层,他是tty_register_driver函数的参数,咱们注册的是一个tty驱动,tty_driver是tty驱动的结构体,它会在register_uart_driver中的过程中赋值uart_state则代表下层,uart_state会在register_uart_driver 的过程中分配空间(他会根据支持的最大串口数量开辟相应的空间uart_driver->nr),它里面真正硬件相关的结构是是uart_state->uart_port,这个uart_port是需要我们从其它地方调用uart_add_one_port来添加的。 在amba_pl011中是用probe函数添加uart_port

 

1、设备驱动层(下层)

struct uart_state {
    struct tty_port port;
    int pm_state;
    struct circ_buf xmit;//缓冲区,发送数据和接收数据的存储空间
    struct tasklet_struct tlet;
    struct uart_port *uart_port; //对应于一个实际的串口设备
};

    在注册 driver 时,会根据 uart_driver->nr 来申请 nr 个 uart_state 空间,用来存放驱动所支持的串口(端口)的物理信息。

struct uart_port {

    spinlock_t lock; /* port lock */
    unsigned long iobase; /* io端口基地址(物理) */
    unsigned char __iomem *membase; /* io内存基地址(虚拟) */
    unsigned int (*serial_in)(struct uart_port *, int);
    void (*serial_out)(struct uart_port *, int, int);
    unsigned int irq; /* 中断号 */
    unsigned long irqflags; /* 中断标志  */
    unsigned int uartclk; /* 串口时钟 */
    unsigned int fifosize; /* 串口缓冲区大小 */
    unsigned char x_char; /* xon/xoff char */
    unsigned char regshift; /* 寄存器位移 */
    unsigned char iotype; /* IO访问方式 */
    unsigned char unused1;
    unsigned int read_status_mask; /* 关心 Rx error status */
    unsigned int ignore_status_mask; /* 忽略 Rx error status */
    struct uart_state *state; /* pointer to parent state */
    struct uart_icount icount; /* 串口信息计数器 */
    struct console *cons; /* struct console, if any */
    
#if defined(CONFIG_SERIAL_CORE_CONSOLE) || defined(SUPPORT_SYSRQ)
    unsigned long sysrq; /* sysrq timeout */
#endif

    upf_t flags;
    unsigned int mctrl; /* 当前的Moden 设置 */
    unsigned int timeout; /* character-based timeout */
    unsigned int type; /* 端口类型 */
    const struct uart_ops *ops; /* 串口端口操作函数 */
    unsigned int custom_divisor;
    unsigned int line; /* 端口索引 */
    resource_size_t mapbase; /* io内存物理基地址 */
    struct device *dev; /* 父设备 */
    unsigned char hub6; /* this should be in the 8250 driver */
    unsigned char suspended;
    unsigned char unused[2];
    void *private_data; /* generic platform data pointer */
};

    这个结构体,是需要我们自己来填充的,有几个串口,那么就需要填充个uart_port ,并且通过 uart_add_one_port 添加到 uart_driver->uart_state->uart_port 中去。当然uart_driver 有多个 uart_state ,每个 uart_state 有一个 uart_port 。在 uart_port 里还有一个非常重要的成员 struct uart_ops *ops ,这个是具体的寄存器操作函数,是需要我们自己来实现的,也就和裸机驱动差不多。

static struct uart_ops amba_pl011_pops = {
    .tx_empty = pl01x_tx_empty,               //串口的tx_fifo是否为空
    .set_mctrl = pl011_set_mctrl,              //设置串口的modem控制,xyz
    .get_mctrl = pl01x_get_mctrl,              //获取modem设置
    .stop_tx = pl011_stop_tx,                //停止传输
    .start_tx = pl011_start_tx,               //开始传输
    .stop_rx = pl011_stop_rx,                //停止接收
    .enable_ms = pl011_enable_ms,              //使能modem的状态信号
    .break_ctl = pl011_break_ctl,              //设置break信号
    .startup = pl011_startup,                //使能串口,用户调用open使最终会调用此函数
    .shutdown = pl011_shutdown,               // 关闭串口,应用程序关闭串口设备文件时,该函数会被调用
    .flush_buffer = pl011_dma_flush_buffer,
    .set_termios = pl011_set_termios,        //设置串口属性,包括波特率等
    .type = pl011_type,                   //判断串口类型是否为amba
    .release_port = pl010_release_port,       //释放端口使用的内存
    .request_port = pl010_request_port,       //请求端口使用的内存
    .config_port = pl010_config_port,        //设置端口类型并申请端口使用的内存
    .verify_port = pl010_verify_port,        //检验串口属性,包括总线类型和波特率

#ifdef CONFIG_CONSOLE_POLL
    .poll_get_char = pl010_get_poll_char,       //获取console的输入
    .poll_put_char = pl010_put_poll_char,       //将数据显示到console中
#endif

};

2、tty核心层(上层)

底层驱动层和tty层之间的联系需要从register_uart_driver中分析,tty_driver是在uart_driver注册过程中构建的

int uart_register_driver(struct uart_driver *drv)

{
    struct tty_driver *normal = NULL;
    int i, retval;

    /* 根据driver支持的最大设备数,申请n个 uart_state 空间,每一个 uart_state 
    都有一个uart_port,支持多少个串口,就开辟多少块空间 */
    drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL);

    /* tty层:分配一个 tty_driver ,并将drv->tty_driver 指向它 */
    normal  = alloc_tty_driver(drv->nr);
    drv->tty_driver = normal;

    /* 对 tty_driver 进行设置 */
    normal->owner = drv->owner;
    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);//设置tty驱动层的处理函数

    /*
     * 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; /* driver->state->tty_port */
        tty_port_init(port);
        port->close_delay     = 500; /* .5 seconds */
        port->closing_wait    = 30000; /* 30 seconds */

        /* 初始化 tasklet */
        tasklet_init(&state->tlet, uart_tasklet_action, (unsigned long)state);
    }

    /* tty层:注册 driver->tty_driver */
    retval = tty_register_driver(normal);
}

注册过程主要做的工作如下所示

    1、根据driver支持的最大设备数,申请n个uart_state空间,每一个uart_state都对应一个 uart_port 。

    2、分配一个tty_driver空间并指向drv->tty_driver。

    3、对tty_driver进行设置,其中包括默认波特率、校验方式等,还有一个重要的 Ops ,uart_ops ,它是tty核心与我们串口驱动通信的接口。

    4、初始化每一个 uart_state 的 tasklet 。

    5、注册 tty_driver 。

 

注册uart_driver实际上是注册一个tty_driver的过程最终生成的设备节点也是tty的,与用户空间打交道的工作也是由用户空间层的函数实现的,好在这一部分内核已经帮我们实现好的,我们只需要知道他们需要什么机构,套用一下他们的框架就可以了。

如下所示为tty核心层的函数:这一层的函数通过如下方式调用设备驱动层

uart_state->uart_port->ops
static const struct tty_operations uart_ops = {
    .open = uart_open,
    .close = uart_close,
    .write = uart_write,
    .put_char = uart_put_char, // 单字节写函数
    .flush_chars = uart_flush_chars, // 刷新数据到硬件函数
    .write_room = uart_write_room, // 指示多少缓冲空闲的函数
    .chars_in_buffer= uart_chars_in_buffer, // 只是多少缓冲满的函数
    .flush_buffer = uart_flush_buffer, // 刷新数据到硬件
    .ioctl = uart_ioctl,
    .throttle = uart_throttle,
    .unthrottle = uart_unthrottle,
    .send_xchar = uart_send_xchar,
    .set_termios = uart_set_termios, // 当termios设置被改变时又tty核心调用
    .set_ldisc = uart_set_ldisc, // 设置线路规程函数
    .stop = uart_stop,
    .start = uart_start,
    .hangup = uart_hangup, // 挂起函数,当驱动挂起tty设备时调用
    .break_ctl = uart_break_ctl, // 线路中断控制函数
    .wait_until_sent= uart_wait_until_sent,

#ifdef CONFIG_PROC_FS
    .proc_fops = &uart_proc_fops,
#endif

    .tiocmget = uart_tiocmget, // 获得当前tty的线路规程的设置
    .tiocmset = uart_tiocmset, // 设置当前tty线路规程的设置

#ifdef CONFIG_CONSOLE_POLL
    .poll_init = uart_poll_init,
    .poll_get_char = uart_poll_get_char,
    .poll_put_char = uart_poll_put_char,
#endif

};

 3、用户空间函数分析

int tty_register_driver(struct tty_driver *driver)
{

    int error;
    int i;
    dev_t dev;
    void **p = NULL;

    if (!(driver->flags & TTY_DRIVER_DEVPTS_MEM) && driver->num) {
        p = kzalloc(driver->num * 2 * sizeof(void *), GFP_KERNEL);
    }

    /* 申请设备号 */
    if (!driver->major) {//设备号未知的情况
        error = alloc_chrdev_region(&dev, driver->minor_start,
        driver->num, driver->name);
    } else {//设备号已知的情况
        dev = MKDEV(driver->major, driver->minor_start);
        error = register_chrdev_region(dev, driver->num, driver->name);
    }

    if (p) { /* 为线路规程和termios分配空间 */
        driver->ttys = (struct tty_struct **)p;
        driver->termios = (struct ktermios **)(p + driver->num);
    } else {
        driver->ttys = NULL;
        driver->termios = NULL;
    }

    /* 创建字符设备,使用 tty_fops */
    cdev_init(&driver->cdev, &tty_fops);
    driver->cdev.owner = driver->owner;
    error = cdev_add(&driver->cdev, dev, driver->num);
    mutex_lock(&tty_mutex);

    /* 将该 driver->tty_drivers 添加到全局链表 tty_drivers */
    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++)
            tty_register_device(driver, i, NULL);
    }

    /* proc 文件系统注册driver */
    proc_tty_register_driver(driver);
    driver->flags |= TTY_DRIVER_INSTALLED;

    return 0;
}

tty_driver 注册过程干了哪些事:

    1、为线路规程和termios分配空间,并使 tty_driver 相应的成员指向它们。

    2、注册字符设备,名字是 uart_driver->name 我们这里是“ttyAMA”,文件操作函数集是 tty_fops。

    3、将该 uart_driver->tty_drivers 添加到全局链表 tty_drivers 。

    4、向proc文件系统添加driver,提供调试节点

猜你喜欢

转载自blog.csdn.net/qq_37600027/article/details/84504042
tty