嵌入式Linux视频笔记----驱动开发

7 篇文章 0 订阅

野火【第四期】Linux系列教学视频之“驱动开发”篇

基本看完了,基本只看视频没看详细的pdf,试验挑着做,一讲代码就懵逼。但还是有了基本的概念,知道驱动相关知识

第2遍看视频,增加截图、代码。

P1 第89讲 内核模块基本概念

内核模块用于解决linux内核可扩展性和可维护性相对较差的缺陷

现代内核派系

宏内核--Linux等、关键功能【内存管理、进程调度、进程间通信等,不可裁剪】和服务功能【文件系统、设备驱动、网络服务等,可裁剪】均在内核、效率高、扩展性差;

微内核--关键功能在内核,服务功能在用户空间、效率低、扩展性高

内核模块:加载、卸载;入口、出口;信息声明

下面两个实验P2 P3都是最简单的实验,纯属教学用途,理解原理。

P2 第90讲 内核模块实验1

实验环境

下载 编译内核

sudo apt install make gcc-arm-linux-gnueabihf gcc bison flex libssl-dev dpkg-dev lzop

安装多个软件包。这些软件包包括makegcc-arm-linux-gnueabihfgccbisonflexlibssl-devdpkg-devlzop

这些软件包通常用于编译和构建软件。例如,make是一个构建工具,可以自动化编译和链接过程;gcc是GNU编译器集合,可以编译C和C++代码;bisonflex是用于生成语法分析器和词法分析器的工具;libssl-dev是OpenSSL库的开发文件,用于支持SSL/TLS加密;dpkg-dev包含用于处理Debian软件包的工具;lzop是一个快速压缩工具。

sudo ./make_deb.sh    # 第2次编译内核成功,耗时8分25秒
    
make mrproper         # 第1次编译内核报错,提示执行此命令

【make mrproper】是一个命令,它用于清理 Linux 内核源代码树中的所有生成文件,包括配置文件和各种备份文件。它比【make clean】更彻底,因为【make clean】只删除大多数生成文件,但保留配置和足够的构建支持来构建外部模块。

进入下个图片之前是漫长的8分钟
内核编译成功

内核模块源文件

内核模块打印函数printk

cat /proc/sys/kernel/printk 输出结果
4--当前控制台日志级别      4--默认消息日志级别      1--最小的控制台级别      7--默认控制台日志级别

makefile分析


配置nfs常用命令【貌似跳线帽必须在wifi侧,网线连接PC与开发板ETH1】

win10

netsh interface ip set address "以太网" static 192.168.0.2 255.255.255.0

ubuntu:

sudo apt install nfs-kernel-server -y
showmount -e

sudo vi /etc/network/interfaces
sudo /etc/init.d/networking restart

开发板:

echo "3 4 1 7">/proc/sys/kernel/printk
sudo dmesg -n 3             # 设置打印级别为3

sudo apt install nfs-common

sudo ifconfig eth1 192.168.0.4 netmask 255.255.255.0

showmount -e 192.168.0.3
sudo mount -t nfs 192.168.0.3:/home/embedfire/workdir /mnt

ping 192.168.0.4

showmount -e 192.168.0.3用于查询IP地址为192.168.0.3的NFS服务器的共享信息。

sudo mount -t nfs用于将NFS共享目录挂载到本地目录。其中,mount是Linux系统中的挂载命令,用于将文件系统挂载到指定的挂载点;-t nfs是mount命令的选项之一,表示要挂载的文件系统类型为NFS

运行效果

插入模块后打印2条信息,默认信息通过dmesg查看
使用lsmod可以看到模块被插入到内核

P3 第91讲 内核模块实验2

带参数的模块、模块共享变量和函数、有依赖关系的模块自动加载卸载

设置MobaXterm长字符串显示


 

模块参数:定义变量、插入内核模块时带参数【类似函数的参数】

运行效果

符号共享:变量共享、函数共享;查看符号表cat /proc/kallsyms | grep my_add;模块自动加载 卸载;

calculation使用module_param模块的变量和函数

模块自动加载、卸载

P4 第92讲 Linux内核是怎么设计字符设备的

字符设备、块设备、网络设备。字符设备使用多且简单

把字符设备抽象为文件

文件描述符本质

硬件层原理:类似裸机开发,实现底层操作接口

完整的file_operations结构体定义
几乎所有成员都是函数指针,用来实现文件的具体操作

驱动层:操作接口注册到内核、主次设备号登记

下面两个视频有详细讲解

文件系统层:mknod

P5 第93讲 设备号的组成与哈希表

设备号:主次设备号;驱动对应1个主设备号,多个次设备号;

kdev_t.h  内核源码截图
设备号dev是unsigned int即32bits,MINORBITS即次设备号位数=20,主设备号就是dev高12bits
embedfire@embedfire-VirtualBox:~$ cat /proc/devices 
Character devices:
  1 mem
  4 /dev/vc/0
  4 tty
  4 ttyS
  5 /dev/tty
  5 /dev/console
  5 /dev/ptmx
  5 ttyprintk
  6 lp
  7 vcs
 10 misc
 13 input
 21 sg
 29 fb
 89 i2c
 99 ppdev
108 ppp
116 alsa
128 ptm
136 pts
180 usb
189 usb_device
204 ttyMAX
226 drm
241 aux
242 hidraw
243 vfio
244 bsg
245 watchdog
246 ptp
247 pps
248 cec
249 rtc
250 dax
251 dimmctl
252 ndctl
253 tpm
254 gpiochip

Block devices:
  7 loop
  8 sd
  9 md
 11 sr
 65 sd
 66 sd
 67 sd
 68 sd
 69 sd
 70 sd
 71 sd
128 sd
129 sd
130 sd
131 sd
132 sd
133 sd
134 sd
135 sd
253 device-mapper
254 mdp
259 blkext
embedfire@embedfire-VirtualBox:~$ 

上面的代码段不是代码,是我的ubuntu使用cat /proc/devices的结果

hash table:哈希表、散列表;数组和链表特性;哈希表=数组+链表

chrdevs【内核专门用来管理设备号】为结构体指针数组

P6 第94讲 从源码看如何管理设备号

关键数据结构:char_device_struct【专门用来管理设备号

结构体指针数组chrdevs[255]

__register_chrdev_region函数代码分析:

主设备号为0,只是让内核动态分配1个设备号,并非真的就是0
主设备号最大实际上是254,而不是255

次设备号冲突的3种情况

