面向对象
-
抽象结构体,进行注册和控制函数
-
Linux 驱动 = 驱动框架 + 单片机
总线驱动设备
-
缺点 每一个硬件需要在内核中写一个.c文件生成 platform_device 结构体
因此采用dts 在外部编译为 dtb 再放入内核,不会导致内核臃肿
-
platform_driver 结构体和 platform_driver 通过名字匹配
-
总线进行匹配顺序(platform_match 函数进行互相匹配):
-
先查看 driver_override(使用最少)
-
再匹配驱动程序中idtable 和device 中的name
-
最后比较 name 和driver里的name
-
-
写程序
-
分配/设置/注册 platform_device 结构体
-
分配/设置/注册 platform_driver 结构体
-
在其中的 probe 函数里,
-
分配/设置/注册 file_operations 结构体,
-
并从 platform_device 中确实所用硬件资源。 指定 platform_driver 的名字。
-
LED驱动框架
写在前面:
什么是ko文件? ko 是一个模块文件,可以装载进内核
模块是一个目标文件,可以完成某种独立的功能,但是自身不是一个独立的进程,不能单独运行,可以动态的载入模块,使其成为内核代码的一部分,与内核其他代码的地位完全相同,当不需要某个模块功能时,还可以卸载模块。
因此可通过 insmod 和rmmod 装载和卸载
board_A_led.c
-
根据编译顺序进行梳理 首先是board_A_led.c
-
该文件为创建platform_device , 提供资源 ;
static struct platform_device board_A_led_dev = { .name = "100ask_led", .num_resources = ARRAY_SIZE(resources), .resource = resources, .dev = { .release = led_dev_release, }, };
-
其中resource定义资源
-
start 表示开始的地址, phys_addr_t 是一个 ui 变量
-
flag 标记,表示资源的类型
-
name 标记该类资源的名字,注意 resource
-
static struct resource resources[] = {
{
.start = GROUP_PIN(5,8),
.flags = IORESOURCE_IRQ,
.name = "100ask_led_pin",
},
{
.start = GROUP_PIN(5,8),
.flags = IORESOURCE_IRQ,
.name = "100ask_led_pin",
},
};
-
要将其编译成ko文件需要有入口函数和出口
-
在insmod时候调用入口函数 init 将 结构体注册进platform_devic 的链表中platform_device_register(&board_A_led_dev); 注册时会匹配一次,依靠bus总线中的match函数,match成功会调用,驱动的probe 但是现在还没注册驱动所以不会匹配成功
static int __init led_dev_init(void) { int err; err = platform_device_register(&board_A_led_dev); return 0; }
-
exit 中platform_device_unregister(&board_A_led_dev);
-
同样需要定义一下入口和出口以及协议
module_init(led_dev_init); module_exit(led_dev_exit); MODULE_LICENSE("GPL");
leddrv.c
由于设备节点的创建需要根据具体的资源来决定,但device_create 需要主设备号 ,因此在文件chip_demo_gpio.c想调用device_create 等函数 需要导出,因此需要先insmod ,注意不要出现交叉依赖,比如A 编译需要B 的函数 ,B编译需要 A 的函数,导致交叉依赖
void led_class_create_device(int minor)
{
device_create(led_class, NULL, MKDEV(major, minor), NULL, "100ask_led%d", minor); /* /dev/100ask_led0,1,... */
}
void led_class_destroy_device(int minor)
{
device_destroy(led_class, MKDEV(major, minor));
}
void register_led_operations(struct led_operations *opr)
{
p_led_opr = opr;
}
EXPORT_SYMBOL(led_class_create_device);
EXPORT_SYMBOL(led_class_destroy_device);
EXPORT_SYMBOL(register_led_operations);
static struct file_operations led_drv = {
.owner = THIS_MODULE,
.open = led_drv_open,
.read = led_drv_read,
.write = led_drv_write,
.release = led_drv_close,
};
-
结构体定义如上,注册在初始化函数中
-
init 函数中 ,注册,创建类,但不创建设备节点
major = register_chrdev(0, "100ask_led", &led_drv); /* /dev/led */ led_class = class_create(THIS_MODULE, "100ask_led_class");
而真正的操作 也就是 open 和write 需要chip_demo_gpio.c 中调用register_led_operations 函数
通过该函数获得p_led_opr = opr; 也就是获得单板的具体操作 ,初始化以及control 在Open 以及write函数中调用
static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset) { int err; char status; struct inode *inode = file_inode(file); int minor = iminor(inode); printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); err = copy_from_user(&status, buf, 1); /* 根据次设备号和status控制LED */ p_led_opr->ctl(minor, status); return 1; } static int led_drv_open (struct inode *node, struct file *file) { int minor = iminor(node); printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); /* 根据次设备号初始化LED */ p_led_opr->init(minor); return 0; }
chip_demo_gpio.c
-
创建platform_driver 结构体,注册时,bus 根据名字进行匹配,具体的匹配原则可看手册,先override 再 id tabel 最后是名字,在注册时,若匹配成功会调用probe 函数
static struct platform_driver chip_demo_gpio_driver = { .probe = chip_demo_gpio_probe, .remove = chip_demo_gpio_remove, .driver = { .name = "100ask_led", }, };
-
初始化函数,先注册platform 结构体,再将led_opr 传给全局变量,也就是注册led_operations结构体
static int __init chip_demo_gpio_drv_init(void) { int err; err = platform_driver_register(&chip_demo_gpio_driver); register_led_operations(&board_demo_led_opr); return 0; }
在调用platform_driver_register后bus 的match 函数会将名字 和device 中进行匹配,若匹配成功调用probe函数
-
通过一个resource结构体指针,在platform_device结构体中寻找对应类型的资源,i 表示索引号 也就是对应个数,如果返回为NULL表示已经找完了
-
记录每个资源的start 值,后续根据该值来判断设备,并且创建设备节点
-
static int chip_demo_gpio_probe(struct platform_device *pdev)
{
struct resource *res;
int i = 0;
while (1)
{
res = platform_get_resource(pdev, IORESOURCE_IRQ, i++);
if (!res)
break;
g_ledpins[g_ledcnt] = res->start;
led_class_create_device(g_ledcnt);
g_ledcnt++;
}
return 0;
}
chip_demo_gpio_remove 函数,同样是挨个寻找资源并销毁设备节点
-
接下来是led_opeations 结构体,其中对led 进行操作,注意源代码只根据Group进行判断,并不会依据pin值进行判断,which值得是资源中的第几个,也就是说若Group相同,那么你两个资源都可以点亮同一个LED,此处应该添加PIN的判断
static int board_demo_led_ctl (int which, char status) /* 控制LED, which-哪个LED, status:1-亮,0-灭 */ { //printk("%s %s line %d, led %d, %s\n", __FILE__, __FUNCTION__, __LINE__, which, status ? "on" : "off"); printk("set led %s: group %d, pin %d\n", status ? "on" : "off", GROUP(g_ledpins[which]), PIN(g_ledpins[which])); switch(GROUP(g_ledpins[which])) { case 5: { if (status) /* on: output 0*/ { /* d. 设置GPIO5_DR输出低电平 * set GPIO5_DR to configure GPIO5_IO03 output 0 * GPIO5_DR 0x020AC000 + 0 * bit[3] = 0b0 */ *GPIO5_DR &= ~(1<<3); } else /* off: output 1*/ { /* e. 设置GPIO5_IO3输出高电平 * set GPIO5_DR to configure GPIO5_IO03 output 1 * GPIO5_DR 0x020AC000 + 0 * bit[3] = 0b1 */ *GPIO5_DR |= (1<<3); } break; } case 1: ……
至此整个框架大致梳理完毕
细节: 次设备号用于区分同一主设备下的不同类型,主设备号相同则说明是同一类型,而次设备号在创建的时候就会指定对应的次设备号,也就是全局变量g_ledcnt ,而具体的单板操作同样也是通过次设备号进行操作!