IMX 平台UART驱动情景分析:read篇--从硬件驱动到行规程的全链路剖析

news/2024/11/27 6:55:14/

往期内容

本专栏往期内容:Uart子系统

  1. UART串口硬件介绍
  2. 深入理解TTY体系:设备节点与驱动程序框架详解
  3. Linux串口应用编程:从UART到GPS模块及字符设备驱动
  4. 解UART 子系统:Linux Kernel 4.9.88 中的核心结构体与设计详解
  5. IMX 平台UART驱动情景分析:注册篇
  6. IMX 平台UART驱动情景分析:open篇

interrupt子系统专栏:

  1. 专栏地址:interrupt子系统
  2. Linux 链式与层级中断控制器讲解:原理与驱动开发
    – 末片,有专栏内容观看顺序

pinctrl和gpio子系统专栏:

  1. 专栏地址:pinctrl和gpio子系统

  2. 编写虚拟的GPIO控制器的驱动程序:和pinctrl的交互使用

    – 末片,有专栏内容观看顺序

input子系统专栏:

  1. 专栏地址:input子系统
  2. input角度:I2C触摸屏驱动分析和编写一个简单的I2C驱动程序
    – 末片,有专栏内容观看顺序

I2C子系统专栏:

  1. 专栏地址:IIC子系统
  2. 具体芯片的IIC控制器驱动程序分析:i2c-imx.c-CSDN博客
    – 末篇,有专栏内容观看顺序

总线和设备树专栏:

  1. 专栏地址:总线和设备树
  2. 设备树与 Linux 内核设备驱动模型的整合-CSDN博客
    – 末篇,有专栏内容观看顺序

img

目录

  • 往期内容
  • 1.内核代码
  • 2.行规程
  • 3.read过程分析
  • 4.数据源头:中断

1.内核代码

硬件相关:

  • drivers/tty/serial/imx.c📎imx.c — imx系列的

  • drivers/tty/serial/stm32-usart.c📎stm32-usart.c — stm32系列的

串口核心层:

  • drivers/tty/serial/serial_core.c📎serial_core.c

TTY层:

  • drivers/tty/tty_io.c📎tty_io.c

2.行规程

img

