您的位置:首页 > 运维架构 > Linux

linux设备驱动之platform平台总线工作原理(三)

2017-05-30 20:47 405 查看
设备为数据,驱动为加工着

1、以led-s3c24xx.c为例来分析platform设备和驱动的注册过程

其中关于led的驱动数据结构为:
static struct platform_driver s3c24xx_led_driver = {
.probe		= s3c24xx_led_probe,
.remove		= s3c24xx_led_remove,
.driver		= {
.name		= "s3c24xx_led",
.owner		= THIS_MODULE,
},
};

struct platform_driver数据结构为
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;
};

关于led的设备数据结构为
static struct platform_device mini2440_led1 = {
.name		= "s3c24xx_led",
.id		= 1,
.dev		= {
.platform_data	= &mini2440_led1_pdata,
},
};
其中设备的name和驱动中的name一致,设备数据结构中的id值用来区分不同的led设备,因为一个板子上可能有多个led,用这个id值来进行区分不同的led灯,这个id值可以自己随意去排,比如有四个led,id值就可以设置为从1到4的数字,还有一种方法是,我们不去指定的id的值,给id值设置为-1.表示板子上led的id,由内核去帮我们分配。成员dev中的platform_data是设备的数据部分。
struct platform_device的数据结构内容为
struct platform_device {
const char	* name;
int		id;
struct device	dev;
u32		num_resources;
struct resource	* resource;

const struct platform_device_id	*id_entry;

/* arch specific additions */
struct pdev_archdata	archdata;
};
可以看到dev成员变量确实在struct platform_device结构体中,看一下这个struct platform_device结构体中dev变量的结构体类型struct device的数据结构内容
struct device {
struct device		*parent;

struct device_private	*p;

struct kobject kobj;
const char		*init_name; /* initial name of the device */
struct device_type	*type;

struct mutex		mutex;	/* mutex to synchronize calls to
* its driver.
*/

struct bus_type	*bus;		/* type of bus device is on */
struct device_driver *driver;	/* which driver has allocated this
device */
void		*platform_data;	/* Platform specific data, device
core doesn't touch it */
struct dev_pm_info	power;

#ifdef CONFIG_NUMA
int		numa_node;	/* NUMA node this device is close to */
#endif
u64		*dma_mask;	/* dma mask (if dma'able device) */
u64		coherent_dma_mask;/* Like dma_mask, but for
alloc_coherent mappings as
not all hardware supports
64 bit addresses for consistent
allocations such descriptors. */

struct device_dma_parameters *dma_parms;

struct list_head	dma_pools;	/* dma pools (if dma'ble) */

struct dma_coherent_mem	*dma_mem; /* internal for coherent mem
override */
/* arch specific additions */
struct dev_archdata	archdata;
#ifdef CONFIG_OF
struct device_node	*of_node;
#endif

dev_t			devt;	/* dev_t, creates the sysfs "dev" */

spinlock_t		devres_lock;
struct list_head	devres_head;

struct klist_node	knode_class;
struct class		*class;
const struct attribute_group **groups;	/* optional groups */

void	(*release)(struct device *dev);
};


其中可以看到在这个struct device结构体类型中有一个void *platform_data;成员,这个成员是一个可以指向任意一个类型的指针,他就是设备的数据部分。回到led设备的数据结构中

static struct platform_device mini2440_led1 = {
.name		= "s3c24xx_led",
.id		= 1,
.dev		= {
.platform_data	= &mini2440_led1_pdata,
},
};
struct device类型的成员dev中的成员platform_data,这个platform_data是一个可以指向任意一个类型的指针,这个位置是一个留白位置,我们可以自己写一个东西,将写完的这个东西绑定到这个platform_data中成员中,这个void *类型的platform_data成员变量所指向的东西是留给我们的,因为是void *类型的,所以我们可以自己去定义一个类型然后绑定到这里面去,这个地方可以说是一个设备中特有的数据部分,因为内核设备和驱动在设计的时候,并不能把所有的设备有的东西都定义好,所以有了这么一个void *类型的成员变量让我们去定义设备特有的数据部分。这个led的设备数据结构中的platform_data成员变量指向的是一个min2440_led1_pdata这么一个数据结构,这个数据结构是写led设备的人自己定义的一个数据结构。看下这个mini2440_led1_pdata变量的数据结构
static struct s3c24xx_led_platdata mini2440_led1_pdata = {
.name		= "led1",
.gpio		= S3C2410_GPB(5),
.flags		= S3C24XX_LEDF_ACTLOW | S3C24XX_LEDF_TRISTATE,
.def_trigger	= "heartbeat",
};
看下这个mini2440_led1_pdata变量的结构体类型 struct s3c24xx_led_platdata
struct s3c24xx_led_platdata {
unsigned int		 gpio;
unsigned int		 flags;

char			*name;
char			*def_trigger;
};
这是一个自己定义的一个数据结构类型,并不是内核事先定义的一个数据结构类型,因为设备中的platform_data成员指向的部分的内容是什么是由我们来决定的。这个struct s3c24xx_led_platdata定义的是led所特有的一些数据部分,platform平台总线的不同设备的platform_data肯定是不一样的,是由我们来去定义这个不一样的地方的,platfrom_data成员指向的那个数据结构是关键,是用来区分platform平台总线什么设备的,这个设备的特有部分的。针对于这个led的设备中platform_data成员指向的led设备特有的自己定义的数据结构struct s3c24xx_led_platdata中,gpio表示这个led所用的引脚号,flags对应的led的属性。name表示led的名字,def_trigger可能表示这个led灯是要跟其他的硬件绑定起来的。下面在继续分下这个min2440_led1_pdata变量,led设备特有的数据部分,移植的人自己定义的
static struct s3c24xx_led_platdata mini2440_led1_pdata = {
.name		= "led1",
.gpio		= S3C2410_GPB(5),
.flags		= S3C24XX_LEDF_ACTLOW | S3C24XX_LEDF_TRISTATE,
.def_trigger	= "heartbeat",
};
name名字为led1,用到的gpio偏移为S3C2410_GPB(5),属性flags为.....,def_trgiger值为heatbeat,说明这个led1将来是用作为心跳灯的。
我们将led特有的数据部分写完后,然后绑定到plotform_data成员中,让这个成员指向我们写的led特有的设备数据部分,在来看下怎么绑定的,有点绕
static struct platform_device mini2440_led1 = {
.name		= "s3c24xx_led",
.id		= 1,
.dev		= {
.platform_data	= &mini2440_led1_pdata,
},
};
将mini2440_led1_pdata这个设备特有的数据结构绑定到platform_data成员中,特有的数据部分我们实现为led设备特有的数据部分,最后这个struct plat_device 类型的mini2440_led1设备的数据结构就填充好了,之后使用register函数将这个mini2440_led1变量进行注册就行,这个变量就表示一个led的设备。

