Linux驱动分离与分层
目的:为了提高软件的重用,跨平台性能!!!
控制器驱动和设备驱动分离!!!
将驱动分离:主机控制器驱动和设备驱动,主机控制器驱动一般是半导体厂家写的。在linux驱动框架下编写具体的设备驱动。
二、驱动-总线-设备
驱动和设备。 驱动就是具体的设备驱动。 设备:是设备属性,包括地址范围,如果是IIC器件地址,速度。
总线主要完成总线下设备和驱动之间的匹配。
驱动
驱动数据类型为device_driver,驱动程序向内核注册驱动采用driver_register。 向总线注册驱动的时候,会检查当前总线下的所有设备,有没有与此驱动匹配的设备,如果有的话就执行驱动里面的probe函数。使用driver_register注册驱动。
设备
设备数据类型为device,通过device_register向内核注册设备。 驱动与设备匹配以后驱动的probe函数就会执行,probe函数就是驱动编写人员去编写的!!!!
三、platform平台驱动模型
1、方便开发,linux提出了驱动分离与分层。 2、进一步引出了驱动-总线-设备驱动模型,或者框架。 3、对于SOC内部的RTC,timer等等不好归结为具体的总线,为此linux内核提出了一个虚拟总线:platform总线,platform设备和platform驱动。
四、platform总线注册
注册的内容就是: struct bus_type platform_bus_type = { .name = “platform”, .dev_groups = platform_dev_groups, .match = platform_match, .uevent = platform_uevent, .pm = &platform_dev_pm_ops, } 对于platform平台而言,platform_match函数就是负责驱动和设备的匹配。
1、无设备树的时候,此时需要驱动开发人员编写设备注册文件,使用platform_device_register函数注册设备。
2,有设备树,修改设备树的设备节点即可。当设备与platform的驱动匹配以后,就会执行platform_driver->probe函数。
使用platform_driver_register向内核注册platform驱动。 platform_driver_register -> __platform_driver_register (platform_driver) -> 设置driver的probe为platform_drv_probe, //如果platform_driver的 // probe函数有效的话。 -> driver_register ->执行device_drive->probe,对于platform总线,也就是platform_drv_probe函数。而platform_drv_probe函数会执行platform_driver下的probe函数。
结论:向内核注册platform驱动的时候,如果驱动和设备匹配成功,最终会执行platform_driver的probe函数。
五、platform匹配过程
根据前面的分析,驱动和设备匹配是通过bus->match函数,platform总线下的match函数就是:platform_match。 platform_match -> of_driver_match_device,设备树 -> acpi_driver_match_device ACPI类型的 -> platform_match_id 根据platform_driver-> id_table -> strcmp(pdev->name, drv->name) //最终的就是比较字符串,就是platform_device->name,和platform_driver->driver->name。无设备树情况下使用。
1、有设备树的时候: of_driver_match_device -> of_match_device(drv->of_match_table, dev) //of_match_table非常重要, 类型为of_device_id。 //compatible属性
实验程序编写
无设备树
两部分:platform_driver、platform_device。以点灯为例。 1、编写向platform总线注册设备。编写驱动需要寄存器地址信息,地址信息使用设备信息,定义在platform_device里面,因此需要在驱动里面获取设备中的信息,或者叫资源。使用函数platform_get_resource()
有设备树
有设备树的时候设备device是由设备树描述的,因此不需要向总线注册设备,而是直接修改设备树。只需要修改设备树,然后编写driver驱动。
驱动和设备匹配成功以后,设备信息就会从设备树节点转为platform_device结构体。
platform提供了很多API函数去获取设备相关信息的。
2022.6.10 左神手撕代码 P57
设备加载
cd /sys/bus/ cd platform/
驱动加载
后期驱动不需要修改 只需要改设备。
无设备树的情况下:
设备代码:
......
#include <linux/irq.h>
#include <linux/platform_device.h>
#define CCM_CCGR1_BASE (0X020C406C)
#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)
#define GPIO1_DR_BASE (0X0209C000)
#define GPIO1_GDIR_BASE (0X0209C004)
#define REGISTER_LENGTH 4
void leddevice_release(struct device *dev)
{
printk("leddevice release\r\n");
}
static struct resource led_resources[] = {
[0] = {
.start = CCM_CCGR1_BASE,
.end = (CCM_CCGR1_BASE + REGISTER_LENGTH -1),
.flags = IORESOURCE_MEM,
},
[1] = {
.start = SW_MUX_GPIO1_IO03_BASE,
.end = (SW_MUX_GPIO1_IO03_BASE + REGISTER_LENGTH -1),
.flags = IORESOURCE_MEM,
},
[2] = {
.start = SW_PAD_GPIO1_IO03_BASE,
.end = (SW_PAD_GPIO1_IO03_BASE + REGISTER_LENGTH -1),
.flags = IORESOURCE_MEM,
},
[3] = {
.start = GPIO1_DR_BASE,
.end = (GPIO1_DR_BASE + REGISTER_LENGTH -1),
.flags = IORESOURCE_MEM,
},
[4] = {
.start = GPIO1_GDIR_BASE,
.end = (GPIO1_GDIR_BASE + REGISTER_LENGTH -1),
.flags = IORESOURCE_MEM,
},
};
static struct platform_device leddevice = {
.name = "imx6ull-led",
.id = -1,
.dev = {
.release = leddevice_release,
},
.num_resources = ARRAY_SIZE(led_resources),
.resource = led_resources,
};
static int __init leddevice_init(void)
{
return platform_device_register(&leddevice);
}
static void __exit leddevice_exit(void){
platform_device_unregister(&leddevice);
}
module_init(leddevice_init);
module_exit(leddevice_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("qhy");
驱动代码:
....
#include <linux/platform_device.h>
#define LEDDEV_CNT 1
#define LEDDEV_NAME "platled"
#define LEDOFF 0
#define LEDON 1
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;
struct leddev_dev{
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
int major;
};
struct leddev_dev leddev;
void led0_switch(u8 sta)
{
u32 val = 0;
if(sta == LEDON){
val = readl(GPIO1_DR);
val &= ~(1 << 3);
writel(val, GPIO1_DR);
}else if(sta == LEDOFF){
val = readl(GPIO1_DR);
val|= (1 << 3);
writel(val, GPIO1_DR);
}
}
static int led_open(struct inode *inode, struct file *filp)
{
filp->private_data = &leddev;
return 0;
}
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
int retvalue;
unsigned char databuf[1];
unsigned char ledstat;
retvalue = copy_from_user(databuf, buf, cnt);
if(retvalue < 0) {
return -EFAULT;
}
ledstat = databuf[0];
if(ledstat == LEDON) {
led0_switch(LEDON);
}else if(ledstat == LEDOFF) {
led0_switch(LEDOFF);
}
return 0;
}
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.write = led_write,
};
static int led_probe(struct platform_device *dev)
{
int i = 0;
int ressize[5];
u32 val = 0;
struct resource *ledsource[5];
printk("led driver and device has matched!\r\n");
for (i = 0; i < 5; i++)
{
ledsource[i] = platform_get_resource(dev,IORESOURCE_MEM,i);
if (ledsource[i] == NULL)
{
dev_err(&dev->dev, "No MEM resource for always on\n");
return -ENXIO;
}
ressize[i] = resource_size(ledsource[i]);
}
IMX6U_CCM_CCGR1 = ioremap(ledsource[0]->start,ressize[0]);
SW_MUX_GPIO1_IO03 = ioremap(ledsource[1]->start,ressize[1]);
SW_PAD_GPIO1_IO03 = ioremap(ledsource[2]->start,ressize[2]);
GPIO1_DR = ioremap(ledsource[3]->start,ressize[3]);
GPIO1_GDIR = ioremap(ledsource[4]->start,ressize[4]);
val = readl(IMX6U_CCM_CCGR1);
val &= ~(3 << 26);
val |= (3 << 26);
writel(val, IMX6U_CCM_CCGR1);
writel(5, SW_MUX_GPIO1_IO03);
writel(0x10B0, SW_PAD_GPIO1_IO03);
val = readl(GPIO1_GDIR);
val &= ~(1 << 3);
val |= (1 << 3);
writel(val, GPIO1_GDIR);
val = readl(GPIO1_DR);
val |= (1 << 3) ;
writel(val, GPIO1_DR);
if (leddev.major) {
leddev.devid = MKDEV(leddev.major, 0);
register_chrdev_region(leddev.devid, LEDDEV_CNT, LEDDEV_NAME);
} else {
alloc_chrdev_region(&leddev.devid, 0, LEDDEV_CNT, LEDDEV_NAME);
leddev.major = MAJOR(leddev.devid);
}
leddev.cdev.owner = THIS_MODULE;
cdev_init(&leddev.cdev, &led_fops);
cdev_add(&leddev.cdev, leddev.devid, LEDDEV_CNT);
leddev.class = class_create(THIS_MODULE, LEDDEV_NAME);
if (IS_ERR(leddev.class)) {
return PTR_ERR(leddev.class);
}
leddev.device = device_create(leddev.class, NULL, leddev.devid, NULL, LEDDEV_NAME);
if (IS_ERR(leddev.device)) {
return PTR_ERR(leddev.device);
}
return 0;
}
static int led_remove(struct platform_device *dev)
{
iounmap(IMX6U_CCM_CCGR1);
iounmap(SW_MUX_GPIO1_IO03);
iounmap(SW_PAD_GPIO1_IO03);
iounmap(GPIO1_DR);
iounmap(GPIO1_GDIR);
cdev_del(&leddev.cdev);
unregister_chrdev_region(leddev.devid, LEDDEV_CNT);
device_destroy(leddev.class, leddev.devid);
class_destroy(leddev.class);
return 0;
}
static struct platform_driver led_driver = {
.driver = {
.name = "imx6ull-led",
},
.probe = led_probe,
.remove = led_remove,
};
static int __init leddriver_init(void)
{
return platform_driver_register(&led_driver);
}
static void __exit leddriver_exit(void)
{
platform_driver_unregister(&led_driver);
}
module_init(leddriver_init);
module_exit(leddriver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("qhy");
应用层代码
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#define LEDOFF 0
#define LEDON 1
int main(int argc, char *argv[])
{
int fd, retvalue;
char *filename;
unsigned char databuf[2];
if(argc != 3){
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
fd = open(filename, O_RDWR);
if(fd < 0){
printf("file %s open failed!\r\n", argv[1]);
return -1;
}
databuf[0] = atoi(argv[2]);
retvalue = write(fd, databuf, sizeof(databuf));
if(retvalue < 0){
printf("LED Control Failed!\r\n");
close(fd);
return -1;
}
retvalue = close(fd);
if(retvalue < 0){
printf("file %s close failed!\r\n", argv[1]);
return -1;
}
return 0;
}
有设备树情况下:
有设备树的情况下,设备是由设备树描述的,不需要向总线注册设备,而是直接修改设备树。 只需要修改设备树,然后编写驱动。 设备树配置!!!
左手撕 P2022.6.11
驱动和设备匹配成功后,设备信息就会从设备树节点转成platform_device结构体。
platform 提供很多获取资源的函数。
代码分解
头文件包含
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
字符设备创建
#define LED_NAME "gpioled"
#define LED_COUNT 1
#define LEDOFF 0
#define LEDON 1
struct gpioled_dev {
struct cdev cdev;
struct class *class;
struct device *device;
dev_t devid;
int major;
int minor;
struct device_node *nd;
int led_gpio;
};
struct gpioled_dev gpioled;
驱动函数
static int gpioled_open(struct inode *inode,struct file *filp)
{
filp->private_data = &gpioled;
return 0;
}
static ssize_t gpioled_write(struct file *filp, const char __user *buf,size_t cnt, loff_t *offt)
{
struct gpioled_dev *dev = (struct gpioled_dev *)filp->private_data;
int retvalue = 0;
unsigned char databuf[1];
retvalue = copy_from_user(databuf,buf,cnt);
if (retvalue < 0)
{
printk("kernel write failed!\r\n");
return -EFAULT;
}
if (databuf[0] == LEDON)
{
gpio_set_value(dev->led_gpio,0);
}
else if(databuf[0] == LEDOFF){
gpio_set_value(dev->led_gpio,1);
}
return 0;
}
static int gpioled_release(struct inode *inode,struct file *filp)
{
return 0;
}
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.write = gpioled_write,
.open = gpioled_open,
.release = gpioled_release,
};
led_probe(字符设备创建流程)
struct of_device_id led_of_match[] = {
{.compatible = "alientek,gpioled"},
{},
};
static int led_probe(struct platform_device *dev)
{
int ret = 0;
gpioled.major = 0;
if (gpioled.major){
gpioled.devid = MKDEV(gpioled.major,0);
ret = register_chrdev_region(gpioled.devid,LED_COUNT,LED_NAME);
} else{
ret = alloc_chrdev_region(&gpioled.devid,0,LED_COUNT,LED_NAME);
gpioled.major = MAJOR(gpioled.devid);
gpioled.minor = MINOR(gpioled.devid);
}printk("led major = %d led minor = %d \r\n",gpioled.major,gpioled.minor);
if (ret < 0){
goto faile_devid;
}
gpioled.cdev.owner = THIS_MODULE;
cdev_init(&gpioled.cdev,&led_fops);
ret = cdev_add(&gpioled.cdev,gpioled.devid,LED_COUNT);
if(ret<0){
goto fail_cdev;
}
gpioled.class = class_create(THIS_MODULE,LED_NAME);
if(IS_ERR(gpioled.class)){
ret = PTR_ERR(gpioled.class);
printk("can't class_create \r\n");
goto fail_class;
}
gpioled.device = device_create(gpioled.class,NULL,gpioled.devid,NULL,LED_NAME);
if(IS_ERR(gpioled.device)){
ret = PTR_ERR(gpioled.device);
printk("can't device_create \r\n");
goto fail_device;
}
gpioled.nd = dev->dev.of_node;
gpioled.led_gpio = of_get_named_gpio(gpioled.nd,"led-goios",0);
if (gpioled.led_gpio < 0)
{
printk("can't find gpio \r\n");
goto fail_findnode;
}
printk("led gpio number = %d\r\n",gpioled.led_gpio);
ret = gpio_request(gpioled.led_gpio,"led-gpio");
if (ret)
{
printk("gpio_request errno \r\n");
ret = -EINVAL;
}
ret = gpio_direction_output(gpioled.led_gpio,1);
if (ret)
{
goto fail_setoutput;
}
gpio_set_value(gpioled.led_gpio,0);
printk("led_probe\r\n");
return 0;
fail_setoutput:
gpio_free(gpioled.led_gpio);
fail_findnode:
device_destroy(gpioled.class,gpioled.devid);
fail_device:
class_destroy(gpioled.class);
fail_class:
cdev_del(&gpioled.cdev);
fail_cdev:
unregister_chrdev_region(gpioled.devid,LED_COUNT);
faile_devid:
return ret;
}
led_remove函数
static int led_remove(struct platform_device *dev)
{
gpio_set_value(gpioled.led_gpio,1);
cdev_del(&gpioled.cdev);
unregister_chrdev_region(gpioled.devid,LED_COUNT);
device_destroy(gpioled.class,gpioled.devid);
class_destroy(gpioled.class);
gpio_free(gpioled.led_gpio);
return 0;
}
platform_driver 函数
static struct platform_driver led_driver = {
.driver = {
.name = "imx6ull-led",
.of_match_table = led_of_match,
},
.probe = led_probe,
.remove = led_remove,
};
模块加载与卸载
static int __init leddriver_init(void)
{
return platform_driver_register(&led_driver);
}
static void __exit leddriver_exit(void)
{
platform_driver_unregister(&led_driver);
}
module_init(leddriver_init);
module_exit(leddriver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("qhy");
|