次设备号冲突的3种情况,报错
static struct char_device_struct *
__register_chrdev_region(unsigned int major, unsigned int baseminor,
			   int minorct, const char *name)
{
	struct char_device_struct *cd, **cp;
	int ret = 0;
	int i;
	
	cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);		//动态申请内存
	if (cd == NULL)
		return ERR_PTR(-ENOMEM);
	
	mutex_lock(&chrdevs_lock);				//加互斥锁保护资源,防止2个进程同时操作结构体

	if (major == 0) {											//判断主设备号是否为0
		ret = find_dynamic_major();				//从chrdevs哈系表中查找一个空闲位置
		if (ret < 0) {													//分配失败
			pr_err("CHRDEV \"%s\" dynamic allocation region is full\n",
			       name);
			goto out;
		}
		major = ret;
	}

	if (major >= CHRDEV_MAJOR_MAX) {	//主设备号不允许超过512
		pr_err("CHRDEV \"%s\" major requested (%u) is greater than the maximum (%u)\n",
		       name, major, CHRDEV_MAJOR_MAX-1);
		ret = -EINVAL;
		goto out;
	}

	cd->major = major;																//保存主设备号
	cd->baseminor = baseminor;											//保存次设备号
	cd->minorct = minorct;														//保存次设备号数量
	strlcpy(cd->name, name, sizeof(cd->name));			//保存设备名称

	i = major_to_index(major);												//计算主设备号对应的哈系表数组下标

	for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)			//遍历链表
		if ((*cp)->major > major ||												//按主设备号从小到大排序
		    ((*cp)->major == major &&										//主设备号相同,按次设备号从小到大排序
		     (((*cp)->baseminor >= baseminor) ||
		      ((*cp)->baseminor + (*cp)->minorct > baseminor))))		//次设备号最大值=baseminor+minorct
			break;

	/* Check for overlapping minor ranges.  主设备号相等时,检查次设备号是否存在冲突*/
	if (*cp && (*cp)->major == major) {
		int old_min = (*cp)->baseminor;				//获取链表节点的次设备号范围
		int old_max = (*cp)->baseminor + (*cp)->minorct - 1;
		int new_min = baseminor;						//获取新设备的次设备号范围
		int new_max = baseminor + minorct - 1;

		/* New driver overlaps from the left. 新设备的次设备号最大值位于链表节点的次设备号范围内报错 */
		if (new_max >= old_min && new_max <= old_max) {
			ret = -EBUSY;
			goto out;
		}

		/* New driver overlaps from the right. 新设备的次设备号最小值位于链表节点的次设备号范围内报错 */
		if (new_min <= old_max && new_min >= old_min) {
			ret = -EBUSY;
			goto out;
		}

		if (new_min < old_min && new_max > old_max) {		//新设备的次设备号包含链表节点的次设备号范围报错
			ret = -EBUSY;
			goto out;
		}

	}

	cd->next = *cp;			//插入新设备的链表节点
	*cp = cd;
	mutex_unlock(&chrdevs_lock);
	return cd;
out:
	mutex_unlock(&chrdevs_lock);
	kfree(cd);
	return ERR_PTR(ret);
}
static int find_dynamic_major(void)		//从chrdevs哈系表中查找一个空闲位置
{
	int i;
	struct char_device_struct *cd;

	for (i = ARRAY_SIZE(chrdevs)-1; i >= CHRDEV_MAJOR_DYN_END; i--) {	//优先使用255-234
		if (chrdevs[i] == NULL)			//找到空闲位置
			return i;
	}

	for (i = CHRDEV_MAJOR_DYN_EXT_START;
	     i >= CHRDEV_MAJOR_DYN_EXT_END; i--) {								//其次使用511-384
		for (cd = chrdevs[major_to_index(i)]; cd; cd = cd->next)	//遍历数组元素i的链表
			if (cd->major == i)
				break;									//主设备号被占用

		if (cd == NULL)
			return i;									//没有被占用
	}

	return -EBUSY;
}

P7 第95讲 从源码看如何保存file_operation接口

结构体cdevkobj_map【专门用来记录file_operation接口】


函数cdev_initcdev_add

P8 第96讲 如何创建一个设备文件

mknod:用法;原理--层层调用、init_special_inode函数分析

mknod引入

名称--文件名称;类型--b c u p;

mknod原理分析 

init_special_inode函数分析

P9 第97讲 open函数如何查找file_operation接口

get_unused_fd_flags

为本次操作分配一个未使用过的文件描述符

do_file_open

生成一个空白struct file结构体

从文件系统中查找到文件对应的inode

do_dentry_open

P10 第98讲 led字符设备驱动实验1

驱动模块=内核模块+驱动接口:内核模块配置GPIO、文件操作接口注册到内核、设备文件绑定文件操作接口、echo控制led


驱动模块初始化:寄存器地址映射ioremap;虚拟地址读写iowrite32【iowrite32、ioread32可以检查cpu大小端,调整字节序,以提高驱动的可移植性】

自定义file_operation接口:结构体函数指针对应的函数实现,实际仅完成写函数;

拷贝数据copy_from_user

P11 第99讲 led字符设备驱动实验2

注册字符设备驱动register_chrdev函数;也可以自己写,更灵活【256个设备文件太多了】

重点:次设备号=0,次设备号数量=256        对应256个设备文件

__register_chrdev函数

调用__register_chrdev_region函数保存新注册的设备号到chrdevs哈希表中
cdev_alloc相当于cdev_init,保存file_operation到cdev中
cdev_add保存cdev到probes哈希表中,方便内核查找file_operation使用

操作演示

主机:make、make copy;

开发板:insmod /mnt/chrdev.ko、cat /proc/devices、sudo mknod /dev/lightLED c 244 0、sudo sh -c "echo on >/dev/lightLED"

插入内核模块后打印出动态分配的主设备号244,#define DEV_NAME  "red_led" 
254-245确实已经被使用,验证P6视频 __register_chrdev_region函数代码分析

创建3个设备文件【P8视频讲mknod】,都可以正常控制led灯
设备文件名称其实无所谓,关键是主次设备号

P12 第100讲 linux设备驱动模型

设备驱动模型:分类分层次、命名自由

早期内核 (2.4之前)没有统一的设备驱动模型,但照样可以用。【上个视频用法就是,需要手动mknod,使用较为复杂】

复杂设备可能依赖基础设备
udev或mdev位于用户空间


sysfs:通过sysfs体现设备驱动模型;是虚拟文件系统;基本驱动对象kobject

proc展示进程相关信息        sysfs体现设备驱动模型
linux将目录和文件均抽象为inode节点,文件inode记录文件内容,目录inode记录目录下文件

设备驱动模型基本元素

kobject、kset、kobj_type

驱动模型经典--kobject kset层层叠加;简单但无用--纯kobject

经典模型,kobject kset层层叠加
kset有链表头,可批量管理kobject,kobject只有 链表节点,无法批量管理kobject
kobject的kset指针指向上级kset对象,parent指针指向上级kset的kobject
kset对象的kobject的parent指针指向上级kset对象的kobject

简单但无用的模型
上层kobject节点无法遍历查找下层kobject

kobject
sysfs中每一个目录都对应一个kobject

前6个成员比较重要        注意链表节点entry

kset

都重要        注意链表头list

kobj_type

操作接口重要

P13 第101讲 kobject:驱动的基石

本节目标

构建一个kobject对象
构建一个sysfs中的目录项(kernfs_node)
把他们关联起来

重点:sysfs目录项 关联 kobject对象

关注sysfs目录项与kobject对象的关联过程,关注kobiect对象默认的属性文件操作接口

通过属性文件操作接口找到sysfs目录项,通过sysfs目录项找到kobject对象


代码

相关结构体关系,配合上个视频的结构体类型

kobject_create_and_add()        lib/kobject.c

关注create、add

kobject_create()         lib/kobject.c

结构体变量dynamic_kobj_ktype包含2个成员:
函数dynamic_kobj_release、结构体变量kobj_sysfs_ops

kobject_init()         lib/kobject.c

默认的属性文件操作接口 kobj->ktype 指向 结构体变量dynamic_kobj_ktype

kobject_init_internal()         lib/kobject.c

