驱动程序分层分离概念_总线驱动设备模型(第十四课)
一、 分层分离概念
分离:把硬件相关的东西抽取出来;把相对稳定的软件部分抽取出来。
分层:左右两边分别向上注册自己的东西,input.c向上提供统一给app操作的接口。每一层都专注与自己的事情。
二、 总线驱动设备模型
在(gpio_keys.c)的入口函数里注册了平台driver
看看这个注册平台driver里做了什么
再看看这个总线里面有什么
如果名字相同就会调用probe函数
如果这个probe函数想要被调用,那么左边的device就应该有一个同名的平台device,来搜索一下
无法在别的文件中搜索到这个名字,所以说这个驱动程序只不过是一个事例程序而已。
用总线驱动模型点亮LED
每注册一个平台设备或平台驱动都会加入到总线的链表,设备加入设备链表,驱动加入驱动链表。然后会进行匹配(比较名字),若匹配成功,则调用对应驱动里面的probe函数
目的:左边的(device)表示某一个LED灯。想要修改哪个LED灯,就只需要修改左边的(led_platform_dev.c)即可,右边的(led_platform_drv.c)保持稳定不变
1、先写左边的(led_platform_dev.c),搜索一下(platform_device)
仿造它
然后在入口函数里注册一个平台设备(最终会调用device_add函数加入到平台总线的设备链表里面),在出口函数里卸载平台设备
2、在写右边的(led_platform_drv.c),看看(gpio_keys.c)怎么注册平台驱动
仿造一下,注意名字要与平台设备的名字相同
在probe函数里我们做一些简单的打印工作
在平台驱动的remove函数中做与probe函数相反地事情,目前还是做一些简单的打印
注意:平台设备下的release函数必须提供
测试:
1.先卸载平台驱动,在卸载平台设备
2.先卸载平台设备,在卸载平台驱动
总结:注册平台设备和平台驱动时会将它们注册到各自的平台链表中,这时平台会进行名字的比对,若比对成功则会调用平台驱动下的probe函数,同理卸载平台设备或平台驱动就是从相应的结构链表中删除相应的结构,这时平台驱动就会remove函数来做一些清理工作。
下面在probe函数中做一些有意义的事情:注册字符设备
就是把以前驱动入口函数所做的工作搬到了probe函数中,差别就在于所需的硬件的资源需要在平台设备获取
在remove函数里释放资源
平台驱动程序(led_driver.c)
/* 分配/设置/注册一个platform_driver */
#include <linux/module.h>
#include <linux/version.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/sched.h>
#include <linux/pm.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/irq.h>
#include <asm/uaccess.h>
#include <asm/io.h>
static int major;
static struct class *led_class;
static struct class_device *led_class_dev;
static volatile unsigned int *gpf_con;
static volatile unsigned int *gpf_dat;
static int pin;
static int led_open(struct inode *inode, struct file *file)
{
/* 配置为输出 */
*gpf_con &= ~(0x3<<(pin*2));
*gpf_con |= (0x1<<(pin*2));
return 0;
}
static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
int val;
copy_from_user(&val, buf, count); // copy_to_user();
if (val == 1)
{
// 点灯
*gpf_dat &= ~(1<<pin);
}
else
{
// 灭灯
*gpf_dat |= (1<<pin);
}
return 0;
}
static struct file_operations led_fops = {
.owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
.open = led_open,
.write = led_write,
};
static int led_probe(struct platform_device *pdev)
{
struct resource *res;
/* 根据platform_device的资源进行ioremap */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
gpf_con = ioremap(res->start, res->end - res->start + 1);
gpf_dat = gpf_con + 1;
//0表示这类资源里面的第0个
res = platform_get_resource(pdev, IORESOURCE_IO, 0);
pin = res->start;
/* 注册字符设备驱动程序 */
printk("led_probe,foud led\n\r");
major = register_chrdev(0, "myled", &led_fops); //注册
led_class = class_create(THIS_MODULE, "myled");
led_class_dev = class_device_create(led_class, NULL, MKDEV(major, 0), NULL, "led");
return 0;
}
static int led_remove(struct platform_device *pdev)
{
printk("led_remove,remove led\n\r");
/* 卸载驱动 */
unregister_chrdev(major, "led");
/* 卸载设备 */
// class_device_destroy(led_class, MKDEV(major, 0));
class_device_unregister(led_class_dev);
/* 卸载类 */
class_destroy(led_class);
/* unremap */
iounmap(gpf_con);
return 0;
}
static struct platform_driver led_drv = {
.probe = led_probe,
.remove = led_remove,
.driver = {
.name = "myled",
}
};
static int led_drv_init(void)
{
platform_driver_register(&led_drv);
return 0;
}
static void led_drv_exit(void)
{
platform_driver_unregister(&led_drv);
}
module_init(led_drv_init);
module_exit(led_drv_exit);
MODULE_LICENSE("GPL");
平台设备程序(led_device.c)
#include <linux/module.h>
#include <linux/version.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/interrupt.h>
#include <linux/list.h>
#include <linux/timer.h>
#include <linux/init.h>
#include <linux/serial_core.h>
#include <linux/platform_device.h>
static struct resource led_resource[] = {
[0] = {
.start = 0x56000050,
.end = 0x56000050 + 16 - 1,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = 6,
.end = 6,
.flags = IORESOURCE_IO,
}
};
void led_release(struct device * dev)
{
printk("led_release\n\r");
}
/* 分配/设置/注册一个platform_device */
static struct platform_device led_dev = {
.name = "myled",
.id = 0,
.num_resources = ARRAY_SIZE(led_resource), //资源大小
.resource = led_resource,
.dev = {
.release = led_release,
},
};
static int led_dev_init(void)
{
platform_device_register(&led_dev);
return 0;
}
static void led_dev_exit(void)
{
platform_device_unregister(&led_dev);
}
module_init(led_dev_init);
module_exit(led_dev_exit);
MODULE_LICENSE("GPL");
测试程序(led_test.c)
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main(int argc, char **argv)
{
int fd;
int val = 1;
fd = open("/dev/led",O_RDWR);
if(fd < 0)
{
printf("can't open!\n");
}
if(argc != 2)
{
printf("Usage :\n\r");
printf("%s <on/off>\n\r", argv[0]);
return 0;
}
if(strcmp(argv[1], "on") ==0)
{
val = 1;
}
else
{
val = 0;
}
write(fd, &val, 4);
return 0;
}
测试:
装载好两个驱动程序后执行(./led_test on)后单板上对应的小灯会发亮,以后想要操作别的小灯就只需要修改(led_device.c)平台设备的相应引脚
个人发现(课程里面并没有讲解):这个总线其实就相当于输入子系统的左边(device)部分,只是把这一部分继续给拓展了,使得关于硬件方面的操作更加简洁。因为在(gpio_keys.c)中平台驱动程序的probe函数里是分配一个input_dev结构体,然后设置它,紧接着向输入子系统注册设备