Linux下基于Platform总线led驱动学习(一)

     

===============================================

操作系统:center os 64位

平台:fl2440

内核版本:Linux 3.0.54

交叉编译器版本:2012.08

===============================================

          通常情况下,Linux设备和驱动通常都需要挂接在一种总线上。对于本身依附于PCI、USB、I2 C、SPI等总线的设备来说,不存在什么问题。然而,有一些设备并不支持这些总线。那么,此时我们需要一种虚拟总线——Platform总线。相应的设备称为platform_device,而驱动称为 platform_driver。

     Platform 设备先被注册然后platfrom驱动加载时会调用驱动程序中的probe()入口函数,扫描系统中已注册的设备,通过Name域找到匹配设备后将驱动和设备绑定。一个驱动可以对应多个设备,但是一个设备只对一个驱动。probe相当于一个中介,用来匹配设备和驱动,这就体现Platform的一个重要的特点:设备与驱动分离,增加了可移植性。

    附上驱动程序注解:

/********************************************************************************* 
 *      Copyright:  (C) qicheng  
 *                  All rights reserved. 
 * 
 *       Filename:  s3c_led.c 
 *    Description:  This is the common LED driver runs on S3C24XX. 
 *                  
 *        Version:  1.0.0(10/27/2011~) 
 *         Author:  Guo Wenxue <[email protected]> 
 *      ChangeLog:  1, Release initial version on "10/27/2011 11:39:10 AM" 
 *                  
 ********************************************************************************/  
#include "s3c_driver.h"  
  
#define DRV_AUTHOR                "[email protected]"  
#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  
  
//#define DEV_MAJOR                 DEV_LED_MAJOR  
#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(LOWLEVEL or HIGHLEVEL ) 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),   // #define ARRAY_SIZE(arr)  (sizeof(arr) / sizeof((arr)[0]) + __must_be_array(arr)) 
	                                 //ARRAY_SIZE用于求设备个数
};  
  
  
  
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; inleds; i++)       //设置每个设备对应的GPIO口输出值
    {  
         s3c2410_gpio_setpin(pdata->leds[i].gpio, ~pdata->leds[i].active_level);   
		 //设置相应GPIO的输出值,pdata->leds[i].gpio表示哪个GPIO口,~pdata->leds[i].active_level表示输出的IO口的值为高电平1(取反后)。
		 
    }  
}  
 
/*定义平台设备结构体*/ 
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; inleds; i++)   
    {   
        if(ON == pdata->leds[i].status)  
        {  
	          /*将GPIO输出引脚设为0,点亮led灯*/
              s3c2410_gpio_setpin(pdata->leds[i].gpio, pdata->leds[i].active_level);   
        }  
        else  
        {  
	          /*将GPIO输出引脚设为1,熄灭led灯*/
              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);  
		//定时器函数。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);
	//container_of是linux内核中常用的宏,这个宏的功能是,通过一个结构变体量中一个成员的地址找到这个结构体变量的首地址。
    //通过成员cdev找到struct led_device结构体的首地址。(inode->i_cdev为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;    //通过arg的值来设置debug
            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)     //用arg传入的值来确定对某个灯操作
            {  
               printk("LED%ld doesn't exist\n", arg);    
               return -ENOTTY;  
            }  
            pdata->leds[arg].status = ON;         //设置当前led状态为亮
            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;  
}  
  
 
 /*fops结构体,存储驱动内核模块提供的对设备进行各种操作的函数的指针*/
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*/  
};  
  


/*driver驱动入口函数*/  
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; inleds; 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));  //用来对一段内存空间全部设置为某个字符(初始化为0)
    led_device.data = dev->dev.platform_data;  
    cdev_init (&(led_device.cdev), &led_fops);  
    led_device.cdev.owner  = THIS_MODULE;  
  
 //初始化 cdev 后,需要把它添加到系统中去。为此可以调用 cdev_add() 函数。
 
    result = cdev_add (&(led_device.cdev), devno , 1); //传入 cdev 结构的指针,起始设备编号,以及设备编号范围。
    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, devno, NULL, DEV_NAME);  
#else  
    device_create (led_device.dev_class, NULL, devno, 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;  
    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);  //将主设备号和次设备号转换成dev_t类型
  
    del_timer(&(led_device.blink_timer));      //使定时器失效
  
    cdev_del(&(led_device.cdev));              //注销字符设备
    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);  //创建平台设备,成功返回0
   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");  


对于这个驱动程序,我们来分步解析:


