目录
一、分层与分离
对于输入子系统分为上下两层,分离下层为两部分,input_handler部分和input_device部分,对于buttons.c硬件相关,evdev.c纯软件稳定,输入子系统参考:输入子系统分析与测试,input.c向上提供统一的接口,不管硬件怎么变化,只需要我们完善硬件相关的操作
二、总线设备驱动模型
是一种左右建立联系的机制,将驱动程序分为两部分,总线设备驱动模型分为三个概念:总线、设备和驱动,即Bus/Dev/Drv模型,是一种编程的机制,例如做一个led的驱动程序,在dev中提供好相关的资源,在drv中根据dev的资源来操作,而在内核中有很多的drv和dev,在include\linux\platform_device.h中,dev部分会注册一个platform_device(平台设备),而drv会去注册一个platform_driver
struct platform_device {
const char *name;
int id;
bool id_auto;
struct device dev;
u32 num_resources;
struct resource *resource;
const struct platform_device_id *id_entry;
char *driver_override; /* Driver name to force a match */
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
/* arch specific additions */
struct pdev_archdata archdata;
};
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
struct device_driver driver;
const struct platform_device_id *id_table;
bool prevent_deferred_probe;
};
怎么去分辨哪个drv对应相应的dev,bus总线来管理,对于bus总线是一个虚拟、软件方面的概念,与硬件无关,以面向对象的思想,在drivers\base\platform.c中,根据bus中的match函数来匹配,其中"if (of_driver_match_device(dev, drv))"是根据设备树来匹配的,再到后面是根据drv部分的id_table与dev部分的name来匹配,若匹配不成功再尝试drv部分的name与dev部分的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);
}
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.dma_configure = platform_dma_configure,
.pm = &platform_dev_pm_ops,
};
2.1 dev部分
在device部分我们更加关注硬件相关的操作,主要的过程如下
把device放入bus的dev链表
从bus的drv链表取出每一个drv,用bus的match函数判断drv能否支持dev
若可以支持,调用drv的probe函数(核心)
2.2 drv部分
在driver部分我们更加关注软件相关,这部分的代码一般比较稳定,不需要做太大的变动,主要的过程如下
把driver放入bus的drv链表
从bus的dev链表取出每一个dev,用bus的match函数判断dev能否支持drv
若可以支持,调用drv的probe函数(核心)
三、利用总线驱动模型点灯
3.1 device部分
先分配/设置/注册一个platform_device,在platform_device结构体设置:resource资源(在其中加入led的寄存器和引脚)、name(建立连接时名字需要一致)、 release释放函数(remove需要) ,在应用程序若没有close设备文件,但是驱动程序中的release函数可能还会被调用, 是因为当应用程序退出后,linux系统会帮我们清除应用程序所打开的文件,调用文件对应的close函数
#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>
/* 分配/设置/注册一个platform_device */
static struct resource led_resource[] = {
[0] = {
.start = 0x56000050, //资源有寄存器起始地址,换寄存器地址就改这里
.end = 0x56000050 + 8 - 1,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = 5, //哪个引脚,换灯的时候改这里就可以
.end = 5,
.flags = IORESOURCE_IRQ,
}
};
static void led_release(struct device * dev)
{
}
static struct platform_device led_dev = {//定义了一个平台设备
.name = "myled",
.id = -1,
.num_resources = ARRAY_SIZE(led_resource), //资源
.resource = led_resource,
.dev = {
.release = led_release,
},
};
static int led_dev_init(void)
{
platform_device_register(&led_dev); //注册一个平台设备 最终调用到device_add,放到平台总线的设备链表中去
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");
3.2 driver部分
分配/设置/注册一个platform_driver,在platform_driver结构体设置:probe函数来配置led的资源,并注册字符设备驱动程序,在remove函数中卸载字符设备驱动程序
#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>
/* 分配/设置/注册一个platform_driver */
static int major;
static struct class *led_class;
static volatile unsigned long *gpio_con;
static volatile unsigned long *gpio_dat;
static int pin;
static int led_open(struct inode *inode, struct file *file)
{
printk("first_drv_open\n");
/* 配置为输出 */
*gpio_con &= ~(0x3<<(pin*2));
*gpio_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)
{
// 点灯
*gpio_dat &= ~(1<<pin);
}
else
{
// 灭灯
*gpio_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);
gpio_con = ioremap(res->start, res->end - res->start + 1);
gpio_dat = gpio_con + 1;
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);//参数三代表资源中的第零个
pin = res->start;
/* 注册字符设备驱动程序 */
printk("led_probe, found led\n");
major = register_chrdev(0, "myled", & led_fops);
led_class = class_create(THIS_MODULE, "myled");
class_device_create(led_class, NULL, MKDEV(major, 0), NULL, "led"); /* /dev/led */
return 0;
}
static int led_remove(struct platform_device *pdev)
{
/* 卸载字符设备驱动程序 */
/* iounmap */
printk("led_remove, remove led\n");
class_device_destroy(led_class, MKDEV(major, 0));
class_destroy(led_class);
unregister_chrdev(major, "myled");
iounmap(gpio_con);
return 0;
}
struct platform_driver led_driver = {
.probe = led_probe,
.remove = led_remove,
.driver = {
.name = "myled",
}
};
static int led_drv_init(void)
{
platform_driver_register(&led_driver);
return 0;
}
static void led_drv_exit(void)
{
platform_driver_unregister(&led_driver);
}
module_init(led_drv_init);
module_exit(led_drv_exit);
MODULE_LICENSE("GPL");
- Makefile:编译程序
KERN_DIR = /work/system/linux-2.6.22.6
all:
make -C $(KERN_DIR) M=`pwd` modules
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
obj-m += led_drv.o
obj-m += led_dev.o
3.3 测试部分
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
/* led_test on
* led_test off
*/
int main(int argc, char **argv)
{
int fd;
int val = 1;
fd = open("dev/led",O_RDWR);
if(fd < 0)
printf("cant't open!\n");
if(argc != 2){
printf("Usage:\n");
printf("%s <on|off>\n", argv[0]);
}
if(strcmp(argv[1], "on") == 0){
val = 1;
}else{
val = 0;
}
write(fd, &val, 4);
return 0;
}
编译驱动程序和测试程序,加载驱动并执行测试程序
# insmod led_drv.ko
# insmod led_dev.ko
led_probe, found led
# ./led_test on
first_drv_open
# ./led_test off
first_drv_open
# rmmod led_dev
led_remove, remove led
总结
- 先加载led_drv.ko,根据name在bus总线中找不到drv支持的dev,加载led_dev.ko后,建立了联系,调用drv的probe函数,在probe函数打印led_probe, found led,卸载dev驱动后会调用drv的remove函数,在remove函数中打印led_remove, remove led
- 一个简单的字符驱动程序被拆分成两部分dev硬件相关部分和drv(稳定,不需要大改动)部分,这就是分离
- 因此只需修改dev中的pin引脚就可以改变其他led的控制,不需要改动drv
3.4 改进代码
对于上述代码是用内核2.6的,现在用内核4.19
drv部分,跟dev提供的资源来操作相关的不同寄存器实现点灯
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#define S3C2440_GPA(n) (0<<16 | n)
#define S3C2440_GPB(n) (1<<16 | n)
#define S3C2440_GPC(n) (2<<16 | n)
#define S3C2440_GPD(n) (3<<16 | n)
#define S3C2440_GPE(n) (4<<16 | n)
#define S3C2440_GPF(n) (5<<16 | n)
#define S3C2440_GPG(n) (6<<16 | n)
#define S3C2440_GPH(n) (7<<16 | n)
#define S3C2440_GPI(n) (8<<16 | n)
#define S3C2440_GPJ(n) (9<<16 | n)
static int led_pin;
static volatile unsigned int *gpio_con;
static volatile unsigned int *gpio_dat;
/* 123. 分配/设置/注册file_operations
* 4. 入口
* 5. 出口
*/
static int major;
static struct class *led_class;
static unsigned int gpio_base[] = {
0x56000000, /* GPACON */
0x56000010, /* GPBCON */
0x56000020, /* GPCCON */
0x56000030, /* GPDCON */
0x56000040, /* GPECON */
0x56000050, /* GPFCON */
0x56000060, /* GPGCON */
0x56000070, /* GPHCON */
0, /* GPICON */
0x560000D0, /* GPJCON */
};
static int led_open (struct inode *node, struct file *filp)
{
/* 把LED引脚配置为输出引脚 */
/* GPF5 - 0x56000050 */
int bank = led_pin >> 16;
int base = gpio_base[bank];
int pin = led_pin & 0xffff;
gpio_con = ioremap(base, 8);
if (gpio_con) {
printk("ioremap(0x%x) = 0x%x\n", base, gpio_con);
}
else {
return -EINVAL;
}
gpio_dat = gpio_con + 1;
*gpio_con &= ~(3<<(pin * 2));
*gpio_con |= (1<<(pin * 2));
return 0;
}
static ssize_t led_write (struct file *filp, const char __user *buf, size_t size, loff_t *off)
{
/* 根据APP传入的值来设置LED引脚 */
unsigned char val;
int pin = led_pin & 0xffff;
copy_from_user(&val, buf, 1);
if (val)
{
/* 点灯 */
*gpio_dat &= ~(1<<pin);
}
else
{
/* 灭灯 */
*gpio_dat |= (1<<pin);
}
return 1; /* 已写入1个数据 */
}
static int led_release (struct inode *node, struct file *filp)
{
printk("iounmap(0x%x)\n", gpio_con);
iounmap(gpio_con);
return 0;
}
static struct file_operations myled_oprs = {
.owner = THIS_MODULE,
.open = led_open,
.write = led_write,
.release = led_release,
};
static int led_probe(struct platform_device *pdev)
{
struct resource *res;
/* 根据platform_device的资源进行ioremap */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
led_pin = res->start;
major = register_chrdev(0, "myled", &myled_oprs);
led_class = class_create(THIS_MODULE, "myled");
device_create(led_class, NULL, MKDEV(major, 0), NULL, "led"); /* /dev/led */
return 0;
}
static int led_remove(struct platform_device *pdev)
{
unregister_chrdev(major, "myled");
device_destroy(led_class, MKDEV(major, 0));
class_destroy(led_class);
return 0;
}
struct platform_driver led_drv = {
.probe = led_probe,
.remove = led_remove,
.driver = {
.name = "myled",
}
};
static int myled_init(void)
{
platform_driver_register(&led_drv);
return 0;
}
static void myled_exit(void)
{
platform_driver_unregister(&led_drv);
}
module_init(myled_init);
module_exit(myled_exit);
MODULE_LICENSE("GPL");
dev部分,当我们想改变led的引脚时,更改led_resource资源就可以了
#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>
#define S3C2440_GPA(n) (0<<16 | n)
#define S3C2440_GPB(n) (1<<16 | n)
#define S3C2440_GPC(n) (2<<16 | n)
#define S3C2440_GPD(n) (3<<16 | n)
#define S3C2440_GPE(n) (4<<16 | n)
#define S3C2440_GPF(n) (5<<16 | n)
#define S3C2440_GPG(n) (6<<16 | n)
#define S3C2440_GPH(n) (7<<16 | n)
#define S3C2440_GPI(n) (8<<16 | n)
#define S3C2440_GPJ(n) (9<<16 | n)
/* 分配/设置/注册一个platform_device */
static struct resource led_resource[] = {
[0] = {
.start = S3C2440_GPF(5),
.end = S3C2440_GPF(5),
.flags = IORESOURCE_MEM,
},
};
static void led_release(struct device * dev)
{
}
static struct platform_device led_dev = {
.name = "myled",
.id = -1,
.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");