platform总线驱动代码分析

版权声明:本文为博主原创文章,未经博主允许不得转载。
https://blog.csdn.net/huangweiqing80/article/details/82776920

一、platform 驱动的工作过程

platform模型驱动编程,需要实现platform_device(设备)与platform_driver(驱动)在platform(虚拟总线)上的注册、匹配,相互绑定,然后再做为一个普通的字符设备进行相应的应用,总之如果编写的是基于字符设备的platform驱动,在遵循并实现platform总线上驱动与设备的特定接口的情况下,最核心的还是字符设备的核心结构:cdev、 file_operations(他包含的操作函数接口)、dev_t(设备号)、设备文件(/dev)等,因为用platform机制编写的字符驱动,它的本质是字符驱动。

我们要记住,platform 驱动只是在字符设备驱动外套一层platform_driver 的外壳。

在一般情况下,2.6内核中已经初始化并挂载了一条platform总线在sysfs文件系统中。那么我们编写platform模型驱动时,需要完成两个工作:

a – 实现platform驱动

b – 实现platform设备
我们在上一篇说过实现platform设备有两种方式

然而在实现这两个工作的过程中还需要实现其他的很多小工作,在后面介绍。platform模型驱动的实现过程核心架构就很简单,如下所示:

platform驱动模型三个对象:platform总线、platform设备、platform驱动。

1、platform总线对应的内核结构:struct bus_type–>它包含的最关键的函数:match() (要注意的是,这块由内核完成,我们不参与)

2、platform设备对应的内核结构:struct platform_device–>注册:platform_device_register(unregister)

3、platform驱动对应的内核结构:struct platform_driver–>注册:platform_driver_register(unregister)

那具体platform总线的作用是什么呢:

设备(或驱动)注册的时候,都会引发总线调用自己的match函数来寻找目前platform总线是否挂载有与该设备(或驱动)名字匹配的驱动(或设备),如果存在则将双方绑定;

如果先注册设备,驱动还没有注册,那么设备在被注册到总线上时,将不会匹配到与自己同名的驱动,然后在驱动注册到总线上时,因为设备已注册,那么总线会立即匹配与绑定这时的同名的设备与驱动,再调用驱动中的probe函数等;

如果是驱动先注册,同设备驱动一样先会匹配失败,匹配失败将导致它的probe函数暂不调用,而是要等到设备注册成功并与自己匹配绑定后才会调用。

二、实现platform 驱动与设备的详细过程

1、思考问题?

在分析platform 之前,可以先思考一下下面的问题:

a – 为什么要用 platform 驱动?不用platform驱动可以吗?

b – 设备驱动中引入platform 概念有什么好处?

现在先不回答,看完下面的分析就明白了,后面会附上总结。

2、设备资源结构体

在struct platform_device 结构体中有一重要成员 struct resource *resource

struct resource {
	resource_size_t start;  资源起始地址   
	resource_size_t end;   资源结束地址
	const char *name;      
	unsigned long flags;   区分是资源什么类型的
	struct resource *parent, *sibling, *child;
};
 
#define IORESOURCE_MEM        0x00000200
#define IORESOURCE_IRQ        0x00000400   

flags 指资源类型,我们常用的是 IORESOURCE_MEM、IORESOURCE_IRQ 这两种。start 和 end 的含义会随着 flags而变更,如

a – flags为IORESOURCE_MEM 时,start 、end 分别表示该platform_device占据的内存的开始地址和结束值;

b – flags为 IORESOURCE_IRQ 时,start 、end 分别表示该platform_device使用的中断号的开始地址和结束值;

下面看一个实例:

static struct  resource beep_resource[] =
{
	[0] = {
        	.start = 0x114000a0,
		.end = 0x114000a0+0x4,
        	.flags = IORESOURCE_MEM,
	},
 
	[1] = {
        	.start = 0x139D0000,
        	.end = 0x139D0000+0x14,
        	.flags = IORESOURCE_MEM,
	},
};

设备资源结构体的意义?
我们可以将我们需要用到的设备资源resource定义在平台设备platform device中,然后我们在平台驱动platform driver中拿出来使用,如:我们在platform device中定义led的IO引脚号,然后我们在platform driver中操作点灯的时候将platform device中定义的resource中的IO引脚号取出来进行电平操作点灯。

3、将字符设备添加到 platform的driver中

