11.2 Linux串口驱动框架

tty 驱动程序框架

tty 驱动程序从下往上分别是设备驱动层、行规程、终端虚拟化、TTY I/O层,它们的功能如下:

  1. 设备驱动层:用于驱动设备,如串口、显示器、键盘等。
  2. 行规程:用于处理控制字符、回显输入数据、缓存输入数据、显示数据输出等,如果应用层不需要这些处理机制可以将行规程设置为原始模式,设置方法参考11.1Linux串口应用程序开发。
  3. 终端虚拟化:用于对显示器、键盘、鼠标组成的终端进行虚拟化。
  4. TTY I/O层:与应用层进行交互。
    在这里插入图片描述

struct uart_driver 对象

uart_driver 对象表示一个 uart 驱动(一个串口驱动可以由多个串口端口),其核心成员如下:

	//所属模块struct module *owner;//驱动名称const char *driver_name;//设备名称,串口设备文件名以此为基础生成const char *dev_name;//主设备号,为0表示系统自动分配int major;//起始次设备号,即第一个串口的次设备号int minor;//此驱动支持的串口个数int nr;//若驱动支持 console 则指向对应的 serial console ,否则为 NULLstruct console *cons;//串口的状态信息,每个串口端口都有自己的状态信息,其中主要包括 tty_port 和 uart_portstruct uart_state *state;//串口驱动对应的 tty 驱动struct tty_driver *tty_driver;

struct console 对象

console 对象用于描述一个控制台驱动,其核心成员如下:

	//该 console 的名称,配合index字段使用,如果name为“ttySTM”,index字段为小于0,则可以和“console=ttySTMn“(n=0,1,2…)来确定index字段的值char name[16];//写函数void (*write)(struct console *, const char *, unsigned);//读函数int	(*read)(struct console *, char *, unsigned);//获取 console 对应的 tty 驱动struct tty_driver *(*device)(struct console *, int *);//初始化 consoleint (*setup)(struct console *, char *);//console 标志short flags;//console 索引,若小于 0 则由命令行参数确定short index;

struct uart_state 对象

uart_state 对象表示一个串口端口的状态信息,其核心成员如下:

	//串口端口所属的 tty 端口,主要包含 tty 端口的 buf 、端口操作函数等信息struct tty_port port;//串口端口,对应一个串口设备,主要包含串口的硬件操作函数等信息struct uart_port *uart_port;

struct uart_port 对象

uart_port 对象表示一个串口端口,其核心成员如下:

	//配置 RS485int (*rs485_config)(struct uart_port *, struct serial_rs485 *rs485);//中断号unsigned int irq;//串口基准时钟unsigned int uartclk;//发送 FIFO 大小unsigned int fifosize;//流控字符unsigned char x_char;//IO 类型unsigned char iotype;//串口标志upf_t flags;//操作函数集合const struct uart_ops *ops;//端口索引unsigned int line;//寄存器逻辑基地址resource_size_t mapbase;//寄存器映射大小resource_size_t mapsize;//所属父设备struct device *dev;//RS485 配置信息struct serial_rs485 rs485;

struct tty_driver 对象

tty_driver 对象用于表示一个 tty 驱动,其核心成员如下:

	//幻数,用于检查结构体是否是一个 tty_driverint magic;//cdev 指针数组,用于关联 tty_port 的字符设备驱动struct cdev **cdevs;//所属模块struct module *owner;//驱动名称const char *driver_name;//设备名称,对于非 TTY_DRIVER_TYPE_PTY 类型的 tty_port ,其设备文件名以此为基础生成const char *name;//主设备号,为0表示系统自动分配int major;//起始次设备号,即第一个 tty_port 的次设备号int minor_start;//此驱动支持的 tty_port 个数unsigned int num;//tty 类型short type;//tty 子类型short subtype;//初始配置参数struct ktermios init_termios;//tty 驱动标志unsigned long flags;//tty_struct 指针数组struct tty_struct **ttys;//tty_port 指针数组struct tty_port **ports;//ktermios 指针数组struct ktermios **termios;//tty 驱动操作函数接口const struct tty_operations *ops;