对其他成员变量初始化

kobject_add()         lib/kobject.c

调用代码:retval = kobject_add(kobj, parent, "%s", name);

parent指向上层节点kobject

kobject_add_varg()        lib/kobject.c

传进来parent指针

kobject_set_name_vargs()        lib/kobject.c

设置格式化的name

kobject_add_internal()       lib/kobject.c

创建kobject时指定的parent可能为空
kobject->kset可能为空

create_dir()       lib/kobject.c

sysfs_create_dir_ns()        fs/sysfs/dir.c

若parent为空,则设置为sysfs根目录​​​​​​

kernfs_create_dir_ns()

kernfs_new_node()

P14 第102讲 kobj_type:用户空间的法宝

本节目标

为kobject对象构建多个属性文件
为每个属性文件设置具体操作接口
vfs的inode对象与sysfs的kernfs_node对象的绑定过程【绑定前vfs只认识inode】

重点

关注属性文件具体操作接口的赋值过程
关注open()、read()、write()函数的底层机制

第一阶段        属性文件具体操作接口赋值

sysfs_create_group()        fs/sysfs/group.c

创建属性文件、绑定具体操作接口

attribute相关结构体

struct attribute_group结构体        include/linux/sysfs.h

struct attribute结构体【文件名和权限】        include/linux/sysfs.h

struct kobj_attribute结构体

internal_create_group()        fs/sysfs/group.c

create_files()       fs/sysfs/group.c

遍历grp->attrs

sysfs_add_file_mode_ns()        fs/sysfs/file.c

执行        ops = &sysfs_file_kfops_rw;

struct kernfs_ops节点的操作函数

__kernfs_create_file()


第二阶段        open read write函数底层机制

open

kernfs_init_inode()

struct kernfs_file_fops

kernfs_fop_open()【ops即sysfs_kf_seq_show,选中代码一定执行】

seq_open()

struct seq_file

read

kernfs_fop_read()

kernfs_of()

seq_read()

sysfs_kf_seq_show()

seq_get_buf()

sysfs_file_ops()

kobj_attr_show(),

P15 第103讲 设备驱动模型实验1-kobject点灯

实验思路

内核模块【动态加载功能】 + led驱动【控制硬件】 + kobject对象【在/sys创建目录项】+ kobj_attribute对象【为kobiect对象的属性文件提供独有的读写接口】

foo_attribute成员变量初始化

foo_show(), foo_store(), kstrtoint(), led_show(), led_store(),


主机:make、make copy;

开发板:insmod /mnt/kobject_led.ko、ls /sys、sudo sh -c "echo 'on' >/sys/led_kobject/led"
相比P11,不需要mknod,路径更深1层,路径从dev换为sys

P16 第104讲 kset:驱动的骨架

体现设备驱动层次关系

kset_create_and_add()        lib/kobject.c

name--kset对应的目录项名称        uevent_ops--消息发送接口,发送消息到用户空间
parent_kobj--kset的上层节点

kset_create()        lib/kobject.c

函数参数与kset_create_and_add() 相同

kset_register()       lib/kobject.c

kset_init()       lib/kobject.c

list节点为链表头

演示:相比P15,多了1层kset。路径更深1层

P17 第105讲 uevent:内核消息的快递包

uevent机制:内核空间kobject对象通过uevent机制向用户空间发信息

开发板实际使用的是udev
重点关注kobject_uevent,其他简单了解即可

内核消息发送:  

消息类型,常用前4种,KOBJ_MAX用于统计类型数量

kobject_uevent()

kobject_uevent_env()

演示:udevadm monitor &,插入模块时传递消息

内存不足,删除多余内核模块即可
加载和卸载内核模块时UDEV都打印消息

P18 第106讲 class:设备的大管家

分类管理硬件设备;配合udev自动创建设备文件

device_create()自动创建dev属性文件,并且发送消息,udev自动调用mknod
在/dev/yyy目录下【右图中有误】

创建class:include/linux/device.h

宏class_create

device_create()

演示:属性文件路径 /sys/class/xxx/my_led/dev、自动创建/dev/my_led

P19 第107讲 xbus:打造自属的驱动总线

驱动总线软硬件代码分离,提高复用性;device--硬件代码;driver_devices--软件代码;bus_type--软硬件匹配

总线管理buses_init()内核启动默认执行        /sys/bus


总线注册bus_register()添加新的总线类型        /sys/bus/xbus/devices drivers

bus_type中3个attribute_group对应3个属性文件
subsys_private中3个kset对应3个目录项


设备注册device_register()添加设备、/sys/bus/xbus/devices/设备名


驱动注册driver_register()关联软件代码        /sys/bus/xbus/drivers/驱动名


设备驱动模型框图:

试验:代码分析;生成3个.ko;依次插入内核模块xbus xdev xdrv,最后一个自动调用其他函数

P20 第108讲 platform :虚拟的平台总线

platform_device继承device,关联硬件代码;

platform_driver继承device_driver,重设probe函数指针;

platform类似bus_type,统一管理、设置match匹配规则【使用xbus时需要手动注册】

resource保存硬件信息,如寄存器地址
 

平台总线注册:内核上电自动执行platform_bus_init()

drivers/base/platform.c
/sys/bus/platform

匹配规则platform_match() 设备树、id_table、名称匹配

主要使用id_table匹配

平台设备注册platform_device_regiter()、resource结构体

平台驱动注册platform_driver_register

平台驱动获取资源platform_get_resource() 寄存器地址

P21 第109讲 设备驱动模型实验2-代码分离

试验:代码分析;只需要2个c文件,因为linux内核启动后已经注册好总线;生成2个.ko;

依次插入内核模块platform_device platform_driver,后者自动调用led_probe;

控制LED亮灭 sudo sh -c "echo 'on'>/dev/my_led"

P22 第110讲 DTS:硬件资源的说明书1

背景--硬件设备中种类逐年递增,板级platform平台设备文件越来越多
导火索--linus发邮件,this vhole ARM thing is a f*cking pain in the ass

设备树:uboot加载到内存,内核使用;设备树源文件编译为二进制设备树;

linux无法直接解析DTS,linux可以解析DTB
通过DTC把DTS编译为DTB

设备树源文件  ebf-buster-linux/arch/arm/boot/dts/imx6ull-seeed-npi.dts

二进制设备树

pc:                ebf-buster-linux/arch/arm/boot/dts/imx6ull-seeed-npi.dtb【手动/内核编译】

开发板:         /boot/dtbs/4.19.71-imx-r1/imx6ull-seeed-npi.dtb【pc编译后拷贝到开发板】

编译:内核编译、手工编译

先不用关心内核配置命令具体含义

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- npi_v7_defconfig
make ARCH=arm -j4 CROSS_COMPILE=arm-linux-gnueabihf- dtbs
第一次编译时生成 DTC arch/arm/boot/dts/imx6ull-seeed-npi.dtb,再次编译不显示这1行

设备树框架:imx6ull.dtsi 通用且常用的设备树;设备主体、子节点追加内容;

头文件【dtsi是最常用的设备树】

#include <dt-bindings/input/input.h>
#include "imx6ull.dtsi"

主体【多个 / 根节点(dtsi里也有根节点)合并、根节点下包含多个子节点】

子节点追加内容【在设备树中增加内容一般不会改动主体,而是通过 & 子节点追加】