前面我们提到platform 驱动只是在字符设备驱动外套一层platform_driver 的外壳,下面我们看一下platform_driver的壳:

static struct file_operations hello_ops=
{
	.open = hello_open,
	.release = hello_release,
	.unlocked_ioctl = hello_ioctl,
};
 
static int hello_remove(struct platform_device *pdev)
{
	注销分配的各种资源
}
 
static int hello_probe(struct platform_device *pdev)
{
	1.申请设备号
	2.cdev初始化注册,&hello_ops
	3.从pdev读出硬件资源
	4.对硬件资源初始化,ioremap,request_irq( )
}
 
static int hello_init(void)
{
	只注册 platform_driver
}
 
static void hello_exit(void)
{
	只注销 platform_driver
}

可以看到,模块加载和卸载函数仅仅通过paltform_driver_register()、paltform_driver_unregister() 函数进行 platform_driver 的注册和注销,而原先注册和注销字符设备的工作已经被移交到 platform_driver 的 probe() 和 remove() 成员函数中。

4、platform是如何匹配device和driver

是platform driver调用platform_driver_register注册platform driver,或者platform_add_devices注册platform device时候会调用到platform_match函数进行匹配,与我们之前讲过的总线的匹配一样,我们可以参考总线驱动模型

5、解决问题

现在可以回答这两个问题了

a – 为什么要用 platform 驱动?不用platform驱动可以吗?

b – 设备驱动中引入platform 概念有什么好处?

引入platform模型符合Linux 设备模型 —— 总线、设备、驱动,设备模型中配套的sysfs节点都可以用,方便我们的开发;当然你也可以选择不用,不过就失去了一些platform带来的便利;

设备驱动中引入platform 概念,隔离BSP和驱动。在BSP中定义platform设备和设备使用的资源、设备的具体匹配信息,而在驱动中,只需要通过API去获取资源和数据,做到了板相关代码和驱动代码的分离,使得驱动具有更好的可扩展性和跨平台性。

三、实例

1、前言

本例中通过使用Linux驱动模型中的platform总线和led驱动框架编写出来的led驱动代码来分析platform总线的工作原理,对此对代码做如下分析:

在platform总线(任意总线)下的驱动代码都是要分为两部分:设备和驱动,在platform总线下是platform_device和platform_driver。

关于这个问题,我在我的上一篇博客中已经做了很好的说明。对于设备部分的注册:

(1)一般是内核的移植工程师在做移植的时候添加的,在我的这个移植好的内核中是放在arch/arm/mach-s5pv210/mach-x210.c文件中,所以如果移植工程师没有添加你需要编写的驱动对应的设备,那么就需要你自己添加,你可以直接在这个文件中添加,系统启动的时候就会直接加载这个文件的代码,所以设备就会被注册到我们的platform总线下,那么就会添加到platform总线管理下的device设备管理相关的数据结构中去(链表),此时platform总线下的match函数就会自动进行匹配(每注册一个设备或者驱动match函数都会被调用),因为此时
相应的驱动还没被注册,所以匹配肯定是失败的;当我们把驱动也注册之后,也会把驱动添加到platform总线管理下的drive驱动管理相关的数据结构中去(也是一个链表),platform总线将会再次执行match函数,此时match函数就会匹配成功,platform总线下的设备和驱动就建立了对应关系了,那么设备就能够工作了。
arch/arm/plat-samsung/devs.c arch/arm/plat-samsung/devs.c