串口驱动注册过程

串口驱动注册包括两个主要步骤,分别是注册串口驱动和在串口驱动下添加串口端口。
5. 串口驱动注册过程

构建并初始化 struct uart_driver调用 uart_register_driver 函数注册 struct uart_driver根据 struct uart_driver 中的端口数量为其分配 struct uart_statestruct uart_state 中包含一个 struct tty_port 和一个 struct uart_port调用 alloc_tty_driver 函数分配一个 struct tty_driver通过宏定义调用 __tty_alloc_driver 函数分配 struct tty_driver,并初始化为前面分配的 struct tty_driver 分配 struct tty_structstruct tty_portstruct ktermiosstruct cdev 指针数组(这里只分配了指针)将分配的 struct tty_driverstruct uart_driver 关联利用 struct uart_driver 初始化前面分配的 struct tty_driver调用 tty_set_operationsstruct uart_driver 函数设置 struct tty_driver 的操作函数集合循环调用 tty_port_init 函数初始化 struct uart_state *state 中的 struct tty_port port 调用 tty_register_driver 函数将 struct uart_driver 中的 struct tty_driver *tty_driver 注册到系统中

注册 uart_driver 的本质就是注册 tty_driver ,只是这个 tty_driver 属于uart_driver ,且对应的的操作函数集合具体操作对象为 uart_driver ( uart_driver 继承于 tty_driver )。
6. 在串口驱动下添加串口端口的过程

先注册 struct uart_driver ,然后构建并初始化 struct uart_port调用 uart_add_one_port 函数在 struct uart_driver 中添加一个 struct uart_port根据 struct uart_port 中的 unsigned int line 成员从 struct uart_driver 中找到对应的 struct uart_statestruct uart_state 中的struct tty_port初始化对应的 struct uart_state ,并将其与 struct uart_port 进行关联通过传入的 struct uart_driver 初始化 struct uart_port,其中包括次设备号、端口名称、端口的 console调用 tty_port_link_device 函数,将 struct uart_state 中的 struct tty_portstruct uart_driver 中的 struct tty_driver 进行关联调用 uart_configure_port 函数对 struct uart_port *uport 进行一些配置如果 struct uart_port 的 iobase 、 mapbase 、 membase 均为 0 则直接退出如果 struct uart_port 的 flags 设置了 UPF_BOOT_AUTOCONF 则调用 config_port 函数配置串口硬件如果 struct uart_portstruct console *cons 不为空,且还未使能则调用 register_console 注册 console利用启动命令行参数中的 console_cmdline 参数进行匹配,匹配成功则设置 console 的 index ,然后调用 console 的 setup 函数进行硬件初始化将 console 添加到 console_drivers 链表中设置 struct tty_port 中的 console 属性(需要 struct uart_port 的 line 与 struct console 的 index 一致才设置,而 struct console 的 index 值可以由 bootargs 参数确定)调用 tty_port_register_device_attr_serdev 函数注册 struct tty_port调用 tty_register_device_attr 函数注册 tty 设备调用 tty_line_name 生成设备文件名分配一个 device 对象,并对其初始化(这里配置了设备号和设备名,注册后会创建设备文件)调用 device_register 注册分配的 device调用 tty_cdev_add 注册字符设备驱动分配 cdev 对象,并进行初始化,主要配置操作函数集合调用 cdev_add 将分配 cdev 对象注册到内核

在 uart_driver 下添加 uart_port 的本质就是在其对应的 tty_driver 下添加 tty_port ,只不过事先通过 uart_state 将 uart_port 和 tty_port 进行了关联

串口打开过程

添加端口的时注册一个 cdev ,并设置其操作函数集合为 tty_fops ,其中提供了 tty_open 函数,在应用层执行 open 时会通过虚拟文件系统调用到此函数,其执行过程如下:

在应用层执行 open 时会通过虚拟文件系统调用 tty_open执行 tty_open_current_tty 函数尝试打开进程所属的 tty (其设备文件名是/dev/tty,设备ID应该是MKDEV(5, 	0)),对于打开串口此函数会执行失败tty_open_current_tty 函数执行失败后会执行 tty_open_by_driver 函数来打开 tty执行 tty_lookup_driver 函数找到对应的 tty_driver执行 tty_driver_lookup_tty 函数从 tty_driver 找到对应的 tty_struct ,对于打开过的 tty 会执行成功,然后对 tty 进行检查和设置,并返回对应的 ttytty_driver_lookup_tty 函数未找到 tty 则调用 tty_init_dev 函数分配 tty_struct 并进行相应的配置调用 alloc_tty_struct 分配 tty_strct调用 kzalloc 分配 tty_strct调用 tty_ldisc_init 绑定行规程调用 tty_driver_install_tty 将 tty_strct 安装到 tty_driver 中调用 tty_ldisc_setup 对配置行规程通过 tty->ops->open 指针调用 serial_core.c 中 的 uart_open 函数调用 tty_port_open 打开对应端口通过 port->ops->activate 调用 serial_core.c 中的 uart_port_activate 函数调用 uart_startup 函数启动串口调用 uart_port_startup 函数启动串口通过 uport->ops->startup 调用串口驱动提供的 startup 函数

串口读过程

读串口数据可分为两部分:
7. 应用程序从行规程中读取数据

与执行 open 过程类似,在应用层执行 read 时会通过虚拟文件系统调用 tty_read 函数,其执行过程如下:在应用层执行 read 时会通过虚拟文件系统调用 tty_read通过 tty_ldisc_ref_wait 函数得到 tty 的行规程通过 ld->ops->read 调用行规程的 read 函数,这里通常是 N_TTY中的 n_tty_read 函数在 n_tty_read 函数中无数据则休眠等待数据,有数据则将数据拷贝到应用层
  1. 串口向行规程上报数据
串口收到数据进入中断程序中断程序从硬件读取数据调用 tty_insert_flip_string 函数将数据存入 tty_port 的 tty_buffer 中调用 tty_flip_buffer_push 函数通知行规程处理数据调用 tty_schedule_flip 函数启动数据处理调用 queue_work 函数启动一个工作队列处理数据,这里的工作队列处理函数为 flush_to_ldisc 函数

串口写过程

在应用层执行 write 时会通过虚拟文件系统调用 tty_write 函数,然后 tty_write 函数通过绑定的行规程调用到行规程的 n_tty_write 函数,接下来由行规程对数据处理后调用 serial_core.c 中的 uart_flush_chars 函数启动发送,如果行规程设置原始模式则不进行处理,直接调用 serial_core.c 的 uart_write 函数启动发送(发送过程由串口的中断或 DMA 完成),其流程如下:

在应用层执行 write 时会通过虚拟文件系统调用 tty_write 函数通过 tty_ldisc_ref_wait 函数获得与tty绑定的行规程调用 do_tty_write 函数执行数据发送操作(在执行 do_tty_write 函数传入的是行规程 write 函数作为实际的写函数)通过 copy_from_user 函数将数据拷贝到 write_buf 中通过 write 指针调用行规程的 write 函数,这里对应的是 n_tty_write 函数如果设置了 OPOST 标志则对数据进行处理后调用 tty 操作函数集合中的 flush_chars 函数发送数据,这里实际对应的是 uart_flush_chars 函数调用 uart_start 启动串口发送调用 __uart_start 函数调用 uart_port 中的 start_tx 函数,即硬件驱动提供的发送启动函数如果没有设置 OPOST 标志则调用 tty 操作函数集合中的 write 函数发送数据,这里实际对应的是 uart_write 函数将数据写入到 uart_state 的 buf 中后在调用 __uart_start 启动串口发送调用 uart_port 中的 start_tx 函数,即硬件驱动提供的发送启动函数通过 tty_ldisc_deref 释放对行规程的占用