节点命名

基本方法

节点标签 【节点名称可能会非常长,使用节点标签更为方便

别名子节点【批量申请别名】

P23 第111讲 DTS:硬件资源的说明书2

常见节点属性

compatible 属性--字符串 芯片厂商及驱动名【驱动名可以设置多个】;

model 属性--字符串 板卡型号

status 属性--字符串 运行状态

reg属性-- 值类型 寄存器地址和长度

address-cells  与  size-cells 属性-- 值类型为无符号32位整型,子节点寄存器地址和长度的数量

查看设备树:ls /sys/firmware/devicetree/base  ls /proc/device-tree
设备树源文件与linux目录项对应关系

添加子节点:pc机--修改源文件、编译、复制;开发板--替换、sync、查看新设备树

make ARCH=arm -j4 CROSS_COMPILE=arm-linux-gnueabihf- dtbs
sudo cp arch/arm/boot/dts/imx6ull-seeed-npi.dtb ~/workdir/
sudo cp /mnt/imx6ull-seeed-npi.dtb /boot/dtbs/4.19.71-imx-r1/imx6ull-seeed-npi.dtb

reboot之后看到子节点led_test

P24 第112讲 获取DTS属性信息

查属性所在的节点-->查节点的属性值

节点表示:DTB-->结构体device_node-->platform_device

linux内核自动转化
device_node如果有compatible属性,则继续转化为platform_device

查节点

路径--of_find_node_by_path();类型--of_find_node_by_type();名字--of_find_node_by_name();compatible属性--of_find_node_by_node()

查节点属性

of_find_property()        节点+属性

of_property_read_u32()函数

of_property_read_u32_array()函数

of_property_read_u32_string()函数

演示:代码分析;insmod /mnt/get_dts_info.ko、ls /dev/get_dts_info、sudo sh -c "echo 1>/dev/get_dts_info"

关键代码

/*.open 函数*/
static int led_chr_dev_open(struct inode *inode, struct file *filp)
{
    int error_status = -1;
    printk("\n open form device \n");

    /*获取DTS属性信息*/
    led_device_node = of_find_node_by_path("/led_test");
    if(led_device_node == NULL)
    {
        printk(KERN_ALERT "\n get led_device_node failed ! \n");
        return -1;
    }

    /*根据 led_device_node 设备节点结构体输出节点的基本信息*/
    printk(KERN_ALERT "name: %s",led_device_node->name); //输出节点名
    printk(KERN_ALERT "child name: %s",led_device_node->child->name);  //输出子节点的节点名

    /*获取 rgb_led_red_device_node 的子节点*/ 
    rgb_led_red_device_node = of_get_next_child(led_device_node,NULL); 
    if(rgb_led_red_device_node == NULL)
    {
        printk(KERN_ALERT "\n get rgb_led_red_device_node failed ! \n");
        return -1;
    }
    printk(KERN_ALERT "name: %s",rgb_led_red_device_node->name); //输出节点名
    printk(KERN_ALERT "parent name: %s",rgb_led_red_device_node->parent->name);  //输出父节点的节点名

    /*获取 rgb_led_red_device_node 节点  的"compatible" 属性 */ 
    rgb_led_red_property = of_find_property(rgb_led_red_device_node,"compatible",&size);
    if(rgb_led_red_property == NULL)
    {
        printk(KERN_ALERT "\n get rgb_led_red_property failed ! \n");
        return -1;
    }
    printk(KERN_ALERT "size = : %d",size);                      //实际读取得到的长度
    printk(KERN_ALERT "name: %s",rgb_led_red_property->name);   //输出属性名
    printk(KERN_ALERT "length: %d",rgb_led_red_property->length);        //输出属性长度
    printk(KERN_ALERT "value : %s",(char*)rgb_led_red_property->value);  //属性值

    /*获取 reg 地址属性*/
    error_status = of_property_read_u32_array(rgb_led_red_device_node,"reg",out_values, 2);
    if(error_status != 0)
    {
        printk(KERN_ALERT "\n get out_values failed ! \n");
        return -1;
    }
    printk(KERN_ALERT"0x%08X ", out_values[0]);
    printk(KERN_ALERT"0x%08X ", out_values[1]);
    return 0;
}

P25 第113讲 设备树实现RGB灯驱动

设备树添加节点信息:内核--设备树文件dts增加代码、编译并替换设备树驱动--代码分析、make、insmod、ls /dev/rgb_led、sudo sh -c "echo '7'>/dev/rgb_led"

platform_driver结构体中结构体指针指向的of_device_id中的元素compatible保存设备树根节点中的compatible
device_node结构体中properties中保存设备树根节点中的compatible

设备树文件dts增加代码、编译并替换设备树

reboot开发板后,可查看属性值等

reg属性内存映射

插入内核模块后可控制led灯

P26 第114讲 Device Tree Overlays:”插件“设备树

设备树:传统--批量、僵化;“插件”--扩展语法、灵活

使用前提:内核配置、挂载ConfigFS

linux官方要求
野火的镜像已经做好相应处理,直接用就行

案例说明:语法

传统设备树+插件设备树最终的效果

使用方式

编译命令

图中为linux内核自带的工具【内核只有工具的源文件,编译内核后才生成】
使用sudo apt install device-tree-compiler命令下载的也一样

内核加载、通用但步骤略复杂

uboot加载,简单,野火linux开发板

修改/boot/uEnv.txt

P27 第115讲 “插件”设备树实现RGB驱动

编写插件设备树文件

/dts-v1/;
/plugin/;
/ {
	fragment@0 {
		target-path = "/";              //【指定插件设备树的父节点路径为根目录】
		__overlay__ {
			/* bar peripheral */
			rgb_led{
                #address-cells = <1>;
                #size-cells = <1>;
                compatible = "fire,rgb_led";


                /*红灯节点*/
                ranges;
                rgb_led_red@0x020C406C{
                    reg = <0x020C406C 0x00000004
                        0x020E006C 0x00000004
                        0x020E02F8 0x00000004
                        0x0209C000 0x00000004
                        0x0209C004 0x00000004>;
                    status = "okay";
                };


                /*绿灯节点*/
                rgb_led_green@0x020C4074{
                    reg = <0x020C4074 0x00000004
                        0x020E01E0 0x00000004
                        0x020E046C 0x00000004
                        0x020A8000 0x00000004
                        0x020A8004 0x00000004>;
                    status = "okay";
                };


                /*蓝灯节点*/
                rgb_led_blue@0x020C4074{
                    reg = <0x020C4074 0x00000004
                        0x020E01DC 0x00000004
                        0x020E0468 0x00000004
                        0x020A8000 0x00000004
                        0x020A8004 0x00000004>;
                    status = "okay";
                };
            };
		};
	};
};

查看dtc工具        ls /home/pi/build/scripts/dtc/

dtc编译        /home/pi/build/scripts/dtc/dtc -I dts -O dtb -o rgb.dtbo led.dts

-I dts【指定源文件为dts格式】        -O dtb【指定生成文件为dtb格式】        -o rgb.dtbo【指定生成的文件名,本质仍为dtb格式】        led.dts【要编译的源文件】

复制sudo cp rgb.dtbo ~/workdir/nfsdir/

恢复原来的设备树

内核加载

sudo mkdir /sys/kernel/config/device-tree/overlays/xxx

