前几天学习了platform驱动,学习过程中参考了网上的好多篇博客,总算是对platform驱动有了一定的了解,写个博客总结一下!下面我将对platform驱动代码及驱动整个流程进行分析,加之我的理解!
学习platform驱动之前我们肯定要先了解一下platform的一些基础知识!
1、什么是platform(平台)总线?
(1). 相对于USB、PCI、I2C、SPI等物理总线来说,platform总线是一种虚拟、抽象出来的总线,实际中并不存在这样的总线。那为什么需要platform总线呢?其实是Linux设备驱动模型为了保持设备驱动的统一性而虚拟出来的总线。因为对于usb设备、i2c设备、pci设备、spi设备等等,他们与cpu的通信都是直接挂在相应的总线下面与我们的cpu进行数据交互的,但是在我们的嵌入式系统当中,并不是所有的设备都能够归属于这些常见的总线,在嵌入式系统里面,SoC系统中集成的独立的外设控制器、挂接在SoC内存空间的外设却不依附与此类总线。所以Linux驱动模型为了保持完整性,将这些设备挂在一条虚拟的总线上(platform总线),而不至于使得有些设备挂在总线上,另一些设备没有挂在总线上。
(2).总线设备驱动模型主要包含总线、设备、驱动三个部分,总线可以是一条真实存在的总线,例如USB、I2C等典型的设备。但是对于一些设备(内部的设备)可能没有现成的总线。Linux 2.6内核中引入了总线设备驱动模型。总线设备驱动模型与之前的三类驱动(字符、块设备、网络设备)没有必然的联系。设备只是搭载到了总线中。在linux内核中假设存在一条虚拟总线,称之为platform总线。platform总线相比与常规的总线模型其优势主要是platform总线是由内核实现的,而不用自己定义总线类型,总线设备来加载总线。platform总线是内核已经实现好的。只需要添加相应的platform device和platform driver。
下面贴上platform驱动的代码:
plat_led.c:
/********************************************************************************
* Copyright: (C) 2017 zoulei<zoulei121@gmail.com>
* All rights reserved.
*
* Filename: plat_led.c
* Description: This file
*
* Version: 1.0.0(02/4/2017)
* Author: zoulei <zoulei121@gmail.com>
* ChangeLog: 1, Release initial version on "02/4/2017 18:04:40 PM"
*
********************************************************************************/
#include "s3c_driver.h"
#define DRV_AUTHOR "zou lei <zoulei121@gmail.com>"
#define DRV_DESC "S3C24XX LED driver"
/* Driver version*/
#define DRV_MAJOR_VER 1
#define DRV_MINOR_VER 0
#define DRV_REVER_VER 0
#define DEV_NAME DEV_LED_NAME
#ifndef DEV_MAJOR
#define DEV_MAJOR 0 /* dynamic major by default */
#endif
#define TIMER_TIMEOUT 40
static int debug = DISABLE;
static int dev_major = DEV_MAJOR;
static int dev_minor = 0;
/* ============================ Platform Device part ===============================*/
/* LED hardware informtation structure*/
struct s3c_led_info
{
unsigned char num; /* The LED number */
unsigned int gpio; /* Which GPIO the LED used */
unsigned char active_level; /* The GPIO pin level(HIGHLEVEL or LOWLEVEL) to turn on or off */
unsigned char status; /* Current LED status: OFF/ON */
unsigned char blink; /* Blink or not */
};
/* The LED platform device private data structure */
struct s3c_led_platform_data
{
struct s3c_led_info *leds;
int nleds;
};
/* LED hardware informtation data*/
static struct s3c_led_info s3c_leds[] = {
[0] = {
.num = 1,
.gpio = S3C2410_GPB(5),
.active_level = LOWLEVEL,
.status = OFF,
.blink = ENABLE,
},
[1] = {
.num = 2,
.gpio = S3C2410_GPB(6),
.active_level = LOWLEVEL,
.status = OFF,
.blink = DISABLE,
},
[2] = {
.num = 3,
.gpio = S3C2410_GPB(8),
.active_level = LOWLEVEL,
.status = OFF,
.blink = DISABLE,
},
[3] = {
.num = 4,
.gpio = S3C2410_GPB(10),
.active_level = LOWLEVEL,
.status = OFF,
.blink = DISABLE,
},
};
/* The LED platform device private data */
static struct s3c_led_platform_data s3c_led_data = {
.leds = s3c_leds,
.nleds = ARRAY_SIZE(s3c_leds),
};
struct led_device
{
struct s3c_led_platform_data *data;
struct cdev cdev;
struct class *dev_class;
struct timer_list blink_timer;
} led_device;
static void platform_led_release(struct device * dev)
{
int i;
struct s3c_led_platform_data *pdata = dev->platform_data;
dbg_print("%s():%d\n", __FUNCTION__,__LINE__);
/* Turn all LED off */
for(i=0; i<pdata->nleds; i++)
{
s3c2410_gpio_setpin(pdata->leds[i].gpio, ~pdata->leds[i].active_level);
}
}
static struct platform_device s3c_led_device = {
.name = "s3c_led",
.id = 1,
.dev =
{
.platform_data = &s3c_led_data,
.release = platform_led_release,
},
};
/* ===================== led device driver part ===========================*/
void led_timer_handler(unsigned long data)
{
int i;
struct s3c_led_platform_data *pdata = (struct s3c_led_platform_data *)data;
for(i=0; i<pdata->nleds; i++)
{
if(ON == pdata->leds[i].status)
{
s3c2410_gpio_setpin(pdata->leds[i].gpio, pdata->leds[i].active_level);
}
else
{
s3c2410_gpio_setpin(pdata->leds[i].gpio, ~pdata->leds[i].active_level);
}
if(ENABLE == pdata->leds[i].blink ) /* LED should blink */
{
/* Switch status between 0 and 1 to turn LED ON or off */
pdata->leds[i].status = pdata->leds[i].status ^ 0x01;
}
mod_timer(&(led_device.blink_timer), jiffies + TIMER_TIMEOUT);
}
}
static int led_open(struct inode *inode, struct file *file)
{
struct led_device *pdev ;
struct s3c_led_platform_data *pdata;
pdev = container_of(inode->i_cdev,struct led_device, cdev);
pdata = pdev->data;
file->private_data = pdata;
return 0;
}
static int led_release(struct inode *inode, struct file *file)
{
return 0;
}
static void print_led_help(void)
{
printk("Follow is the ioctl() command for LED driver:\n");
printk("Enable Driver debug command: %u\n", SET_DRV_DEBUG);
printk("Get Driver verion command : %u\n", GET_DRV_VER);
printk("Turn LED on command : %u\n", LED_ON);
printk("Turn LED off command : %u\n", LED_OFF);
printk("Turn LED blink command : %u\n", LED_BLINK);
}
/* compatible with kernel version >=2.6.38*/
static long led_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct s3c_led_platform_data *pdata = file->private_data;
switch (cmd)
{
case SET_DRV_DEBUG:
dbg_print("%s driver debug now.\n", DISABLE == arg ? "Disable" : "Enable");
debug = (0==arg) ? DISABLE : ENABLE;
break;
case GET_DRV_VER:
print_version(DRV_VERSION);
return DRV_VERSION;
case LED_OFF:
if(pdata->nleds <= arg)
{
printk("LED%ld doesn't exist\n", arg);
return -ENOTTY;
}
pdata->leds[arg].status = OFF;
pdata->leds[arg].blink = DISABLE;
break;
case LED_ON:
if(pdata->nleds <= arg)
{
printk("LED%ld doesn't exist\n", arg);
return -ENOTTY;
}
pdata->leds[arg].status = ON;
pdata->leds[arg].blink = DISABLE;
break;
case LED_BLINK:
if(pdata->nleds <= arg)
{
printk("LED%ld doesn't exist\n", arg);
return -ENOTTY;
}
pdata->leds[arg].blink = ENABLE;
pdata->leds[arg].status = ON;
break;
default:
dbg_print("%s driver don't support ioctl command=%d\n", DEV_NAME, cmd);
print_led_help();
return -EINVAL;
}
return 0;
}
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.release = led_release,
.unlocked_ioctl = led_ioctl, /* compatible with kernel version >=2.6.38*/
};
static int s3c_led_probe(struct platform_device *dev)
{
struct s3c_led_platform_data *pdata = dev->dev.platform_data;
int result = 0;
int i;
dev_t devno;
/* Initialize the LED status */
for(i=0; i<pdata->nleds; i++)
{
s3c2410_gpio_cfgpin(pdata->leds[i].gpio, S3C2410_GPIO_OUTPUT);
if(ON == pdata->leds[i].status)
{
s3c2410_gpio_setpin(pdata->leds[i].gpio, pdata->leds[i].active_level);
}
else
{
s3c2410_gpio_setpin(pdata->leds[i].gpio, ~pdata->leds[i].active_level);
}
}
/* Alloc the device for driver */
if (0 != dev_major)
{
devno = MKDEV(dev_major, dev_minor);
result = register_chrdev_region(devno, 1, DEV_NAME);
}
else
{
result = alloc_chrdev_region(&devno, dev_minor, 1, DEV_NAME);
dev_major = MAJOR(devno);
}
/* Alloc for device major failure */
if (result < 0)
{
printk("%s driver can't get major %d\n", DEV_NAME, dev_major);
return result;
}
/* Initialize button structure and register cdev*/
memset(&led_device, 0, sizeof(led_device));
led_device.data = dev->dev.platform_data;
cdev_init (&(led_device.cdev), &led_fops);
led_device.cdev.owner = THIS_MODULE;
result = cdev_add (&(led_device.cdev), devno , 1);
if (result)
{
printk (KERN_NOTICE "error %d add %s device", result, DEV_NAME);
goto ERROR;
}
led_device.dev_class = class_create(THIS_MODULE, DEV_NAME);
if(IS_ERR(led_device.dev_class))
{
printk("%s driver create class failture\n",DEV_NAME);
result = -ENOMEM;
goto ERROR;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24)
device_create(led_device.dev_class, NULL, MKDEV(dev_major,0), NULL, DEV_NAME);
#else
device_create (led_device.dev_class, NULL, MKDEV(dev_major,0), DEV_NAME);
#endif
/* Initial the LED blink timer */
init_timer(&(led_device.blink_timer));
led_device.blink_timer.function = led_timer_handler;
led_device.blink_timer.data = (unsigned long)pdata;
led_device.blink_timer.expires = jiffies + TIMER_TIMEOUT; //jiffies为当前时间
add_timer(&(led_device.blink_timer));
printk("S3C %s driver version %d.%d.%d initiliazed.\n", DEV_NAME, DRV_MAJOR_VER, DRV_MINOR_VER, DRV_REVER_VER);
return 0;
ERROR:
printk("S3C %s driver version %d.%d.%d install failure.\n", DEV_NAME, DRV_MAJOR_VER, DRV_MINOR_VER, DRV_REVER_VER);
cdev_del(&(led_device.cdev));
unregister_chrdev_region(devno, 1);
return result;
}
static int s3c_led_remove(struct platform_device *dev)
{
dev_t devno = MKDEV(dev_major, dev_minor);
del_timer(&(led_device.blink_timer));
cdev_del(&(led_device.cdev));
//for()
device_destroy(led_device.dev_class, devno);
class_destroy(led_device.dev_class);
unregister_chrdev_region(devno, 1);
printk("S3C %s driver removed\n", DEV_NAME);
return 0;
}
static struct platform_driver s3c_led_driver = {
.probe = s3c_led_probe,
.remove = s3c_led_remove,
.driver = {
.name = "s3c_led",
.owner = THIS_MODULE,
},
};
static int __init s3c_led_init(void)
{
int ret = 0;
ret = platform_device_register(&s3c_led_device);
if(ret)
{
printk(KERN_ERR "%s:%d: Can't register platform device %d\n", __FUNCTION__,__LINE__, ret);
goto fail_reg_plat_dev;
}
dbg_print("Regist S3C LED Platform Device successfully.\n");
ret = platform_driver_register(&s3c_led_driver);
if(ret)
{
printk(KERN_ERR "%s:%d: Can't register platform driver %d\n", __FUNCTION__,__LINE__, ret);
goto fail_reg_plat_drv;
}
dbg_print("Regist S3C LED Platform Driver successfully.\n");
return 0;
fail_reg_plat_drv:
platform_driver_unregister(&s3c_led_driver);
fail_reg_plat_dev:
return ret;
}
static void s3c_led_exit(void)
{
dbg_print("%s():%d remove LED platform drvier\n", __FUNCTION__,__LINE__);
platform_driver_unregister(&s3c_led_driver);
dbg_print("%s():%d remove LED platform device\n", __FUNCTION__,__LINE__);
platform_device_unregister(&s3c_led_device);
}
module_init(s3c_led_init);
module_exit(s3c_led_exit);
module_param(debug, int, S_IRUGO);
module_param(dev_major, int, S_IRUGO);
module_param(dev_minor, int, S_IRUGO);
MODULE_AUTHOR(DRV_AUTHOR);
MODULE_DESCRIPTION(DRV_DESC);
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:S3C24XX_led");
s3c_drive.h:
#ifndef __S3C_DRIVER_H
#define __S3C_DRIVER_H
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/kref.h>
#include <linux/spinlock.h>
#include <asm/uaccess.h>
#include <linux/mutex.h>
#include <linux/delay.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/i2c.h>
#include <linux/string.h>
#include <linux/bcd.h>
#include <linux/miscdevice.h>
#include <linux/poll.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/sysfs.h>
#include <linux/proc_fs.h>
#include <linux/rtc.h>
#include <linux/spinlock.h>
#include <linux/usb.h>
#include <asm/uaccess.h>
#include <asm/delay.h>
#include <linux/syscalls.h> /* For sys_access*/
#include <linux/platform_device.h>
#include <linux/unistd.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <linux/serial.h>
#include <linux/serial_core.h>
#include <linux/irq.h>
#include <mach/regs-gpio.h>
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)
#include <mach/hardware.h>
#include <mach/gpio.h>
#include <asm/irq.h>
#else
#include <asm-arm/irq.h>
#include <asm/arch/gpio.h>
#include <asm/arch/hardware.h>
#endif
#include "plat_ioctl.h"
/* ===========================================================================
* * S3C24XX device driver common macro definition
* *===========================================================================*/
#define ENPULLUP 1
#define DISPULLUP 0
#define HIGHLEVEL 1
#define LOWLEVEL 0
#define INPUT 1
#define OUTPUT 0
#define OFF 0
#define ON 1
#define ENABLE 1
#define DISABLE 0
#define TRUE 1
#define FALSE 0
/* ===========================================================================
* * S3C24XX device driver name and Major number define
* *===========================================================================*/
#define DEV_LED_NAME "led"
#define DEV_LED_MAJOR 203
#define DEV_BUTTON_NAME "button"
#define DEV_BUTTON_MAJOR "211"
#define DEV_ADC_NAME "adc"
#define DEV_ADC_MAJOR "212"
/* ***** Bit Operate Define *****/
#define SET_BIT(data, i) ((data) |= (1 << (i))) /* Set the bit "i" in "data" to 1 */
#define CLR_BIT(data, i) ((data) &= ~(1 << (i))) /* Clear the bit "i" in "data" to 0 */
#define NOT_BIT(data, i) ((data) ^= (1 << (i))) /* Inverse the bit "i" in "data" */
#define GET_BIT(data, i) ((data) >> (i) & 1) /* Get the value of bit "i" in "data" */
#define L_SHIFT(data, i) ((data) << (i)) /* Shift "data" left for "i" bit */
#define R_SHIFT(data, i) ((data) >> (i)) /* Shift "data" Right for "i" bit */
/* ===========================================================================
* * S3C24XX device driver common function definition
* *===========================================================================*/
#define SLEEP(x) {DECLARE_WAIT_QUEUE_HEAD (stSleep); if (10 > x) mdelay ((x * 1000)); \
else wait_event_interruptible_timeout (stSleep, 0, (x / 10));}
#define VERSION_CODE(a,b,c) ( ((a)<<16) + ((b)<<8) + (c))
#define DRV_VERSION VERSION_CODE(DRV_MAJOR_VER, DRV_MINOR_VER, DRV_REVER_VER)
#define MAJOR_VER(a) ((a)>>16&0xFF)
#define MINOR_VER(a) ((a)>>8&0xFF)
#define REVER_VER(a) ((a)&0xFF)
#define dbg_print(format,args...) if(DISABLE!=debug) \
{printk("[kernel] ");printk(format, ##args);}
static inline void print_version(int version)
{
#ifdef __KERNEL__
printk("%d.%d.%d\n", MAJOR_VER(version), MINOR_VER(version), REVER_VER(version));
#else
printf("%d.%d.%d\n", MAJOR_VER(version), MINOR_VER(version), REVER_VER(version));
#endif
}
#endif /* __S3C_DRIVER_H */
plat_ioctl.h:
#ifndef __PLAT_IOCTL_H
#define __PLAT_IOCTL_H
#include <asm/ioctl.h>
/*===========================================================================
* * Common ioctl command definition
* *===========================================================================*/
#define PLATDRV_MAGIC 0x60
/*===========================================================================
* * ioctl command for all the drivers 0x01~0x0F
* *===========================================================================*/
/*args is enable or disable*/
#define SET_DRV_DEBUG _IO (PLATDRV_MAGIC, 0x01)
#define GET_DRV_VER _IO (PLATDRV_MAGIC, 0x02)
/*===========================================================================
* * ioctl command for few ioctl() cmd driver 0x10~0x2F
* *===========================================================================*/
/* LED driver */
#define LED_OFF _IO (PLATDRV_MAGIC, 0x18)
#define LED_ON _IO (PLATDRV_MAGIC, 0x19)
#define LED_BLINK _IO (PLATDRV_MAGIC, 0x1A)
#define ADC_SET_CHANNEL _IO (PLATDRV_MAGIC, 0x1B)
/*===========================================================================
* * ioctl command for GPRS driver 0x30~0x4F
* *===========================================================================*/
#define GPRS_POWERDOWN _IO (PLATDRV_MAGIC, 0x30)
#define GPRS_POWERON _IO (PLATDRV_MAGIC, 0x31)
#define GPRS_RESET _IO (PLATDRV_MAGIC, 0x32)
#define GPRS_POWERMON _IO (PLATDRV_MAGIC, 0x33)
#define GPRS_CHK_SIMDOOR _IO (PLATDRV_MAGIC, 0x36)
#define GPRS_SET_DTR _IO (PLATDRV_MAGIC, 0x37)
#define GPRS_SET_RTS _IO (PLATDRV_MAGIC, 0x38)
#define GPRS_GET_RING _IO (PLATDRV_MAGIC, 0x39)
#define SET_PWUP_TIME _IO (PLATDRV_MAGIC, 0x3A)
#define SET_PWDOWN_TIME _IO (PLATDRV_MAGIC, 0x3B)
#define SET_RESET_TIME _IO (PLATDRV_MAGIC, 0x3C)
#define GPRS_CHK_MODEL _IO (PLATDRV_MAGIC, 0x3E)
/*===========================================================================
* * ioctl command for EEPROM driver 0x50~0x5F
* *===========================================================================*/
#define LL_POWEROFF _IO (PLATDRV_MAGIC, 0x50)
#define LL_POWERON _IO (PLATDRV_MAGIC, 0x51)
#define LL_STOP _IO (PLATDRV_MAGIC, 0x52)
#define LL_START _IO (PLATDRV_MAGIC, 0x53)
#define LL_READ _IO (PLATDRV_MAGIC, 0x54)
#define LL_WRITE _IO (PLATDRV_MAGIC, 0x55)
#define LL_DATALOW _IO (PLATDRV_MAGIC, 0x56)
#define LL_ACKNAK _IO (PLATDRV_MAGIC, 0x57)
#endif /* __PLAT_IOCTL_H */
由于platform总线已经在内核中注册定义好了,所以一般我们只需要添加相应的驱动和设备就可以了,然而这个代码我们是添加led设备和驱动!
为了能看懂这个程序,我们得分析分析platform总线的内核代码!首先系统启动的时候会先调用platform_bus_init来初始化这个虚拟总线注册设备(即将挂接在这条总线上得设备本文的话就是led设备),platform_bus_types是内核已经为我们实现好了的,所以我们只关心platform_device和platform_drive
[zoulei@CentOS linux-3.0]$ vim drivers/base/platform.c
910 struct bus_type platform_bus_type = {
911 .name = "platform",
912 .dev_attrs = platform_dev_attrs,
913 .match = platform_match, //指定配对函数
914 .uevent = platform_uevent,
915 .pm = &platform_dev_pm_ops,
916 };
917 EXPORT_SYMBOL_GPL(platform_bus_type);
918
919 int __init platform_bus_init(void)
920 {
921 int error;
922
923 early_platform_cleanup();
924
925 error = device_register(&platform_bus);
926 if (error)
927 return error;
928 error = bus_register(&platform_bus_type);
929 if (error)
930 device_unregister(&platform_bus);
931 return error;
932 }
****************************************************************************************************
说明: 当有驱动或者设备注册到 platform 总线时,内核自动调用 match 函数, 判断设备和驱动的name 是否一致 。在结构体 platform_bus_type中定义了总线名字"platform"*********************************************************************************************************
platform机制中比较重要的三个结构体:platform_drive,platform_device resource
platform_device结构体用来描述设备的名称、资源信息等。该结构被定义在#linux 3.0/include/linux/platform_device.h中,定义原型如下
[zoulei@CentOS include]$ vim linux/platform_device.h
19 struct platform_device {
20 const char * name; //设备名
21 int id; //设备编号,配合设备名使用
22 struct device dev;
23 u32 num_resources;
24 struct resource * resource; //设备资源
25
26 const struct platform_device_id *id_entry;
27
28 /* MFD cell pointer */
29 struct mfd_cell *mfd_cell;
30
31 /* arch specific additions */
32 struct pdev_archdata archdata;
33 };
platform_device结构体中最重要的一个成员:struct resource * resource。struct resource被定义在#linux 3.0/include/linux/ioport.h中,定义原型如下:
[zoulei@CentOS linux-3.0]$ vim include/linux/ioport.h
18 struct resource {
19 resource_size_t start; /*资源的开始物理地址*/
20 resource_size_t end; /*资源的结束物理地址*/
21 const char *name; //资源名称
22 unsigned long flags; //资源类型
23 struct resource *parent, *sibling, *child; //资源链表指针
24 };
接下来来看platform_driver结构体的原型定义,在include/linux/platform_device.h中,代码如下:
[zoulei@CentOS linux-3.0]$ vim include/linux/platform_device.h
119 struct platform_driver { /*提供了设备的具体操作方法,如probe、remove等。*/ 120 int (*probe)(struct platform_device *); 121 int (*remove)(struct platform_device *); 122 void (*shutdown)(struct platform_device *); 123 int (*suspend)(struct platform_device *, pm_message_t state); 124 int (*resume)(struct platform_device *); 125 struct device_driver driver; 126 const struct platform_device_id *id_table; 127 };
*************************************************************************************************************************
说明:可以看到,platform_driver结构体内嵌了device_driver,并且实现了prob、remove等操作。其实,当内核需要调用probe函数时,它会调用driver->probe,在dricer->probe中再调用platform_driver->probe。如果想了解清楚的话建议查看内核源代码。********************************************************************************************************
总线设备和驱动的注册
platform机制下,必须先注册设备,再注册驱动。因为driver在注册的时候会按照name去遍历总线上的设备链表,认领设备。
通过调用函数platform_add_devices()向系统中添加该设备,该函数内部调用platform_device_register()进行设备注册。要注意的是,这里的platform_device设备的注册过程必须在相应设备驱动加载之前被调用,即执行platform_driver_register()之前,原因是驱动注册时需要匹配内核中所有已注册的设备名。
[zoulei@CentOS linux-3.0]$ vim drivers/base/platform.c
109 int platform_add_devices(struct platform_device **devs, int num) 110 { 111 int i, ret = 0; 112 113 for (i = 0; i < num; i++) { 114 ret = platform_device_register(devs[i]); 115 if (ret) { 116 while (--i >= 0) 117 platform_device_unregister(devs[i]); 118 break; 119 } 120 } 121 122 return ret; 123 } 124 EXPORT_SYMBOL_GPL(platform_add_devices);
内核提供的platform_driver结构体的注册函数为platform_driver_register(),其原型定义在driver/base/platform.c文件中具体实现代码如下:
platform_device与platform_driver匹配的过程437 int platform_driver_register(struct platform_driver *drv) 438 { 439 drv->driver.bus = &platform_bus_type; 440 if (drv->probe) 441 drv->driver.probe = platform_drv_probe; 442 if (drv->remove) 443 drv->driver.remove = platform_drv_remove; 444 if (drv->shutdown) 445 drv->driver.shutdown = platform_drv_shutdown; 446 447 return driver_register(&drv->driver); 448 } 449 EXPORT_SYMBOL_GPL(platform_driver_register);
追踪__driver_attach这个函数,在这个函数里面分别调driver_match_device,driver_probe_device函数。如果匹配成功就调用probe函数,否则返回
[zoulei@CentOS linux-3.0]$ vim drivers/base/dd.c
static int __driver_attach(struct device *dev, void *data) { struct device_driver *drv = data; /* * Lock device and try to bind to it. We drop the error * here and always return 0, because we need to keep trying * to bind to devices and some drivers will return an error * simply if it didn't support the device. * * driver_probe_device() will spit a warning if there * is an error. */ if (!driver_match_device(drv, dev)) return 0; if (dev->parent) /* Needed for USB */ device_lock(dev->parent); device_lock(dev); if (!dev->driver) driver_probe_device(drv, dev); device_unlock(dev); if (dev->parent) device_unlock(dev->parent); return 0; }
匹配的过程中调用了bus的match函数,我们在platform的初始化里,有platform_bus_type.match = platform_match,这里的platform_match函数定义在drivers/base/platform.c, line 641.
[zoulei@CentOS linux-3.0]$ vim drivers/base/platform.c
627 static int platform_match(struct device *dev, struct device_driver *drv) 628 { 629 struct platform_device *pdev = to_platform_device(dev); 630 struct platform_driver *pdrv = to_platform_driver(drv); 631 632 /* Attempt an OF style match first */ 633 if (of_driver_match_device(dev, drv)) 634 return 1; 635 636 /* Then try to match against the id table */ 637 if (pdrv->id_table) 638 return platform_match_id(pdrv->id_table, pdev) != NULL; 639 640 /* fall-back to driver name match */ 641 return (strcmp(pdev->name, drv->name) == 0);//从此处可以看到,内核是按照name来匹配的 642 }
*******************************************************************************************************************************/*小节:上面我们分析了内核platform虚拟总线的设备和驱动的注册和定义,那么下面我们将对照led驱动和设备实例来进一步分析
1. 其中我们可以看到plat_led.c程序中的第一个结构体是struct s3c_led_info,这个结构体是用来保存led硬件设备的信息。然后又定义了此结构体的一个数组s3c_leds[ ],这个数组里面保存了具体每盏灯的硬件信息。然后将信息再转移到s3c_led_data中(一共有几盏灯,每盏灯的具体信息)。
2. 首先看到我们定义的platform_device s3c_led_device设备的代码,其中.dev是对.platform_data进行了设备的信息转移,这样我们就需要看到我们用来保存信息的结构体static struct s3c_led_platform_data s3c_led_data;先是定义struct s3c_led_platform_data结构体,这个是led设备的私有数据结构体,接着就马上定义了一个此类型结构体变量用来保存我们设备的私有信息。
3.然后又定义了platform_led_release函数,值得注意的是这段代码struct s3c_led_platform_data *pdata = dev->platform_data;
因为这个函数是我们注册成功后才会去调用,当我们注册成功后,这样我们内核中就有了s3c_led_device这样一个结构体了,当你调用这个函数的时候里面的形参是struct device *dev,因为注册过了,所以存在了这里的*dev其实就是&s3c_led_data,系统会自动去调用这个当形参。
struct device *dev =&s3c_led_device->dev;
struct s3c_led_platform_data *pdata =dev->platform_data;
pdata=&s3c_led_data;
4.接着是定义了一个led_timer_handler时钟处理函数,这个函数是为了实现led灯闪烁的功能,然后又定义led_open,led_release,led_ioctl三个函数,这三个函数操作是保存在结构体file_operations led_fops里的。
5.比较重要的一个函数来了,那就是probe探测函数(探测函数,在注册平台设备时被调用)。probe函数一般完成硬件设备使能,struct resource的获取以及虚拟地址的动态映射和具体类型设备的注册(因为平台设备只是一种虚拟的设备类型);probe函数里面首先是获得设备的私有数据,然后将led状态进行初始化,再申请设备号,接着就是初始化一个cdev传入已经设置好的file operations最后cdev加入内核
当然probe函数里面的传值跟上面那个platform_led_release函数里面的差不多只是有一点点变化,就是形参要求得不同,我们可以这样理解。
struct platform_device *dev =&s3c_led_device;
struct s3c_led_platform_data *pdata =dev->dev.platform_data;
pdata=&s3c_led_data;
6.上面我们分析的是probe函数,与之对应的就是一个remove注销函数(删除函数,在注销平台设备时被调用),remove函数完成硬件设备的关闭,struct resource以及虚拟地址的动态映射的释放和具体类型设备的注销。(struct resource是platform_device里面的结构体成员)
7.接下来也是比较重要的struct platform_driver s3c_led_driver结构体,我们可以发现里面的成员 .driver中的.name与.dev中的.name名字是相同的,这就是驱动与设备能相互匹配的关键!!!
8.当定义好这2个platform类型的设备和驱动后,最后我们就需要看其led初始化函数s3c_led_init了,里面包含了这2个platform类型的设备和驱动的注册首先进行设备的注册,将设备加入platform设备总线中;然后进行的是驱动的注册,将驱动加入platform设备总线中。这里值得注意的我们是先注册设备的,因为驱动注册时需要匹配内核中所以已注册的设备名。
9.最后的最后就是是led卸载模块函数s3c_led_exit的定义。然后这个程序的流程算是分析完了!
总结:其实通过对platform总线的学习,最为重要的无非是platform_drive,platform_device,对于任何一种Linux设备驱动模型下的总线都由两个部分组成:描述设备相关的结构体和描述驱动相关的结构体,在platform总线下就是platform_device和platform_driver,在那个platform_led.c程序中还有一段自动创建设备节点的代码,我觉得这个人分析的还不错值得一看http://www.cnblogs.com/zhaobinyouth/p/6238191.html之前我们都是使用命令mknod手动创建设备节点,
led_device.dev_class = class_create(THIS_MODULE, DEV_NAME); if(IS_ERR(led_device.dev_class)) { printk("%s driver create class failture\n",DEV_NAME); result = -ENOMEM; goto ERROR; } #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24) device_create(led_device.dev_class, NULL, MKDEV(dev_major,0), NULL, DEV_NAME); #else device_create (led_device.dev_class, NULL, MKDEV(dev_major,0), DEV_NAME); #endif
参考:http://blog.csdn.net/liuqiang_mail/article/details/7890838
参考:http://blog.chinaunix.net/uid-25014876-id-111745.html
参考:http://blog.csdn.net/liuzijiang1123/article/details/45100021