前言
尝试把linux串口的实现底层硬件到应用层上下打通,目前掌握的信息linux有平台驱动,厂家遵循规范进行硬件层编写。串口实现的步骤:上电初始化,中断处理 ,数据接收,数据发送。
TTY 和8250驱动
tty一词源于Teletypes,或Teletypewriters,它是最早出现的一种终端设备,类似电传打字机,由Teletype公司生产。最初tty是指连接到Unix系统上的物理或者虚拟终端。终端是一种字符型设备,通常使用tty来统称各种类型的终端设备。随着时间的推移,当通过串行口能够建立起终端连接后,这个名字也用来指任何的串口设备。
它还有多种类,例如串口(ttySn、ttySACn、ttyOn)、USB到串口的转换器(ttyUSBn),还有需要特殊处理才能正常工作的调制解调器(比如传统的WinModem类设备)等。tty虚拟设备支持虚拟控制台,它能通过键盘及网络连接或者通过xterm会话登录到计算机上。
其实起初终端和控制台都不是个人电脑的概念,而是多人共用的小型中型大型计算机上的概念。终端为主机提供了人机接口,每个人都通过终端使用主机的资源。终端有字符终端和图形终端两种。一台主机可以连很多终端。控制台是一种特殊的人机接口, 是人控制主机的第一人机接口。
而主机对于控制台的信任度高于其他终端。对此还可以结合内核启动代码中init进程打开/dev/console和执行两次sys_dup(0),以及标准输入、标准输出、标准出错,还有就是进程fork后的标准输入输出的复制情况来一起理解。而个人计算机只有控制台,没有终端。当然愿意的话,可以在串口上连一两台字符哑终端。
但是linux按POSIX标准把个人计算机当成小型机来用,在控制台上通过getty软件虚拟了六个字符哑终端(或者叫虚拟控制台终端tty1-tty6)(数量可以在/etc/inittab里自己调整)和一个图型终端,在虚拟图形终端中又可以通过软件(如rxvt)再虚拟无限多个伪终端(pts/0等)。
但这全是虚拟的,虽然用起来一样,但实际上没有物理实体。所以在个人计算机上,只有一个实际的控制台,没有终端,所有终端都是在控制台上用软件模拟的。要把个人计算机当主机再通过串口或网卡外连真正的物理终端也可以,论成本,谁又会怎么做呢。
终端按照其自身能力分类,可以分为:
哑终端(瘦客户端)
早期的计算机终端是通过串行RS-232通信的,它只能解释有限数量的控制码(CR,LF等),但没有能力处理执行特殊的转义序列功能(如清行、清屏或控制光标的位置)。简单来说就是处理能力有限的终端机,他们一般基本上只具有和机械电传打字机类似的有限功能。这种类型的终端称为哑终端。
现在仍然在现代类Unix系统上得到支持,通过设置环境变量TERM=dumb。哑终端有时用来指任何类型的通过RS-232连接的传统计算机终端,不对数据进行本地处理或本地执行用户程序的串行通信终端。哑终端有时也指功能有限,只有单色文本处理能力或直接传输每一个键入的字符而不等待主机轮询的公共计算机终端。
智能终端(胖客户端)
智能终端就是有能力处理转义序列,也就是说处理能力较强的终端机。
Linux系统的终端设备一般分为控制台、伪终端pty、串口终端(/dev/ttySn)和其它类型4种。
8250驱动时的设备名“ttySn”
在Linux kernel中,tty驱动不像spi,iic等那么架构简单,它是一个庞大的系统,它的框架大体如下图一。我们作为普通的驱动开发移植人员,不会从零写tty驱动,一般都是厂家根据现有的tty驱动和自家芯片修改,拿到板子按照厂家的配置,串口应该使能直接使用的。但是开发的过程中也有可能需要用到串口,一般会修改serial驱动,这样我们不会动tty_core层。
原文链接:https://blog.csdn.net/liangzc1124/article/details/127469767
tty framework加载过程
tty framework加载自己默认需要的字符设备驱动
加载discipline的一些处理数据的数据结构和ops。
以串口tty为例,然后加载一些串口相关的tty driver,tty port。前面我们说过,tty的实体有很多种,uart只是其中一种,所以如果需要使用串口tty,就需要向内添加和init一些串口tty相关的数据结构和ops,如果需要还可以天console driver实现 uart console driver。
上面这些步骤只是让tty core支持了uart,最后还要通过platform bus的probe添加相关的devtempfs节点供用户空间使用——即 ttySxx。
- Linux中的8250驱动是用于支持8250及其兼容的UART(通用异步收发传输器)芯片的驱动。这些芯片广泛用于各种计算机和嵌入式系统中的串口通信。以下是对Linux 8250驱动的详细解释:
一、8250 UART芯片概述
8250 UART芯片是一种广泛使用的串口通信芯片,它支持标准的串口通信协议。通过该芯片,计算机或嵌入式设备可以与其他设备进行串行通信,如打印机、调制解调器、其他计算机等。
二、Linux 8250驱动的主要功能
Linux 8250驱动的主要功能包括:
初始化:在系统启动时或设备被使用时,驱动会对8250 UART芯片进行初始化,设置其工作模式和参数。
数据传输:驱动负责在CPU和UART芯片之间传输数据。这包括将CPU发送的数据写入UART芯片的发送缓冲区,并从UART芯片的接收缓冲区读取数据到CPU。
中断处理:当UART芯片接收到数据或发送缓冲区为空时,它会产生中断信号。Linux 8250驱动会注册中断处理函数来响应这些中断,并在中断处理函数中完成数据的读取或发送操作。
错误处理:驱动还负责检测和处理串口通信中的错误,如帧错误、奇偶校验错误等。
三、Linux 8250驱动的实现细节
Linux 8250驱动的实现涉及多个方面,包括硬件寄存器的操作、中断处理程序的编写、设备树的配置等。以下是一些关键的实现细节:
硬件寄存器的操作:8250 UART芯片具有多个控制寄存器和数据寄存器,用于控制串口的工作模式和传输数据。Linux 8250驱动通过读写这些寄存器来与UART芯片进行交互。
中断处理:在Linux内核中,中断处理是一个重要的机制。Linux 8250驱动通过注册中断处理函数来响应UART芯片的中断信号。当中断发生时,内核会调用注册的中断处理函数来执行相应的操作。
设备树配置:在基于设备树的Linux系统中,8250 UART芯片的配置信息(如中断号、物理地址等)被存储在设备树文件中。Linux 8250驱动在初始化时会从设备树中读取这些配置信息,并根据这些信息来配置UART芯片。
四、Linux 8250驱动的示例代码
由于Linux 8250驱动的源代码较长且复杂,这里无法直接给出完整的示例代码。但可以参考Linux内核源代码中的drivers/tty/serial/8250.c文件来了解Linux 8250驱动的具体实现。
五、注意事项
兼容性:8250 UART芯片有多种兼容型号,如16450、16550等。Linux 8250驱动需要能够识别并正确处理这些兼容型号。
性能优化:在高速串口通信中,驱动的性能优化非常重要。Linux 8250驱动通过合理的中断处理和数据传输策略来提高性能。
错误处理:串口通信中可能会出现各种错误,如硬件故障、信号干扰等。Linux 8250驱动需要能够检测并处理这些错误,以确保通信的可靠性。
总之,Linux 8250驱动是Linux系统中用于支持8250及其兼容UART芯片的重要组件。它通过操作硬件寄存器、处理中断和配置设备树等方式来实现串口通信的功能。
字符设备驱动开发步骤
串口是一种字符设备,那么字符设备驱动开发都有哪些步骤呢?
我们在学习裸机或者 STM32 的时候关于驱动的开发就是初始化相应的外设寄存器,在 Linux 驱
动开发中肯定也是要初始化相应的外设寄存器,这个是毫无疑问的。只是在 Linux 驱动开发中
我们需要按照其规定的框架来编写驱动,所以说学 Linux 驱动开发重点是学习其驱动框架。
MMU 内存管理单元
在编写驱动之前,我们需要先简单了解一下 MMU 这个神器,MMU 全称叫做 Memory
Manage Unit,也就是内存管理单元。在老版本的 Linux 中要求处理器必须有 MMU,但是现在
Linux 内核已经支持无 MMU 的处理器了。MMU 主要完成的功能如下:
①、完成虚拟空间到物理空间的映射。
②、内存保护,设置存储器的访问权限,设置虚拟存储空间的缓冲特性。
我们重点来看一下第①点,也就是虚拟空间到物理空间的映射,也叫做地址映射。首先了
解两个地址概念:虚拟地址(VA,Virtual Address)、物理地址(PA,PhyscicalAddress)。对于 32 位
的处理器来说,虚拟地址范围是 2^32=4GB,我们的开发板上有 512MB 的 DDR3,这 512MB 的
内存就是物理内存,经过 MMU 可以将其映射到整个 4GB 的虚拟空间,如图所示:
Linux 内核启动的时候会初始化 MMU,设置好内存映射,设置好以后 CPU 访问的都是虚
拟地址。比如 I.MX6ULL 的 GPIO1_IO03 引脚的复用寄存器
IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 的地址为 0X020E0068。如果没有开启 MMU 的话
直接向 0X020E0068 这个寄存器地址写入数据就可以配置 GPIO1_IO03 的复用功能。现在开启
了 MMU,并且设置了内存映射,因此就不能直接向 0X020E0068 这个地址写入数据了。我们必
须得到 0X020E0068 这个物理地址在 Linux 系统里面对应的虚拟地址,这里就涉及到了物理内
存和虚拟内存之间的转换,需要用到两个函数:ioremap 和 iounmap。
ioremap 函数用于获取指 定 物 理 地 址 空 间 对 应 的 虚 拟 地 址 空 间 , 定 义 在
arch/arm/include/asm/io.h 文件中
1 #define ioremap(cookie,size) __arm_ioremap((cookie), (size),
MT_DEVICE)
2
3 void __iomem * __arm_ioremap(phys_addr_t phys_addr, size_t size,
unsigned int mtype)
4 {
5 return arch_ioremap_caller(phys_addr, size, mtype,
__builtin_return_address(0));
6 }
ioremap 是个宏,有两个参数:cookie 和 size,真正起作用的是函数__arm_ioremap,此函
数有三个参数和一个返回值,这些参数和返回值的含义如下:
phys_addr:要映射的物理起始地址。
size:要映射的内存空间大小。
mtype:ioremap 的类型,可以选择 MT_DEVICE、MT_DEVICE_NONSHARED、
MT_DEVICE_CACHED 和 MT_DEVICE_WC,ioremap 函数选择 MT_DEVICE。
返回值:__iomem 类型的指针,指向映射后的虚拟空间首地址。
假如我们要获取 I.MX6ULL 的 IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 寄存器对应
的虚拟地址,使用如下代码即可:
#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
static void __iomem* SW_MUX_GPIO1_IO03;
SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);
宏 SW_MUX_GPIO1_IO03_BASE 是寄存器物理地址,SW_MUX_GPIO1_IO03 是映射后
的虚拟地址。对于 I.MX6ULL 来说一个寄存器是 4 字节(32 位)的,因此映射的内存长度为 4。
映射完成以后直接对 SW_MUX_GPIO1_IO03 进行读写操作即可。
卸载驱动的时候需要使用 iounmap 函数释放掉 ioremap 函数所做的映射,iounmap 函数原
型如下:
void iounmap (volatile void __iomem *addr)
iounmap 只有一个参数 addr,此参数就是要取消映射的虚拟地址空间首地址。假如我们现
在要取消掉 IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 寄存器的地址映射,使用如下代码
即可:
iounmap(SW_MUX_GPIO1_IO03);
使用 ioremap 函数将寄存器的物
理地址映射到虚拟地址以后,我们就可以直接通过指针访问这些地址,但是 Linux 内核不建议
这么做,而是推荐使用一组操作函数来对映射后的内存进行读写操作。
1 u8 readb(const volatile void __iomem *addr)
2 u16 readw(const volatile void __iomem *addr)
3 u32 readl(const volatile void __iomem *addr)
readb、readw 和 readl 这三个函数分别对应 8bit、16bit 和 32bit 读操作,参数 addr 就是要
读取写内存地址,返回值就是读取到的数据。
1 void writeb(u8 value, volatile void __iomem *addr)
2 void writew(u16 value, volatile void __iomem *addr)
3 void writel(u32 value, volatile void __iomem *addr)
writeb、writew 和 writel 这三个函数分别对应 8bit、16bit 和 32bit 写操作,参数 value 是要
写入的数值,addr 是要写入的地址。
设备树
我们多次提到“设备树”这个概念,因为时机未到,所以当时并没有详细的讲
解什么是“设备树”,本章我们就来详细的谈一谈设备树。掌握设备树是 Linux 驱动开发人员必
备的技能!因为在新版本的 Linux 中,ARM 相关的驱动全部采用了设备树(也有支持老式驱动
的,比较少),最新出的 CPU 其驱动开发也基本都是基于设备树的,比如 ST 新出的 STM32MP157、
NXP的 I.MX8系列等。我们所使用的Linux版本为 4.1.15,其支持设备树,所以正点原子I.MX6UALPHA 开发板的所有 Linux 驱动都是基于设备树的。本章我们就来了解一下设备树的起源、重点学习一下设备树语法。
设备树(Device Tree),将这个词分开就是“设备”和“树”,描述设备树的文件叫做 DTS(Device
Tree Source),这个 DTS 文件采用树形结构描述板级设备,也就是开发板上的设备信息,比如
CPU 数量、 内存基地址、IIC 接口上接了哪些设备、SPI 接口上接了哪些设备等等,如图
所示:
在 3.x 版本(具体哪个版本笔者也无从考证)以前的 Linux 内核中 ARM 架构并没有采用设备
树。在没有设备树的时候 Linux 是如何描述 ARM 架构中的板级信息呢?在 Linux 内核源码中
大量的 arch/arm/mach-xxx 和 arch/arm/plat-xxx 文件夹,这些文件夹里面的文件就是对应平台下
的板级信息。
设备树源文件扩展名为.dts,但是我们在前面移植 Linux 的时候却一直在使
用.dtb 文件,那么 DTS 和 DTB 这两个文件是什么关系呢?DTS 是设备树源码文件,DTB 是将
DTS 编译以后得到的二进制文件。将.c 文件编译为.o 需要用到 gcc 编译器,那么将.dts 编译为.dtb
需要什么工具呢?需要用到 DTC 工具!DTC 工具源码在 Linux 内核的 scripts/dtc 目录下。和 C 语言一样,设备树也支持头文件,设备树的头文件扩展名为.dtsi