linux TTY子系统 之 串口

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[]
 
 
 
posted @ 2023-04-17 20:42  流水灯  阅读(416)  评论(0编辑  收藏  举报