嵌入式系统的发展大致经历了以下三个阶段:
第一阶段:嵌入技术的早期阶段。嵌入式系统以功能简单的专用计算机或单片机为核心的可编程控制器形式存在,具有监测、伺服、设备指示等功能。这种系统大部分应用于各类工业控制和飞机、导弹等武器装备中。 [3]
第二阶段:以高端嵌入式CPU和嵌入式操作系统为标志。这一阶段系统的主要特点是计算机硬件出现了高可靠、低功耗的嵌入式CPU,如ARM、PowerPC等,且支持操作系统,支持复杂应用程序的开发和运行。 [3]
第三阶段:以芯片技术和Internet技术为标志。微电子技术发展迅速,SOC(片上系统)使嵌入式系统越来越小,功能却越来越强。大多数嵌入式系统还孤立于Internet之外,但随着Internet的发展及Internet技术与信息家电、工业控制技术等结合日益密切,嵌入式技术正在进入快速发展和广泛应用的时期。 [3]
嵌入式驱动的作用
随着芯片的集成度越来越高,相当多的处理器如ARM处理器集成了UART、I2C控制器、SPI控制器、USB控制器、ADC控制器等外设,而如何使这些模块正常工作,并能够控制外部的芯片满足最后功能的需求,驱动的概念应运而生,它是应用或系统访问实际硬件的桥梁。
驱动针对的对象是存储器和外设(包括 CPU 内部集成的存储器和外设),而不是针对 CPU 核。
Linux 将存储器和外设分为 3 个基础大类。
字符设备。
块设备。
网络设备。
字符设备指那些必须以串行顺序依次进行访问的设备,如触摸屏、磁带驱动器、鼠标等。块
设备可以用任意顺序进行访问,以块为单位进行操作,如硬盘、软驱等。字符设备不经过系统的
快速缓冲,而块设备经过系统的快速缓冲。但是,字符设备和块设备并没有明显的界限,如对于
Flash 设备,符合块设备的特点,但是我们仍然可以把它作为一个字符设备来访问
除网络设备外,字符设备与块设备都被映射到 Linux 文件系统的文件和目录,
通过文件系统的系统调用接口 open()、write()、read()、close()等即可访问字符设备和块设备。所
有的字符设备和块设备都被统一地呈现给用户
内核
是计算机上配置的底层软件,是操作系统最基本、最核心的部分,实现操作系统内核功能的程序就是内核程序。
U-boot
U-boot代码有三种:uboot官方的uboot代码、半导体厂商的uboot代码、开发板厂商的uboot代码。但是我们一般不会直接用uboot官方的U-Boot源码的,因为支持太弱了。uboot官方的uboot源码是给半导体厂商准备的,半导体厂商会下载uboot官方的uboot源码,然后将自家相应的芯片移植进去。也就是说半导体厂商会自己维护一个版本的uboot,这个版本的uboot相当于是他们定制的。既然是定制的,那么肯定对自家的芯片支持会很全,虽然uboot官网的源码中一般也会支持他们的芯片,但是绝对是没有半导体厂商自己维护的uboot全面。所以最常用的就是半导体厂商或者开发板厂商的uboot,如果你用的半导体厂商的评估板,那么就使用半导体厂商的uboot,如果你是购买的第三方开发板,比如正点原子的I.MX6ULL开发板,那么就使用正点原子提供的uboot源码(也是在半导体厂商的uboot上修改的)。当然了,也可以在购买了第三方开发板以后使用半导体厂商提供的uboot,只不过有些外设驱动可能不支持,需要自己移植,这个就是我们常说的uboot移植。
半导体厂商会将uboot移植到他们自己的原厂开发板上,测试好以后就会将这个uboot发布出去,这就是大家常说的原厂BSP包。我们一般做产品的时候就会参考原厂的开发板做硬件,然后在原厂提供的BSP包上做修改
Linux目录结构

设备号
在Linux系统中每一个设备都有相应的设备号,通过该设备号查找对应的设备,从而进行之后的文件操作。设备号有主设备号与次设备号之分,主设备号用来表示一个特定的驱动,次设备号用来管理下面的设备。

