当前位置: 首页 > news >正文

【notes9】kbuild,内核模块化,设备树,platform总线,设备驱动模型

文章目录

  • 1.kbuild编译系统
    • 1.1 Kconfig
    • 1.2 Makefile
  • 2.内核模块化
    • 2.1 传参
    • 2.2 符号导出
    • 2.4 错误码
    • 2.5 uevent
    • 2.3 设备号
      • 块设备申请主设备号
    • 3.字符设备驱动
      • 设备读写
      • 杂项设备
  • 4.设备树
    • 4.1 时钟
    • 4.1 中断属性
    • 4.2 pinctrl & gpio
  • 5.platform总线
  • 6.设备驱动模型

1.kbuild编译系统

kernel源码目录第一层有Kconfig(后厨原材料)和.config(点菜的清单)文件。menuconfig(菜单),Deconfig(预制菜单 熟客使用)。

obj-y+=foo.o bar.o// 表示foo.c和bar.c文件被编译成foo.o和bar.o// kernel/driver/spi/build-in.a静态库是该目录下所有目标文件(.o文件)打包而成

目标由多个源文件编译得到:追加的效果





malloc不是内核API而是libc函数,参考【notes8】。

1.1 Kconfig

参考【openbmc1】内核添加驱动。

驱动模块使用的资源一摸一样(如gpio),只能加载一个,如下只能选一个,互斥:

depends on只有依赖条件成立,配置项才显示。

1.2 Makefile


条件编译:最常用的两种格式#ifdef和#ifndef 。#undef :取消已定义的标识符

如下book145.c和_public.c都有 #include"_public.h",会重复包含。

在_public.c中如下这样写,_public.h就不会被重复包含。

如下$前一个tab键不能8个空格。make默认是make all,如果将all这行book3删除,则make不会编译book3,可指定make book3,book3相当于标签。-欧2是让编译效率最高,一般正式发布用。gcc命令选项 :-c编译不链接。


如下是编译内核,第一行判断路径是否存在。



EXRA_CFLAGS用于额外编译选项:

2.内核模块化



2.1 传参

权限位S_IRUGO是用在sysfs文件系统里,static int…只是初始化给默认值。


2.2 符号导出

驱动程序编译生成的 ko 文件是相互独立的, 即模块之间变量或者函数在正常情况下无法进行互相访问。extern:声明来自另一个模块。

2.4 错误码



如下fail里将return ret。如上地址是64位内核空间最后一页,指针落到这段地址说明是错误的无效指针。

2.5 uevent