+    // 定义一个结构体用于放置本设备的私有数据
+    struct x210_led_platdata {
+        unsigned int gpio;    // led设备用到的GPIO 
+        char *device_name;    // led设备在/sys/class/leds/目录下的名字
+        char *gpio_name;      // 使用gpiolob申请gpio资源时分配的名字             
+    };
+
+    // 定义x210_led_platdata类型的变量,分别对应板子上的4颗led小灯
+    static struct x210_led_platdata x210_led1_pdata = {
+        .gpio = S5PV210_GPJ0(3),
+        .device_name = "led1",
+        .gpio_name = "led1-gpj0_3",
+    };
+
+    static struct x210_led_platdata x210_led2_pdata = {
+        .gpio = S5PV210_GPJ0(4),
+        .device_name = "led2",
+        .gpio_name = "led2-gpj0_4",
+    };
+
+    static struct x210_led_platdata x210_led3_pdata = {
+        .gpio = S5PV210_GPJ0(5),
+        .device_name = "led3",
+        .gpio_name = "led3-gpj0_5",
+    };
+
+    static struct x210_led_platdata x210_led4_pdata = {
+        .gpio = S5PV210_GPD0(1),
+        .device_name = "led4",
+        .gpio_name = "led4-gpd0_1",
+    };
+
+    // 定义4个platform_device结构体
+    static struct platform_device x210_led1 = {
+        .name        = "x210_led",
+       .id        = 0,
+      .dev        = {
+         .platform_data    = &x210_led1_pdata,
+        .release = x210_led1_release,
+        },
+    };
+    
+    static struct platform_device x210_led2 = {
+       .name        = "x210_led",
+       .id        = 1,
+        .dev        = {
+            .platform_data    = &x210_led2_pdata,
+            .release = x210_led2_release,
+        },
+    };
+    
+    static struct platform_device x210_led3 = {
+        .name        = "x210_led",
+        .id        = 2,
+       .dev        = {
+            .platform_data    = &x210_led3_pdata,
+            .release = x210_led3_release,
+        },
+    };
+    
+    static struct platform_device x210_led4 = {
+        .name        = "x210_led",
+        .id        = 3,
+       .dev        = {
+            .platform_data    = &x210_led4_pdata,
+            .release = x210_led4_release,
+        },
+    };

arch/arm/mach-s3c24xx/mach-smdk2410.c

    static struct platform_device *smdk2410_devices[] __initdata = {
    	&s3c_device_ohci,
    	&s3c_device_lcd,
    	&s3c_device_wdt,
    	&s3c_device_i2c0,
    	&s3c_device_iis,
 +      &x210_led4
 +      &x210_led4
 +      &x210_led4
 +      &x210_led4
    };

只要在板级文件中添加上上面的代码就可以代替下面的leds-x210-device.c。因为系统初始化时会调用arch/arm/mach-s3c24xx/mach-smdk2410.c中的smdk2410_init --> platform_add_devices
(2)设备注册部分也可以单独编写, 我们下驱动的时候提供 xxxxx_device.c(用来编写设备部分)和xxx_driver(用来编写驱动部分),将他们编译成模块,系统启动之后分别使用insmod装载设备和驱动(顺序无所谓)。这种情况一般使用在调试阶段,如果确定我们的驱动是没有bug的情况下,最好还是把驱动编译进内核,把他们放在他们应该在的位置。

2、led驱动代码

本例子采用的是单独编写编译的方式,代码分析如下:

(1)设备部分:leds-x210-device.c

#include <linux/module.h>        // module_init  module_exit
#include <linux/init.h>            // __init   __exit
#include <linux/fs.h>
#include <linux/leds.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <mach/gpio.h>
#include <linux/platform_device.h>

// 定义一个结构体用于放置本设备的私有数据
struct x210_led_platdata {
    unsigned int gpio;    // led设备用到的GPIO 
    char *device_name;    // led设备在/sys/class/leds/目录下的名字
    char *gpio_name;      // 使用gpiolob申请gpio资源时分配的名字             
};


// 定义x210_led_platdata类型的变量,分别对应板子上的4颗led小灯
static struct x210_led_platdata x210_led1_pdata = {
    .gpio = S5PV210_GPJ0(3),
    .device_name = "led1",
    .gpio_name = "led1-gpj0_3",
};

static struct x210_led_platdata x210_led2_pdata = {
    .gpio = S5PV210_GPJ0(4),
    .device_name = "led2",
    .gpio_name = "led2-gpj0_4",
};

static struct x210_led_platdata x210_led3_pdata = {
    .gpio = S5PV210_GPJ0(5),
    .device_name = "led3",
    .gpio_name = "led3-gpj0_5",
};

static struct x210_led_platdata x210_led4_pdata = {
    .gpio = S5PV210_GPD0(1),
    .device_name = "led4",
    .gpio_name = "led4-gpd0_1",
};


// 定义4个release函数,当我们卸载设备时会调用platform_device结构体中的device结构体下的release函数
void x210_led1_release(struct device *dev)
{
    printk(KERN_INFO "x210_led1_release\n");
}

void x210_led2_release(struct device *dev)
{
    printk(KERN_INFO "x210_led1_release\n");
}

void x210_led3_release(struct device *dev)
{
    printk(KERN_INFO "x210_led1_release\n");
}

void x210_led4_release(struct device *dev)
{
    printk(KERN_INFO "x210_led1_release\n");
}