设备节点
在Linux操作系统中一切皆文件,设备访问也是通过文件的方式来进行的,对于用来进行设备访问的文件称之为设备节点,设备节点被创建在/dev目录下,将内核中注册的设备与用户层进行链接,这样应用程序才能对设备进行访问。
每个节点都通过一些属性信息来描述节点信息,属性就是键—值对
label: node-name@unit-address label:节点标签,方便访问节点:通过&label访问节点,追加节点信息 node-name:节点名字,为字符串,描述节点功能 unit-address:设备的地址或寄存器首地址,若某个节点没有地址或者寄存器,可以省略
Linux 设备注册流程
首先驱动向Linux内核进行设备号申请,之后的字符设备注册时,会对申请的设备号进行使用。而Linux 内核会将字符设备抽象成一个具体的struct cdev结构体,该结构体记录了字符设备的字符设备号、内核对象等信息,cdev_init(…)函数对结构体进行初始化之后,cdev_add(…)函数将设备号和cdev结构体进行链接,这时设备号才真正指向了内核中注册的设备。设备注册成功之后,此时还不能对字符设备进行文件操作,所以需要设备节节点来充当内核和用户层通信的桥梁
驱动分类
Linux驱动分为三个基础大类:字符设备驱动,块设备驱动,网络设备驱动。
字符设备(Char Device)
字符(char)设备是个能够像字节流(类似文件)一样被访问的设备。
对字符设备发出读/写请求时,实际的硬件I/O操作一般紧接着发生。
字符设备驱动程序通常至少要实现open、close、read和write系统调用。
比如我们常见的lcd、触摸屏、键盘、led、串口等等,他们一般对应具体的硬件都是进行出具的采集、处理、传输。
块设备(Block Device)
一个块设备驱动程序主要通过传输固定大小的数据(一般为512或1k)来访问设备。
块设备通过buffer cache(内存缓冲区)访问,可以随机存取,即:任何块都可以读写,不必考虑它在设备的什么地方。
块设备可以通过它们的设备特殊文件访问,但是更常见的是通过文件系统进行访问。
只有一个块设备可以支持一个安装的文件系统。
比如我们常见的电脑硬盘、SD卡、U盘、光盘等。
网络设备(Net Device)
任何网络事务都经过一个网络接口形成,即一个能够和其他主机交换数据的设备。
访问网络接口的方法仍然是给它们分配一个唯一的名字(比如eth0),但这个名字在文件系统中不存在对应的节点。
内核和网络设备驱动程序间的通信,完全不同于内核和字符以及块驱动程序之间的通信,内核调用一套和数据包传输相关的函(socket函数)而不是read、write等。
比如我们常见的网卡设备、蓝牙设备。
驱动调用大致流程
1.加载一个驱动模块,产生一个设备文件,有唯一对应的inode结构体
2.应用层调用open函数打开设备文件,对于上层open调用到内核时会发生一次软中断,从用户空间进入到内核空间。
3.open会调用到sys_open(内核函数),sys_open根据文件的地址,找到设备文件对应的struct inode结构体描述的信息,可以知道接下来要操作的设备类型(字符设备还是块设备),还会分配一个struct file结构体。
4.根据struct inode结构体里面记录的主设备号和次设备号,在驱动链表(管理所有设备的驱动)里面,根据找到字符设备驱动
5.每个字符设备都有一个struct cdev结构体。此结构体描述了字符设备所有信息,其中最重要的一项就是字符设备的操作函数接口
6.找到struct cdev结构体后,linux内核就会将struct cdev结构体所在的内存空间首地址记录在struct inode结构体i_cdev成员中,将struct cdev结构体中的记录的函数操作接口地址记录在struct file结构体的f_ops成员中。
7.执行xxx_open驱动函数。
驱动两种运行模式
- 将驱动编译进 Linux 内核中,当 Linux 内核启动的时就会自动运行驱动程序。
- 将驱动编译成模块(Linux 下模块扩展名为.ko),在Linux 内核启动以后使用相应命令加载驱动模块。
模块操作命令
加载模块
insmod XXX.ko
为模块分配内核内存、将模块代码和数据装入内存、通过内核符号表解析模块中的内核引用、调用模块初始化函数(module_init)
insmod要加载的模块有依赖模块,且其依赖的模块尚未加载,那么该insmod操作将失败
modprobe XXX.ko
加载模块时会同时加载该模块所依赖的其他模块,提供了模块的依赖性分析、错误检查、错误报告
modprobe 提示无法打开“modules.dep”这个文件 ,输入 depmod 命令即可自动生成 modules.dep
卸载模块
rmmod XXX.ko
查看模块信息
lsmod
查看系统中加载的所有模块及模块间的依赖关系
modinfo (模块路径)
查看详细信息,内核模块描述信息,编译系统信息
内核模块标准模版
设备树
引入设备树之前:一些与硬件设备相关的具体信息都要写在驱动代码中,如果外设发生相应的变化,那么驱动代码就需要改动。
引入设备树之后:通过设备树对硬件信息的抽象,驱动代码只要负责处理逻辑,而关于设备的具体信息存放到设备树文件中。如果只是硬件接口信息的变化而没有驱动逻辑的变化,开发者只需要修改设备树文件信息,不需要改写驱动代码。
Device Tree 数据可读性较⾼,遵循 DTS 规范,通常被描述在.dtsi 和.dts 源⽂件。在内核编译的过程中,
被编译为.dtb 的⼆进制⽂件。在开机启动阶段,dtb 会被 bootloader(如 U-Boot)加载到 RAM 的某个地
址空间,并且将该地址作为参数传递给 Kernel space。内核解析整个 dtb ⽂件,提炼每个设备信息以初始
化。
设备树修改指南
https://wiki.t-firefly.com/zh_CN/ROC-RK3588-RT/linux_dts_manual.html
DTS、DTB和DTC

