深入浅出UART驱动开发与调试:从基础调试到虚拟驱动实现

往期内容

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

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

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.UART驱动调试方法
    • 1.1 怎么得到UART硬件上收发的数据
      • 1.1.1 接收到的原始数据(收)
      • 1.1.2 发送出去的数据(发)
    • 1.2 proc文件
      • 1.2.1 /proc/interrupts
      • 1.2.2 /proc/tty/drivers
      • 1.2.3 /proc/tty/driver(非常有用)
      • 1.2.4 /proc/tty/ldiscs
    • 1.3 sys文件
  • 2.编写虚拟UART驱动程序
    • 2.1 要做的事
    • 2.2 虚拟的UART
    • 2.3 编程
      • 2.3.1 代码说明
      • 2.3.2 /proc文件
      • 2.3.3. 触发中断
    • 2.4 调试

1.UART驱动调试方法

img

1.1 怎么得到UART硬件上收发的数据

1.1.1 接收到的原始数据(收)

可以在接收中断函数里把它打印出来,这些数据也会存入UART对应的tty_port的buffer里:

img

imx_rxint// 读取硬件状态// 得到数据// 在对应的uart_port中更新统计信息, 比如sport->port.icount.rx++;------添加打印---------// 把数据存入tty_port里的tty_buffertty_insert_flip_char(port, rx, flg)------添加打印,确保是否接收到数据---------// 通知行规程来处理tty_flip_buffer_push(port);tty_schedule_flip(port);queue_work(system_unbound_wq, &buf->work); // 使用工作队列来处理// 对应flush_to_ldisc函数

1.1.2 发送出去的数据(发)

所有要发送出去的串口数据,都会通过uart_write函数发送,所有可以在uart_write中把它们打印出来:

imgimg

1.2 proc文件

1.2.1 /proc/interrupts

查看中断次数。

img

1.2.2 /proc/tty/drivers

img

1.2.3 /proc/tty/driver(非常有用)

img

1.2.4 /proc/tty/ldiscs

img

1.3 sys文件

drivers\tty\serial\serial_core.c中,有如下代码:

img

这写代码会在/sys目录中创建串口的对应文件,查看这些文件可以得到串口的很多参数。

怎么找到这些文件?在开发板上执行:

cd /sys
find -name uartclk  // 就可以找到这些文件所在目录

2.编写虚拟UART驱动程序

2.1 要做的事

img

  • 注册一个uart_driver:它里面有名字、主次设备号等

  • 对于每一个port,调用uart_add_one_port,里面的核心是uart_ops,提供了硬件操作函数

    • uart_add_one_port由platform_driver的probe函数调用

    • 所以:

      • 编写设备树节点
      • 注册platform_driver

2.2 虚拟的UART

img为了做实验,还要创建一个虚拟文件:/proc/virt_uart_buf

  • 要发数据给虚拟串口时,执行:echo “xxx” > /proc/virt_uart_buf
  • 要读取虚拟串口的数据时,执行:cat /proc/virt_uart_buf

2.3 编程

📎virtual_uart.c

📎serial_send_recv.c – 测试程序

# 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
# 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
# 2.1 ARCH,          比如: export ARCH=arm64
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=aarch64-linux-gnu-
# 2.3 PATH,          比如: export PATH=$PATH:/home/book/100ask_roc-rk3399-pc/ToolChain-6.3.1/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin 
# 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
#       请参考各开发板的高级用户使用手册KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88all:make -C $(KERN_DIR) M=`pwd` modules clean:make -C $(KERN_DIR) M=`pwd` modules cleanrm -rf modules.orderobj-m   += virtual_uart.o
/{virtual_uart: virtual_uart_100ask {compatible = "100ask,virtual_uart";interrupt-parent = <&intc>;interrupts = <GIC_SPI 99 IRQ_TYPE_LEVEL_HIGH>;};};

无非就是实现uart_driver、uart_ops、file_operations virt_uart_buf_fops、/proc/virt_uart_buf,采用plataform_driver

2.3.1 代码说明

1. 基本结构和宏定义

  • BUF_LEN 1024:定义了环形缓冲区的长度,1024字节。
  • NEXT_PLACE(i):计算缓冲区的下一个位置,这里通过位与操作实现循环数组(环形缓冲区)。

2. 环形缓冲区相关

代码定义了两个环形缓冲区:

  • txbuf:发送缓冲区,用于存储要发送的数据。
  • rxbuf:接收缓冲区,用于存储接收的数据。