sudo sh -c "cat /mnt/nfsdir/rgb.dtbo >/sys/kernel/config/device-tree/overlays/xxx.dtbo"

lsattr rgb.dtbo
lsattr: Permission denied While reading flags on rgb.dtbo试验失败

恢复原来的设备树
验证失败
视频中的效果【每次开启都执行一遍】

uboot加载,简单,野火linux开发板

修改/boot/uEnv.txt

sudo cp /mnt/nfsdir/rgb.dtbo /tmp/
重启后就有rgb_led

P28 第116讲 iomux节点:pinctrl子系统初窥

iomuxc节点【指设备树的节点】汇总引脚配置信息,pinctrl子系统预存iomuxc节点信息

开发驱动时只需从inctrl子系统读取引脚配置信息,避免人工翻阅datasheet,提高效率

iomuxc节点:imx6ull.dtsi、详细分析imx6ull-seeed-npi.dts

default状态对应引脚组pinctrl_hog_1的配置信息

节点引脚配置方式

节点配置信息记录:懵逼

0x1b0b1用于配置属性寄存器conf_reg
宏定义前两个数为寄存器偏移地址
mux_mode为0,配置为UART1_TX_DATA;因为是发送引脚,input_reg和input_val直接给0即可

P29 第117讲 imx_pinctrl和pinctrl_dev:引脚名字和编号该存在哪里?(上)

pinctrl子系统需要预先确定引脚的数量和名字

注意看注释

imx6ul_pinctrl_probe()

对照代码分析

P30 第118讲 imx_pinctrl和pinctrl_dev:引脚名字和编号该存在哪里?(中)

分析linux内核代码只看主线,没必要每一行代码都搞清楚

imx_pinctrl: 存储引脚的名字和编号原始表
imx_pinctrl_probe()函数【调用imx_pinctrl_probe(pdev, pinctrl_info);,pinctrl_info包含芯片引脚的编号和名字】

res获取到iomuxc的reg属性,即寄存器基地址和长度
imx_pinctrl_desc
3组函数接口imx_pctrl_ops imx_pmx_ops imx_pinconf_ops很关键,在此赋值
上1个图中少的关键代码 P32有讲解

pinctrl_desc:以基数树的方式存储引脚的名字,记录引脚的使用状态。每个引脚对应1个pinctrl_desc结构体

devm_pinctrl_register_and_init()函数

drivers/pinctrl/core.c

int devm_pinctrl_register_and_init(struct device *dev,
				   struct pinctrl_desc *pctldesc,
				   void *driver_data,
				   struct pinctrl_dev **pctldev)
{
	struct pinctrl_dev **ptr;
	int error;

	ptr = devres_alloc(devm_pinctrl_dev_release, sizeof(*ptr), GFP_KERNEL);
	if (!ptr)
		return -ENOMEM;

	error = pinctrl_register_and_init(pctldesc, dev, driver_data, pctldev);
	if (error) {
		devres_free(ptr);
		return error;
	}

	*ptr = *pctldev;
	devres_add(dev, ptr);

	return 0;
}

pinctrl_register_and_init()

drivers/pinctrl/core.c

int pinctrl_register_and_init(struct pinctrl_desc *pctldesc,
                  struct device *dev, void *driver_data,
                  struct pinctrl_dev **pctldev)
{
    struct pinctrl_dev *p;

    p = pinctrl_init_controller(pctldesc, dev, driver_data);
    if (IS_ERR(p))
        return PTR_ERR(p);

    *pctldev = p;

    return 0;
}

pinctrl_init_controller()

drivers/pinctrl/core.c

实际参数:imx_pinctrl_desc、iomuxc节点对应的device结构体、ipctl

static struct pinctrl_dev *
pinctrl_init_controller(struct pinctrl_desc *pctldesc, struct device *dev,
            void *driver_data)
{
    struct pinctrl_dev *pctldev;
    ...
    pctldev = kzalloc(sizeof(*pctldev), GFP_KERNEL);
    ...
    pctldev->owner = pctldesc->owner;
    pctldev->desc = pctldesc;
    pctldev->driver_data = driver_data;

    /*初始化基数树【pctldev->pin_desc_tree是数据库根节点】*/
    INIT_RADIX_TREE(&pctldev->pin_desc_tree, GFP_KERNEL);
    ...
    pctldev->dev = dev;
    ...
    ret = pinctrl_register_pins(pctldev, pctldesc->pins, pctldesc->npins);
    ...
    return pctldev;
    ...
}

P31 第119讲 imx_pinctrl和pinctrl_dev:引脚名字和编号该存在哪里?(下)

初始化基数树【特殊数据结构,可简单理解为性能强大的数据库】

pinctrl_register_pins()为每一个引脚生成1个pin_desc结构体

drivers/pinctrl/core.c

【实参:,imx6ul_pinctrl_pads(包含引脚的命名和编号),芯片引脚数量】

static int pinctrl_register_pins(struct pinctrl_dev *pctldev,
                 const struct pinctrl_pin_desc *pins,
                 unsigned num_descs)
{
    unsigned i;
    int ret = 0;

    for (i = 0; i < num_descs; i++) {
        ret = pinctrl_register_one_pin(pctldev, &pins[i]);
        if (ret)
            return ret;
    }

    return 0;
}

pinctrl_register_one_pin()

drivers/pinctrl/core.c

static int pinctrl_register_one_pin(struct pinctrl_dev *pctldev,
                    const struct pinctrl_pin_desc *pin)
{
    struct pin_desc *pindesc;

    pindesc = pin_desc_get(pctldev, pin->number);
    if (pindesc) {
        dev_err(pctldev->dev, "pin %d already registered\n",
            pin->number);
        return -EINVAL;
    }

    pindesc = kzalloc(sizeof(*pindesc), GFP_KERNEL);
    ...
    pindesc->pctldev = pctldev;
    ...
    if (pin->name) {
        pindesc->name = pin->name;
    } else {
        pindesc->name = kasprintf(GFP_KERNEL, "PIN%u", pin->number);
        if (!pindesc->name) {
            kfree(pindesc);
            return -ENOMEM;
        }
        pindesc->dynamic_name = true;
    }
    ...
    radix_tree_insert(&pctldev->pin_desc_tree, pin->number, pindesc);
    ...
    return 0;
}

pin_desc_get()在基数树上查找引脚

P32 第120讲 pin function和pin group:iomuxc节点解析始末(上)

设备树iomuxc节点层次:pinctrl子系统的设备树节点iomuxc、外设功能function/group、功能和IO组的标识属性fsl,pins

层次关系说明

  • iomuxc:pinctrl子系统的设备树节点
  • function:芯片具有外设功能,一个功能对应一个或多个IO组配置信息
  • group:IO组中每个IO的配置信息
  • fsl,pins:imx6ull中,功能和IO组的标识属性

imx_pinctrl_probe_dt()

drivers/pinctrl/freescale/pinctrl-imx.c

static int imx_pinctrl_probe_dt(struct platform_device *pdev,
                struct imx_pinctrl *ipctl)
{
    //iomux节点
    struct device_node *np = pdev->dev.of_node;
    struct device_node *child;
    struct pinctrl_dev *pctl = ipctl->pctl;
    
