Linux串口字符设备驱动

串口是仅次于LED简单的设备,作为了解Linux整体系统的驱动框架实例是一个不错的选择。在这里我们能够看到Linux系统如何讲一个简单的串口搭建成一套复杂的功能模块,支持多串口、多线程、同步、异步数据接收和发送功能。串口硬件知识和无系统的串口读写操作知识详细链接UART与Printf - SOC裸机_uart printf-CSDN博客

博客也耦合了字符设备驱动模型和TTY子系统驱动模型,参见对应的博客了解细节知识。

常见的字符设备有鼠标、键盘、串口、控制台和LED设备,每个字符设备在/dev目录下对应一个设备文件,linux用户程序通过设备文件(或称设备节点)来使用驱动程序操作字符设

TTY结构体关系图

TTY设备

TTY 驱动类型如下,串口也属于TTY

复制代码

/* tty driver types */
#define TTY_DRIVER_TYPE_SYSTEM        0x0001
#define TTY_DRIVER_TYPE_CONSOLE        0x0002
#define TTY_DRIVER_TYPE_SERIAL        0x0003
#define TTY_DRIVER_TYPE_PTY        0x0004
#define TTY_DRIVER_TYPE_SCC        0x0005    /* scc driver */
#define TTY_DRIVER_TYPE_SYSCONS        0x0006

复制代码

串口驱动程序分析

串口驱动程序层次结构如下图所示。简单来说,串口驱动程序层次结构可以分为两层,下层为串口驱动层,它直接与硬件相接触,需要填充一个 struct uart_ops 的结构体。上层为tty层,包括tty核心层及线规层(线路规程),它们各自都有一个 ops 结构体,用户空间可以通过tty注册的字符设备节点来访问串口设备。

芯片厂商定义一个 struct uart_driver 类型全局变量,只是填充了一些名字、设备号等信息,这些都是不涉及底层硬件访问的。

复制代码

static struct uart_driver imx_uart_uart_driver = {
    .owner          = THIS_MODULE,
    .driver_name    = DRIVER_NAME,
    .dev_name       = DEV_NAME,
    .major          = SERIAL_IMX_MAJOR,
    .minor          = MINOR_START,
    .nr             = ARRAY_SIZE(imx_uart_ports),
    .cons           = IMX_CONSOLE,
};

复制代码

在struct uart_driver imx_uart_uart_driver 结构体中,有两个成员未被赋值,分别是tty_driver和uart_state。对于tty_driver,代表的是上层,它会在 uart_ register_driver 的过程中赋值。而uart_state ,则代表下层,uart_state也会在uart_ register_driver 的过程中分配空间,但是它里面真正设置硬件相关的东西是 uart_state->uart_port ,这个uart_port 是需要从其它地方调用 uart_add_one_port 来添加的。

调用 uart_register_driver 把 struct uart_driver 类型变量注册到系统中,由下面代码可知,无论芯片有几个串口,uart_register_driver 函数只会执行一遍,共用一套驱动代码,在系统启动时执行

复制代码

static int __init imx_uart_init(void)
{
    int ret = uart_register_driver(&imx_uart_uart_driver);

    if (ret)
        return ret;

    ret = platform_driver_register(&imx_uart_platform_driver);
    if (ret != 0)
        uart_unregister_driver(&imx_uart_uart_driver);

    return ret;
}

 module_init(imx_uart_init);

复制代码

uart_register_driver 函数

  1、根据 uart_driver->nr 来申请 nr 个 uart_state 空间,用来存放驱动所支持的串口(端口)物理信息

  2、创建一个 struct tty_driver 类型变量 normal,并且调用 tty_register_driver 把 normal 注册到系统中。串口属于TTY设备,TTY设备还有其他类型设备,注册串口驱动即把串口驱动注册到TTY层。芯片上所有的串口设备共用 normal

复制代码

int uart_register_driver(struct uart_driver *drv)
{
    struct tty_driver *normal;
...

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

    retval = tty_register_driver(normal);
...
}

复制代码

在 tty_register_driver 内,由于已经指定了 driver->major,driver->flags == TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV,所以功能是

  1、按芯片串口总数申请设备号

  2、把 driver 添加到链表 tty_drivers

复制代码

