深入浅出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_buffer
    tty_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.88

all:
	make -C $(KERN_DIR) M=`pwd` modules 

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order

obj-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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

韦东山stm32hal库--定时器喂狗模型按键消抖原理+实操详细步骤

一.定时器按键消抖的原理: 按键消抖的原因: 当我们按下按键的后, 端口从高电平变成低电平, 理想的情况是, 按下, 只发生一次中断, 中断程序只记录一个数据. 但是我们使用的是金属弹片, 实际的情况就是如上图所示, 可能会发生多次中断,难道我们要记录3/4次数据吗? 答:按键按下…

Web前端学习_CSS盒子模型

content padding border margin <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>CSS盒子模型</title><style></style> </head> <body> <div class"demo&quo…

将自定义 AWS S3 快照存储库连接到 Elastic Cloud

作者&#xff1a;来自 Elastic Annie Hansen, Stef Nestor 在本博客中&#xff0c;我们将介绍如何通过 Elasticsearch 的快照将我们已提交的集群数据备份到 AWS S3 存储桶中。在 Elastic Cloud&#xff08;企业版&#xff09;中&#xff0c;Elastic 在其 found-snapshots 存储…

部署 Prometheus

实验环境 IP地址服务192.168.88.10Prometheus服务端, Consul, Grafana, Node-Exporter192.168.88.77MySQL, Node-Exporter192.168.88.30Nginx&#xff0c;Node-Exporter 一、Prometheus Server 端安装和相关配置 【Prometheus1.sh】 &#xff08;1&#xff09;上传 prometh…

第29天 MCU入门

目录 MCU介绍 MCU的组成与作用 电子产品项目开发流程 硬件开发流程 常用元器件初步了解 硬件原理图与PCB板 常见电源符号和名称 电阻 电阻的分类 贴片电阻的封装说明&#xff1a; 色环电阻的计算 贴片电阻阻值计算 上拉电阻与下拉电阻 电容 电容的读数 二极管 LED 灯电路 钳位作…

汽车免拆诊断案例 | 2017款捷豹F-PACE车发动机偶尔怠速不稳

故障现象  一辆2017款捷豹F-PACE车&#xff0c;搭载2.0 L GTDi发动机&#xff0c;累计行驶里程约为16万km。车主反映&#xff0c;车辆组合仪表上发动机故障灯点亮&#xff08;图1&#xff09;&#xff0c;且发动机偶尔怠速不稳。 图1 发动机故障灯点亮 故障诊断 接车后试车…

Cobalt Strike 4.8 用户指南-第十一节 C2扩展

11.1、概述 Beacon 的 HTTP 指标由 Malleable Command and Control &#xff08;Malleable C2&#xff09; 配置文件控制。Malleable C2 配置文件是一个简单的程序&#xff0c;它指定如何转换数据并将其存储在事务中。转换和存储数据的同一程序&#xff08;向后解释&#xff0…

上传镜像docker hub登不上和docker desktop的etx4.vhdx占用空间很大等解决办法

平时使用docker一般都在Linux服务器上&#xff0c;但这次需要将镜像上传到docker hub上&#xff0c;但是服务器上一直无法登录本人的账号&#xff0c;&#xff08;这里的问题应该docker 网络配置中没有开代理的问题&#xff0c;因服务器上有其他用户使用&#xff0c;不可能直接…

[BUUCTF]ciscn_2019_n_8

题目 解题 先连接看看有什么信息 返回whats your name 没有其他信息 看程序基本信息 32位 拉到ida32查看 打开发现如下 由上述代码可知&#xff0c;需要将数组0-12装满&#xff0c;装什么都可以&#xff0c;将var[13]17才能执行system("/bin/sh") payload fro…

orangepi _全志H616

1. 全志H616简介 1.1. 为什么学&#xff1a; 学习目标依然是Linux系统&#xff0c;平台是ARM架构 蜂巢快递柜&#xff0c;配送机器人&#xff0c;这些应用场景用C51,STM32单片机无法实现 &#xff08;UI界面&#xff0c;提高用户的体验感&#xff09;第三方介入库的局限性&a…

信息收集之网站架构类型和目录扫描(一)