总结:platform_data其实就是设备注册时提供的设备有关的一些数据(譬如设备对应的gpio、使用到的中断号、设备名称.....),在注册一个平台设备的时候,我们为这个设备先提供这些设备数据,这些数据在我们的设备和驱动match之后,match函数会被调用会识别设备和驱动,将设备和驱动匹配上,会由设备方转给驱动方,提供的这些数据就是为了将来设备和驱动匹配上后,设备数据结构中会将这些数据部分转给驱动,驱动拿到这些数据后,通过这些数据得知设备的具体信息,然后来操作设备。这样做的好处是我们的驱动源码中不携带数据,只负责对硬件的操作方法,但是并不知道我们具体操作的硬件是哪一个硬件,驱动是通过设备和驱动match匹配上之后,设备将我们提供的数据部分转交给驱动后,驱动才知道要操作哪个设备,因为数据中包括了设备的所用到的资源(譬如设备对应的gpio、使用到的中断号等等),而驱动又有对这一类型设备的操作方法,所以驱动拿到这个设备的数据后,直接用操作方法就可以操作这个设备了。数据和操作逻辑分开实现是一个好的设计逻辑,因为这样,这个驱动就具有一般性,当硬件换了的时候,也就是设备用的gpio换了的时候,我们不需要改驱动的源码,而是在移植的时候,将更换硬件后的设备的资源进行更改就行,驱动源码是不用变的。
既然说设备和驱动match之后,设备会将我们提供的这个设备的特有的数据部分转交给驱动,然后驱动就可以知道用自己的方法操作哪个硬件了,那么设备将数据部分转交给驱动是转交的呢,在probe函数中
static int s3c24xx_led_probe(struct platform_device *dev)
{
struct s3c24xx_led_platdata *pdata = dev->dev.platform_data;
struct s3c24xx_gpio_led *led;
int ret;

led = kzalloc(sizeof(struct s3c24xx_gpio_led), GFP_KERNEL);
if (led == NULL) {
dev_err(&dev->dev, "No memory for device\n");
return -ENOMEM;
}

platform_set_drvdata(dev, led);

led->cdev.brightness_set = s3c24xx_led_set;
led->cdev.default_trigger = pdata->def_trigger;
led->cdev.name = pdata->name;
led->cdev.flags |= LED_CORE_SUSPENDRESUME;

led->pdata = pdata;

/* no point in having a pull-up if we are always driving */

if (pdata->flags & S3C24XX_LEDF_TRISTATE) {
s3c2410_gpio_setpin(pdata->gpio, 0);
s3c2410_gpio_cfgpin(pdata->gpio, S3C2410_GPIO_INPUT);
} else {
s3c2410_gpio_pullup(pdata->gpio, 0);
s3c2410_gpio_setpin(pdata->gpio, 0);
s3c2410_gpio_cfgpin(pdata->gpio, S3C2410_GPIO_OUTPUT);
}

/* register our new led device */

ret = led_classdev_register(&dev->dev, &led->cdev);
if (ret < 0) {
dev_err(&dev->dev, "led_classdev_register failed\n");
kfree(led);
return ret;
}

return 0;
}
我们可以看到在led驱动代码probe函数中开始有一行代码
struct s3c24xx_led_platdata *pdata = dev->dev.platform_data;
这行代码就是将设备的数据部分转交给了驱动,因为驱动的probe函数中定义了一个struct s3c24xx_led_platdata类型的pdata结构体指针,而probe函数的参数是
struct platform_device类型的dev指针,经过上面的分析我们知道,这个一个设备的数据结构,这个设备的数据结构中有一个成员变量dev,这个dev是一个struct device类型的结构体,这个struct device类型的结构体中有一个platform_data成员变量,用来指向我们写的这个设备所特有的数据部分,在这个led中,这个特有的数据部分的数据类型为s3c24xx_led_platdata,因此我们从上面的代码就可以看出来,驱动代码的probe函数中,通过pdata这个指针指向了led设备的特有数据部分platform_data,这就是设备将我们提供的数据部分转交给驱动的过程。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  linux 设备 驱动