Linux-4.9.88\drivers\tty[📎tty_ldisc.c](https://www.yuque.com/attachments/yuque/0/2024/txt/40457654/1731853957034-ca0a7ae6-b27d-4406-810a-ae72d3c13119.txt)

行规程(TTY Line Discipline)是TTY层的关键模块,用于处理从硬件到用户空间的数据流和控制流。

  • 基本概念:
    • 行规程位于串口驱动和TTY层之间,负责处理数据格式化、缓冲区管理,以及对数据进行规范化处理(如规范模式和原始模式)。
    • 通过tty_ldisc_ops接口,行规程定义了一系列操作函数,比如readwriteioctl等。
  • n_tty规程:
    • n_tty是默认的行规程,主要用于处理规范模式和原始模式的字符数据。
    • 在规范模式下,n_tty_read会等待换行符或缓冲区满时才返回数据。
    • 在原始模式下,数据直接传递到用户空间。

行规程是通过tty_ldisc_ops结构实现扩展的,用户可以根据需要编写自定义的行规程,用于特殊协议或数据处理需求。

具体的相关结构体看之前的文章:理解UART 子系统:Linux Kernel 4.9.88 中的核心结构体与设计详解

具体的tty体系介绍看:深入理解TTY体系:设备节点与驱动程序框架详解

3.read过程分析

read调用涉及从应用程序到硬件的完整数据流,其关键点包括:

  1. 应用程序读取数据
    • 用户空间通过read系统调用读取数据,最终映射到tty_read函数。
    • 如果数据缓冲区为空,调用线程会进入休眠状态。
  2. 中断接收数据
    • 硬件接收到数据后触发中断(如imx_rxint),中断处理函数将数据从硬件FIFO读取到内核缓冲区。
  3. 行规程处理
    • 中断数据通过调用ldisc->ops->receive_buf传递到行规程的接收函数(如n_tty_receive_buf)。
    • 行规程对数据进行处理(如行编辑、缓冲区管理)后,将其存储到行规程的缓冲区中。
  4. 唤醒应用程序
    • 行规程在接收到完整数据或触发条件(如换行符)时,通过wake_up_interruptible唤醒休眠的用户进程。
  5. 返回用户数据
    • 应用程序进程被唤醒后,从行规程缓冲区读取数据并返回给用户。

扩展要点:

  • 锁机制:通过mutex_lock和信号量保证并发访问的安全性。
  • 数据拷贝:从硬件缓冲区到内核缓冲区,再到用户空间缓冲区,多级拷贝可能会影响性能。

imgimg

\Linux-4.9.88\drivers\tty\tty_io.c
static const struct file_operations tty_fops = {.llseek		= no_llseek,.read		= tty_read,.write		= tty_write,.poll		= tty_poll,.unlocked_ioctl	= tty_ioctl,.compat_ioctl	= tty_compat_ioctl,.open		= tty_open,.release	= tty_release,.fasync		= tty_fasync,
};

具体的也不再缀述了,在之前讲解open的时候,如果能看懂的话,那么read和write其实也很好懂,只不过中间过了个行规程而已,直接看tty_read函数,在此之前先来讲一下行规程的结构体tty_ldisc

\Linux-4.9.88\include\linux\tty_ldisc.h
struct tty_ldisc {struct tty_ldisc_ops *ops;  // 指向TTY行规约操作的结构体struct tty_struct *tty;      // 指向相关的TTY结构体
};//其成员:
\Linux-4.9.88\Linux-4.9.88\include\linux\tty_ldisc.h
struct tty_ldisc_ops {int magic;  // 用于验证的魔数,确保结构体的正确性char *name; // 行规约的名称int num;    // 行规约的编号int flags;  // 行规约的标志,定义了该行规约的特性/** The following routines are called from above.*/int (*open)(struct tty_struct *);  // 打开TTY的方法void (*close)(struct tty_struct *); // 关闭TTY的方法void (*flush_buffer)(struct tty_struct *tty); // 刷新TTY缓冲区的方法ssize_t (*read)(struct tty_struct *tty, struct file *file,unsigned char __user *buf, size_t nr); // 读取TTY的数据ssize_t (*write)(struct tty_struct *tty, struct file *file,const unsigned char *buf, size_t nr); // 写入数据到TTYint (*ioctl)(struct tty_struct *tty, struct file *file,unsigned int cmd, unsigned long arg); // 控制TTY的ioctl方法long (*compat_ioctl)(struct tty_struct *tty, struct file *file,unsigned int cmd, unsigned long arg); // 兼容ioctl方法void (*set_termios)(struct tty_struct *tty, struct ktermios *old); // 设置TTY的终端IO设置unsigned int (*poll)(struct tty_struct *, struct file *,struct poll_table_struct *); // 查询TTY的状态int (*hangup)(struct tty_struct *tty); // 挂起TTY的方法/** The following routines are called from below.*/void (*receive_buf)(struct tty_struct *, const unsigned char *cp,char *fp, int count); // 接收数据到TTY缓冲区void (*write_wakeup)(struct tty_struct *); // 唤醒等待写入的进程void (*dcd_change)(struct tty_struct *, unsigned int); // 处理DCD信号变化int (*receive_buf2)(struct tty_struct *, const unsigned char *cp,char *fp, int count); // 备用接收方法struct module *owner; // 所属模块的指针int refcount; // 引用计数,管理行规约的生命周期
};

那么正式来分析tty_read函数

Linux-4.9.88\Linux-4.9.88\drivers\tty\tty_io.cstatic ssize_t tty_read(struct file *file, char __user *buf, size_t count,loff_t *ppos)
{int i;  // 用于存储读取的字节数struct inode *inode = file_inode(file);  // 获取与文件关联的inode结构体struct tty_struct *tty = file_tty(file);  // 获取与文件关联的tty结构体struct tty_ldisc *ld;  // 指向当前行规约的指针// 检查TTY设备的状态,确保设备没有故障if (tty_paranoia_check(tty, inode, "tty_read"))return -EIO;  // 如果有错误,返回输入输出错误if (!tty || tty_io_error(tty))return -EIO;  // 如果TTY为空或发生IO错误,返回输入输出错误/* 我们希望等待行规约完成此时的操作 */ld = tty_ldisc_ref_wait(tty);  // 获取行规约的引用并等待if (!ld)return hung_up_tty_read(file, buf, count, ppos);  // 如果行规约为空,处理挂起的TTY读取if (ld->ops->read)  // 检查是否有读取操作i = ld->ops->read(tty, file, buf, count);  // 调用行规约的读取方法elsei = -EIO;  // 如果没有读取操作,返回输入输出错误tty_ldisc_deref(ld);  // 释放行规约的引用if (i > 0)  // 如果读取成功tty_update_time(&inode->i_atime);  // 更新文件的访问时间return i;  // 返回读取的字节数或错误代码
}

其中调用i = ld->ops->read(tty, file, buf, count)行规约的读取方法,这个方法是什么???可以去看\Linux-4.9.88\drivers\tty\n_tty.c:

\Linux-4.9.88\drivers\tty\n_tty.c
static struct tty_ldisc_ops n_tty_ops = {.magic           = TTY_LDISC_MAGIC,.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,
};

也就是调用到了n_tty_read

\Linux-4.9.88\Linux-4.9.88\drivers\tty\n_tty.c
static ssize_t n_tty_read(struct tty_struct *tty, struct file *file,unsigned char __user *buf, size_t nr)
{struct n_tty_data *ldata = tty->disc_data;  // 获取TTY设备的线性数据结构unsigned char __user *b = buf;  // 用户空间缓冲区指针DEFINE_WAIT_FUNC(wait, woken_wake_function);  // 定义等待函数int c;int minimum, time;ssize_t retval = 0;  // 返回值初始化为0long timeout;int packet;size_t tail;c = job_control(tty, file);  // 控制当前作业的状态if (c < 0)return c;  // 如果返回值小于0,表示错误,直接返回/**	Internal serialization of reads.*/if (file->f_flags & O_NONBLOCK) {  // 检查文件是否为非阻塞模式if (!mutex_trylock(&ldata->atomic_read_lock))  // 尝试获取锁return -EAGAIN;  // 如果获取失败,返回重试错误} else {if (mutex_lock_interruptible(&ldata->atomic_read_lock))  // 获取锁,可能会被中断return -ERESTARTSYS;  // 被中断返回错误}down_read(&tty->termios_rwsem);  // 获取读取信号量minimum = time = 0;  // 最小字符和时间初始化timeout = MAX_SCHEDULE_TIMEOUT;  // 超时设为最大值if (!ldata->icanon) {  // 检查是否为非规范模式minimum = MIN_CHAR(tty);  // 获取最小字符数if (minimum) {time = (HZ / 10) * TIME_CHAR(tty);  // 计算超时时间} else {timeout = (HZ / 10) * TIME_CHAR(tty);minimum = 1;  // 如果没有最小字符,设置为1}}packet = tty->packet;  // 获取数据包状态tail = ldata->read_tail;  // 获取当前读取尾部位置add_wait_queue(&tty->read_wait, &wait);  // 添加等待队列以便调度while (nr) {  // 循环直到没有读取的字节/* First test for status change. */if (packet && tty->link->ctrl_status) {  // 检查控制状态unsigned char cs;if (b != buf)break;  // 如果已经读过数据,退出循环spin_lock_irq(&tty->link->ctrl_lock);  // 加锁控制状态cs = tty->link->ctrl_status;  // 获取控制状态tty->link->ctrl_status = 0;  // 清除状态spin_unlock_irq(&tty->link->ctrl_lock);  // 解锁if (put_user(cs, b)) {  // 将控制状态写入用户缓冲区retval = -EFAULT;  // 如果失败,返回错误break;}b++;  // 移动缓冲区指针nr--;  // 减少待读取字节数break;}if (!input_available_p(tty, 0)) {  // 检查输入是否可用up_read(&tty->termios_rwsem);  // 解锁信号量tty_buffer_flush_work(tty->port);  // 刷新TTY缓冲区down_read(&tty->termios_rwsem);  // 再次获取信号量if (!input_available_p(tty, 0)) {  // 如果仍然不可用if (test_bit(TTY_OTHER_CLOSED, &tty->flags)) {  // 检查其他TTY是否关闭retval = -EIO;  // 返回输入输出错误break;}if (tty_hung_up_p(file))  // 检查文件是否挂起break;if (!timeout)  // 检查超时break;if (file->f_flags & O_NONBLOCK) {  // 非阻塞模式retval = -EAGAIN;  // 返回重试错误break;}if (signal_pending(current)) {  // 检查当前进程是否有信号retval = -ERESTARTSYS;  // 返回被中断错误break;}up_read(&tty->termios_rwsem);  // 解锁信号量timeout = wait_woken(&wait, TASK_INTERRUPTIBLE, timeout);  // 等待被唤醒down_read(&tty->termios_rwsem);  // 再次获取信号量continue;  // 继续循环}}if (ldata->icanon && !L_EXTPROC(tty)) {  // 检查是否为规范模式retval = canon_copy_from_read_buf(tty, &b, &nr);  // 从读取缓冲区拷贝数据if (retval)break;  // 如果出错,退出} else {int uncopied;/* Deal with packet mode. */if (packet && b == buf) {  // 检查数据包模式if (put_user(TIOCPKT_DATA, b)) {  // 写入数据包标识retval = -EFAULT;  // 如果失败,返回错误break;}b++;  // 移动缓冲区指针nr--;  // 减少待读取字节数}uncopied = copy_from_read_buf(tty, &b, &nr);  // 从读取缓冲区拷贝数据uncopied += copy_from_read_buf(tty, &b, &nr);  // 再次拷贝数据if (uncopied) {retval = -EFAULT;  // 如果出错,返回错误break;}}n_tty_check_unthrottle(tty);  // 检查并解除限流if (b - buf >= minimum)  // 检查是否已读取到最小字节数break;if (time)  // 如果设置了超时时间timeout = time;  // 更新超时时间}if (tail != ldata->read_tail)  // 检查读取尾部是否改变n_tty_kick_worker(tty);  // 触发工作线程up_read(&tty->termios_rwsem);  // 解锁信号量remove_wait_queue(&tty->read_wait, &wait);  // 从等待队列中移除mutex_unlock(&ldata->atomic_read_lock);  // 解锁互斥量if (b - buf)  // 如果有数据被读取retval = b - buf;  // 更新返回值为实际读取字节数return retval;  // 返回读取字节数或错误码
}

retval = canon_copy_from_read_buf(tty, &b, &nr),无非就是阻塞等待行规有数据,然后唤醒调用该函数从读取缓冲区拷贝数据。

4.数据源头:中断

文件:drivers\tty\serial\imx.c📎imx.c

函数:imx_rxint

数据流的起点是硬件的中断处理。

  • 中断注册
    • serial_imx_probe函数中通过devm_request_irq注册了中断处理函数(如imx_rxint)。
    • 接收中断(RX)用于处理UART接收到的数据;发送中断(TX)则负责处理发送缓冲区的空闲信号。
  • 中断处理逻辑
    • 中断触发后,imx_rxint从硬件寄存器中读取数据,并通过tty_insert_flip_chartty_insert_flip_string将数据填入TTY缓冲区。
    • 使用tty_schedule_flip将缓冲区中的数据传递给行规程。
  • 效率优化
    • 为了避免频繁的中断触发,通常结合DMA或FIFO机制处理大批量数据。
    • 中断处理函数需要尽可能简短高效,以避免阻塞其他中断的处理。

img

在之前讲解imx.c的probe函数中,就有个注册中断处理函数,有中断,才能将数据传给行规,行规有数据了,才能唤醒休眠等待的read:

static int serial_imx_probe(struct platform_device *pdev)
{if (txirq > 0) {ret = devm_request_irq(&pdev->dev, rxirq, imx_rxint, 0,dev_name(&pdev->dev), sport);  //这个是读中断if (ret) {dev_err(&pdev->dev, "failed to request rx irq: %d\n",ret);return ret;}ret = devm_request_irq(&pdev->dev, txirq, imx_txint, 0,dev_name(&pdev->dev), sport);  //这个是写中断if (ret) {dev_err(&pdev->dev, "failed to request tx irq: %d\n",ret);return ret;}//............
}

省略的不相关的,想要具体了解的话可以看之前的关于注册过程的分析。其中读中断处理函数就是imx_rxint:

static irqreturn_t imx_rxint(int irq, void *dev_id)
{struct imx_port *sport = dev_id;  // 获取设备结构unsigned int rx, flg, ignored = 0;  // rx: 接收到的数据, flg: 状态标志, ignored: 忽略的错误计数struct tty_port *port = &sport->port.state->port;  // 获取 tty_port 结构unsigned long flags, temp;  // 用于保存状态和临时变量spin_lock_irqsave(&sport->port.lock, flags);  // 加锁并保存当前中断状态while (readl(sport->port.membase + USR2) & USR2_RDR) {  // 检查是否有接收数据flg = TTY_NORMAL;  // 默认状态标志sport->port.icount.rx++;  // 接收计数增加rx = readl(sport->port.membase + URXD0);  // 从接收数据寄存器读取数据temp = readl(sport->port.membase + USR2);  // 读取状态寄存器if (temp & USR2_BRCD) {  // 检查是否为中断信号writel(USR2_BRCD, sport->port.membase + USR2);  // 清除中断信号if (uart_handle_break(&sport->port))  // 处理断开的连接continue;  // 如果处理成功,继续下一个循环}if (uart_handle_sysrq_char(&sport->port, (unsigned char)rx))  // 检查是否为系统请求字符continue;  // 如果处理成功,继续下一个循环if (unlikely(rx & URXD_ERR)) {  // 检查是否有错误if (rx & URXD_BRK)  // 检查是否是断开信号sport->port.icount.brk++;  // 增加断开计数else if (rx & URXD_PRERR)  // 检查是否是奇偶校验错误sport->port.icount.parity++;  // 增加奇偶校验错误计数else if (rx & URXD_FRMERR)  // 检查是否是帧错误sport->port.icount.frame++;  // 增加帧错误计数if (rx & URXD_OVRRUN)  // 检查是否是溢出错误sport->port.icount.overrun++;  // 增加溢出错误计数if (rx & sport->port.ignore_status_mask) {  // 如果错误被忽略if (++ignored > 100)  // 如果忽略计数超过 100goto out;  // 跳转到出错处理continue;  // 继续下一个循环}rx &= (sport->port.read_status_mask | 0xFF);  // 清除不相关的状态位if (rx & URXD_BRK)  // 如果是断开信号flg = TTY_BREAK;  // 设置状态标志为断开else if (rx & URXD_PRERR)  // 如果是奇偶校验错误flg = TTY_PARITY;  // 设置状态标志为奇偶校验else if (rx & URXD_FRMERR)  // 如果是帧错误flg = TTY_FRAME;  // 设置状态标志为帧错误if (rx & URXD_OVRRUN)  // 如果是溢出错误flg = TTY_OVERRUN;  // 设置状态标志为溢出}#ifdef SUPPORT_SYSRQsport->port.sysrq = 0;  // 重置系统请求标志
#endif// 检查是否需要忽略读取if (sport->port.ignore_status_mask & URXD_DUMMY_READ)goto out;  // 跳转到出错处理// 将接收到的字符插入到 tty 缓冲区if (tty_insert_flip_char(port, rx, flg) == 0)  sport->port.icount.buf_overrun++;  // 如果缓冲区溢出,增加计数}out:spin_unlock_irqrestore(&sport->port.lock, flags);  // 解锁并恢复中断状态tty_flip_buffer_push(port);  // 将缓冲区中的数据推送到 ttyreturn IRQ_HANDLED;  // 返回中断处理成功
}

末尾不就调用到了tty_flip_buffer_push,将缓冲区中的数据推送到 tty:

\Linux-4.9.88\drivers\tty\tty_buffer.c
void tty_flip_buffer_push(struct tty_port *port)
{tty_schedule_flip(port);
}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);  //采用工作队列的方式,唤醒等待的read
}