目录 前言 1.查看域名的基本信息 2.常见的网站架构类型 3.目录扫描 前言 最近也是到了期末周了,比较空闲,把信息收集的一些方式和思路简单总结一下,顺便学习一些新的工具和一些未接触到的知识面. 1.查看域名的基本信息 新学了一个工具,kali中的whois也可以进行查看,当然在…

消息中间件用途介绍

1. 解耦&#xff08;Decoupling&#xff09;&#xff1a; • 消息中间件能够将消息的生产者&#xff08;Producer&#xff09;和消费者&#xff08;Consumer&#xff09;分离开来&#xff0c;使它们不必直接相互依赖。这种设计降低了系统的耦合度&#xff0c;提升了系统的可扩展…

【Maven】Nexus私服

6. Maven的私服 6.1 什么是私服 Maven 私服是一种特殊的远程仓库&#xff0c;它是架设在局域网内的仓库服务&#xff0c;用来代理位于外部的远程仓库&#xff08;中央仓库、其他远程公共仓库&#xff09;。一些无法从外部仓库下载到的构件&#xff0c;如项目组其他人员开发的…

学习ASP.NET Core的身份认证(基于Cookie的身份认证3)

用户通过验证后调用HttpContext.SignInAsync函数将用户的身份信息保存在认证Cookie中,以便后续的请求可以验证用户的身份,该函数原型如下所示&#xff0c;其中properties参数的主要属性已在前篇文章中学习&#xff0c;本文学习scheme和principal的意义及用法。 public static …

【mac】终端左边太长处理,自定义显示名称(terminal路径显示特别长)

1、打开终端 2、步骤 &#xff08;1&#xff09;修改~/.zshrc文件 nano ~/.zshrc&#xff08;2&#xff09;添加或修改PS1&#xff0c;我是自定义了名字为“macminiPro” export PS1"macminiPro$ "&#xff08;3&#xff09;使用 nano: Ctrl o &#xff08;字母…

uniapp关闭sourceMap的生成,提高编译、生产打包速度

警告信息&#xff1a;[警告⚠] packageF\components\mpvue-echarts\echarts.min.js 文件体积超过 500KB&#xff0c;已跳过压缩以及 ES6 转 ES5 的处理&#xff0c;手机端使用过大的js库影响性能。 遇到问题&#xff1a;由于微信小程序引入了mpvue-echarts\echarts.min.js&…

PyTorch 模型转换为 ONNX 格式

PyTorch 模型转换为 ONNX 格式 在深度学习领域&#xff0c;模型的可移植性和可解释性是非常重要的。本文将介绍如何使用 PyTorch 训练一个简单的卷积神经网络&#xff08;CNN&#xff09;来分类 MNIST 数据集&#xff0c;并将训练好的模型转换为 ONNX 格式。我们还将讨论 PTH …

Three.js 和其他 WebGL 库 对比

在WebGL开发中&#xff0c;Three.js是一个非常流行的库&#xff0c;它简化了3D图形的创建和渲染过程。然而&#xff0c;市场上还有许多其他的WebGL库&#xff0c;如 Babylon.js、PlayCanvas、PIXI.js 和 Cesium&#xff0c;它们也有各自的特点和优势。本文将对Three.js 与这些常…

通过 JNI 实现 Java 与 Rust 的 Channel 消息传递

做纯粹的自己。“你要搞清楚自己人生的剧本——不是父母的续集&#xff0c;不是子女的前传&#xff0c;更不是朋友的外篇。对待生命你不妨再大胆一点&#xff0c;因为你好歹要失去它。如果这世上真有奇迹&#xff0c;那只是努力的另一个名字”。 一、crossbeam_channel 参考 cr…

摆脱复杂配置!使用MusicGPT部署你的私人AI音乐生成环境

文章目录 前言1. 本地部署2. 使用方法介绍3. 内网穿透工具下载安装4. 配置公网地址5. 配置固定公网地址 前言 今天给大家分享一个超酷的技能&#xff1a;如何在你的Windows电脑上快速部署一款文字生成音乐的AI创作服务——MusicGPT&#xff0c;并且通过cpolar内网穿透工具&…