structkobject*mykobject01;structkset*mykset;structkobj_typemytype;staticintmykobj_init(void){intret;// 创建并添加一个ksetmykset=kset_create_and_add("mykset",NULL,NULL);// 分配并初始化一个kobjectmykobject01=kzalloc(sizeof(structkobject),GFP_KERNEL);mykobject01->kset=mykset;// 初始化并添加kobject到ksetret=kobject_init_and_add(mykobject01,&mytype,NULL,"%s","mykobject01");// mykobject01// 触发一个uevent事件,表示kobject的属性发生了变化ret=kobject_uevent(mykobject01,KOBJ_CHANGE);return0;}staticvoidmykobj_exit(void){kobject_put(mykobject01);// 释放kobjectkset_unregister(mykset);}module_init(mykobj_init);module_exit(mykobj_exit);MODULE_LICENSE("GPL");// insmod 上面.ko// ls /sys/mykset/mykobject01/ (里面是空的,没有任何属性文件)// dmesg | tail -n 1 或 udevadm monitor --kernel// kobject_uevent: KOBJ_CHANGE for mykobject01




如下device_create里调用kobject_uevent告诉用户态的二进制udev机制,说有一个设备节点的创建,udev帮对设备文件创建。


2.3 设备号

具有相同主设备号(哪一类)设备使用相同驱动程序,次设备号(哪一个)用来标识连接系统中相同的设备。l:符号链接,s:套接字,p:管道。


如下次设备号个数不是次设备号(hello_minor)。

块设备申请主设备号

hello_major为0是系统自动分配的主设备号即register_blkdev函数分配。

3.字符设备驱动

如下crw—中c就是char字符设备(以字节为单位输入输出,没有固定大小,也没有缓冲区,数据会立即传输: 串口、鼠标)。块设备以b开头(以块为单位输入输出,有缓冲区,数据下发以后会在缓冲区中缓存,可以被分区,还可以格式化它的文件系统【不能裸设备访问,坏块多,都会上文件系统】:硬盘、U盘)。网络设备ifconfig查看。


cdev_init是方法填充,cdev_add是属性(如主次设备号)的填充。


设备读写


write函数中一个进程写没问题,两进程写:第一个进程运行到kzalloc时,第二个进程也执行了kzalloc,只第二个进程地址保存在c中,第一个进程分配内存空间地址丢失造成内存泄漏。第一个进程运行到kzalloc时第二个进程调用了kfree,这时第一个进程执行到copy_from_user出现问题。


如下cdev_map是整个绿色:

杂项设备

structfile_operationsmisc_fops={//文件操作集.owner=THIS_MODULE////将owner字段指向本模块,可以避免在模块的操作正在被使用时卸载该模.read=.write=};structmiscdevicemisc_dev={//杂项设备结构体.minor=MISC_DYNAMIC_MINOR,//动态申请的次设备号.name="test",//杂项设备名字是hello_misc.fops=&misc_fops,//文件操作集};staticint__initmisc_init(void){intret;ret=misc_register(&misc_dev);//在初始化函数中注册杂项设备if(ret<0){printk("misc registe is error \n");//打印注册杂项设备失败}return0;}staticvoid__exitmisc_exit(void){misc_deregister(&misc_dev);//在卸载函数中注销杂项设备printk(" misc goodbye! \n");}module_init(misc_init);module_exit(misc_exit);MODULE_LICENSE("GPL");

4.设备树

dts -》 dtb - 》device_node - 》platform_device。

DTC(Device Tree Compiler,源代码和相关工具放在scripts/dtc目录,CONFIG_DTC,也可反编译)编译器将dts/dtsi文件编译成二进制dtb文件。

reg=<0x1000x4>:地址从0x100开始的4字节寄存器区域 spi和i2c设备属于非内存映射设备,它们的地址对cpu不可访问,而父设备的驱动(即总线控制器驱动)代表cpu访问,spi reg代表spi chip select0,i2c reg代表设备地址

4.1 时钟

时钟(类似心跳)生产者(板卡厂商,定义):1.clock-cells:=0表示一个时钟,=1表示多个时钟。

2.clock-frequency:频率,单位HZ
3.assigned-clocks:

4.assigned-clocks-rates:

时钟消费者(驱动,引用)
1.clocks:类似assigned-clocks
2.clock-names:

mac1: ftgmac@14060000 {
compatible = “aspeed,ast2700-mac”, “faraday,ftgmac100”;
reg = <0x0 0x14060000 0x0 0x200>;
interrupts-extended = <&intc1_4 1>;

clocks = <&syscon1 SCU1_CLK_GATE_MAC1CLK>;

syscon1: syscon@14c02000 {
compatible = “aspeed,ast2700-scu1”, “syscon”, “simple-mfd”;
aspeed,ast2700-scu.h , clk-ast2700.c

4.1 中断属性

1.interrupts:下图有3位(父亲是gic控制器),interrupt-cells给引用的子节点用的,不是当前节点。
2.interrupt-controller:当前节点是一个中断控制器。

ft5x06:ft5x06@38{//触摸屏有事件 → 产生中断信号 → 通过RK_PB5引脚 → GPIO控制器接收 → GPIO控制器向GIC报告中断号33 → CPU处理中断interrupts=<RK_PB5 IRQ_TYPE_LEVEL_LOW>;interrupt-parent=<&gpio0>};RK_PB5是Rockchip(瑞芯微)平台的GPIO引脚命名: RK:Rockchip缩写 P:Port(端口) B5:B组的第5个引脚 RK_PB5 通常对应具体的物理引脚号,比如GPIO0_B5// 获取中断号intirq=gpio_to_irq(RK_PB5);// 将GPIO引脚转换为Linux内核中断号// 注册中断处理函数request_irq(irq,touch_handler,IRQF_TRIGGER_LOW,"ft5x06",...);

3.interrupt-cells:约束children的interrupt属性有几位组成,为了解析方便。
4.interrupt-parent:指向谁提供中断即中断控制器。

irq_of_parse_and_map函数:解析设备节点的"interrupts"属性, 并将对应的中断号映射到系统的中断号,拿到irq,为了后续request_irq。

irq_get_trigger_type 函数:获取interrupts属性的最后一位即触发方式。

4.2 pinctrl & gpio

pinctrl指定pin复用功能(gpio,串口,i2c…),


/proc/device-tree/…


如下gpio0:32到64。每个gpio控制器里有4组,每组8个。

5.platform总线




案例

// paltform_device.c#include<linux/init.h>#include<linux/module.h>#include<linux/platform_device.h>#defineGPIO0_BASE(0xfdd60000)#defineGPIO0_DR(GPIO0_BASE+0x0004)#defineGPIO0_DDR(GPIO0_BASE+0x000C)staticstructresourceled_resource[]={[0]=DEFINE_RES_MEM(GPIO0_DR,4),//数据 寄存器[1]=DEFINE_RES_MEM(GPIO0_DDR,4),//方向 寄存器};staticvoidled_release(structdevice*dev){}unsignedintled_hwinfo[1]={7};// 片选偏移 ,led - GPIIO0_C7引脚staticstructplatform_deviceled_pdev={// 没有设备树之前 //////////////////////////重点.name="led_pdev",//匹配下面paltform_driver.c.id=0,.num_resources=ARRAY_SIZE(led_resource),.resource=led_resource,.dev={.release=led_release,.platform_data=led_hwinfo,},};///////////////////////////////////////////////////////////////////////////////////////static__initintled_pdev_init(void){printk("pdev init\n");platform_device_register(&led_pdev);// platform总线添加一设备return0;}module_init(led_pdev_init);static__exitvoidled_pdev_exit(void){printk("pdev exit\n");platform_device_unregister(&led_pdev);}module_exit(led_pdev_exit);MODULE_DESCRIPTION("Platform LED device");MODULE_AUTHOR("embedfire");MODULE_LICENSE("GPL");
// paltform_driver.c#include<linux/init.h>#include<linux/module.h>#include<linux/platform_device.h>#include<linux/mod_devicetable.h>#include<linux/io.h>#include<linux/cdev.h>#include<linux/fs.h>#defineDEV_MAJOR243#defineDEV_NAME"led"staticstructclass*led_test_class;//结构体led_data来管理我们LED灯的硬件信息structled_data{unsignedintled_pin;unsignedint__iomem*va_DDR;unsignedint__iomem*va_DR;structcdevled_cdev;};staticintled_cdev_open(structinode*inode,structfile*filp){unsignedintval=0;structled_data*cur_led=container_of(inode->i_cdev,structled_data,led_cdev);printk("led_cdev_open() \n");// 设置引脚输出val=readl(cur_led->va_DDR);// 方向val|=((unsignedint)0X1<<(cur_led->led_pin+16));val|=((unsignedint)0X1<<(cur_led->led_pin));writel(val,cur_led->va_DDR);//设置默认输出高电平val=readl(cur_led->va_DR);// 数据val|=((unsignedint)0X1<<(cur_led->led_pin+16));val|=((unsignedint)0x1<<(cur_led->led_pin));writel(val,cur_led->va_DR);filp->private_data=cur_led;return0;}staticintled_cdev_release(structinode*inode,structfile*filp){return0;}staticssize_tled_cdev_write(structfile*filp,constchar__user*buf,size_tcount,loff_t*ppos){unsignedlongval=0;unsignedlongret=0;inttmp=count;structled_data*cur_led=(structled_data*)filp->private_data;val=kstrtoul_from_user(buf,tmp,10,&ret);val=readl(cur_led->va_DR);// 方向if(ret==0){val|=((unsignedint)0x1<<((cur_led->led_pin)+16));val&=~((unsignedint)0X1<<(cur_led->led_pin));}else{val|=((unsignedint)0x1<<(cur_led->led_pin+16));val|=((unsignedint)0X1<<(cur_led->led_pin));}writel(val,cur_led->va_DR);*ppos+=tmp;// 更新文件位置指针returntmp;// 返回写入的字节数}staticstructfile_operationsled_cdev_fops={.open=led_cdev_open,.release=led_cdev_release,.write=led_cdev_write,};/////////////////////////////////////////////////////////////////////////////////////////staticintled_pdrv_probe(structplatform_device*pdev)// 传入device,驱动需要去提取设备的资源,完成字符设备的注册等工作{structled_data*cur_led;unsignedint*led_hwinfo;structresource*mem_DR;structresource*mem_DDR;dev_tcur_dev;intret=0;printk("led platform driver probe\n");//第一步:提取平台设备提供的资源//devm_kzalloc函数申请cur_led和led_hwinfo结构体内存大小cur_led=devm_kzalloc(&pdev->dev,sizeof(structled_data),GFP_KERNEL);if(!cur_led)return-ENOMEM;led_hwinfo=devm_kzalloc(&pdev->dev,sizeof(unsignedint)*2,GFP_KERNEL);if(!led_hwinfo)return-ENOMEM;led_hwinfo=dev_get_platdata(&pdev->dev);// LED灯的寄存器偏移量 即 7cur_led->led_pin=led_hwinfo[0];mem_DR=platform_get_resource(pdev,IORESOURCE_MEM,0);// 數據寄存器mem_DDR=platform_get_resource(pdev,IORESOURCE_MEM,1);//方向寄存器cur_led->va_DR=devm_ioremap(&pdev->dev,mem_DR->start,resource_size(mem_DR));// 寄存器地址转化为虚拟地址cur_led->va_DDR=devm_ioremap(&pdev->dev,mem_DDR->start,resource_size(mem_DDR));//第二步:注册字符设备(才有fops)cur_dev=MKDEV(DEV_MAJOR,pdev->id);register_chrdev_region(cur_dev,1,"led_cdev");cdev_init(&cur_led->led_cdev,&led_cdev_fops);ret=cdev_add(&cur_led->led_cdev,cur_dev,1);if(ret<0){printk("fail to add cdev\n");gotoadd_err;}device_create(led_test_class,NULL,cur_dev,NULL,DEV_NAME"%d",pdev->id);// /dev/led0//platform_set_drvdata函数,将LED数据信息存入在平台驱动结构体中pdev->dev->driver_data中platform_set_drvdata(pdev,cur_led);return0;add_err:unregister_chrdev_region(cur_dev,1);returnret;}staticintled_pdrv_remove(structplatform_device*pdev){dev_tcur_dev;//platform_get_drvdata,获取当前LED灯对应的结构体structled_data*cur_data=platform_get_drvdata(pdev);printk("led platform driver remove\n");cur_dev=MKDEV(DEV_MAJOR,pdev->id);//cdev_del删除对应的字符设备cdev_del(&cur_data->led_cdev);//删除/dev目录下的设备device_destroy(led_test_class,cur_dev);//unregister_chrdev_region, 注销掉当前的字符设备编号unregister_chrdev_region(cur_dev,1);return0;}staticstructplatform_device_idled_pdev_ids[]={{.name="led_pdev"},//匹配{}};MODULE_DEVICE_TABLE(platform,led_pdev_ids);//平台总线匹配过程中 ,只会根据id_table中的name值进行匹配,若和平台设备的name值相等,则表示匹配成功; 反之,则匹配不成功,表明当前内核没有该驱动能够支持的设备。staticstructplatform_driverled_pdrv={//////////////////////////重点.probe=led_pdrv_probe,.remove=led_pdrv_remove,.driver.name="led_pdev",.id_table=led_pdev_ids,};static__initintled_pdrv_init(void){printk("led platform driver init\n");//class_create,来创建一个led类led_test_class=class_create(THIS_MODULE,"test_leds");//调用函数platform_driver_register,注册我们的平台驱动结构体,这样当加载该内核模块时, 就会有新的平台驱动加入到内核中。 第20-27行,注销platform_driver_register(&led_pdrv);return0;}module_init(led_pdrv_init);static__exitvoidled_pdrv_exit(void){printk("led platform driver exit\n");platform_driver_unregister(&led_pdrv);class_destroy(led_test_class);}module_exit(led_pdrv_exit);MODULE_AUTHOR("Embedfire");MODULE_LICENSE("GPL");MODULE_DESCRIPTION("the example for platform driver");

6.设备驱动模型

...__list_add(structlist_head*new,//node3structlist_head*prev,//headstructlist_head*next)//node2{next->prev=new;//node2->prev 等于 node3new->next=next;//node3->next等于 node2new->prev=prev;//node3->prev 等于 head}

如下是类似管道,先入先出,node123从左往右按顺序排列。list_add函数调用如下栈,先入后出,如上图。

// kobject是最基础的结构体,/sys/device目录是一个kobject,里面还有很多kobject// drivers/base/core.c:staticstructkobject*class_kobj=NULL;staticstructkobject*devices_kobj=NULL;/* 创建/sys/class */class_kobj=kobject_create_and_add("class",NULL);/* 创建 /sys/devices */devices_kobj=kobject_create_and_add("devices",NULL);


如上注册完bus后生成如下目录。

如下创建链接就行,没必要重复。


如下比device注册做的事情少很多。

创建链接指向module,表示该驱动是由哪个内核模块提供能力,同样module也反向指向驱动,表示它提供什么样的驱动能力。

driver去扫匹配成功后不退出,因为挂在这总线上设备不一定只有一个,比如usb上挂鼠标,挂键盘。device只扫一个driver就停止。bus_for_each_drv扫的是klist_driver。如下粉色框是driver_register封装。

http://www.cnnetsun.cn/news/1495950.html

相关文章:

  • Ubuntu TeamViewer 安装与使用
  • STM32硬件SPI驱动W25Q128实战:CubeMX配置+DMA高速读写避坑指南
  • 如何用go2rtc在5分钟内搭建零延迟的家庭监控系统
  • NNDL作业五--前馈神经网络作业题
  • 终极指南:如何用org-roam保护敏感笔记的安全与隐私
  • 告别Python版本混乱!Windows下用pyenv-win + virtualenvwrapper打造多项目开发环境(保姆级避坑指南)
  • 怎么查看员工文件操作记录?简单操作 防止员工删除文件
  • 毕业设计救星:ENSP校园网仿真全流程指南(附ACL防火墙配置)
  • 5分钟找回你的Windows 10:ExplorerPatcher终极界面定制指南
  • J4125工控机变身全能家庭服务器:PVE安装配置全流程(含网卡直通)
  • Rocky、Ubuntu软件源定制
  • 终极指南:RealChar语音识别技术深度对比——Whisper、Google Speech与本地部署方案
  • 终极指南:如何在MyBinder上快速配置和运行Rust Jupyter笔记本
  • RealChar AI对话系统测试与调试完整指南:确保稳定性的10个关键方法
  • Harbor+Helm实战:5分钟搞定Chart包推送与拉取(附常见报错解决)
  • 别再为UE4崩溃发愁了!手把手教你用RoadRunner 2022b为CARLA 0.9.10制作高性能仿真地图(Ubuntu 18.04环境)
  • 终极使用指南:5步掌握Retrieval-based Voice Conversion WebUI核心功能
  • 解锁visio的ai潜能,用快马平台kimi模型打造你的智能图表设计助手
  • SleeperX:Mac终极睡眠管理解决方案,重新定义电源控制体验
  • Android 7+ 抓包神器:用ADB一键导入Charles证书到系统根目录(附模拟器避坑指南)
  • 10倍效率提升:http-parser深度调试指南与实战案例
  • 幻兽帕鲁低配优化DX10/11配置方案:Steam启动参数与游戏文件修改排障手册
  • RWKV7-1.5B-g1a镜像优势解析:离线加载兼容+软链修复+日志分级排查设计
  • 逆向工程师的日常:我是如何从混淆的JS里‘挖’出极验滑块关键算法的
  • ShopXO电商系统文件读取漏洞复现:从零搭建到漏洞利用(附Burp抓包技巧)
  • web.py 2025技术路线图:从Python 2到现代Web框架的终极演进指南
  • Trelby:打破创作枷锁的开源剧本引擎
  • 手机也能跑AI?DeepSeek-R1-Distill-Qwen-1.5B零基础部署指南
  • 6S推行总反弹?搭配红牌作战才是根治良方
  • 终极指南:Basscss隐藏元素的7种最佳实现方案