console 注册过程

在前面的串口驱动注册过程已经介绍了 console 的注册,这里在简单梳理一下 console 的注册步骤:

构建并初始化 struct console 对象
将 struct console 对象的地址给 struct uart_driver 的 cons 成员
调用 uart_register_driver 函数注册 struct uart_driver
调用 uart_add_one_port 函数添加一个串口端口,此时会顺便完成对 console 的注册将 struct uart_driver 的 cons 赋给 struct uart_port 的 cons调用 uart_configure_port 函数若 struct uart_port 的 cons 成员有效,且未使能则调用 register_console 函数注册 console利用启动命令行中的 console_cmdline 参数进行匹配,匹配成功则设置 console 的 index ,然后调用 setup 函数进行配置将新注册的 console 添加到 console_drivers 链表中当 struct uart_port 的 line 与其 console 的 index 一致则设置 struct tty_port 的 console 标志(因此在多个 uart_port 中只有一个 uart_port 真正的拥有 struct uart_driver 中的 console)

printk 执行流程

printk 的执行流程大致如下:

通过 va_start 取出不定参数列表,然后调用 vprintk_func然后调用 vprintk_default 函数再调用 vprintk_emit 函数调用 vprintk_store 函数使用 vscnprintf 进行格式化处理调用 log_output 输出数据(并未通过硬件输出,实际上是将数据存储在 log_buf 中)调用 console_unlock 函数,可能是直接调用,也可能是通过 wake_up_klogd 函数唤醒工作队列,然后由工作队列处理函数去调用通过 log_from_idx 从 log_buf 取出一个 msg如果 msg 优先级不够则跳过通过 call_console_drivers 将数据从 console 输出,系统中可能会有多个 console

printk 使用

	/** fmt 格式字符串,其前面还包含描述优先级的字符,它们的定义如下:*     ASCII的标题开始字符(SOH),表示后面是优先级字符*     #define KERN_SOH			"\001"*     不同优先级定义,数字越小优先级越高*     #define KERN_EMERG		KERN_SOH "0"*     #define KERN_ALERT		KERN_SOH "1"*     #define KERN_CRIT		KERN_SOH "2"*     #define KERN_ERR			KERN_SOH "3"*     #define KERN_WARNING		KERN_SOH "4"*     #define KERN_NOTICE		KERN_SOH "5"*     #define KERN_INFO		KERN_SOH "6"*     #define KERN_DEBUG		KERN_SOH "7"* ... 不定参数列表,与格式字符串有关**/int printk(const char *fmt, ...)

设置系统的输出优先级:
系统输出等级存储在 /proc/sys/kernel/printk 文件中,它有4个参数,依次是:控制台消息级别、默认信息级别、最小控制台级别、默认控制台级别

early_printk

当在 uboot 的命令行参数中传入 earlyprintk 且在内核配置选项中使能 Early printk 选项后系统会调用 setup_early_printk 函数创建一个 console (此 console 的 write 函数最终会调用 printascii 输出数据, STM 官方内核已经通过汇编实现 printascii 函数),用于在串口初始化完成以前输出调试信息,具体使用步骤如下:

  1. 配置内核
Kernel hacking  ---> [*] Kernel low-level debugging functions (read help!)(0x40010000) Physical base address of debug UART (NEW)(0xfe010000) Virtual base address of debug UART (NEW)[*] Early printk
  1. 在 bootargs 中加入 earlyprintk
    earlyprintk 注册过程:
当 bootargs 中包含 earlyprintk 且内核配置选项中使能 Early printk 时在系统初始化阶段会调用 setup_early_printk调用 register_console 注册一个 console ,用于系统早期输出(通过 console 通过的 write 函数输出,在STM32中此 write 函数最终会调用 printascii 输出数据)

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

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

相关文章

如何获取unicode字符串的LPCWSTR?