http://www.ppmy.cn/news/1550273.html

相关文章

嵌入式的C/C++:深入理解 inline 、extern与 extern “C“的用法与特点

目录 一、inline 1、什么是 inline 函数 2、inline 的使用方法 3、使用 inline 的注意事项 4、inline 的优缺点 5、inline 和 宏的比较 6、小结 二、extern 1. 使用场景 2. 特点和注意事项 三、extern "C" 1. 为什么需要 extern "C" 2. 使用场…

一个专为云原生环境设计的高性能分布式文件系统

大家好&#xff0c;今天给大家分享一款开源创新的分布式 POSIX 文件系统JuiceFS&#xff0c;旨在解决海量云存储与各类应用平台&#xff08;如大数据、机器学习、人工智能等&#xff09;之间高效对接的问题。 项目介绍 JuiceFS 是一款面向云原生设计的高性能分布式文件系统&am…

001 MATLAB介绍

前言&#xff1a; 软件获取渠道有很多&#xff0c;难点也就是百度网盘下载慢&#xff1b; 线上版本每月有时间限制。 01 MATLAB介绍 性质&#xff1a; MATLAB即Matrix Laboratory 矩阵实验室的意思&#xff0c;是功能强大的计算机高级语言, 已广泛应用于各学科研究部门、…