并定义了如下指针和变量:

  • tx_buf_r, tx_buf_w:发送缓冲区的读写位置。
  • rx_buf_w:接收缓冲区的写位置。

环形缓冲区的相关操作:

  • is_txbuf_empty():判断发送缓冲区是否为空。
  • is_txbuf_full():判断发送缓冲区是否已满。
  • txbuf_put():向发送缓冲区放入一个字节。
  • txbuf_get():从发送缓冲区取出一个字节。
  • txbuf_count():计算缓冲区中的有效数据字节数。

3. UART驱动结构体

  • uart_driver virt_uart_drv:表示一个UART驱动结构体,其中包括驱动名称、设备名称、设备数量等信息。
struct uart_driver virt_uart_drv = {.owner = THIS_MODULE,.driver_name = "VIRT_UART",.dev_name = "ttyVIRT",  //最后设备节点的名字:/dev/ttyVIRTx.major = 0, // 动态分配主设备号.minor = 0, // 动态分配次设备号.nr = 1,    // UART设备数量为1
};
  • uart_port virt_port:表示虚拟串口硬件信息(包含硬件资源配置),如I/O地址、IRQ、FIFO大小、操作集等。

4. proc文件系统的创建

  • proc_create():创建一个proc文件,virt_uart_buf用来与用户空间交互。
uart_proc_file = proc_create("virt_uart_buf", 0, NULL, &virt_uart_buf_fops);

通过 /proc/virt_uart_buf 文件,可以读写虚拟UART的缓冲区。virt_uart_buf_fops是文件操作集,定义了read和write方法。

5. 文件操作函数

  • virt_uart_buf_read():从虚拟UART的发送缓冲区读取数据,拷贝给用户空间的缓冲区。
ssize_t virt_uart_buf_read(struct file *file, char __user *buf, size_t size, loff_t *ppos) {int cnt = txbuf_count();int i;unsigned char val;cnt = (cnt > size) ? size : cnt; // 限制读取字节数for (i = 0; i < cnt; i++) {txbuf_get(&val); // 从环形缓冲区获取数据copy_to_user(buf + i, &val, 1); // 复制数据到用户空间}return cnt;
}
  • virt_uart_buf_write():从用户空间接收数据,存入接收缓冲区,并模拟触发RX中断。
static ssize_t virt_uart_buf_write(struct file *file, const char __user *buf, size_t size, loff_t *off) {copy_from_user(rxbuf, buf, size); // 从用户空间拷贝数据到接收缓冲区rx_buf_w = size; // 更新接收缓冲区写指针irq_set_irqchip_state(virt_port->irq, IRQCHIP_STATE_PENDING, 1); // 模拟RX中断return size;
}

6. UART操作函数

这些函数定义了UART操作,如启动、停止传输等:

  • virt_tx_empty():判断发送缓冲区是否为空,这里总是返回1,因为数据在缓冲区瞬间发送完毕。
  • virt_start_tx():开始发送数据。它从UART内部的环形缓冲区读取数据并写入txbuf,表示发送操作。
  • virt_set_termios():配置UART波特率、停止位等参数,这里未实现。
  • virt_startup():启动UART设备,这里返回0,表示不需要额外初始化。
  • virt_set_mctrl()virt_get_mctrl():控制UART调制解调器状态,暂未实现。
  • virt_shutdown():关闭UART设备。
  • virt_type():返回虚拟UART类型的字符串。

7. 中断处理函数

  • virt_uart_rxint():虚拟的RX中断处理函数,处理接收的数据,将接收到的数据放入TTY层。