int tty_register_driver(struct tty_driver *driver)
{
    int error;
    int i;
    dev_t dev;
    struct device *d;

    if (!driver->major) {
...
    } else {
        dev = MKDEV(driver->major, driver->minor_start);
        error = register_chrdev_region(dev, driver->num, driver->name);
    }
...

    mutex_lock(&tty_mutex);
    list_add(&driver->tty_drivers, &tty_drivers);
    mutex_unlock(&tty_mutex);

...
    driver->flags |= TTY_DRIVER_INSTALLED;
    return 0;
...
}

复制代码

串口设备和驱动通过platform总线进行匹配,当设备树的串口设备和驱动匹配,执行 imx_uart_probe,有几个串口执行几遍

如下设备树有两个串口设备

复制代码

                uart7: serial@2018000 {
                    compatible = "fsl,imx6ul-uart",
                             "fsl,imx6q-uart";
                    reg = <0x02018000 0x4000>;
                    interrupts = <GIC_SPI 39 IRQ_TYPE_LEVEL_HIGH>;
                    clocks = <&clks IMX6UL_CLK_UART7_IPG>,
                         <&clks IMX6UL_CLK_UART7_SERIAL>;
                    clock-names = "ipg", "per";
                    dmas = <&sdma 43 4 0>, <&sdma 44 4 0>;
                    dma-names = "rx", "tx";
                    status = "disabled";
                };

                uart1: serial@2020000 {
                    compatible = "fsl,imx6ul-uart",
                             "fsl,imx6q-uart";
                    reg = <0x02020000 0x4000>;
                    interrupts = <GIC_SPI 26 IRQ_TYPE_LEVEL_HIGH>;
                    clocks = <&clks IMX6UL_CLK_UART1_IPG>,
                         <&clks IMX6UL_CLK_UART1_SERIAL>;
                    clock-names = "ipg", "per";
                    status = "disabled";
                };

static const struct of_device_id imx_uart_dt_ids[] = {
    { .compatible = "fsl,imx6q-uart", .data = &imx_uart_devdata[IMX6Q_UART], },
    { .compatible = "fsl,imx53-uart", .data = &imx_uart_devdata[IMX53_UART], },
    { .compatible = "fsl,imx1-uart", .data = &imx_uart_devdata[IMX1_UART], },
    { .compatible = "fsl,imx21-uart", .data = &imx_uart_devdata[IMX21_UART], },
    { /* sentinel */ }
};

复制代码

imx_uart_probe() 功能:

  1、创建一个 struct imx_port 类型变量 sport,并添加到数组 imx_uart_ports[] 内,数组容量大小等于芯片串口总数。

  2、调用 uart_add_one_port() ,令imx_uart_uart_driver->state[串口号]->uart_port 指向 sport->port,一个 sport->port 对应一个串口设备,记录了串口基地址、时钟、中断号等信息;还记录了芯片厂家提供的操作imx_uart_pops,进行硬件寄存器级的适配

复制代码

static const struct uart_ops imx_uart_pops = {
    .tx_empty    = imx_uart_tx_empty,
    .set_mctrl    = imx_uart_set_mctrl,
    .get_mctrl    = imx_uart_get_mctrl,
    .stop_tx    = imx_uart_stop_tx,
    .start_tx    = imx_uart_start_tx,
    .stop_rx    = imx_uart_stop_rx,
    .enable_ms    = imx_uart_enable_ms,
    .break_ctl    = imx_uart_break_ctl,
    .startup    = imx_uart_startup,
    .shutdown    = imx_uart_shutdown,
    .flush_buffer    = imx_uart_flush_buffer,
    .set_termios    = imx_uart_set_termios,
    .type        = imx_uart_type,
    .config_port    = imx_uart_config_port,
    .verify_port    = imx_uart_verify_port,
#if defined(CONFIG_CONSOLE_POLL)
    .poll_init      = imx_uart_poll_init,
    .poll_get_char  = imx_uart_poll_get_char,
    .poll_put_char  = imx_uart_poll_put_char,
#endif
};

复制代码

uart_add_one_port ---> tty_port_register_device_attr_serdev

分析可知,serdev_tty_port_register 返回值为 -ENODEV,所以会执行  tty_register_device_attr()

复制代码