// 定义4个platform_device结构体
static struct platform_device x210_led1 = {
    .name        = "x210_led",
    .id        = 0,
    .dev        = {
        .platform_data    = &x210_led1_pdata,
        .release = x210_led1_release,
    },
};

static struct platform_device x210_led2 = {
    .name        = "x210_led",
    .id        = 1,
    .dev        = {
        .platform_data    = &x210_led2_pdata,
        .release = x210_led2_release,
    },
};

static struct platform_device x210_led3 = {
    .name        = "x210_led",
    .id        = 2,
    .dev        = {
        .platform_data    = &x210_led3_pdata,
        .release = x210_led3_release,
    },
};

static struct platform_device x210_led4 = {
    .name        = "x210_led",
    .id        = 3,
    .dev        = {
        .platform_data    = &x210_led4_pdata,
        .release = x210_led4_release,
    },
};


// 将4个platform_device结构体地址放入一个数组中
static struct platform_device *x210_devices[] = {
    &x210_led1,
    &x210_led2,
    &x210_led3,
    &x210_led4,
};

// 入口函数
static int __init leds_x210_init(void)
{
    if (platform_add_devices(x210_devices, ARRAY_SIZE(x210_devices)))  // 循环注册platform平台设备
    {
        printk(KERN_ERR "platform_add_devices failed.\n");
        return -1;
    }
    
    return 0;
}

// 出口函数(卸载)
static void __exit leds_x210_exit(void)
{
    int i = 0;
    for (i = 0; i < 4; i++)
       platform_device_unregister(x210_devices[i]);    // 当执行到这个函数的时候就会去执行platform_device结构体中的device结构体下的release函数 
}

/*函数入口和出口修饰*/
module_init(leds_x210_init);
module_exit(leds_x210_exit);

/*描述模块信息*/
MODULE_LICENSE("GPL");                // 描述模块的许可证
MODULE_AUTHOR("Tao Deng <> [email protected]");            // 描述模块的作者
MODULE_DESCRIPTION("led device for x210.");    // 描述模块的介绍信息
MODULE_ALIAS("alias DT");            // 描述模块的别名信息

device

(2)驱动部分:leds-x210-driver.c

#include <linux/module.h>        // module_init  module_exit
#include <linux/init.h>            // __init   __exit
#include <linux/fs.h>
#include <linux/leds.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <mach/gpio.h>
#include <linux/platform_device.h>
#include <linux/slab.h>

enum LED{
    LED_ON_ = 0,
    LED_OFF_ = 1,
};

struct x210_led_platdata {
    unsigned int gpio;
    char *device_name;
    char *gpio_name;
};

struct x210_platform {
    struct led_classdev led_class;
    unsigned int gpio;
};

static inline struct x210_platform *pdev_to_gpio(struct platform_device *dev)
{
    return platform_get_drvdata(dev);
}

static void x210_led_set(struct led_classdev *led_cdev, enum led_brightness brightness)
{
    struct x210_platform *pdata = container_of(led_cdev, struct x210_platform, led_class);  // 使用led_classdev结构体反推得到x210_platform结构体
    
    if (0 < brightness)
        gpio_set_value(pdata->gpio, LED_ON_);       // 使led小灯发亮
    else
        gpio_set_value(pdata->gpio, LED_OFF_);      // 使led小灯熄灭
}

static int x210_led_probe(struct platform_device *dev)
{
    // 定义变量
    int ret = 0;
    struct x210_led_platdata *platdata = dev->dev.platform_data;
    struct x210_platform *pform = NULL;
    
    // 分配空间
    pform = kzalloc(sizeof(struct x210_platform), GFP_KERNEL);    // 将pform指针指向系统分配的地址空间
    if (NULL == pform) {
        printk(KERN_ERR "kzalloc failed.\n");
        return -ENOMEM;
    }
    
    platform_set_drvdata(dev, pform);    // 将pform值写入到传进来的platform_device结构体中的device结构体下的私有数据区中去,以便后面释放内存时用
    
    //驱动框架接口填充
    pform->led_class.name = platdata->device_name;             
    pform->led_class.brightness = 0;    
    pform->led_class.brightness_set = x210_led_set;  
    
    // 保存gpio管脚信息
    pform->gpio = platdata->gpio;
    
    //注册led驱动
    ret =  led_classdev_register(NULL, &pform->led_class);
    if (ret < 0) {
        printk(KERN_ERR "led_classdev_register failed.\n");
        goto err_led_classdev_register;
    }
    
    // 向gpiolib管理器申请gpio资源 
    if (gpio_request(platdata->gpio, platdata->gpio_name))
    {
        printk(KERN_ERR "gpio_request failed.\n");
        goto err_gpio_request;
    }
    
    // 设置GPIO输出高电平,关闭LED
    gpio_direction_output(platdata->gpio, LED_OFF_);
    
    printk(KERN_INFO "x210_led_probe succeseful.\n");

    return 0;

err_gpio_request:
    led_classdev_unregister(&pform->led_class);
    
err_led_classdev_register:

    return -1;
}