DTS
设备树源码文件,硬件的相应信息都会写在.dts为后缀的文件中,每一款硬件可以单独写一份xxxx.dts
DTSI
对于一些相同的dts配置可以抽象到dtsi文件中,然后可以用include的方式到dts文件中
同一芯片可以做一个dtsi,不同的板子不同的dts,然后include同一dtsi
对于同一个节点的设置情况,dts中的配置会覆盖dtsi中的配置
DTC
dtc是编译dts的工具
DTB
dts经过dtc编译之后会得到dtb文件,设备树的二进制执行文件
dtb通过Bootloader引导程序加载到内核。
设备树实践
查找设备数根文件
/mnt/rockchip/RK3588/Android/SDK/szjcyy/x3588_android12_r10/device/rockchip/rk3588/i3588
下查看i3588.mk,包含的文件中找,

最终找到,dts根文件为rk3588-9tripod-i3588
include device/rockchip/rk3588/i3588/BoardConfig.mk

最后前往/kernel-5.10/arch/arm64/boot/dts/rockchip/rk3588-9tripod-i3588.dts

DTS架构

设备控制接口(ioctl)

指令集
从指令集的角度来讲,中央处理器也可以分为两类,即 RISC(精简指令集计算机)和 CISC
(复杂指令集计算机)。CSIC 强调增强指令的能力、减少目标代码的数量,但是指令复杂,指令周
期长;而 RISC 强调尽可能减少指令集、指令单周期执行,但是目标代码会更大。ARM、MIPS、
PowerPC 等 CPU 内核都采用了 RISC 指令集
支持的命令行工具列表
/bin

设备目录
3./dev
设备文件存储目录,应用程序通过对这些文件的读写和控制就可以访问实际的设备。

典型的开发环境

单独编译模块

CPU,GPU频率修改

CPUFreq governor:用于 CPU 升降频检测,根据系统负载,决定 CPU 频率。目前 Linux4.4 内核中包含
了如下几种 governor:
conservative:根据 CPU 负载动态调频,按一定的比例平滑的升高或降低频率。
ondemand:根据 CPU 负载动态调频,调频幅度比较大,可直接调到最高频或最低频。
interactive:根据 CPU 负载动态调频,相比 ondemand,响应时间更快,可配置参数更多,更灵活。
userspace:提供相应接口供用户态应用程序调整频率。
powersave:功耗优先,始终将频率设置在最低值。
performance:性能优先,始终将频率设置为最高值。
schedutil:EAS 使用 governor。EAS(Energy Aware Scheduling)是新一代的任务调度策略, 结合
CPUFreq和 CPUIdle 的策略, 在为某个任务选择运行 CPU 时, 同时考虑了性能和功耗, 保证了系
统能耗最低,并且不会对性能造成影响。Schedutil 调度策略就是专门给 EAS 使用的 CPU 调频策
略。
温度控制

内核开发文档


温控降频策略
Thermal governor:用于决定 cooling device 是否需要降频,降到什么程度。目前 Linux4.4 内核中包含了
如下几种 governor:
power_allocator:引入 PID(比例-积分-微分)控制,根据当前温度,动态给各 cooling device 分配
power,并将 power 转换为频率,从而达到根据温度限制频率的效果。
step_wise :根据当前温度,cooling device 逐级降频。
fair share :频率档位比较多的 cooling device 优先降频。
userspace:不限制频率。

Uboot功能

Recovery介绍


声卡相关
查询声卡
cat /proc/asound/cards
查询声卡支持的采样率,格式,声道数等。
Usage: tinypcminfo [-D card] [-d device]


1522

被折叠的 条评论
为什么被折叠?



