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; }
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。
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 流程
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); }
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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
2019-04-17 VMware 多个虚拟机多个外网IP
2017-04-17 FreeRTOS之命名规则
2017-04-17 FreeRTOS之Tasks和Co-routines的区别
2017-04-17 FreeRTOSConfig.h部分参数详解