今天在学习window编程方面的内容时,我想要修改一个窗口的标题,这个标题的内容是窗口的高度,这就遇到一个问题,设置标题的方法是SetWindowText,其第二个形参是LPCWSTR类型,怎么把内容显示到窗口标题栏上呢&a…

两数之和 ? 三数之和? 四数之和? 统统搞定

🎈个人主页:🎈 :✨✨✨初阶牛✨✨✨ 🐻推荐专栏1: 🍔🍟🌯C语言初阶 🐻推荐专栏2: 🍔🍟🌯C语言进阶 🔑个人信条: 🌵知行合一 前言 声明…

useContext

可以跨组件传值 其实主要的就是三步 1、const xxx React.createContext();创建一个context 2、<xxx.Provider value{{ num, setNum }}>父组件设置要传递的值 3、const { num, setNum } React.useContext(xxx);子组件下使用 特点&#xff1a; 1、可以有多个xxx.Pr…

【数字图像处理技术与应用】2023-2024上图像处理期中-云南农业大学

一、填空题&#xff08;每空2 分&#xff0c;共 30 分&#xff09; 1、图像就是3D 场景在 二维 平面上的影像&#xff0c;根据其存储方式和表现形式&#xff0c;可以将图像分为 模拟 图像和数字图像两大类&#xff1b; 2、在用计算机对数字图像处理中&#xff0c;常用一个 二…

全国(山东、安徽)职业技能大赛--信息安全管理与评估大赛题目+答案讲解——2023年国赛模拟题-linux应急响应

🍬 博主介绍👨‍🎓 博主介绍:大家好,我是 hacker-routing ,很高兴认识大家~ ✨主攻领域:【渗透领域】【应急响应】 【python】 【VulnHub靶场复现】【面试分析】 🎉点赞➕评论➕收藏 == 养成习惯(一键三连)😋 🎉欢迎关注💗一起学习👍一起讨论⭐️一起进步…

MATLAB - MPC - 优化问题(Optimization Problem)

系列文章目录 前言 模型预测控制可在每个控制间隔内解决一个优化问题&#xff0c;具体来说就是二次规划(QP)。求解结果决定了被控对象在下一个控制间隔之前使用的操纵变量&#xff08;MV&#xff09;。 该 QP 问题具有以下特点&#xff1a; 目标或 "成本 "函数 - …

数据结构(JS实现)

