里面图片上传不了,完整的见 上传的附件
Linux drv 一篇杂烩
一 平台设备总线:
- 平台设备总线是一条虚拟总线,就是把一个设备驱动依照实际需要可以一分为二,分为platform_drv 和platform_dev 。并且可以继续一分为二。
drv通用代码 dev 硬件资源。
比如i2c-bus:
2.
平台设备驱动和直接采用字符设备file_operaiton的写法的区别:
- 直接 注册写file_operaiton 操作硬件的哪个引脚 是直接写死的。
- Hello_driver 操作哪个引脚是由hello_dev 传入的参数决定。
分层dev和drv 引脚资源获取
----- hello_dev
static struct resource hello_dev_res[] = {
{
.start = 2, /* pin2 */
.end = 2, /* pin2 */
.flags = IORESOURCE_TYPE_BITS,
},
};
static struct platform_device hello_dev = {
.name = "100ask_hello",
.dev = {
.release = hello_dev_release,
},
.num_resources = 1,
.resource = hello_dev_res,
};
----hello_drv
static int hello_probe(struct platform_device *pdev)
{
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
/* 1. 获得资源 */
pin = pdev->resource[0].start;
/* 2. 注册file_operatoins */
major = register_chrdev(0, "hello_drv", &hello_fops);
return 0;
}
static struct platform_driver hello_driver = {
.probe = hello_probe,
.remove = hello_remove,
.driver = {
.name = "100ask_hello",
},
};
- Plaform_dev 和plaform_drv 的匹配方法(常用 总共5种)
- 比较name
Return (strcmp(pdev->name,drv->name)==0)
- 比较id_idtable
If(pdev->id_table)
return platform_match_id(pdev->id_table,pdev)!=NULL;
- 设备找驱动和驱动找设备
当一个设备接入进来,设备会放入bus的设备链表,然后与bus的drv链表一一比较,直到找到匹配的驱动为止。
当一个驱动接入进来,驱动会放入bus的drv链表,然后与bus的drv会一一比较bus的dev链表,是否能枚举该设备。
一个dev只能由一个drv来匹配,但是一个drv可以支持多个设备。比如一个usb鼠标驱动可以支持多个鼠标,但是一个鼠标只需要一个驱动支持。
.probe
比较
不匹配,比较下一个
新接入dev 匹配过程。
- Linux驱动 = 框架 + 硬件操作
- dev_set_drvdata
static void dev_set_drvdata(struct device *dev,void *data)
{
dev->driver_data=data;
}
- 平台设备驱动调试
modprobe led_device //加载设备模块
modprobe led_driver //加载驱动模块
驱动加载成功后,
ls /sys/bus/platform/drivers 查看是否生成平台设备驱动
ls /sys/bus/platform/devices 查看总线下的设备
匹配成功就会调用 driver 的 .probe 函数.
查看设备相关情况
Ls /dev/
Ls /dev/100as*_led
cd /sys/
Ls
输出:blocks bus class dev devices firemaware fs hypervisor kernel module power
根据不同类型的设备 可以进一步进入到 bus class dev devices 等模块进一步查看设备驱动信息。
Insmod -f led_driver.ko -f 表示强制加载。
- 问:在设备树增加节点后,在 /sys/xxx 什么目录下查看这个节点?创建设备节点后,目录又在哪?
- /sys/firmware/devicetree/base
- 某个节点 -> platform_device -> /sys/bus/paltform/devices
- 某个节点-> i2c_client -> /sys/bus/i2c/devices
- 某个节点-> spi_dev -> /sys/bus/spi/devices
- 为什么要引入设备树?
随着接入的硬件设备种类越来越多,,linus本人也越来越难以忍受。各大厂商不停地往linux内核里面注入,导致linux系统变得越来越臃肿,并且有很多可能会重复和冗余而不是精简高效的。
用hardcode方式将HW 配置信息嵌入到内核代码的方法,改用bootloader传递一个DB的形式。
缺点: 1. 代码巨大
- 修改麻烦
采用dts :
配置文件和内核分开。 易于维护和修改。
查看设备树:
- 查看 .dtb 文件 可以通过find 命令查找
- Ls /sys/firmware/devicetree/base
10 单片机和linux 分层区别
图2为补充图1
在文件中搜索 grep “gpio-leds” * -nr/nwr
- Lcd 接线框图
、
11 Dts 文件常规知识:
Gpiob:gpio@5003000{
Gpio-controller;
Gpio-cells=<2> //cells 表示 用几个int 类型来描述。
}
Led0:cpu{
Label=”cpu”;
Gpios=<&gpio5 3 GPIO_ACTIVE_LOW>; 如上所述 这里的gpio 采用了两个int来描述的。
...
}
@I2C3{
Clock-frequency=<100000>;
Compatible=”sil,sii9022”;
Reg=<0x40>;
Reset-gpios=<&gpiob_10_GPIO_ACTIVE_LOW> //GPIO_ACTIVE_LOW表示低电平有效。
}
对于pinctrl 信息,厂家都有提供gui工具生成。
Grep “sai2” * -nwr | grep imx6ull
12 对于声卡 有的接口是SAI接口 既有SAI 又有I2C接口
在 内核搜索 grep “wm8960” * -nr 查看别人写的dts。
Cd linux-4.9.88/arch/arm/arm/boot/dts grep grep “wm8960” * -nr
grep “fsl,imx6ul-evk-wm8960”* -nr
驱动只提供能力,不提供策略。策略由APP决定。
13 App读取按键的4种方式:
举例:妈妈照看小孩
- 查询方式 简单,累。
(2)休眠-唤醒方式 妈妈陪小孩一起睡,小孩醒了会吵醒,但是妈妈干不了活了。
(3)poll方式 定个闹钟。妈妈要么是被吵醒要么是被闹钟唤醒。
(4)异步通知方式 妈妈小孩互不干扰。
14 中断
Dtb反汇编生成.dts文件
dtc -I dtb -0 dts
中断分级:
14.1
Static int gpio_key_probe(参数...)
{
Irq=platform_get_resource(pdev,IORESOURCE_IRQ,0);
Resquest_irq(...);
}
14.2 获取中断资源。
If(!pdev->dev.of_node) // 没有采用了dts
{
Res=platform_get_resource(pdev,IORESOURCE_IRQ,0);
Irq=res->start
}
Else
{
Irq=of_irq_get(pdev->dev.of_node,0); //从dts获取资源。
}
Err=Resquest_irq(...);
Poll_wait 不休眠,只是在队列中放入当前进程。
设备树:
Sr501
{
.compatible = “100as*,sr501” ;一般是公司名、设备名这样命名。
}
Dht11温湿度传感器:
Static int dht11_probe()
{
Dht11_data_pin=gpiod_get(&pdev->dev,NULL,GPIOD_OUT_LOW);
}
在采用dth11 捕获中断时,中断里面只做数据记录,在中断底半步或者工作队列中做计算。
Copytouser的应用 可以拷贝多个字节。
获取ns
Ktime_get_boot_ns(); 校准。
Ktime_get_bootime_ns(); 芯片不一样可能版本就不一样。
Irq=gpiod_to_irq(hs00038_echo);
背景说明:
在Linux设备树(linux 3.x版本引入)中, 设备的中断号不再在"irq.h"中硬编码定义, 而是在需要时自己手动去申请获得对应的硬件中断的软件中断号.( 前提是GPIO的相关模块已经被编入内核 )
通过GPIO号得到对应的软件中断号, 该中断号是request_irq()函数的第一个参数.
I2C 框架
I2cdetect -y 0
address-cells 和 #size-cells 描述子节点应如何编写 reg 属性值,一般 reg 属性是某个外设的寄存器地址范围信息。
Aips1:aips-bus@2000000{
.compatible=”fsl,aips-bus”;
#address-cell =<1>;
#size-cell=<1>;
Reg=<0x02000000 0x100000> ; reg区域
}
- #address-cells,用来描述子节点"reg"属性的地址表中用来描述首地址的cell的数量,
- #size-cells,用来描述子节点"reg"属性的地址表中用来描述地址长度的cell的数量。
有了这两个属性,子节点中的"reg"就可以描述一块连续的地址区域。
Input_report_rel();
Input_report_rel();
Input_report_rel();
Input_sync(); 表示这一轮上报已经结束。上报同步。
Hexdump /dev/input/event0
Lcd 原理
Lcd 有屏幕自带 缓冲的 有不带的。
怎么写framebuffer驱动?
答: 分配fb_info ->设置 fb_info fb_var fb_fix ->注册fb_info ->硬件操作。
Ls /dev/fb*
Fb-test /dev/fb2
Oled用的是有的用的是SPI接口。也有i2c接口
LCD接口
RGB:(DPI)RGB565/RGB666/RGB888
MCU:I8080/M6800(8/9/16/18/24bit)
SPI:3line/4line
MIPI-DSI:Data_N/P、Clock_P/N
总结写驱动的三种方法:
- 资源和驱动写在同一个文件里。File_operation
- 资源用platform_device指定,驱动在platform_driver实现。
- 资源用dts指定。
工作队列:
中断的下半部采用tasklet,它们都是在中断上下文中执行,它们无法休眠。当要处理更复杂的事情时,往往更耗时,这些更耗时的操作放在定时器或者下半部中,会导致系统很卡;并且循环等待某件事情完成也很浪费cpu资源。
如果使用线程来处理耗时的工作,那就可以解决系统卡顿的问题。因为线程可以休眠。
在内核中我们并不需要自己去创建线程,可以使用“工作队列”,内核初始化工作队列,就会为他创建了内核线程。以后我们使用“工作队列”,只需要把“工作”放入“工作队列中”,对应的内核线程就会取出“工作”,执行里面的函数。
工作队列的使用场景:耗时,甚至可能需要休眠,那么可以使用工作队列。
缺点:多个工作是在某个内核线程中依序执行的,前面函数执行很慢,会影响后面的函数。在多cpu下,一个工作队列可以有多个内核线程,可以在一定程度上缓解这个问题。
设备树常用 OF 操作函数
要使用 of 函数,必须先配置内核 CONFIG_OF = y,其实内核默认已经使能,所以内核移植时不需要配置这个选项。
疑问:
ls /sys/bus/platform/drivers
ls /sys/bus/platform/devices
Ls /sys/devices/platform/