struct device *tty_port_register_device_attr_serdev(struct tty_port *port,
        struct tty_driver *driver, unsigned index,
        struct device *device, void *drvdata,
        const struct attribute_group **attr_grp)
{
    struct device *dev;

    tty_port_link_device(port, driver, index);

    dev = serdev_tty_port_register(port, device, driver, index);
    if (PTR_ERR(dev) != -ENODEV) {
        /* Skip creating cdev if we registered a serdev device */
        return dev;
    }

    return tty_register_device_attr(driver, index, device, drvdata,
            attr_grp);
}

复制代码

tty_register_device_attr

  1、创建设备文件,其类是 tty_class,设备节点文件名为 struct uart_driver 的 dev_name + 编号,如 ttymxc0

  2、创建并注册一个字符设备

小结

对接底层的部分,Kernel 主要是提供了两个接口:

  1、uart_register_driver (一次调用)

  2、uart_add_one_port (多次调用)

通过这两个接口,实现了芯片将自己的 UART 对接到 Linux Kernel UART Driver 中。

芯片厂商需要自行设计并实现的部分有:

  1、uart_drvier 结构(一个)

  2、uart_port 结构(多个)

  3、uart_ops 对串口的操作集(可能一个,可能多个)

所以从结构上来看,整个对接过程为:

 从数据结构以及相互之间的关系来看:

调用关系

串口属于字符设备,用户空间的任何open、write、read等操作,首先对应到了tty 层的注册到字符设备的struct file_operation,也就是tty_fops。

OPEN 流程

打开一个字符设备,首先调用 struct file_operation 的 tty_open

复制代码

static int tty_open(struct inode *inode, struct file *filp)
{
    struct tty_struct *tty;
    int noctty, retval;
    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;

    tty = tty_open_current_tty(device, filp);
    if (!tty)
        tty = tty_open_by_driver(device, filp); 
...
  if (tty->ops->open)
        retval = tty->ops->open(tty, filp);
    else
        retval = -ENODEV;
    filp->f_flags = saved_flags;

...
    clear_bit(TTY_HUPPED, &tty->flags);

    noctty = (filp->f_flags & O_NOCTTY) ||
         (IS_ENABLED(CONFIG_VT) && device == MKDEV(TTY_MAJOR, 0)) ||
         device == MKDEV(TTYAUX_MAJOR, 1) ||
         (tty->driver->type == TTY_DRIVER_TYPE_PTY &&
          tty->driver->subtype == PTY_TYPE_MASTER);
    if (!noctty)
        tty_open_proc_set_tty(filp, tty);
    tty_unlock(tty);
    return 0;
}

复制代码

获取当前进程的tty : tty_open_current_tty(),如果第一次获取,tty 为 null,所以执行 tty_open_by_driver(),得到 struct tty_struct tty,tty->ops 就是 struct tty_operations uart_ops。

tty_open_by_driver ---> tty_init_dev ---> alloc_tty_struct ---> tty_ldisc_get ---> get_ldops 获取到线路规程 struct tty_ldisc_ops n_tty_ops

复制代码

static struct tty_ldisc_ops *get_ldops(int disc)
{
...
    ldops = tty_ldiscs[disc];
...
}

int tty_register_ldisc(struct tty_ldisc_ops *new_ldisc)
{
...
    tty_ldiscs[new_ldisc->num] = new_ldisc;
...
}

static struct tty_ldisc_ops n_tty_ops = {
    .owner         = THIS_MODULE,
    .num         = N_TTY,
    .name            = "n_tty",
    .open            = n_tty_open,
    .close           = n_tty_close,
    .flush_buffer    = n_tty_flush_buffer,
    .read            = n_tty_read,
    .write           = n_tty_write,
    .ioctl           = n_tty_ioctl,
    .set_termios     = n_tty_set_termios,
    .poll            = n_tty_poll,
    .receive_buf     = n_tty_receive_buf,
    .write_wakeup    = n_tty_write_wakeup,
    .receive_buf2     = n_tty_receive_buf2,
};
EXPORT_SYMBOL_GPL(n_tty_inherit_ops);

void __init n_tty_init(void)
{
    tty_register_ldisc(&n_tty_ops);
}

复制代码

tty_open ---> uart_open ---> uart_port_activate ---> imx_uart_startup,最终调用的就是芯片厂商提供的硬件寄存器级的函数 imx_uart_startup

WRITE 流程

 tty_write ---> file_tty_write ---> do_tty_write ---> n_tty_write ---> uart_write ---> __uart_start ---> imx_uart_start_tx 

