一、总线设备驱动模型
总线设备驱动模型采用分层分离的方式来进行,采用bus/dev/drv的模型,把设备从驱动中剥离出来。
二、platform总线模型详解
首先查看bus_type实例platform_bus_type.
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match, //bind platform device to platform driver.
.uevent = platform_uevent,
.dma_configure = platform_dma_configure,
.pm = &platform_dev_pm_ops,
};
其中platform_match函数(在注册驱动时,这个函数会在driver_match_device(drv, dev)函数中调用),比较的顺序如下。
- platform_device. driver_override和 platform_driver.driver.name
- 设备树
- platform_device. name和 platform_driver.id_table[i].name
- platform_device.name和 platform_driver.driver.name
static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);
/* When driver_override is set, only bind to the matching driver */
if (pdev->driver_override)
return !strcmp(pdev->driver_override, drv->name);
/* Attempt an OF style match first */
if (of_driver_match_device(dev, drv))
return 1;
/* Then try ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1;
/* Then try to match against the id table */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;
/* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == 0);
}
再次,查看注册平台设备和注册平台驱动时的流程
注册平台设备:
int platform_device_register(struct platform_device *pdev)
platform_device_add(pdev);
device_add(&pdev->dev); //struct device
error = bus_add_device(dev); //放入链表
bus_probe_device(dev); //probe drivers for a new device
device_initial_probe(dev);
__device_attach(dev, true);
bus_for_each_drv(dev->bus, NULL, &data,__device_attach_driver); //在drv链表中, bus_type->subsys_private->klist_drivers
__device_attach_driver //attempt to bind device & driver together. struct device_driver和struct device
driver_match_device(drv, dev); //查看drv和dev是否匹配,drv->bus->match ? drv->bus->match(dev, drv) : 1;在这里会调用bus里面的match函数,指向了platform_match
driver_probe_device(drv, dev); //attempt to bind device & driver together,调用drv的probe函数
really_probe(dev, drv);
dev->driver = drv; //dev里面的driver = drv。将两者联系起来了。
dev->bus->probe(dev); //在platform_bus_type中没有定义probe函数,所以会调用drv->probe(dev)函数
//或者
drv->probe(dev);
注册平台驱动:
platform_driver_register(drv)
int __platform_driver_register(struct platform_driver *drv, struct module *owner)
drv->driver.owner = owner;
drv->driver.bus = &platform_bus_type;
drv->driver.probe = platform_drv_probe;
drv->driver.remove = platform_drv_remove;
drv->driver.shutdown = platform_drv_shutdown;
driver_register(&drv->driver); //register driver with bus
bus_add_driver(drv); //放入链表,这种bus type的驱动列表
driver_attach(drv); //try to bind driver to devices.
bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
__driver_attach
driver_match_device(drv, dev); //查看drv和dev是否匹配,drv->bus->match ? drv->bus->match(dev, drv) : 1;在这里会调用bus里面的match函数,指向了platform_match
device_driver_attach(drv, dev); //attach a specific driver to a specific device
driver_probe_device(drv, dev);
really_probe(dev, drv);
dev->driver = drv; //dev里面的driver = drv。将两者联系起来了。
dev->bus->probe(dev); //在platform_bus_type中没有定义probe函数,所以会调用drv->probe(dev)函数
//或者
drv->probe(dev);
三、LED软件架构
四、详细代码
4.1 led_drv.c
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include "led_operations.h"
#define LED_NUM 2
/*1、确定主设备号*/
static int major = 0;
//static char kernel_buf[1024];
static struct class *led_class;
struct led_operations* p_led_opr;
#define MIN(a,b) (a < b ? a : b)
static ssize_t led_drv_read (struct file *, char __user *, size_t, loff_t *);
static ssize_t led_drv_write (struct file *, const char __user *, size_t, loff_t *);
static int led_drv_open (struct inode *, struct file *);
static int led_drv_close (struct inode *, struct file *);
void led_class_create_device(int minor)
{
device_create(led_class, NULL, MKDEV(major, minor), NULL, "tanzhenwen_led%d",minor);// /dev/tanzhenwen_led0,设备节点
}
EXPORT_SYMBOL(led_class_create_device);
void led_class_destroy_device(int minor)
{
device_destroy(led_class, MKDEV(major, minor));
}
EXPORT_SYMBOL(led_class_destroy_device);
void register_led_operations(struct led_operations *opr)
{
p_led_opr = opr;
}
EXPORT_SYMBOL(register_led_operations);
/*2、定义自己的file_operations结构体*/
static const struct file_operations led_drv_fops = {
.owner = THIS_MODULE,
.open = led_drv_open,
.read = led_drv_read,
.write = led_drv_write,
.release = led_drv_close,
};
/*3、实现对应的open、read、write等函数,填入file_operations结构体内*/
static ssize_t led_drv_read (struct file *file, char __user *buf, size_t size, loff_t *off)
{
//int err;
printk("%s %s line %d\n",__FILE__, __FUNCTION__, __LINE__);
//err = copy_to_user(buf, kernel_buf, MIN(1024, size));
return 0;
}
static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *off)
{
char status;
struct inode *inode = file_inode(file);
int dev = iminor(inode) & 0x0f;
printk("%s %s line %d\n",__FILE__, __FUNCTION__, __LINE__);
copy_from_user(&status, buf, 1);
/*根据此设备号和status控制LED*/
p_led_opr->ctl(dev,status);
return 1;
}
static int led_drv_open (struct inode *node, struct file *file)
{
int minor = iminor(node) & 0x0f;
printk("%s %s line %d\n",__FILE__, __FUNCTION__, __LINE__);
/*根据此设备号初始化结构体*/
//p_led_opr->init(minor);
p_led_opr->init();
p_led_opr->open(minor);
return 0;
}
static int led_drv_close (struct inode *node, struct file *file)
{
printk("%s %s line %d\n",__FILE__, __FUNCTION__, __LINE__);
return 0;
}
/*4、吧file_operations结构体告诉内核:注册驱动程序*/
/*5、谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数*/
static int __init led_init(void)
{
int err;
// int i;
printk("%s %s line %d\n",__FILE__, __FUNCTION__, __LINE__);
major = register_chrdev(0, "tanzhenwen_led", &led_drv_fops);
//自动创建设备节点
led_class = class_create(THIS_MODULE, "tanzhenwen_led_class");
err = PTR_ERR(led_class);
if (IS_ERR(led_class))
{
unregister_chrdev(major,"tanzhenwen_led");
return -1;
}
//在这里如果这么用的话,小心在注册这个驱动的时候,p_led_opr为空
//p_led_opr->init();
/*
for(i=0; i<LED_NUM; i++)
{
device_create(led_class, NULL, MKDEV(major, i), NULL, "tanzhenwen_led%d",i);// /dev/tanzhenwen_led0,设备节点
}
*/
//p_led_opr = get_board_led_opr();
return 0;
}
/*6、有入口函数就有出口函数:卸载驱动时就会去调用这个出口函数*/
static void __exit led_exit(void)
{
// int i;
printk("%s %s line %d\n",__FILE__, __FUNCTION__, __LINE__);
/*
for(i = 0; i < LED_NUM; i++)
{
device_destroy(led_class, MKDEV(major, i));
}
*/
//在这里如果这么用的话,小心在卸载这个驱动的时候,p_led_opr为空
if(p_led_opr)
p_led_opr->exit();
class_destroy(led_class);
unregister_chrdev( major, "tanzhenwen_led");
}
/*7、其它完善:提供设备信息,自动创建设备节点*/
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Tanzhenwen");
led_drv.h
#ifndef _LED_DRV_H
#define _LED_DRV_H
#include "led_operations.h"
void led_class_create_device(int minor);
void led_class_destroy_device(int minor);
void register_led_operations(struct led_operations *opr);
#endif /* _LEDDRV_H */
4.2 chip_demo_gpio.c
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/platform_device.h>
#include <linux/module.h>
#include <asm/io.h>
#include "led_operations.h"
#include "led_resource.h"
#include "led_drv.h"
/* 寄存器 */
//RCC PLL4 Control Register (RCC_PLL4CR) 0x50000000+0x894 //使能PLL4时钟
static volatile unsigned int *RCC_PLL4CR;
//RCC_MP_AHB4ENSETR地址:0x50000000 + 0xA28 //使能GPIO
static volatile unsigned int *RCC_MP_AHB4ENSETR;
//GPIOA_MODER地址:0x50002000 + 0x00,设置bit[21:20]=0b01, //PA10输出
static volatile unsigned int *GPIOA_MODER;
//GPIOA_ODR地址: 0x50002000 + 0x14 //读寄存、修改值、写回去(低效)
static volatile unsigned int *GPIOA_ODR;
//GPIOA_BSRR地址: 0x50002000 + 0x18 // 直接写寄存器,一次操作即可,高效
static volatile unsigned int *GPIOA_BSRR;
//GPIOG_MODER地址:0x50008000 + 0x00, //PG8输出
static volatile unsigned int *GPIOG_MODER;
//GPIOG_ODR地址: 0x50008000 + 0x14 //读寄存、修改值、写回去(低效)
static volatile unsigned int *GPIOG_ODR;
//GPIOG_BSRR地址: 0x50008000 + 0x18 // 直接写寄存器,一次操作即可,高效
static volatile unsigned int *GPIOG_BSRR;
static int g_ledpins[100];
static int g_ledcnt = 0;
static int board_demo_led_init(void)
{
/*
printk("init gpio:group %d, pin %d \n", GROUP(g_ledpins[which]), PIN(g_ledpins[which]));
switch(GROUP(g_ledpins[which]))
{
case 0 : printk("init the pin of group 0 ... ");break;
case 1 : printk("init the pin of group 1 ... ");break;
case 2 : printk("init the pin of group 2 ... ");break;
case 3 : printk("init the pin of group 3 ... ");break;
}
*/
/* ioremap :使用虚拟地址,后期可以用虚拟地址来访问寄存器 */
//ioremap(mem_addr, mem_len);
//RCC PLL4 Control Register (RCC_PLL4CR) 0x50000000+0x894 //使能PLL4时钟
if(!RCC_PLL4CR)
{
RCC_PLL4CR = ioremap(0x50000000+0x894, 4);
//RCC_MP_AHB4ENSETR地址:0x50000000 + 0xA28 //使能GPIO
RCC_MP_AHB4ENSETR = ioremap(0x50000000 + 0xA28, 4);
//GPIOA_MODER地址:0x50002000 + 0x00,设置bit[21:20]=0b01, //PA10输出
GPIOA_MODER = ioremap(0x50002000 + 0x00, 4);
//GPIOA_ODR地址: 0x50002000 + 0x14 //读寄存、修改值、写回去(低效)
GPIOA_ODR = ioremap(0x50002000 + 0x14, 4);
//GPIOA_BSRR地址: 0x50002000 + 0x18 // 直接写寄存器,一次操作即可,高效
GPIOA_BSRR = ioremap(0x50002000 + 0x18, 4);
//PG8输出
//GPIOG_MODER地址:0x50008000 + 0x00,
GPIOG_MODER = ioremap(0x50008000 + 0x00, 4);
//GPIOG_ODR地址: 0x50008000 + 0x14 //读寄存、修改值、写回去(低效)
GPIOG_ODR = ioremap(0x50008000 + 0x14, 4);
//GPIOG_BSRR地址: 0x50008000 + 0x18 // 直接写寄存器,一次操作即可,高效
GPIOG_BSRR = ioremap(0x50008000 + 0x18, 4);
}
printk("%s %s %d, led\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
static void board_stm32mp157_led_exit(void)
{
iounmap(RCC_PLL4CR);
iounmap(RCC_MP_AHB4ENSETR);
iounmap(GPIOA_MODER);
iounmap(GPIOA_ODR);
iounmap(GPIOA_BSRR);
iounmap(GPIOG_MODER);
iounmap(GPIOG_ODR);
iounmap(GPIOG_BSRR);
}
static int board_stm32mp157_led_open(int which)
{
if(which == 0)
{
/* 配置gpio,使能、配置为gpio,配置输入输出 */
//配置时钟
*RCC_PLL4CR |= (1<<0);
while((*RCC_PLL4CR & (1<<1)) == 0);
//使能GPIOA
*RCC_MP_AHB4ENSETR |= (1<<0);
//设置PA10用作输出,先清零,在设置为输出
*GPIOA_MODER &= ~(3<<20);
*GPIOA_MODER |= (0x01<<20);
}
else if(which == 1)
{
/* 配置gpio,使能、配置为gpio,配置输入输出 */
//PG8
//配置时钟
*RCC_PLL4CR |= (1<<0);
while((*RCC_PLL4CR & (1<<1)) == 0);
//使能GPIOG
*RCC_MP_AHB4ENSETR |= (1<<6);
//设置PG8用作输出,先清零,在设置为输出
*GPIOG_MODER &= ~(3<<16);
*GPIOG_MODER |= (0x01<<16);
}
return 0;
}
static int board_demo_led_ctl(int which, char status)
{
//printk("%s %s %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 0 : printk("set the pin of group 0 ... ");break;
case 1 : printk("set the pin of group 1 ... ");break;
case 2 : printk("set the pin of group 2 ... ");break;
case 3 : printk("set the pin of group 3 ... ");break;
}*/
if(which == 0)
{
if(status)
{
//灯亮
*GPIOA_BSRR = (1<<26); //输出低电平 GPIOA10
}
else
{
//灯灭
*GPIOA_BSRR = (1<<10); //输出高电平
}
}
else if(which == 1)
{
if(status)
{
//灯亮
*GPIOG_BSRR = (1<<24); //输出低电平 GPIOA10
}
else
{
//灯灭
*GPIOG_BSRR = (1<<8); //输出高电平
}
}
return 0;
}
static struct led_operations board_demo_led_opr = {
.init = board_demo_led_init,
.open = board_stm32mp157_led_open,
.exit = board_stm32mp157_led_exit,
.ctl = board_demo_led_ctl,
};
/*
*
*/
static int chip_demo_gpio_probe(struct platform_device *dev)
{
int i = 0;
struct resource *res;
/* 记录引脚 */
while(1)
{
/* 获取资源 */
res = platform_get_resource(dev, IORESOURCE_IRQ, i++);
if(!res) break;
g_ledpins[g_ledcnt] = res->start;
/* device create */
led_class_create_device(g_ledcnt);
g_ledcnt++;
}
printk("end of chip_demo_gpio_probe\n");
return 0;
}
static int chip_demo_gpio_remove(struct platform_device *dev)
{
int i;
/* device destroy */
for(i = 0; i< g_ledcnt; i++)
{
led_class_destroy_device(i);
}
g_ledcnt = 0;
return 0;
}
static struct platform_driver chip_demo_gpio_drv = {
.probe = chip_demo_gpio_probe,
.remove = chip_demo_gpio_remove,
.driver = {
.name = "tanzhenwen_led",
},
};
static int chip_demo_gpio_drv_init(void)
{
int err;
printk("start of chip_demo_gpio_probe\n");
err = platform_driver_register(&chip_demo_gpio_drv); //最终会调用chip_demo_gpio_probe函数
register_led_operations(&board_demo_led_opr); //注册board_demo_led_opr结构体,告诉led_drv.c中
printk("end of chip_demo_gpio_probe\n");
return 0;
}
static void chip_demo_gpio_drv_exit(void)
{
platform_driver_unregister(&chip_demo_gpio_drv);
}
module_init(chip_demo_gpio_drv_init);
module_exit(chip_demo_gpio_drv_exit);
MODULE_LICENSE("GPL");
led_operations.h
#ifndef _LED_OPERATIONS_H_
#define _LED_OPERATIONS_H_
struct led_operations{
int (*init) (void); /*初始化LED,which-哪一个LED*/
int (*open) (int which);
void (*exit) (void);
int (*ctl) (int which, char status);/*控制对应的LED的亮灭*/
//int (*read) (int which,char status);/*读对应引脚的状态*/
};
struct led_operations *get_board_led_opr(void);
#endif
led_resource.h
#ifndef _LED_RESOURCE_H_
#define _LED_RESOURCE_H_
/* bit[31:16] = gropu*/
/* bit[15:0] = which pin*/
#define GROUP(x) (x>>16)
#define PIN(x) (x&0xFFFF)
#define GROUP_PIN(g,pin) ((g<<16) | (pin))
struct led_resource{
int pin;
};
struct led_resource *get_led_resource(void);
#endif
4.3 board_A_led.c
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/ioport.h>
#include <linux/platform_device.h>
#include "led_resource.h"
static struct resource board_A_resources[] =
{
{
.start = GROUP_PIN(1,10),
//.end = ,
.flags = IORESOURCE_IRQ,
},
{
.start = GROUP_PIN(7,8),
//.end = ,
.flags = IORESOURCE_IRQ,
}
};
static void led_drv_release(struct device *dev)
{
}
static struct platform_device board_A_dev = {
.name = "tanzhenwen_led",
.resource = board_A_resources,
.num_resources = ARRAY_SIZE(board_A_resources),
.dev = {
.release = led_drv_release,
},
};
static int led_dev_init(void)
{
int err;
err = platform_device_register(&board_A_dev);
return 0;
}
static void led_dev_exit(void)
{
platform_device_unregister(&board_A_dev);
}
module_init(led_dev_init);
module_exit(led_dev_exit);
MODULE_AUTHOR("Tanzhenwen");
MODULE_VERSION("1.0");
MODULE_LICENSE("GPL");
4.4 Makefile
KERN_DIR = /home/book/100ask_stm32mp157_pro-sdk/Linux-5.4
all:
make -C $(KERN_DIR) M=`pwd` modules
$(CROSS_COMPILE)gcc -o led_test led_test.c
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
rm -f hello_drv_test
# 参考内核源码drivers/char/ipmi/Makefile
# 要想把a.c, b.c编译成ab.ko, 可以这样指定:
# ab-y := a.o b.o
# obj-m += ab.o
# led_drv.c board_demo.c 编译成 100ask.ko
obj-m := led_drv.o board_A_led.o chip_demo_gpio.o
四、测试
led_test.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
/*
* ./ledtest /dev/100ask_led0 on
* ./ledtest /dev/100ask_led0 off
*/
int main(int argc, char **argv)
{
int fd;
char status;
/* 1. 判断参数 */
if (argc != 3)
{
printf("Usage: %s <dev> <on | off>\n", argv[0]);
return -1;
}
/* 2. 打开文件 */
fd = open(argv[1], O_RDWR);
if (fd == -1)
{
printf("can not open file %s\n", argv[1]);
return -1;
}
/* 3. 写文件 */
if (0 == strcmp(argv[2], "on"))
{
status = 1;
write(fd, &status, 1);
}
else
{
status = 0;
write(fd, &status, 1);
}
close(fd);
return 0;
}