    flat_funcs = imx_pinctrl_dt_is_flat_functions(np);
    if (flat_funcs) {
        nfuncs = 1;    //imx6ull会执行这里
    } else {
        nfuncs = of_get_child_count(np);
        if (nfuncs == 0) {
            dev_err(&pdev->dev, "no functions defined\n");
            return -EINVAL;
        }
    }
    
    for (i = 0; i < nfuncs; i++) {
        struct function_desc *function;

        function = devm_kzalloc(&pdev->dev, sizeof(*function),
                    GFP_KERNEL);
        if (!function)
            return -ENOMEM;

        mutex_lock(&ipctl->mutex);
        radix_tree_insert(&pctl->pin_function_tree, i, function);
        mutex_unlock(&ipctl->mutex);
    }
    
    pctl->num_functions = nfuncs;
    ipctl->group_index = 0;
    
    if (flat_funcs) {
        pctl->num_groups = of_get_child_count(np);
    } else {
        pctl->num_groups = 0;
        for_each_child_of_node(np, child)
            pctl->num_groups += of_get_child_count(child);
    }
    
    if (flat_funcs) {
        imx_pinctrl_parse_functions(np, ipctl, 0);
    } else {
        i = 0;
        for_each_child_of_node(np, child)
            imx_pinctrl_parse_functions(child, ipctl, i++);
    }

imx_pinctrl_dt_is_flat_functions()

drivers/pinctrl/freescale/pinctrl-imx.c

static bool imx_pinctrl_dt_is_flat_functions(struct device_node *np)
{
    struct device_node *function_np;
    struct device_node *pinctrl_np;

    for_each_child_of_node(np, function_np) {    //遍历子节点
    
        if (of_property_read_bool(function_np, "fsl,pins"))
            return true;
            
        //子节点没有则遍历子节点的子节点
        for_each_child_of_node(function_np, pinctrl_np) {
            if (of_property_read_bool(pinctrl_np, "fsl,pins"))
                return false;
        }
    }

    return true;
}

P33 第121讲 pin function和pin group:iomuxc节点解析始末(中)

imx_pinctrl_parse_functions()、pinmux_generic_get_function()、imx_pinctrl_parse_groups()

P34 第122讲 pin function和pin group:iomuxc节点解析始末(下)

imx_pinctrl_parse_pin_mem()、

P35 第123讲 pin state:pinctrl-names的真相

pinctrl_enable()-->create_pinctrl()-->pinctrl_dt_to_map()

P36 第124讲 pinctl_map:引脚三千,只取一瓢(上)

pinctl_map数组:0--所有引脚复用;1~n--单个引脚属性
imx_dt_node_to_map()、imx_pinctrl_find_group_by_name()、pinctrl_generic_get_group()、pin_get_name()、pin_desc_get()

P37 第125讲 pinctl_map:引脚三千,只取一瓢(下)

dt_remember_or_free_map()、pinctrl_register_map()、pinmux_validate_map()、pinconf_validate_map()

P38 第126讲 pinctl_setting:如何统一管理pin state(上)

pinctl_map保存了所有pin state所需要的pin group 引脚组 信息;pinctl_setting把pin group信息按pin state分类保存
for_each_maps()宏、add_setting()、find_state()、create_state()、pinmux_map_to_setting()、pinmux_func_name_to_selector()

P39 第127讲 pinctl_setting:如何统一管理pin state(下)

pinmux_generic_get_function_name()、pinmux_generic_get_function_groups()、pinctrl_get_group_selector()、pinctrl_generic_get_group_name()

P40 第128讲 pinctrl子系统的引脚设置接口

状态查询pinctrl_lookup_state、状态设置pinctrl_select_state
pinctrl_commit_state()、pinmux_enable_setting()、imx_pmx_set()、imx_pmx_set_one_pin_mem()

P41 第129讲 pinctrl子系统实验:RGB灯引脚初始化

相当于利用内核中的寄存器信息
platform设备引脚初始化:注册时配对成功自动执行really_probe()-->pinctrl_bind_pins()配置引脚
RGB灯引脚状态初始化:更改代码
试验:去掉内核模块io相关硬件寄存器代码后make copy、增加内核设备树并编译 替换 重启、加载ko、sudo sh -c "echo '7'>/dev/rgb_led"

P42 第130讲 gpio1节点:GPIO子系统初窥

设备树gpio1节点包含GPIO1控制器的寄存器基地址,类似的还有gpio2~5
gpio1节点属性解释;新增rgb_led节点,使用gpio子系统 rgb_led_red = <&gpio1 4 GPIO_ACTIVE_LOW &gpio1 10 GPIO_ACTIVE_LOW>;
常用函数:of_find_node_by_path()获取rgb_led指针;of_get_named_gpio()获取GPIO编号;gpio_request() gpio_free()配对使用;gpio_direction_output()设置为输出及默认电平、gpio_direction_input()输入;gpio_get_value() gpio_set_value()读输入 写输出

P43 第131讲 GPIO子系统实验:IO引脚高低电平控制

P42函数使用
更改内核设备树并编译 替换 重启、加载ko、sudo sh -c "echo '7'>/dev/rgb_led"

P44 第132讲 十面埋伏的并发(一):硬件同步原语

并发根源:多线程、多进程、中断

硬件同步原语:原子操作

核心问题在于:写代码是C语言,操作系统调度时使用汇编语言【图中为arm汇编】

原子整型操作接口ATOMIC_INIT定义 赋初值;atomic_set()赋值;atomic_read()读;atomic_add()/atomic_sub() atomic_inc()/atomic_dec();

位原子操作函数:set_bit() clear_bit() addr为变量值的内存位置

演示:代码分析;通过使用原子整型变量test_atomic,保证驱动程序在关闭前只能打开1次,核心代码如下

static atomic_t test_atomic = ATOMIC_INIT(1);				//【定义整型原子变量并设置初始值为1】

/*字符设备操作函数集,open函数*/
static int led_chr_dev_open(struct inode *inode, struct file *filp)
{
	if(atomic_read(&test_atomic))				//【读原子变量值】
	{
		atomic_set(&test_atomic,0);					//【将整型原子变量的值设为0】
	}
    else
    {
		printk("\n driver on using!  open failed !!!\n");
		return - EBUSY;
    }
	printk("\n open form driver \n");
	return 0;
}

static int led_chrdev_release(struct inode *inode, struct file *filp)
{
    atomic_set(&test_atomic,1);					//【将整型原子变量的值设为1】
	printk("KERN_ALERT  \n finished  !!!\n");

	return 0;
}


/*字符设备操作函数集*/
static struct file_operations led_chr_dev_fops =
	{
		.owner = THIS_MODULE,
		.open = led_chr_dev_open,
		.write = led_chr_dev_write,
		.release = led_chrdev_release,
};
视频效果,我验证时出现
ls /dev/rgb_led
ls: cannot access '/dev/rgb_led': No such file or directory

P45 第133讲 十面埋伏的并发(二):自旋锁

原子操作:简单易用,保护范围小
自旋锁:多核场合、短时轻量加锁、加锁失败忙等待pause指令、开销小
加锁步骤:查看锁状态,为空则持有;CAS函数将其变为原子指令;
自旋锁API:定义、初始化、获取、尝试获取、释放
试验:类似P44

P46 第134讲 十面埋伏的并发(三):信号量

计数器、获取失败触发上下文调度、开销大、适用长期资源占用
API:定义、初始化、获取、尝试获取、释放
试验:类似P44

P47 第135讲 十面埋伏的并发(四):互斥锁

P47 十面埋伏的并发4:互斥锁
信号量与互斥锁:信号量--强调同步、生产消费模型;互斥锁--强调互斥、资源独占;均触发上下文调度
自旋锁与互斥锁:自旋锁--开销低、保持cpu使用权;互斥锁--开销高、加锁失败释放cpu
API:定义、初始化、获取、尝试获取、释放
试验:类似P44

P48 第136讲 IIC驱动框架简介

物理总线:SCL SDA
框架图:I2C核心--硬件无关代码;I2C 总线驱动--读写时序;I2C设备驱动--i2c_driver和i2c_client
核心数据结构:i2c_adapter--NXP构建并且注册;i2c_algorithm--master_xfer产生I2C通信时序 NXP实现;i2c_client--;i2c_driver--probe i2c设备和i2c驱动匹配后,回调该函数指针
I2C 总线驱动分析:i2c总线注册--内核启动执行i2c_init();i2c总线定义--结构体i2c_bus_type;i2c设备和i2c驱动匹配规则--i2c_device_match();
设备树节点:compatible、reg;i2c_imx_probe()、结构体变量i2c_imx_algo、i2c_imx_xfer()

P49 第137讲 IIC核心函数与“万能”驱动(上)

注册一个i2c适配器:i2c_add_adapter()
注册一个i2c驱动:i2c_add_driver()
收发iic消息:i2c_transfer();i2c_msg结构体--iic设备地址、消息传输方向、消息数据的长度、消息缓冲区;i2c_master_send()、i2c_master_recv()、i2c_transfer_buffer_flags();
"万能"的i2c驱动:i2c-dev.c分析;内核集成i2c_dev驱动模块,开机自动加载、为每个i2c_adapter生成一个设备文件;

P50 第138讲 IIC核心函数与“万能”驱动(下)

i2c_dev_init()、i2cdev_attach_adapter()。。。。
i2cdev_fops:i2cdev_open()、i2cdev_read()、i2cdev_write()
讲代码总是一脸懵逼,应该在课堂笔记代码上多多加中文注释

P51 第139讲 IIC驱动实验:读取mpu6050数据(上)

MPU6050:空间运动传感器芯片
设备树节点:iomuxc子节点、i2c1子节点 地址由芯片决定
pinctrl对设备树插件支持不完善,试验使用传统设备树

P52 第140讲 IIC驱动实验:读取mpu6050数据(下)

芯片手册决定配置地址和命令
读取芯片信息

P53 第141讲 设备驱动的非阻塞IO:O_NONBLOCK

IO操作:数据读写;用户空间-->内核空间-->file_operation
阻塞操作--系统默认,进入休眠,等待资源可用;非阻塞操作--不可用则返回错误码
O_NONBLOCK用法演示

P54 第142讲 设备驱动的阻塞IO:等待队列

等待队列头 初始化 元素:wait_queue_head、init_waitqueue_head()、wait_queue_entry
添加 移除:add_wait_queue()、remove_wait_queue()
等待事件:wait_event() wait_event_interruptible() wait_event_timeout()
等待唤醒:wake_up() wake_up_interruptible()
试验:先调用wait_event进入等待状态,后唤醒

P55 第143讲 POLL机制基本概念

file_operations->poll
同步阻塞--单文件读写;异步阻塞--多文件轮询
poll函数:监视多个文件的指定事件;试验

P56 第144讲 POLL底层机制剖析(上)

系统调用接口sys_poll、timespec64结构体
do_sys_poll()函数:复制用户空间pollfd数组到内核空间--分配静态数组内存(一个poll_list结构体)、动态分配内存(一组poll_list结构体)

P57 第145讲 POLL底层机制剖析(下)

do_sys_poll()函数:poll_initwait();do_poll()--确保线程/进程被唤醒后,继续执行1次循环体内容、遍历1组poll_list、遍历1个poll_list中的1组pollfd;
驱动层poll底层接口:poll_wait()、poll_get_entry()

对于不熟的人来说,看视频不能开倍速,不能走神

P58 第146讲 彻底掌握POLL机制:动手设计一个POLL实验

主要调用3个函数:APP的main、驱动的poll和write
详细看md笔记

P59 第147讲 Completion机制基本概念

信号量/互斥量:进程/线程没有优先级
完成量:一个线程的运行依赖另一个线程
completion结构体:
初始化completion:init_completion()
completion休眠:wait_for_completion()、wait_for_completion_timeout()、wait_for_completion_interruptible()
complete唤醒:complete()、complete_all()
实验现象类似P58

P60 第148讲 kthread_worker:把内核线程当工人?

驱动传输数据 :低速数据--驱动传输;高速数据--内核传输
定义变量:kthread_worker、kthread_work、task_struct
初始化:kthread_init_worker()、kthread_init_work()
创建内核线程:kthread_run()
启动工作:kthread_queue_work(&hi_worker, &hi_work);
模块退出:kthread_flush_worker(&hi_worker); kthread_stop(kworker_task);
通过内核线程点灯

P61 彻底掌握kthread_worker队列化机制--讲代码
kthread_worker_fn()、kthread_init_work()、kthread_queue_work()、kthread_flush_worker()

P62 SPI物理总线
信号线:SCK、MOSI、MISO、CS;一主多从,全双工,上百MHz
spi时序:起始信号、停止信号、上升沿输出下降沿采样
spi通信模式:CPOL=1, SCK空闲状态为高电平; CPHA=1,在 SCK “偶数边沿”采样;组合出4大模式
常见spi设备:EEPROM、FLASH、实时时钟、AD转换器

P63 SPI驱动框架简介
SPI框架图:实际挂载到SPI总线,但因为使用设备树,包装为platform总线;SPI核心层--提供SPI控制器驱动和设备驱动的注册方法、注销方法、SPI通信硬件无关接口;SPI主机驱动--产生SPI 读写时序、spi_master(spi_controller);SPI设备驱动--通过SPI主机驱动与CPU交换数据、spi_device和spi_driver
核心数据结构:spi_controller对应1个SPI总线;使用异步传输方式;spi_register_master()
spi_device:代表1个具体设备;通过modalias配对
spi_driver:spi_register_driver()
spi 总线注册:spi_init开机运行、bus_register()、class_register()
spi控制器驱动:设备树节点、module_platform_driver() module_driver()

P64 SPI主控制器驱动和核心函数
spi_imx_probe()函数:获取设备树节点信息,初始化spi时钟、dma...;保存spi寄存器起始地址,填充spi控制器回调函数;代码详解
spi_bitbang_start()函数:spi_register_master()
核心函数:spi_setup()函数--设置spi设备的片选信号、传输单位、最大传输速率...;spi_imx_setup()函数--spi_imx_chipselect()函数高低有效设置;spi_message结构体包含多个spi_transfer结构体内的消息、spi_message_init()、spi_message_init_no_memset()、spi_message_add_tail();发送--spi_sync()函数、spi_async()函数

P65 SPI数据传输剖析:同步、异步
如果把笔记中的代码加上详细注释,讲课效果应该会好很多
spi_register_controller()、spi_controller_initialize_queue()
spi_init_queue()函数-- 初始化内核线程工人、初始化内核具体工作; spi_start_queue()函数--启动内核具体工作;spi_pump_messages()函数--内核工作具体处理;__spi_pump_messages()函数
spi_sync()函数--阻塞当前线程;__spi_sync()函数;__spi_queued_transfer()函数、spi_transfer_one_message()函数、spi_finalize_current_message()函数
spi_async()函数--异步传输数据、__spi_async()函数、spi_queued_transfer()函数、__spi_queued_transfer()函数

P66 pidev:SPI万能驱动 上
万能:Linux内核开放的相对通用的驱动;内核集成spidev驱动模块
设备树节点:pinctrl子节点、spidev子节点
spidev_init()函数、spidev_fops文件操作接口--unlocked_ioctl ioctl()函数底层操作接口(32位系统)、compat_ioctl ioctl()函数底层操作接口(64位系统)
spidev_probe()函数:创建字符设备、次设备号按位图分配、设备文件名后缀数字的含义 spi控制器编号+spi设备片选信号编号

P67 pidev:SPI万能驱动 下
spidev_fops文件操作接口
spidev_open()函数--为tx_buffer、rx_buffer分配4096字节内存;
spidev_read()函数--spidev_sync_read()函数、spidev_sync()函数
spidev_ioctl()函数--文件控制命令

P68 SPI驱动试验 上
SPI回写试验
全双工:使用spidev_ioctl()函数default分支;spi_ioc_transfer结构体、spi_ioc_transfer()函数;spidev_message()函数
SPI驱动实验:SPI 3的 MIOS与MOSI引脚使用跳线帽短接;spi3引脚与uart2存在引脚复用;

P69 SPI驱动试验 下
编译替换设备树、spi_app代码介绍 编译、屏蔽系统3个设备树插件、重启运行app

P70 Linux中断基础概念
回顾:裸机开发通用中断控制器(GIC)
GIC中断控制器节点:初始化中断控制器、设置其他中断控制器节点的描述格式
外设中断控制器节点:管理某一种具体中断
其他设备使用中断节点:使用某一种具体中断
常用函数:申请中断 request_irq();释放中断 free_irq();使能中断 enable_irq();禁止中断,等待中断执行完毕 disable_irq();禁止中断,不等待中断执行完 disable_irq_nosync();禁止处理器中断 local_irq_disable();开处理器中断 local_irq_enable()

P71 Linux按键中断实验
设备树节点:iomuxc子节点、自定义按键节点
代码讲解:irq_of_parse_and_map()。。。

P72 软中断和tasklet基础概念
中断上下半部机制:上半部--传统的中断处理函数、快速任务处理、硬件信号控制;下半部--非中断处理函数,由上半部设置工作任务、数据读取 复制 包装 转发 计算
软中断:实现中断下半部的一种机制;注册一个软中断open_softirq();主动触发指定软中断raise_softirq()
tasklet机制:基于软中断实现的下半部机制;tasklet_struct结构体、tasklet_init()、调度执行tasket对象tasklet_schedule()、注销tasket对象tasklet_kill()

P73 tasklet试验
通过打印不同字符区分中断上下半部

P74 工作队列试验
tasklet机制:软中断环境下执行、不允许休眠、降低系统实时性
工作队列机制:类似于kthread_worker;进程上下文环境执行、允许休眠、不影响实时性
创建工作队列:alloc_workqueue();内核默认创建1个,用户一般不需要创建
使用工作队列:work_struct、schedule_work()
实验现象类似P73

P75 输入input子系统基础概念
统一管理外部输入设备:按键、键盘、鼠标、触摸屏
分层模型:核心层、事件处理层
创建input设备类:input_init()函数 开机自动运行
input_dev结构体
注册/销毁输入设备:input_allocate_device()、input_register_device()、input_unregister_device()、input_free_device()
上报输入事件:input_event()--input_report_key()、input_report_rel()、input_report_abs()、input_sync()

P76 输入input子系统试验
通用输入设备(evdev.c)识别:/dev/input/event*  难以识别;查看输入设备名和event对应关系 cat /proc/bus/input/devices
BIT_MASK宏 常用且不会溢出
input_set_capability()

P77 电容触摸屏驱动简介
硬件接口:IIC接口、触摸信号中断、RST信号
驱动核心:i2c驱动框架、中断机制、input子系统、多点触摸协议
多点触摸(Multi-touch) 协议:TypeB优于TypeA;多点触摸事件类型;TypeB设备上报时序
API:初始化 input_mt_init_slots();上报触摸点的序号 input_mt_slot();为触摸点分配ID input_mt_report_slot_state();上报触摸点坐标信息 input_report_abs()、touchscreen_report_pos();上报时序案例
多点触摸屏驱动框架:I2C驱动入口;yyy_probe()中devm_request_threaded_irq函数无需手动释放中断

P78 电容触摸屏驱动试验 上
设备树节点:omuxc子节点--IIC 复位引脚;iomuxc_snvs子节点--触摸中断;i2c1子节点--触摸屏4个接口引脚
GT9157触摸芯片介绍:上电时序与 I2C设备地址;寄存器配置
电容触摸屏驱动:linux自带;goodix_ts_data结构体;goodix_chip_data结构体
试验:代码基于Linux源码移植;

P79 电容触摸屏驱动试验 下
代码讲解: goodix_request_irq(ts)、goodix_ts_irq_handler(int irq, void *dev_id)、goodix_process_events(struct goodix_ts_data *ts)、 goodix_ts_report_touch(struct goodix_ts_data *ts, u8 *coor_data)
试验:编译替换设备树;编译代码、加入驱动模块、触摸并查看二进制文件
hexdump命令:二进制文件查看工具
触摸屏数据显示格式说明

P80 PWM驱动试验
相关寄存器
设备树节点:pwm3子节点、iomuxc子节点、red_led_pwm子节点
pwm_device结构体;pwm_state结构体--- period、duty_cycle、polarity、enable;
获取pwm_device:devm_of_pwm_get()函数--解析pwm设备树节点信息,生成pwm_device对象
配置pwm频率和占空比pwm_config();配置pwm极性pwm_set_polarity();使能 禁止pwm输出pwm_enable() pwm_disable()
试验:编译替换设备树;编译代码、加入驱动模块、红灯亮10%

P81 LCD驱动框架分析
framebuffer机制:帧缓冲、显示图像信息、设备文件 /dev/fbx(x=0~n)
分层模型:核心层--drivers/video/fbdev/core/fbmem.c、linux内核实现、创建graphics设备类,占据主设备号29、为不同的显示设备提供文件通用处理接口;硬件设备层--驱动人员实现、提供显示时序、显存、像素格式等硬件信息、提供显示设备的私有文件操作接口、创建lcd设备文件/dev/fbx(x=0~n)
核心层分析:fbmem_init(void) 开机自动执行--FB_MAJOR宏、fb_fops文件操作接口
硬件设备层分析:fb_info 结构体--时序、显存、像素格式、fbops显示设备的私有文件操作接口
注册 register_framebuffer()、do_register_framebuffer()、fb_open()

老师的英语发音需要好好练习了

P82 LCD驱动试验
设备树节点:iomuxc子节点、backlight子节点、lcdif节点、lcd设备节点
试验:编译替换设备树;三色+图片循环显示
位图转换器 SEGGER:图片转化c文件


 

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值