自建免费网站,wordpress建立数据库,做网站学什么代码,网页素材网站有哪些前言 #xff08;1#xff09;前面已经已经详细介绍了LED驱动如何进行编写的代码。如果韦东山Linux驱动入门实验班#xff08;4#xff09;LED驱动已经看懂了#xff0c;驱动入门实验班后面的那些模块实验#xff0c;其实和单片机操作差不太多了。我就不再浪费时间进行讲…前言 1前面已经已经详细介绍了LED驱动如何进行编写的代码。如果韦东山Linux驱动入门实验班4LED驱动已经看懂了驱动入门实验班后面的那些模块实验其实和单片机操作差不太多了。我就不再浪费时间进行讲解了。 2本文主要进行讲解驱动的分层和分离平台总线模型。 3对于韦东山老师的代码我进行了微调因为他代码写的比较着急所以我感觉有些地方感觉有点冗余了就自作主张的进行了调整。但是原来他的部分没有删除如果你认为韦东山老师的比我的好就可以注释掉我微调的部分。微调部分我会进行说明 4代码仓库gitee仓库github仓库 5注意大家下载我仓库里面的代码再阅读本文会跟好理解一点。我仓库里面的代码依旧加上了详细的注释觉得本文过于冗余。可以看我仓库代码和正点原子的文档学习 驱动分层
什么是驱动分层 1通过前面的学习我们会发现Linux开发将一个程序分成了两个层应用层和驱动层。但是我们在编写驱动层程序的时候会发现驱动还是和指定的引脚以及开发板有关。 2这时候肯定会有人说了我只需要你改一下哪个数组就可以了啊这有什么难的吗 3不难但是对于 Linux 这样一个成熟、庞大、复杂的操作系统代码的重用性非常重要否则的话就会在 Linux 内核中存在大量无意义的重复代码。尤其是驱动程序因为驱动程序占用了 Linux内核代码量的大头如果不对驱动程序加以管理任由重复的代码肆意增加那么用不了多久Linux 内核的文件数量就庞大到无法接受的地步。 4可能有些人会说了我之前编写的程序也没有重复啊。不过就只是要改一下驱动程序的gpios[]结构体的定义。但是你要知道在开发大型项目的时候你将设备信息存入驱动程序中。每次调整都需要打开对应的驱动程序如果我们不小心动了驱动程序的某个地方产生了bug排查是非常麻烦的。所以说为了让Linux系统不变的臃肿同时为了安全性于是Linux决定将驱动程序拆分成驱动程序和硬件描述程序驱动程序一旦编写了基本就不再需要再打开了。 5现在我们知道了驱动程序与硬件描述程序的剥离之后。Linux于是提出了平台总线模型。我们将驱动程序和硬件描述信息都挂载在这个总线上面。 6有了这个总线我们就可以先把我们设备上的一些信息挂载在总线上。然后给这些设备命一个名字。 7当我们需要使用指定设备的时候驱动就可以通过给设备命的名字来找到设备信息。一旦驱动和设备匹配上就可以执行指定的程序了。 8这样做有什么好处这样我们的驱动程序可以直接进行移植不需要受限于任何设备。而我们拿到一块开发板之后可以直接把驱动移植过来然后再编写设备描述信息即可驱动代码几乎不再需要进行什么调整。 设备如何挂载在平台总线上 上面我们说了Linux发明了平台总线我们只需要将设备和驱动挂载在总线上如果匹配上了就会自动执行程序。那么设备是如何挂载在平台总线上。 platform_device_register() 1platform_device_register()这个函数可以将设备信息挂载在平台总线上。我们只需要传入struct platform_device结构体类型指针。 2如下是platform_device()结构体的定义虽然参数很多但是真正需要关注的只有nameidresourcenum_resourcesdev。 1name设备的名字 用来和驱动相匹配。 名字相同才能匹配成功 2idID 是用来区分如果设备名字相同的时候(通过在后面添加一个数字来代表不同的设备 因为有时候有这种需求) 3resource资源结构体数组。我们需要写的设备信息写在这个数组里面。 4num_resources资源结构体数量。表明这个设备结构体有多少个其实就是一个宏定义#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) 5dev与平台设备相关联的设备对象。这个只需要知道要给.release参数传入一个函数指针即可。当设备被卸载的时候会调用这个函数。 // platform 设备结构体
struct platform_device led_device {.name led_device, //platform 设备的名字 用来和 platform 驱动相匹配。 名字相同才能匹配成功.id -1, // ID 是用来区分如果设备名字相同的时候(通过在后面添加一个数字来代表不同的设备 因为有时候有这种需求).resource led_resource, //指向一个资源结构体数组。 一般包含设备信息.num_resources ARRAY_SIZE(led_resource), //资源结构体数量.dev {.release LED_release,}
};platform_device_unregister() 1这个用于将设备从平台总线上卸载。当这个设备不需要的时候我们就可以对他进行卸载。 2与platform_device_register()相同也是只需要传入struct platform_device结构体类型指针。 设备程序的入口和出口 1设备程序和驱动程序一样都是使用insmod和rmmod进行装载和卸载。程序入口都是使用module_init()宏来进行定义程序出口也都是使用module_exit()来定义。 2他也必须使用调用MODULE_LICENSE(“GPL”);指定模块为GPL协议 驱动如何挂载在平台总线上
platform_driver_register() 1这个函数只需要传入一个static struct platform_driver类型结构体指针。 2这个驱动的结构体比设备的那个结构体还是好理解很多的。 1我们只需要知道platform_driver结构体.driver中的.name和platform_device结构体中的.name一模一样就可以了。 2当设备和驱动匹配上了之后我们就会执行probe函数。如果设备和驱动一旦断开联系了就执行remove函数。你可以这么理解一对男女男生和女生结婚了那么就会颁发结婚证执行probe函数。有一天他们俩因为一些事情感情破裂了就会给他们一个离婚证执行remove函数。 static struct platform_driver led_driver {.driver {.name led_device, //根据这个名字找到设备.owner THIS_MODULE,},.probe led_drver_probe, //注册平台之后内核如果发现支持某一个平台设备这个函数就会被调用。入口函数.remove led_drver_remove, //出口函数
};platform_driver_unregister() 这个是用于在平台上卸载驱动的。和platform_driver_register()一样都是传入一个static struct platform_driver结构体指针。 和上一篇博客的区别
硬件信息不同 1这里主要就是把原来放在gpios[] 数组中的硬件描述信息放在另外一个文件中。让驱动程序与硬件平台剥离。 2原来的硬件描述是存放在一个数组中而现在的硬件描述是放在一个结构体指针里面然后通过Linux中的平台总线获得设备信息。 /********** 原来的硬件描述 **********/
static struct gpio_desc gpios[] {{131, led0 }, //引脚编号名字
};
/********** 现在的硬件描述 **********/
static struct gpio_desc *gpios; //描述gpio初始化程序不同 1在上一篇博客中我们的驱动初始化函数都是放在module_init()这个宏里面。 2但是现在不一样了module_init()这个宏里面的那个函数只做一件事情就是将static struct platform_driver结构体注册到平台总线上。 /********** 原来的module_init()中的函数任务 **********/
static int __init gpio_drv_init(void)
{//申请GPIO//将GPIO设置为输出//注册字符驱动程序//创建类//创建设备名/dev目录下的设备名
}
/********** 现在的module_init()中的函数任务 **********/
//注册时候的入口函数
static int __init led_drver_init(void)
{int ret 0;// platform驱动注册到 Linux 内核ret platform_driver_register(led_driver); //注意这里是driver表示是驱动if(ret0){printk(platform_driver_register error \n);return ret;}printk(platform_driver_register ok \n);return ret;
}初始化程序执行条件不同 1原来我们初始化程序只需要装载驱动程序即可。 2现在不一样了我们需要注册相互匹配的设备程序和驱动程序初始化程序probe函数才会执行。 3注意设备程序和驱动程序的注册没有顺序。随便你先注册谁。 获取GPIO数量信息不同 1下面这里有些人可能对pdev有疑问这个是啥玩意这个东西很简单pdev就是被匹配上的设备struct platform_device结构体指针。 2当驱动和设备匹配上之后执行probe函数。而这个函数的传入值struct platform_device *pdev就是被匹配上的设备struct platform_device结构体指针。 3因为struct platform_device结构体的num_resources记录了资源结构体的数量。所以可以通过pdev - num_resources获取GPIO数量。 4我们看韦东山老师的代码会发现好长好麻烦。一开始我也是这么想的命名一条指令就可以解决搞这么长干嘛后面简单的思考了一下。代码些这么长还是一定作用的。 5我们来看platform_get_resource()这个函数就是用于返回struct platform_device结构体中资源resource中flags被标记为IORESOURCE_IRQ的GPIO的GPIO信息。当我们这个GPIO的flags被标记为IORESOURCE_IRQ就会返回一个指针指向这里。 6如果我们的设备有些GPIO的flags没有被标记为了IORESOURCE_IRQ那么就不会被count统计在内。 /********** 原来获取GPIO数量 **********/
int count sizeof(gpios)/sizeof(gpios[0]); //统计有多少个GPIO
/********** 现在获取GPIO数量作者的方法 **********/
count pdev - num_resources;
/********** 现在获取GPIO数量韦东山老师的方法 **********/
while (1)
{/* 下面这9行是用于统计有多少个GPIO的* dev一个指向 platform_device 结构的指针表示要获取资源信息的设备。* type一个无符号整数表示要获取的资源类型。在 Linux 内核中资源类型使用常量来表示例如 IORESOURCE_MEM 表示内存资源IORESOURCE_IRQ 表示中断资源等。你可以根据需要选择适当的资源类型。* num一个无符号整数表示要获取的资源的索引号。在一个设备中可能存在多个相同类型的资源通过索引号可以区分它们。* 返回值返回一个指向 resource 结构的指针表示获取到的资源信息。resource 结构包含了资源的起始地址、大小等信息。如果没有找到指定的资源函数将返回 NULL。*/led_resource platform_get_resource(pdev, IORESOURCE_IRQ, count);if (led_resource){count;}else{break;}
}
设备结构体gpios空间分配不同 1在原来的代码里面我们的gpios被定义成了一个数组gpios[]。所以当我们在这个数组里面写入设备信息的时候这个数组会自动改变空间大小。 2但是这里的设备信息不在驱动文件里面了因此我们需要使用内存的动态分配了。我们知道GPIO有多少个之后调用内存动态分配函数获取一个空间然后把这个空间的首地址指针返回给gpios。 3需要注意的一点是驱动的动态内存分配不能使用malloc而是需要使用kmalloc。这个和驱动不能使用printf函数只能使用printk函数是一个道理。 /********** 原来gpios空间分配 **********/
static struct gpio_desc gpios[] {{131, led0 }, //引脚编号名字
};
/********** 现在gpios空间分配 **********/
static struct gpio_desc *gpios; //描述gpio
/* 作用 kmalloc是Linux内核中的一个内存分配函数用于在内核空间中动态分配内存。* 它类似于C语言中的malloc函数但是在内核中使用kmalloc而不是 malloc因为内核空间和用户空间有不同的内存管理机制。* size 要分配的内存大小以字节为单位。* flags 分配内存时的标志表示内存的类型和分配策略是一个 gfp_t 类型的值。常常采用GFP_KERNEL* GFP_KERNEL是内存分配的标志之一它表示在内核中以普通的内核上下文进行内存分配。* 返回值 如果内存分配成功返回指向分配内存区域的指针。如果内存分配失败例如内存不足返回NULL。
*/
gpios kmalloc(count * sizeof(struct gpio_desc), GFP_KERNEL); 设备结构体gpios硬件信息获取不同 1原来我们直接向gpios[]数组里面写入硬件信息就可以了。 2因为现在我们将硬件信息写入了设备程序里面了所以需要其他办法获得。从设备程序中获得硬件信息有两种方法。 1通过pedv指针直接获得硬件信息。 2通过platform_get_resource()函数获得。 3 1我们对比下面的代码会发现led_resource和pdev-resource[i]是一个东西啊。 2可以这么说但是还是有一定的区别。如果pdev-resource[i]的flags不是IORESOURCE_IRQ那么就不会被提取出来成led_resource。 /********** 原来gpios获得硬件信息 **********/
static struct gpio_desc gpios[] {{131, led0 }, //引脚编号名字
};
/********** 现在gpios获得硬件信息 **********/
//通过pedv指针直接获得硬件信息。
gpios[i].gpio pdev-resource[i].start;
sprintf(gpios[i].name, %s, pdev-resource[i].name); //将platform_device.resource.name传递给gpios[i].name//通过platform_get_resource()函数获得。
struct resource *led_resource;
led_resource platform_get_resource(pdev, IORESOURCE_IRQ, i); //从节点里面取出第i项
gpios[i].gpio led_resource-start; //将需要操作的IO号传递给gpios[i].gpio
sprintf(gpios[i].name, %s, led_resource-name); //将platform_device.resource.name传递给gpios[i].name