1. static int __init s3c_led_init(void)

    这个函数是所有驱动的入口,可以使用_ _init标识,来说明这个函数的唯一性,标识说明该函数只有在初始化时时可用的,初始化以后其他地方不可调用。

static int __init s3c_led_init(void)  
{  
   int       ret = 0;  
  
   ret = platform_device_register(&s3c_led_device);  //创建平台设备,成功返回0
   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;  
}  
这部分函数主要做两件事,一是创建platform设备,而是创建platform驱动。


2.设备和驱动结构体:

      

static struct platform_device s3c_led_device = {

    .name    = "s3c_led",

    .id      = 1,

    .dev     =

    {

        .platform_data = &s3c_led_data,

        .release = platform_led_release,

    },

};

 static struct platform_driver s3c_led_driver = {

    .probe      = s3c_led_probe,

    .remove     = s3c_led_remove,

    .driver     = {

        .name       = "s3c_led",

        .owner      = THIS_MODULE,

    },

};
        设备
platform_device s3c_led_device结构
体用来存放硬件的所有信息,每次通过该结构体将硬件信息传给操作相关函数。
        驱动和设备的结构体成员有差别,但是用来匹配识别的name域必须是相同的。只有这样才能实现互相匹配,实现驱动找设备,设备找驱动。(系统为platform总线定义了一个bus_type(总线类型)的实例platform_bus_type,在此结构体中有一个成员函数: .match,系统通过这个函数完成相关匹配)
 
3.probe()函数:
        驱动和设备互通过name域相匹配之后,驱动会调用结构体下面的s3c_led_probe()函数。     
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; inleds; 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));  //用来对一段内存空间全部设置为某个字符(初始化为0)
    led_device.data = dev->dev.platform_data;  
    cdev_init (&(led_device.cdev), &led_fops);  
    led_device.cdev.owner  = THIS_MODULE;  
  
 //初始化 cdev 后,需要把它添加到系统中去。为此可以调用 cdev_add() 函数。
 
    result = cdev_add (&(led_device.cdev), devno , 1); //传入 cdev 结构的指针,起始设备编号,以及设备编号范围。
    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  failture\n",DEV_NAME);   
        result =  -ENOMEM;   
        goto ERROR;   
    }  
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24)       
    device_create(led_device.dev_class, NULL, devno, NULL, DEV_NAME);  
#else  
    device_create (led_device.dev_class, NULL, devno, 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;   //超时时间
    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;  
  
}  
  
这个函数主要用于做一下几件事:
  • 初始化led灯
  • 分配设备号
  • 设备的注册
  • 自动创建设备节点
  • 初始化定时器(后面会详细介绍)

4.file_operations led_fops()结构体:
    用来建立设备编号与驱动程序连接。其中封装了一些应用程序可以调用的函数。简单来说,只有出现在结构体的函数,应用程序才可以调用。而这些函数(操作)主要是用来实现系统调用的,如open,read等。我们可以认为文件是一个“对象”,而操作它的函数是“方法”。
   
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*/  
};  
       显而易见,该结构体提供给应用程序可调用的函数有open ,read,release,ioctl的四个函数。
     上面使用的是  标准C的标记化结构初始化语法 对结构体初始化,这样是驱动在结构化的定义发生变化是更具有可移植性。

5.s3c_led_remove()
static int s3c_led_remove(struct platform_device *dev)  
{  
    dev_t devno = MKDEV(dev_major, dev_minor);  //将主设备号和次设备号转换成dev_t类型
  
    del_timer(&(led_device.blink_timer));      //使定时器失效
  
    cdev_del(&(led_device.cdev));              //注销字符设备
    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;  
}  
    所有工作结束后,所申请的空间以及设备号等等都要释放,这是一个良好的习惯。

6. s3c_led_exit() 
      该函数在模块被移除前注销接口并向系统返回所有资源。
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);  
}  

7.参数传递:
module_param(debug, int, S_IRUGO);  
module_param(dev_major, int, S_IRUGO);  
module_param(dev_minor, int, S_IRUGO);  

以第一个为例子:dubug为参数名,int为参数类型,S_IRUGO为权限。
具体用法为:insmod s3c_led.ko  debug=xxx   dev_major=xxx dev_minor=XX


在开发板具体测试请参考我的另一篇博客:http://blog.csdn.net/qicheng777/article/details/69660301

 

本文参考博客:http://blog.csdn.net/u010944778/article/details/44904607

推荐一篇关于三大结构体的博客:

    http://www.cnblogs.com/xiaojiang1025/p/6363626.html  

    

你可能感兴趣的:(linux内核驱动,fl2440,platform驱动,Linux)