前言
十多年的STM32开发编程习惯,导致现在必须了解到寄存器大概如何操作才能安心编程,调试。现在学习LINUX也尝试深入到内核寄存器级别。
串口学习
记得最初学习51断断续续弄了1年,把串口调通后,感觉打开了单片机学习的大门。后面再学习就快很多。这次学习LINUX也从最重要的串口开始。
STM32串口与LINUX串口
STM使用串口主要分为4个部分。 上电初始化,中断处理 ,数据接收,数据发送
我尝试也按照这个逻辑找到linux串口的这四个部分。
LINUX串口上电初始化
linux万物皆文件,应用层通过 根目录下的 /dev/ttyS0 来进行操作。
int fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY | O_NDELAY);
if (fd == -1) {
perror("open");
return -1;
}
tcgetattr(fd, &options); // 获取当前串口配置
// 设置波特率为9600
cfsetispeed(&options, B9600);
cfsetospeed(&options, B9600);
// 设置数据位为8,停止位为1,无校验位
options.c_cflag &= ~PARENB; // 清除校验位
options.c_cflag &= ~CSTOPB; // 停止位为1
options.c_cflag |= CS8; // 数据位为8
// 设置非规范模式,使得串口读写不受终端控制字符的影响
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
// 设置输入模式和输出模式
options.c_iflag &= ~(IXON | IXOFF | IXANY); // 关闭软件流控制
options.c_oflag &= ~OPOST; // 关闭输出处理,如输出换行符转换为回车换行
tcsetattr(fd, TCSANOW, &options); // 应用新的串口配置
char buffer[256];
int n = read(fd, buffer, sizeof(buffer)); // 从串口读取数据
if (n > 0) {
// 处理读取到的数据...
}
const char* message = "Hello, serial port!";
write(fd, message, strlen(message)); // 向串口写入数据
如上段代码,通常的读写,设置等操作都有。但我更关心ttyS0内部流程。
例如ttyS0是谁创建和管理的
- 在Linux和其他Unix-like系统中,/dev/ttyS0(以及类似的/dev/ttySn设备)是由内核在启动时自动创建的。这些设备文件代表了系统上的串行端口,通常用于与其他硬件设备或外部设备通信。 具体来说,当Linux内核启动时,它会检测到计算机上的硬件设备,并为每个检测到的串行接口创建一个对应的设备文件。这些设备文件通常位于/dev目录下,并以ttyS为前缀,后跟一个数字,该数字表示串行端口的编号。例如,/dev/ttyS0通常代表计算机上的第一个串行端口。 这些设备文件的创建是内核设备管理子系统的一部分,它负责管理硬件设备和提供用户空间程序与这些设备之间的接口。因此,/dev/ttyS0并不是由某个特定的用户或程序“创建”的,而是由内核在启动过程中自动检测和创建的。 需要注意的是,随着USB转串口适配器等设备的普及,现在也可以通过USB接口提供串行通信功能。这些USB设备在Linux中通常会被映射为/dev/ttyUSBn设备文件,其创建过程与/dev/ttySn类似,也是由内核在设备连接时自动完成的。
ttys0 是如何初始化的?
- *在Linux系统中,/dev/ttyS0(代表第一个串行端口)的初始化实际上是在系统启动过程中,当内核检测到串行接口硬件时发生的。具体来说,这个过程可以分为几个阶段:
**内核启动阶段:**当Linux内核启动时,它会进行硬件检测和初始化。在这个过程中,内核会识别系统上的各种硬件设备,包括串行端口。
**设备驱动加载:**内核包含了对串行端口的支持,通过相应的设备驱动程序。当内核检测到串行接口时,它会加载相应的驱动程序,并创建对应的设备文件,如/dev/ttyS0。
**设备文件创建:*一旦驱动程序加载完成,内核会在/dev目录下创建对应的设备文件。对于串行端口,这通常是/dev/ttySn形式的文件,其中n是端口的编号。
用户空间配置:虽然内核负责创建设备文件和提供基本的设备驱动,但串行端口的详细配置(如波特率、数据位、停止位等)通常是在用户空间完成的。这可以通过使用stty命令或通过编程方式(如使用termios结构)来完成。
因此,/dev/ttyS0的初始化主要是在系统启动时,由内核在硬件检测和驱动程序加载阶段完成的。用户空间的程序或脚本随后可以配置串行端口的具体参数以满足通信需求。需要注意的是,虽然设备文件在系统启动时被创建,但如果没有被用户空间程序打开和配置,串行端口本身可能不会处于活跃状态。只有在程序打开设备文件并对其进行适当配置后,串行端口才会开始实际的数据传输。
内核是如何检测到或者说知道要初始化ttys0的。
- 在Linux内核启动过程中,检测并初始化像/dev/ttyS0这样的串行设备涉及多个步骤。以下是内核如何检测到ttyS0(或任何其他串行端口)的概述:
硬件探测:
内核在启动时会进行硬件探测,这通常是通过读取系统的ACPI表、PCI配置空间、或者通过直接查询处理器的IO端口等方式来实现的。
对于传统的串行端口(如COM端口),内核可能会检查特定的IO地址范围来识别串行接口。
设备树或硬件配置:
在一些嵌入式系统或特定硬件平台上,设备的存在和配置可能会通过设备树(Device Tree)来描述。内核会解析这个设备树来了解哪些设备是连接在系统上的。
对于PC架构,BIOS或UEFI可能会在启动时将硬件配置信息传递给内核。
驱动程序加载:
一旦内核识别出串行设备,它会加载相应的设备驱动程序。在Linux中,串行设备通常由8250、16550或其他类似的串行驱动程序支持。
这些驱动程序包含了与硬件通信所需的代码,以及注册设备文件(如/dev/ttyS0)的逻辑。
设备文件创建:
当驱动程序成功加载并与硬件通信后,它会在/dev目录下创建代表该设备的文件。对于串行端口,这会是/dev/ttySn的形式。
这些设备文件允许用户空间程序通过标准的文件操作API(如open(), read(), write()等)与设备进行交互。
用户空间配置:
虽然内核负责设备的初始检测和驱动加载,但设备的详细配置(波特率、奇偶校验等)通常是在用户空间通过termios结构进行设置的。
用户空间程序可以使用tcgetattr()和tcsetattr()系统调用来获取和设置这些配置。
总的来说,Linux内核通过硬件探测、解析设备树或配置信息、加载相应的驱动程序,并最终创建代表设备的文件来检测到ttyS0或其他串行端口。一旦设备文件被创建,用户空间程序就可以通过标准的文件操作来与设备进行通信了
具体到项目,imx6ull 的ttys0驱动代码是谁提供的
- 关于i.MX6U的/dev/ttyS0的代码,可以从以下几个方面来归纳:
设备驱动代码:
/dev/ttyS0作为串行端口的设备文件,其底层的设备驱动代码通常是由处理器或芯片制造商(如NXP,即原Freescale,现为NXP的一部分)提供的。这些代码是内核的一部分,用于管理串行端口的硬件操作。
内核集成:
Linux内核开发者会将这些设备驱动代码集成到内核中。当内核启动时,它会加载这些驱动程序,从而识别并初始化串行端口。
用户空间工具:
虽然内核负责管理硬件和创建设备文件,但用户空间工具(如stty命令或自定义程序)通常用于配置串行端口的参数(如波特率、数据位等)。这些工具可能由Linux发行版的维护者或其他第三方开发者编写。
开发板制造商:
在特定开发板(如正点原子的i.MX6U开发板)的上下文中,开发板制造商可能会提供定制的Linux镜像或SDK,其中包含针对其硬件优化的驱动程序和配置。这些制造商可能会根据NXP提供的原始驱动代码进行必要的修改或适配。
综上所述,/dev/ttyS0的代码涉及多个层面和贡献者:
设备驱动代码主要由芯片制造商(如NXP)编写。
内核开发者将这些驱动集成到Linux内核中。
用户空间配置工具可能由Linux发行版维护者或第三方开发者提供。
开发板制造商可能提供定制的驱动程序和配置,以适应其特定硬件。
因此,不能单一地说/dev/ttyS0的代码是由某一个人或团队编写的,它是一个多方合作的成果。
ttys0 相关代码在哪里?
- ttyS0的驱动代码在Linux内核源代码中的位置可以归纳为以下几点:
内核源代码仓库:
Linux内核源代码存储在一个名为linux的Git仓库中,该仓库通常托管在https://github.com/torvalds/linux。这是查找任何内核驱动代码的首选位置。
驱动代码目录:
串行端口的驱动代码通常位于内核源代码的drivers/tty/serial/目录下。这个目录包含了多种串行设备的驱动程序,其中可能包括8250、16550等通用的串行端口驱动。
具体驱动文件:
对于像ttyS0这样的标准串行端口,其驱动可能实现在如8250.c或类似的文件中。这些文件包含了与特定串行硬件通信所需的代码。
查找和阅读代码:
要查找具体的驱动代码,你可以克隆Linux内核的Git仓库,并在本地使用文本编辑器或IDE进行搜索和阅读。
另外,也可以使用GitHub的在线代码浏览功能来查找和查看相关代码。
内核配置和编译:
在编译自定义内核时,需要确保相关的串行端口驱动被配置为启用状态。这通常是通过内核配置文件(如.config)中的选项来控制的。
文档和社区资源:
除了直接查看源代码外,还可以参考Linux内核的官方文档和社区资源,这些资源可以提供关于串行端口驱动实现的更多上下文和解释。
综上所述,要找到ttyS0的驱动代码,你应该查看Linux内核源代码仓库中的drivers/tty/serial/目录,并查找与你的串行硬件相关的具体驱动文件。
总结一下linux内核专门有个终端架构,也就是tty接口,我们需要在设备树上进行配置并告知linux内核。内核调用芯片厂家写好的驱动程序。但是还不够深入,我要继续往下挖,中断如何处理的目前还没看到。在linux内核中UART 驱动使用 platform 驱动框架
- UART驱动使用platform驱动框架可以归纳为以下几个关键点:
UART与platform驱动的关系:
UART驱动本质上是一个platform驱动。在Linux内核中,platform驱动模型用于管理那些不依赖于PCI、USB等总线但与平台(即特定的硬件或处理器架构)紧密相关的设备。
设备树中的UART节点:
在设备树(Device Tree)中,UART设备通常被定义为一个节点,包含了该设备的所有必要信息,如兼容属性(compatible)、状态(status)、中断(interrupts)和寄存器地址(reg)等。
设备树使得内核能够在启动时自动识别并配置这些设备,无需硬编码。
UART驱动的注册与匹配:
UART驱动的编写需要实现与设备树节点的匹配。这通常通过定义of_match_table来实现,其中包含与设备树中UART节点compatible属性相匹配的字符串。
当内核启动时,它会遍历设备树,并根据compatible属性找到匹配的驱动,从而完成设备的初始化。
probe函数与设备初始化:
一旦UART设备与驱动匹配成功,内核会调用驱动的probe函数。在这个函数中,驱动会进行必要的硬件初始化,如设置寄存器、申请中断等。
probe函数也是驱动与设备硬件直接交互的入口点,它负责配置UART的通信参数(如波特率、数据位、停止位等)。
数据收发与中断处理:
UART驱动需要实现数据收发的功能,这通常涉及到对UART硬件寄存器的读写操作。
中断处理也是UART驱动的重要组成部分,用于处理接收到的数据或发送完成等事件。
platform驱动的通用性:
使用platform驱动框架的好处之一是它的通用性。无论是UART还是其他类型的平台设备,都可以通过定义相应的设备树节点和编写匹配的驱动来实现设备的初始化和配置。
综上所述,UART驱动使用platform驱动框架可以简化驱动的编写过程,提高代码的复用性和可维护性。同时,通过设备树的支持,使得驱动能够更加灵活地适应不同的硬件配置。
目前看中断与设备树有关,设备树是一个与底层链接的关键节点。后面重点先学习设备树。今天就到这。