计算两数之和

1、输入的值固定 package org.example;import java.util.Arrays; import java.util.*; import java.util.Scanner;public class Main {public static void main(String[] args) {//在静态方法中调用非静态方法&#xff0c;需要先创建该类的实例&#xff0c;然后通过实例来调用…

使用ENSP实现默认路由

一、项目拓扑 二、项目实现 1.路由器AR1配置 进入系统试图 sys将路由器命名为R1 sysname R1关闭信息中心 undo info-center enable 进入g0/0/0接口 int g0/0/0将g0/0/0接口IP地址配置为2.2.2.1/24 ip address 2.2.2.1 24进入g0/0/1接口 int g0/0/1将g0/0/1接口IP地址配置为1.…

第三届航空航天与控制工程国际 (ICoACE 2024)

重要信息 会议官网&#xff1a;www.icoace.com 线下召开&#xff1a;2024年11月29日-12月1日 会议地点&#xff1a;陕西西安理工大学金花校区 &#xff08;西安市金花南路5号&#xff09; 大会简介 2024年第三届航空航天与控制工程国际学术会议&#xff08;ICoACE 2024&a…

jmeter使用方法简介以及一个自动测试解决方案

目录 一 jmeter是什么二 jmeter下载以及部署2.1 下载2.2 安装部署 三 jmeter使用方法3.1 图形化界面3.2 命令行3.3 动态传参 四 一个自动测试解决方案五 其他 这篇文章讨论一下jmeter工具的使用方法&#xff0c;以Linux发行版debian为例&#xff0c;Windows下使用方法类似。 一…

ChatGPT的应用场景:开启无限可能的大门

ChatGPT的应用场景&#xff1a;开启无限可能的大门 随着人工智能技术的快速发展&#xff0c;自然语言处理领域迎来了前所未有的突破。其中&#xff0c;ChatGPT作为一款基于Transformer架构的语言模型&#xff0c;凭借其强大的语言理解和生成能力&#xff0c;在多个行业和场景中…