1. 程式人生 > >linux設備驅動之platform平臺總線工作原理(三)

linux設備驅動之platform平臺總線工作原理(三)

linux設備和驅動



設備為數據,驅動為加工著


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,這就是設備將我們提供的數據部分轉交給驅動的過程。


本文出自 “whylinux” 博客,請務必保留此出處http://whylinux.blog.51cto.com/10900429/1930747

linux設備驅動之platform平臺總線工作原理(三)