红色字体是 ops 中的函数,数据流如下:

 READ 流程

tty_read ---> iterate_tty_read ---> n_tty_read ---> copy_from_read_buf ---> read_buf_addr,从  struct n_tty_data:: read_buf[] 读取串口数据,串口接收中断把数据写入此处,接下来看串口接收中断流程。

在芯片级对接层,在 UART 接收数据之前,一般的,如果是中断的方式,那么需要首先通过 irq_request 来申请中断,并且挂接中断服务程序

imx_uart_probe ----> devm_request_irq,设置接收中断服务函数为 imx_uart_int

imx_uart_int ---> __imx_uart_rxint ---> tty_insert_flip_char ---> __tty_insert_flip_char,将 ch 放到了 tty_port->buf.tail
 

__imx_uart_rxint ---> tty_flip_buffer_push ---> tty_schedule_flip

复制代码

void tty_schedule_flip(struct tty_port *port)
{
    struct tty_bufhead *buf = &port->buf;

    /* paired w/ acquire in flush_to_ldisc(); ensures
     * flush_to_ldisc() sees buffer data.
     */
    smp_store_release(&buf->tail->commit, buf->tail->used);
    queue_work(system_unbound_wq, &buf->work);
}

复制代码

buf->work里面记录了函数指针 flush_to_ldisc(),在 tty_buffer_init() 内设置的

复制代码

void tty_buffer_init(struct tty_port *port)
{
    struct tty_bufhead *buf = &port->buf;

    mutex_init(&buf->lock);
    tty_buffer_reset(&buf->sentinel, 0);
    buf->head = &buf->sentinel;
    buf->tail = &buf->sentinel;
    init_llist_head(&buf->free);
    atomic_set(&buf->mem_used, 0);
    atomic_set(&buf->priority, 0);
    INIT_WORK(&buf->work, flush_to_ldisc);
    buf->mem_limit = TTYB_DEFAULT_MEM_LIMIT;
}

复制代码

 flush_to_ldisc ---> receive_buf ---> n_tty_receive_buf ---> n_tty_receive_buf_common ---> __receive_buf ---> n_tty_receive_buf_real_raw ---> read_buf_addr

 最终把数据写入 struct n_tty_data:: read_buf[]

参考数据:

UART与Printf - SOC裸机_uart printf-CSDN博客

https://blog.51cto.com/u_14592069/5824831

Linux应用开发笔记(六)串口和TTY体系(串口子系统)_ttys0 ttyusb0哪个是发送端-CSDN博客

Linux字符设备软件栈-CSDN博客

Linux系统的tty架构及UART驱动详解_linux tty驱动-CSDN博客

http://news.eeworld.com.cn/mcu/ic599918.html?ipcity__1865=eqR2Dv4IxUgD%2FD0lKjh4iqAKG%3DrGQ3sQC4D&alichlgref=http%3A%2F%2Fnews.eeworld.com.cn%2Fmcu%2Fic599918.html

Linux下tty串口驱动数据的发送、接收过程源码实例详解_tty_flip_buffer_push-CSDN博客

 从本章开始,我们将学习linux tty子系统,tty即为Teletypewritter的缩写,即为电传打印机,即用于人机交互的控制台界面,而cpu与电传打印机之间是通过串口通信的,因此在后来,cpu通过串口与设备进行通信的模块,也就定义了tty子系统(可包括通过串口连接的键盘、通过chunk通信的蓝牙、红外等设备)。现在linux tty子系统包括串口终端、串口设备、usb转串口、虚拟终端、伪终端等等(此处针对tty的描述可能不准确,需要详细了解的童鞋可搜索网络,另外针对linux应用层终端、进程组等之间的关联不再本次专栏之中,本专栏主要专注linux内核子系统的实现及架构说明)。本专栏主要包含如下内容:

一、linux tty子系统总体说明

二、linux tty子系统相关的数据结构及设备抽象说明(包括tty 设备、tty驱动、tty的线路规程的介绍)

三、linux tty驱动、设备、线路规程的注册以及注销接口说明

而针对串口在系统的分析,则放入linux串口子系统专栏中。

本章主要对tty子系统做一个总体说明。主要说明tty子系统内部的划分以及与外部的关联等等。