static int x210_led_remove(struct platform_device *dev)
{
    struct x210_led_platdata *platdata = dev->dev.platform_data;  
    struct x210_platform *pform = pdev_to_gpio(dev);     // 取出platform_device结构体中的device结构体中的私有数据区的x210_platform指针
    
    // 卸载led驱动
    led_classdev_unregister(&pform->led_class);

    // 关闭所有led
    gpio_set_value(platdata->gpio, LED_OFF_);
    
    // 释放申请的GPIO资源
    gpio_free(platdata->gpio);
    
    // 释放内存
    kfree(pform);         // 这个一定要放在最后
    
    printk(KERN_INFO "x210_led_remove succeseful.\n");
    
    return 0;
}

static struct platform_driver x210_led_driver = {
    .probe        = x210_led_probe,
    .remove        = x210_led_remove,
    .driver        = {
        .name        = "x210_led",
        .owner        = THIS_MODULE,
    },
};

static int __init leds_x210_init(void)
{
    int ret = 0;
    
    ret = platform_driver_register(&x210_led_driver);
    if (ret)
       printk(KERN_ERR "platform_driver_register failed.\n");
    
    return ret; 
}

static void __exit leds_x210_exit(void)
{
    platform_driver_unregister(&x210_led_driver);    
}

/*函数入口和出口*/
module_init(leds_x210_init);
module_exit(leds_x210_exit);

/*描述模块信息*/
MODULE_LICENSE("GPL");                // 描述模块的许可证
MODULE_AUTHOR("Tao Deng <> [email protected]");            // 描述模块的作者
MODULE_DESCRIPTION("led driver for x210.");    // 描述模块的介绍信息
MODULE_ALIAS("alias DT");            // 描述模块的别名信息

driver

3、platform总线工作原理

(1)insmod之后的现象

在这里插入图片描述

当我们执行第一个insmod的时候,并没有出现后面的4条打印信息,只是执行了leds_x210_init函数(一次insmod只能对应执行一次xxx_init函数,

一次rmmod只能对应执行一次xxx_exit函数),这里并没有放置打印语句的信息,因为此时匹配并不会成功,所以不会执行驱动层probe函数;

而当执行第二个insmod的时候,此时platform总线下的match函数匹配就会成功,因为对应有4个led设备,所以就会匹配4次,执行了4次probe

函数打印出相应的信息。

(2)rmmod时的现象

在这里插入图片描述

当我们卸载调驱动或者是设备中的任何一个,都会执行驱动层的remove函数;如果是先rmmod设备,那么先执行驱动层中的remove函数,之后就

会执行设备层中的platform_device结构体中的device结构体下的release函数,每一个设备都会执行一次,因为这里卸载了4个设备,所以就会执行

4次;如果是先rmmod驱动,那么直接就会执行驱动层的remove函数。

(3)总结:

insmod注册执行入口函数leds_x210_init -> platform总线下match函数进行匹配 -> 匹配成功则执行驱动层platform_driver结构体下的probe函数(失败就没什么可说的了)->

初始化驱动。

rmmod卸载执行出口函数leds_x210_exit -> 执行驱动层platform_driver结构体下的remove函数 -> 如果是设备则还需要执行设备层platform_device结构体中的device结构体下的

release函数。

(4)当我们注册了设备之后,在platform总线的device中会出现我们注册的设备;当我们注册了驱动之后,在platform总线的driver中会出现驱动名;

但是只要match函数没有匹配成功就不会执行probe函数,那么属于操作led驱动框架下的接口/sys/class/leds下的设备接口就不会出现,因为

led_classdev_register函数在probe中执行。

猜你喜欢

转载自blog.csdn.net/huangweiqing80/article/details/82776920
今日推荐