目录 链表链表的特点链表中的常见操作单链表append(data)尾部追加新节点toString()输出链表的节点数据插入节点insert(position,data)get(position)获取链表指定位置节点的数据indexOf(data)查找对应数据节点的位置update(position, newData)更新指定位置节点数据removeAt(posi…

【STM32】STM32学习笔记-ADC单通道 ADC多通道(22)

00. 目录 文章目录 00. 目录01. ADC简介02. ADC相关API2.1 RCC_ADCCLKConfig2.2 ADC_RegularChannelConfig2.3 ADC_Init2.4 ADC_InitTypeDef2.5 ADC_Cmd2.6 ADC_ResetCalibration2.7 ADC_GetResetCalibrationStatus2.8 ADC_StartCalibration2.9 ADC_GetCalibrationStatus2.10 A…

197.【2023年华为OD机试真题(C卷)】执行时长(模拟题-JavaPythonC++JS实现)

🚀点击这里可直接跳转到本专栏,可查阅顶置最新的华为OD机试宝典~ 本专栏所有题目均包含优质解题思路,高质量解题代码(Java&Python&C++&JS分别实现),详细代码讲解,助你深入学习,深度掌握! 文章目录 一. 题目-执行时长二.解题思路三.题解代码Python题解代码…

前端接收后端传的文件流并下载解决乱码问题

因项目需求手写了一个导出&#xff0c;但是前端获取时出现了乱码&#xff0c;搜到一下解决方案&#xff1a; 两种情况&#xff1a; 1.如果这个接口是get的请求&#xff1a; 后端返回文件流&#xff0c;前端可能会导出txt或者excel的时候&#xff0c;里面的中文会出现乱码文章…

java 音乐会售票平台系统Myeclipse开发mysql数据库struts2结构java编程计算机网页项目

一、源码特点 java 音乐会售票平台系统 是一套完善的web设计系统&#xff0c;对理解JSP java编程开发语言有帮助struts2框架开发mvc模式&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发 环境为TOCAT7.0,Myeclipse8.5开发&#xff0c;数据…

【投稿优惠|优质会议】2024年材料化学与清洁能源国际学术会议(IACMCCE 2024)

【投稿优惠|优质会议】2024年材料化学与清洁能源国际学术会议(IACMCCE 2024) 2024 International Conference Environmental Engineering and Mechatronics Integration(ICEEMI 2024) 一、【会议简介】 随着全球能源需求的不断增长&#xff0c;清洁能源的研究与应用成为了国际…

三叠云流程制造ERP:构建智慧工厂,实现高效生产管理

在数字化经济的浪潮下&#xff0c;新一代信息技术快速发展&#xff0c;深度整合&#xff0c;引领了工业的创新和变革&#xff0c;推动了企业向智能化发展。解决生产管理、销售管理和技术管理等难题的关键&#xff0c;在于管理者能否及时准确地掌握企业运营信息。三叠云流程制造…

读书之深入理解ffmpeg_简单笔记2(初步)

再回看第一遍通读后的笔记&#xff0c;感觉还有很多的细节需要一一攻克,。 mp4的封装格式&#xff0c;解析方式。 flv的封装格式&#xff0c;解析方式。 ts的封装格式&#xff0c;解析方式。 第四章 封装和解封装 4.2 视频文件转flv &#xff08;头文件和文件内容&#xff0…

Django发送QQ邮件

创建一个表单&#xff0c;供用户填写他们的姓名和电子邮件、电子邮件收件人和可选的注释 创建blog/forms.py from django import formsclass EmailPostForm(forms.Form):name forms.CharField(max_length25)email forms.EmailField()to forms.EmailField()comments forms.…

【ARMv8架构系统安装PySide2】

ARMv8架构系统安装PySide2 Step1. 下载Qt资源包Step2. 配置和安装Qt5Step3. 检查Qt-5.15.2安装情况Step4. 安装PySide2所需的依赖库Step5. 下载和配置PySide2Step6. 检验PySide2是否安装成功 Step1. 下载Qt资源包 if you need the whole Qt5 (~900MB): wget http://master.qt…

密码学(一)

文章目录 前言一、Cryptographic Primitives二、Cryptographic Keys2.1 Symmetric key cryptography2.2 asymmetric key cryptography 三、Confidentiality3.1 Symmetric key encryption algorithms3.2 asymmetric key block ciphers3.3 其他 四、Integrity4.1 symmetric key s…

【C程序设计】C数组

C 语言支持数组数据结构&#xff0c;它可以存储一个固定大小的相同类型元素的顺序集合。数组是用来存储一系列数据&#xff0c;但它往往被认为是一系列相同类型的变量。 数组的声明并不是声明一个个单独的变量&#xff0c;比如 runoob0、runoob1、...、runoob99&#xff0c;而…

Python从入门到网络爬虫(文件I/O详解)

Python提供了强大而灵活的文件I/O&#xff08;输入/输出&#xff09;工具&#xff0c;能够读取、写入和处理各种文件类型。本文将深入介绍Python文件I/O的技巧和示例代码&#xff0c;帮助大家更好地理解如何在Python中处理文件。 打开文件 在Python中&#xff0c;可以使用open…

【安全篇 / FortiGuard】(7.4) ❀ 01. FortiGuard服务到期后会怎么样?❀ FortiGate 防火墙

【简介】很多企业为了网络的安全&#xff0c;都会购买FortiGuard服务&#xff0c;但是FortiGuard服务都是有期限的&#xff0c;由于各种原因&#xff0c;企业在超过服务期限后没有继续购买FortiGuard服务&#xff0c;那么会出现什么情况&#xff1f;防火墙还能继续工作吗&#…