tty子系统框架说明
      下图是简要的tty子系统框架说明。tty设备抽象了字符设备文件节点,用于和具体文件系统关联(之前在介绍文件系统中已经说明过,大多说的文件系统均包含针对字符设备文件节点、块设备文件节点的处理)。

通过tty字符设备文件节点,即完成了与xxx_fs、vfs的关联,应用层通过vfs接口,即可实现对字符设备文件节点的操作;
tty核心层提供了字符设备文件节点的操作接口(struct file_operations tty_fops),包括tty_open、tty_read、tty_write、tty_poll、tty_ioctl等接口;
针对tty核心层接口,通过调用线路规程,进行数据的处理(线路规程是linux tty子系统的一个比较好的抽象,针对tty子系统支持10多种线路规程,默认的线路规程为N_TTY,另外支持ppp、slip、Bluetooth、irda等线路规程,实现通过串行传输系统实现具体网络的通信)。针对同一个串行驱动程序,通过调用不同的线路规程,即可实现不同协议的通信操作;
线路规程的操作接口通过tty_driver的操作接口,实现与串行设备的通信。
而tty_driver的操作接口,则一般完成与具体的设备的通信,实现串行设备的驱动程序(但针对串口而言,又进行了uart_driver、uart_port的抽象,而uart_port的ops中则实现设备的驱动程序接口(包括读写数据、tx与rx启停控制等接口))
 

       以上是简要的说明了vfs、fs、tty之间的关联,以及tty内部各模块的关联等内容。下面从数据结构的关联进一步说明说明tty的框架。

tty子系统中数据结构的关联
 

     针对tty子系统,针对设备驱动以及行为进行了抽象,tty_driver是对tty控制器的抽象;而tty_port则是对依附于tty控制器上的tty端口的抽象;而线路规程ldisc我认为可以理解为针对不同协议数据出的的抽象,如要借助于串行设备实现ppp拨号协议的通信,则使用ppp的线路规程的接口进行实现即可;

      下面就是这些数据结构以及tty接口之间的关联,这些关联也印证了上面的关联操作。而tty driver的注册、tty port的注册即是完成下图数据结构间的关联(如在tty driver、tty port的注册,则完成了对应字符设备的注册以及将其对应的设备注册至tty_class中,而在字符设备文件的打开时,则会完成tty_driver、tty_struct、tty_port、tty_ldisc的关联)。

tty子系统中接口间的关联
上面介绍了数据结构间的关联,下面我们说明下接口层是如何借助数据结构间的关联,完成接口间的调用关系的。

vfs的系统调用接口,通过字符设备操作接口,最终调用tty字符设备文件节点的操作函数的调用(即tty_open、tty_read、tty_write、tty_poll等接口);
tty_open、tty_read、tty_write、tty_poll等接口,则借助tty_ldisc数据结构类型的变量,调用tty_ldisc_ops中对应的接口;
tty_ldisc_ops中的接口,则借助tty_driver数据结构类型的变量,调用tty_operations数据结构中各成员变量对应的函数指针;
在tty_operations中的函数指针中,则针对一部分设备驱动,则会调用tty_port中的ops的接口。
 

      本文主要是对tty子系统的架构从数据结构之间的关联、接口调用关系进行简要说明,后续章节中则对数据结构、注册函数、接口调用进行详细的说明。
————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
                        
原文链接:https://blog.csdn.net/lickylin/article/details/105850967

   在上一章我们主要介绍了tty子系统的软件架构,并简要说明了数据结构间的关系,本章我们详细说明一下数据结构的抽象以及相关数据结构的说明。struct tty_file_private struct file struct tty_struct struct tty_driver struct tty_operations struct tty_ldisc struct tty_port

针对tty子系统,主要的数据结构抽象说明如下:

针对每一个tty控制器,抽象了一个tty_driver的数据结构,该数据结构即为tty控制器的抽象,该数据结构包含该tty控制器的访问接口(包括读、写等接口)
针对每一个tty端口设备(也可称为tty端口),抽象为tty_port数据结构,该结构体包含了存储从tty端口中接收的数据以及每一个数据对应的flag信息,并包含对应的接口(包括数据载波监听是否启动判断、tty端口激活及关闭、dtr/rts启动、tty port释放等接口);
在之前我们已经介绍了,借助一个tty端口传输不同格式的协议数据,即可实现不同的协议传输,即针对一个tty端口可实现多个不同的协议,因此tty驱动模块针对协议数据传输抽象出线路规程,现内核中实现了tty线路规程、irda线路规程、SLIP线路规程、PPP线路规程等
以上即为针对物理设备以及协议规程的抽象,而针对linux tty子系统,为了将tty子模块与上层文件系统关联,实现tty设备文件,又抽象了tty_struct数据结构,该数据结构包含一个tty端口、tty端口对应的线路规程、该tty端口所依附的tty控制器以及打开该tty端口的文件变量,即通过该数据结构,即可完成应用程序读写tty端口设备的功能。
 

        对于tty子系统而言,主要即为上述四个数据结构,同时在数据传输的过程中,又定义了辅助数据传输的数据结构tty_buffer等,下面我们分别介绍这四个数据结构。

tty子系统数据结构说明
tty_driver数据结构
       tty_driver即为tty控制器的抽象,该数据结构包含了该tty控制器所支持的tty端口个数、与tty端口通信的方法等信息。具体说明如下:

包含该ttyy控制器上所有已注册tty端口对应的字符设备变量(对于flag为TTY_DRIVER_DYNAMIC_DEV);
tty端口对应字符设备的主设备号、次设备号起始值等
定义了tty_driver的类型(通过type、subtype变量进行定义,对于串口而言,其type为TTY_DRIVER_TYPE_SERIAL,而subtype为SERIAL_TYPE_NORMAL);
定义了tty_struct类型的二维指针,主要指向该tty控制器所支持的所有tty端口对应的tty_struct类型的变量;
定义了该tty控制器支持的所有tty端口的属性相关变量(ktermios类型);
而driver_state则定义了该tty控制器的私有变量,主要由具体的tty_dirver决定是否使用该变量;
定义了该tty控制器所支持的操作接口(即struct tty_operations类型变量);
定义了链表类型变量,用于将所有注册至系统的tty_driver链接至一起;
 

而针对tty_operations,其定义如下,主要内容如下:

定义了install接口,该接口完成tty_struct、tty_driver等数据结构之间的关联;
remove则是解除这些数据结构间的关联;
open、close、shutdown则对应于tty端口的打开与关闭,其中在open函数中则包含启动数据收发中断等;
cleanup则释放tty端口相关的资源;
而write、put_char一般即完成将数据写入tty端口中(若需要构造复杂的框架,如uart而言,则只是将数据写入uart port的缓存中,在调用start接口时,才是将数据从缓存写入tty端口中)
ioctl主要是用于通过字符设备对tty端口设置控制信息等;
包含了数据流速控制相关的接口throttle、unthrottle等
包含了对于写buff空间相关的接口等


 

struct tty_port
       针对tty端口而言,主要包括该tty端口所对应的buff接口,用于存储该tty端口所接受的数据,同时也包含了指向tty控制器的指针以及该tty端口所包含的操作接口等信息。

tty端口的操作接口如下,主要包括:

判断tty端口的数据载波信号检测是否开启接口
启动DTR/RTS接口
激活与关闭一个tty端口
tty端口释放的接口,若在申请tty端口时,申请了额外的内存,如sdio_uart中申请的是sdio_uart_port类型的内存,
       因此此处需要定义destruct,自动释放该内存,若在实现tty driver的代码中需要使用tty_port_put接口,借助tty port的引用计数实现tty port的释放,且需要实现该接口

       针对tty端口而言,另外重要的数据结构即为tty_bufhead、tty_buffer这两个数据结构的定义如下,针对tty_bufhead,包含了一个工作队列,而在该类型数据变量的初始化时,则定义该工作队列的回调函数为flush_to_ldisc。

      当tty端口的接收中断接收到数据后,则调用tty_insert_flip_char将数据与flag写入tty端口对应的tty_buffer中,然后调用schedule_work,调用tty_bufhead的工作队列回调函数flush_to_ldisc,实现将接收数据写入tty端口的线路规程中,并唤醒线路规程的read接口,从而实现将数据写入到应用程序提供的buffer中。

struct tty_struct
       该数据结构主要用于实现vfs与tty子系统的关联,从而实现应用程序通过读写tty字符设备,完成对tty端口的操作,该数据结构包含的主要内容如下:

tty端口号、该tty端口所关联的线路规程、读写等待队列;
线路规程、tty driver相关的私有数据变量指针;
该tty端口对应字符设备已打开的所有struct file类型的链表;
该tty端口所依附的tty_driver及其对应的操作接口等。


 

以上即为这四个数据结构及其关联数据结构的介绍,下面通过数据结构的关联图,说明它们之间的关系。

如下图所示,主要涉及如下几个方面:

tty子系统定义了字符设备节点相关操作接口,主要包括tty_open、tty_release、tty_ioctl、tty_read、tty_write等,用于实现对tty端口对应字符设备的访问;
而tty_struct通过其tty_files链表及struct file的private_data指针,实现了file与tty_struct的相互关联(从下图可知,当多个应用程序同时打开一个tty端口对应的字符设备时,tty子系统仅创建一个tty_struct,而多个struct file变量则通过链表链接至tty_struct);
tty字符设备的tty_xxx接口通过调用线路规程的操作接口,进行数据读写,而线路规程的相关接口,则调用tty_driver的操作接口,实现与tty端口的通信;
系统中所有已注册的tty_driver均链接至tty_drivers链表上。而tty_driver则借助其ttys、ports成员,完成与tty_struct、tty_port的关联。
 

       而针对tty_driver_register、tty_cdev_add、tty_open等接口的调用,也就是建立下图的数据结构之间的关联,当它们之间的关联建立完成以后,即可实现数据的收发。

tty子系统的层次关系
在上面的数据结构关联中也基本上知道了tty子系统各层间的关联,下面是逻辑抽象,主要说明如下:

通过tty字符设备接口层,实现将tty端口关联为tty字符设备文件,可通过vfs提供的接口访问该字符设备文件;
tty字符设备接口通过完成tty_struct、tty_ldisc、tty_port、tty_driver的关联,并调用tty_ldisc提供给的接口,实现与线路规程的通信;
线路规程通过调用tty_driver的接口,实现与tty控制器的通信;
tty控制器的接口会调用tty端口提供的操作接口,进行端口的激活、流控等功能,同时完成与tty端口的通信,并将读取的数据返回给线路规程层,进而上传给应用程序。
针对具体的tty控制器驱动,只需要实现tty_driver、tty_port数据结构对应的实例(如下面的sdio uart实现的tty_driver、tty_port;串口实现的tty_driver、tty_port;usb转串口实现tty_driver、tty_port;伪端口、控制台等均需要实现对应的tty_driver、tty_port类型的变量,),则应用程序则可通过对应的tty字符设备文件实现对tty端口的访问
 

     以上即为本篇文章的主要内容,这段时间耽搁了,没有及时更新,下面会具体介绍下tty字符设备、tty线路规程、tty注册相关的内容。
————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
                        
原文链接:https://blog.csdn.net/lickylin/article/details/105850996

  • 26
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux字符设备驱动实验是指在Linux操作系统中编写和测试字符设备驱动程序的过程。字符设备驱动程序负责与字符设备进行交互,包括输入输出数据、控制设备和处理设备的状态等。 在进行Linux字符设备驱动实验之前,首先需要了解字符设备字符设备驱动的基本概念及其工作原理。字符设备是指以字符为单位进行输入输出的设备,如串口、打印机等。字符设备驱动是指将操作系统与字符设备进行交互的程序。 在实验中,我们通常需要编写一个字符设备驱动程序,包括初始化设备、读写数据、控制设备等功能。首先,我们需要定义字符设备驱动的数据结构,包括设备号、驱动程序打开、关闭等函数的实现。然后,我们需要实现字符设备驱动的读写函数来实现数据的输入输出。最后,我们可以进行一些附加功能的实现,如控制设备的状态、处理中断等。 在实验过程中,我们需要使用Linux内核提供的字符设备接口来进行字符设备驱动的编写和测试。可以使用一些工具和命令来加载和测试字符设备驱动程序,如insmod、rmmod等。通过这些工具和命令,我们可以加载和卸载字符设备驱动程序,并在用户空间进行数据的读写操作,来测试字符设备驱动的功能和性能。 Linux字符设备驱动实验可以帮助我们深入了解字符设备字符设备驱动的工作原理,并学习Linux内核的开发和调试技术。通过实验,我们可以更好地理解操作系统和驱动程序之间的关系,提高我们在Linux系统开发和嵌入式系统开发中的能力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值