static irqreturn_t virt_uart_rxint(int irq, void *dev_id) {struct uart_port *port = dev_id;struct tty_port *tport = &port->state->port;unsigned long flags;int i;spin_lock_irqsave(&port->lock, flags);for (i = 0; i < rx_buf_w; i++) {port->icount.rx++; // 增加接收计数tty_insert_flip_char(tport, rxbuf[i], TTY_NORMAL); // 插入TTY缓冲区 / put data to ldisc}rx_buf_w = 0;spin_unlock_irqrestore(&port->lock, flags);tty_flip_buffer_push(tport); // 推送到用户空间return IRQ_HANDLED;
}

8. 平台设备驱动

  • virtual_uart_probe():平台设备的探测函数,用于初始化UART设备并请求中断。这个函数负责:
static int virtual_uart_probe(struct platform_device *pdev) {rxirq = platform_get_irq(pdev, 0); // 获取中断号virt_port = devm_kzalloc(&pdev->dev, sizeof(*virt_port), GFP_KERNEL); // 分配port结构体virt_port->irq = rxirq; // 设置中断号ret = devm_request_irq(&pdev->dev, rxirq, virt_uart_rxint, 0, dev_name(&pdev->dev), virt_port); // 注册中断return uart_add_one_port(&virt_uart_drv, virt_port); // 添加一个UART端口
}
  1. 创建proc文件。
  2. 从设备树中获取中断号并注册中断处理函数。
  3. 分配并初始化uart_port结构体,注册UART设备。
  • virtual_uart_remove():用于清理和移除UART设备,包括删除proc文件和反注册UART端口。
static int virtual_uart_remove(struct platform_device *pdev) {uart_remove_one_port(&virt_uart_drv, virt_port);proc_remove(uart_proc_file);return 0;
}

9. 设备树匹配

  • of_device_id virtual_uart_of_match[]:定义设备树匹配表,用于匹配“100ask,virtual_uart”兼容字符串。

10. 平台驱动结构体

  • platform_driver virtual_uart_driver:定义平台驱动结构体,其中包含proberemove函数,以及设备名称和设备树匹配表。

11. 模块初始化与退出

  • virtual_uart_init():模块初始化函数,注册UART驱动并注册平台驱动。
  • virtual_uart_exit():模块退出函数,反注册平台驱动和UART驱动。

调用关系总结:

  • 模块加载时,module_init()调用virtual_uart_init(),注册UART驱动并调用platform_driver_register()注册平台驱动。
  • virtual_uart_probe()会被调用,分配和初始化uart_port,注册中断处理函数并将UART端口注册到系统中。
  • 中断处理函数virt_uart_rxint()会在接收中断时被调用,处理接收的数据。
  • 用户可以通过/proc/virt_uart_buf文件读取和写入虚拟UART缓冲区,触发相关操作。

2.3.2 /proc文件

参考/proc/cmdline,怎么找到它对应的驱动?在Linux内核源码下执行以下命令搜索:

grep "cmdline" * -nr | grep proc

得到:

fs/proc/cmdline.c:26:   proc_create("cmdline", 0, NULL, &cmdline_proc_fops);

2.3.3. 触发中断

使用如下函数:

int irq_set_irqchip_state(unsigned int irq, enum irqchip_irq_state which,bool val);

怎么找到它的?在中断子系统中,我们知道往GIC寄存器GICD_ISPENDRn写入某一位就可以触发中断。内核代码中怎么访问这些寄存器?
drivers\irqchip\irq-gic.c中可以看到irq_chip中的"irq_set_irqchip_state"被用来设置中断状态:

static struct irq_chip gic_chip = {.irq_mask		= gic_mask_irq,.irq_unmask		= gic_unmask_irq,.irq_eoi		= gic_eoi_irq,.irq_set_type		= gic_set_type,.irq_get_irqchip_state	= gic_irq_get_irqchip_state,.irq_set_irqchip_state	= gic_irq_set_irqchip_state, /* 2. 继续搜"irq_set_irqchip_state" */.flags			= IRQCHIP_SET_TYPE_MASKED |IRQCHIP_SKIP_SET_WAKE |IRQCHIP_MASK_ON_SUSPEND,
};static int gic_irq_set_irqchip_state(struct irq_data *d,enum irqchip_irq_state which, bool val)
{u32 reg;switch (which) {case IRQCHIP_STATE_PENDING:reg = val ? GIC_DIST_PENDING_SET : GIC_DIST_PENDING_CLEAR; /* 1. 找到寄存器 */break;case IRQCHIP_STATE_ACTIVE:reg = val ? GIC_DIST_ACTIVE_SET : GIC_DIST_ACTIVE_CLEAR;break;case IRQCHIP_STATE_MASKED:reg = val ? GIC_DIST_ENABLE_CLEAR : GIC_DIST_ENABLE_SET;break;default:return -EINVAL;}gic_poke_irq(d, reg);return 0;
}

继续搜"irq_set_irqchip_state",在drivers\irqchip\irq-gic.c中可以看到:

int irq_set_irqchip_state(unsigned int irq, enum irqchip_irq_state which,bool val)
{......
}EXPORT_SYMBOL_GPL(irq_set_irqchip_state);

以后就可与使用如下代码触发某个中断:

irq_set_irqchip_state(irq, IRQCHIP_STATE_PENDING, 1);

2.4 调试

装载驱动程序后,可以知道其设备节点是:/dev/ttyVIRT0
运行测试程序后,出现了input/output error之类的错误,如何去调试查看呢?
>>>strace -o log.txt ./serial send recv /dev/ttyVIRT0
该命令会将输出信息保存到log.txt中,方便我们去查看

img

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/61134.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux网络——IO模型和多路转接

通常所谓的IO&#xff0c;其本质就是等待通信和进行通信&#xff0c;即IO 等 拷贝。 那么想要做到高效的IO&#xff0c;就要在单位时间内&#xff0c;减少“等”的比重。 一.五种IO模型 阻塞 IO: 在内核将数据准备好之前, 系统调用会一直等待. 所有的套接字, 默认都是阻塞方…

VM Virutal Box的Ubuntu虚拟机与windows宿主机之间设置共享文件夹(自动挂载,永久有效)

本文参考如下链接 How to access a shared folder in VirtualBox? - Ask Ubuntu &#xff08;1&#xff09;安装增强功能&#xff08;Guest Additions&#xff09; 首先&#xff0c;在网上下载VBoxGuestAdditions光盘映像文件 下载地址&#xff1a;Index of http://…

AI的魔力:如何为开源软件注入智慧,开启无限可能

“AI的魔力&#xff1a;如何为开源软件注入智慧&#xff0c;开启无限可能” 引言&#xff1a; 在科技发展的浪潮中&#xff0c;开源软件生态一直扮演着推动创新与共享的重要角色。从Linux到Python&#xff0c;开源项目赋予了开发者全球协作的机会&#xff0c;推动了技术的飞速…

IThenticate 查重有无免费午餐?深度解析

经历过论文“折磨”的过来人&#xff0c;深知查重工具是写论文不可或缺的助手。而 iThenticate 查重系统&#xff0c;深受出版商、学术机构和研究人员喜爱。不过&#xff0c;每次看到它那昂贵的价格&#xff0c;就让很多小伙伴直呼&#xff0c;IThenticate查重系统就没有免费的…

启动SpringBoot

前言&#xff1a;大家好我是小帅&#xff0c;今天我们来学习SpringBoot 文章目录 1. 环境准备2. Maven2.1 什么是Maven2.2 创建⼀个Maven项⽬2.3 依赖管理2.3.1 依赖配置2.3.2 依赖传递2.3.4 依赖排除2.3.5 Maven Help插件&#xff08;plugin&#xff09; 2.4 Maven 仓库2.6 中…

DHCP服务(包含配置过程)

目录 一、 DHCP的定义 二、 使用DHCP的好处 三、 DHCP的分配方式 四、 DHCP的租约过程 1. 客户机请求IP 2. 服务器响应 3. 客户机选择IP 4. 服务器确定租约 5. 重新登录 6. 更新租约 五、 DHCP服务配置过程 一、 DHCP的定义 DHCP&#xff08;Dynamic Host Configur…

使用 Certbot 为 Nginx 自动配置 SSL 证书

1.安装Certbot和Nginx插件 sudo apt-get update sudo apt-get install certbot python3-certbot-nginx 2.获取和安装证书 运行Certbot自动安装SSL证书。注意替换 your_domain sudo certbot --nginx -d your_domain Certbot将自动与Lets Encrypt的服务器通信&#xff0c;验证域…

ros2键盘实现车辆: 简单的油门_刹车_挡位_前后左右移动控制

参考: ROS python 实现键盘控制 底盘移动 https://blog.csdn.net/u011326325/article/details/131609340游戏手柄控制 1.背景与需求 1.之前实现过 键盘控制 底盘移动的程序, 底盘是线速度控制, 效果还不错. 2.新的底盘 只支持油门控制, 使用线速度控制问题比较多, 和底盘适配…

DICOM医学影像应用篇——窗宽窗位概念、原理及实现详解

目录 窗宽窗位调整&#xff08;Windowing&#xff09;在DICOM医学影像中的应用 窗宽窗位的基本概念 窗宽&#xff08;Window Width, WW&#xff09; 窗位&#xff08;Window Level, WL&#xff09; 窗宽窗位调整的基本原理 映射逻辑 数学公式 窗宽窗位调整的C实现 代码…

天锐绿盾加密软件与Ping32联合打造企业级安全保护系统,确保敏感数据防泄密与加密管理

随着信息技术的飞速发展&#xff0c;企业在日常经营过程中产生和处理的大量敏感数据&#xff0c;面临着越来越复杂的安全威胁。尤其是在金融、医疗、法律等领域&#xff0c;数据泄漏不仅会造成企业巨大的经济损失&#xff0c;还可能破坏企业的信誉和客户信任。因此&#xff0c;…

HarmonyOS:@Provide装饰器和@Consume装饰器:与后代组件双向同步

一、前言 Provide和Consume&#xff0c;应用于与后代组件的双向数据同步&#xff0c;应用于状态数据在多个层级之间传递的场景。不同于上文提到的父子组件之间通过命名参数机制传递&#xff0c;Provide和Consume摆脱参数传递机制的束缚&#xff0c;实现跨层级传递。 其中Provi…

【Spring MVC】如何运用应用分层思想实现简单图书管理系统前后端交互工作

前言 &#x1f31f;&#x1f31f;本期讲解关于SpringMVC的编程思想之应用分层~~~ &#x1f308;感兴趣的小伙伴看一看小编主页&#xff1a;GGBondlctrl-CSDN博客 &#x1f525; 你的点赞就是小编不断更新的最大动力 &#x1f386;那…

【Linux】项目自动化构建工具-make/Makefile

【Linux】项目自动化构建工具-make/Makefile make 和 makefile 的概念如何清理项目推导过程Linux第⼀个小程序−倒计时 &#x1f30f;个人博客主页&#xff1a;个人主页 make 和 makefile 的概念 make是一个命令工具&#xff0c;是一个解释makefile中指令的命令工具&#xf…

arcgis for js点击聚合要素查询其包含的所有要素

功能说明 上一篇讲了实现聚合效果, 但是点击聚合效果无法获取到该聚合点包含的所有点信息 这一篇是对如何实现该功能的案例 实现 各属性说明需要自行去官网查阅 官网案例 聚合API 没空说废话了, 加班到12点,得休息了, 直接运行代码看效果就行, 相关重点和注意事项都在代码注…

【计算机视觉】图像基本操作

1. 数字图像表示 一幅尺寸为MN的图像可以用矩阵表示&#xff0c;每个矩阵元素代表一个像素&#xff0c;元素的值代表这个位置图像的亮度&#xff1b;其中&#xff0c;彩色图像使用3维矩阵MN3表示&#xff1b;对于图像显示来说&#xff0c;一般使用无符号8位整数来表示图像亮度&…

javaweb-day03-前端零碎

1.Ajax &#xff08;1&#xff09;概述 &#xff08;2&#xff09;原生Ajax-繁琐&#xff0c;现已基本弃用 2.Ajax-Axios &#xff08;2&#xff09;案例 3.前端工程化 3.1 基础 3.2 vue项目 &#xff08;1&#xff09;项目目录结构 &#xff08;2&#xff09;主要开发…

论文阅读:A Software Platform for Manipulating theCamera Imaging Pipeline

论文代码开源链接&#xff1a; A Software Platform for Manipulating the Camera Imaging Pipelinehttps://karaimer.github.io/camera-pipeline/摘要&#xff1a;论文提出了一个Pipline软件平台&#xff0c;可以方便地访问相机成像Pipline的每个阶段。该软件允许修改单个模块…

Python毕业设计选题:基于django+vue的智能停车系统的设计与实现

开发语言&#xff1a;Python框架&#xff1a;djangoPython版本&#xff1a;python3.7.7数据库&#xff1a;mysql 5.7数据库工具&#xff1a;Navicat11开发软件&#xff1a;PyCharm 系统展示 管理员登录 管理员功能界面 车主管理 车辆信息管理 车位信息管理 车位类型管理 系统…

使用phpStudy小皮面板模拟后端服务器,搭建H5网站运行生产环境

一.下载安装小皮 小皮面板官网下载网址&#xff1a;小皮面板(phpstudy) - 让天下没有难配的服务器环境&#xff01; 安装说明&#xff08;特别注意&#xff09; 1. 安装路径不能包含“中文”或者“空格”&#xff0c;否则会报错&#xff08;例如错误提示&#xff1a;Cant cha…

【jmeter】服务器使用jmeter压力测试(从安装到简单压测示例)

一、服务器上安装jmeter 1、官方下载地址&#xff0c;https://jmeter.apache.org/download_jmeter.cgi 2、服务器上用wget下载 # 更新系统 sudo yum update -y# 安装 wget 以便下载 JMeter sudo yum install wget -y# 下载 JMeter 压缩包&#xff08;使用 JMeter 官方网站的最…