第一章 基于linux 3.4.2内核的S3C24xx的LED程序学习笔记

在学习这位作者对S3C24xx的LED驱动程序笔记的基础上,整理下自己的学习笔记,加深理解。
参考:https://blog.csdn.net/woshidahuaidan2011/article/details/51695106

首先common-smdk.c函数位于linux-3.4.2\arch\arm\mach-s3c24xx目录下
1.构建设备信息结构体

static struct s3c24xx_led_platdata smdk_pdata_led4 = {
    .gpio       = S3C2410_GPF(4),
    .flags      = S3C24XX_LEDF_ACTLOW | S3C24XX_LEDF_TRISTATE,
    .name       = "led4",
    .def_trigger    = "timer",
};

struct s3c24xx_led_platdata {
    unsigned int         gpio;
    unsigned int         flags;

    char            *name;
    char            *def_trigger;
};

该结构体中包含设备的名称、设备的引脚、标志(低电平有效也就是低电平灯亮,三态无效)以及触发方式。
目前有以下几种触发方式:
“backlight” – 受帧缓存器系统的控制,led此时作为背光灯LED
“default-on” – 此时led亮灭受对应LED引脚的控制
“heartbeat” –led收到其控制频率的双倍的闪动
“ide-disk” - LED 指示磁盘有活动
“timer” - LED 以设定的频率闪动

2.定义平台设备信息

static struct platform_device smdk_led4 = {
    .name       = "s3c24xx_led",
    .id     = 0,
    .dev        = {
        .platform_data = &smdk_pdata_led4,
    },
};

struct platform_device {
    const char  * name;
    int     id;
    struct device   dev;
    u32     num_resources;
    struct resource * resource;

    const struct platform_device_id *id_entry;

    /* MFD cell pointer */
    struct mfd_cell *mfd_cell;

    /* arch specific additions */
    struct pdev_archdata    archdata;
};

这里定义平台设备信息,这里的三个led的平台设备名称为s3c24xx_led,改名字用作sys/device下显示的目录名,这个名字很重要,后期设备信息与驱动的匹配就依靠该设备名;接下来是id号码,id用于给具有相同平台设备名的几个设备各自不同的编号,如果只有一个设备,通常填-1;然后是一个设备结构体,这里只填写该结构体的platform_data信息,在结构体device中,platform_data就是linux为了适应不同的单板(比如单板的封装、硬件的引脚的连线等不同),告诉系统本单板是如何连线等硬件结构的。比如led通过platform_data指出了其连接led的引脚,工作状态,led单个设备的名字和触发方式等信息。系统通过调用dev_get_platdata函数来获取platform_data的信息。
从c语言定义上来理解platform_data的话,其定义 void *platform_data,也就是一个void类型的指针,可以指向任何类型,因此可以理解为platform_data可以是人为任意定义的东西,这个东西只是让驱动设计者传递驱动程序想要传入进去的信息而已。

3.若平台设备信息有多个,则定义一个platform_device结构体数组指针

/* devices we initialise */

static struct platform_device __initdata *smdk_devs[] = {
    &smdk_led4,
    &smdk_led5,
    &smdk_led6,
    &smdk_led7,
};

成员都是刚才定义的平台设备信息结构体,这样当程序要把所有设定好的平台设备信息注册进内核的时候,就可以直接把smdk_devs传递个内核。
__initdata表示这是初始化有关的数据,初始化完毕后这个内存可以被释放。

4.接下来是初始化函数:

void __init smdk_machine_init(void)
{
    /* Configure the LEDs (even if we have no LED support)*/

    s3c_gpio_cfgpin(S3C2410_GPF(4), S3C2410_GPIO_OUTPUT);
    s3c_gpio_cfgpin(S3C2410_GPF(5), S3C2410_GPIO_OUTPUT);
    s3c_gpio_cfgpin(S3C2410_GPF(6), S3C2410_GPIO_OUTPUT);
    s3c_gpio_cfgpin(S3C2410_GPF(7), S3C2410_GPIO_OUTPUT);

    s3c2410_gpio_setpin(S3C2410_GPF(4), 1);
    s3c2410_gpio_setpin(S3C2410_GPF(5), 1);
    s3c2410_gpio_setpin(S3C2410_GPF(6), 1);
    s3c2410_gpio_setpin(S3C2410_GPF(7), 1);
    ......
    platform_add_devices(smdk_devs, ARRAY_SIZE(smdk_devs));
    ......
}

/*
 * platform_add_devices - add a numbers of platform devices
 * @devs: array of platform devices to add
 * @num: number of platform devices in array
 */
int platform_add_devices(struct platform_device **devs, int num)
{
    int i, ret = 0;

    for (i = 0; i < num; i++) {
        ret = platform_device_register(devs[i]);
        if (ret) {
            while (--i >= 0)
                platform_device_unregister(devs[i]);
            break;
        }
    }

    return ret;
}

/*
 * platform_device_register - add a platform-level device
 * @pdev: platform device we're adding
 */
int platform_device_register(struct platform_device *pdev)
{
    device_initialize(&pdev->dev);
    arch_setup_pdev_archdata(pdev);
    return platform_device_add(pdev);
}

/*
 * platform_device_add - add a platform device to device hierarchy
 * @pdev: platform device we're adding
 *
 * This is part 2 of platform_device_register(), though may be called
 * separately _iff_ pdev was allocated by platform_device_alloc().
 */
int platform_device_add(struct platform_device *pdev)
{
    ......
    ret = device_add(&pdev->dev);
    ......
}

在初始化函数中,主要就是对GPIO口的设置以及将上面的smdk_dev数组添加进设备平台链表中。
其中void s3c2410_gpio_cfgpin(unsigned int pin, unsigned int function)函数设置GPIO口的工作模式、输入、输出、中断等
而void s3c2410_gpio_pullup(unsigned int pin, unsigned int to)函数设置相应GPIO口的电平,pin=S3C2410_GPF(7),to=1,则把S3C2410的gpf7置高。
platform_add_devices(smdk_devs, ARRAY_SIZE(smdk_devs))函数就是将刚才设定的所有平台设备信息添加进设备链表中。
通过调用platform_device_register(struct platform_device *pdev)将每一个平台设备信息用platform_device_add(struct platform_device *pdev)函数加入到设备链表中,最终调用的还是device_add()函数。

所以一个驱动包含两部分,一个为平台设备信息,其主要包含一些要写驱动程序的硬件或者自定义信息;另一个就是真正的驱动函数。
(1)第一部分就是对硬件的初始化,用函数struct s3c24xx_led_platdata()设置设备的各种信息,然后用平台设备函数struct platform_device()将设备信息放入其中,并分配name与id号等信息,如果有多个相同name的设备,把它们再放入一个struct platform_device __initdata *smdk_devs[]数组中,最后将这个数组加入到设备链表中。
接下来看一下真正的驱动函数:该函数位于 drivers/leds/leds-s3c24xx.c
关于驱动函数我们从下向上看:

/* 驱动代码作者
 * 驱动代码描述
 * 驱动代码遵循协议
 * 驱动代码别名
 */
MODULE_AUTHOR("Ben Dooks <[email protected]>");  
MODULE_DESCRIPTION("S3C24XX LED driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:s3c24xx_led");

继续分析:

module_platform_driver(s3c24xx_led_driver);

这个宏被定义在include\linux\platform_device.h中

/* module_platform_driver() - Helper macro for drivers that don't do
 * anything special in module init/exit.  This eliminates a lot of
 * boilerplate.  Each module may only use this macro once, and
 * calling it replaces module_init() and module_exit()
 */
#define module_platform_driver(__platform_driver) \
    module_driver(__platform_driver, platform_driver_register, \
            platform_driver_unregister)

根据注释可以看到,该宏是为了减少要写的样本文件而存在的,原来都是调用module_init()和module_exit()宏来进行注册和卸载,现在只需要使用module_platform_driver()就可以自动替换注册和卸载宏,在调用probe函数的时候就会调用platform_driver_register()函数对驱动进行注册,而调用remove函数的时候就会调用platform_driver_unregister函数卸载驱动。
该宏调用s3c24xx_led_driver结构体,而结构体中

static struct platform_driver s3c24xx_led_driver = {
    .probe      = s3c24xx_led_probe,
    .remove     = s3c24xx_led_remove,
    .driver     = {
        .name       = "s3c24xx_led",
        .owner      = THIS_MODULE,
    },
};

.probe= s3c24xx_led_probe,这个函数是告诉内核,当加载驱动的时候就去调用这个探测函数s3c24xx_led_probe,也就是说,当你调用驱动之后所能实现的所有功能都在这个函数中。当要卸载驱动时,调用.remove对应的s3c24xx_led_remove函数;之后初始化了driver结构体的两个成员,其中name要与平台设备信息结构体定义的name相同,内核会比较name中的字符串来确定是否注册驱动,而驱动也依靠该name获取驱动对应设备的电气特性;最后的.owner= THIS_MODULE,是个宏定义: #define THIS_MODULE (&__this_module)。是一个struct module变量,代表当前模块,可以通过THIS_MODULE宏来引用模块的struct module结构,比如使用THIS_MODULE->state可以获得当前模块的状态。
static int s3c24xx_led_probe(struct platform_device *dev)函数是驱动函数的核心,分析一下代码:

static int s3c24xx_led_probe(struct platform_device *dev)
{
/* 首先定义一个s3c24xx_led_platdata结构体指针变量pdata,
 * 让它指向platform_device中dev结构体成员的私有数据
 * 即获取平台设备中定义的smdk_pdata_ledx的信息,也就是定义引脚对应、
 * 触发方式、子设备信息的名字(LEDn)、标志灯等信息
 */
    struct s3c24xx_led_platdata *pdata = dev->dev.platform_data;

/* 定义一个s3c24xx_gpio_led结构体指针变量
 * 这个结构体中有两个成员:
 * struct led_classdev和struct s3c24xx_led_platdata
 */
    struct s3c24xx_gpio_led *led;
    int ret;

/* kzalloc函数是为struct s3c24xx_led_platdata数据结构分配内存。
 * 每当driver probe一个具体的device实例的时候,都需要建立一些私有的数据结构
 * 来保存该device的一些具体的硬件信息(对于led驱动来说,这个数据结构就是
 * struct s3c24xx_led_platdata)。
 * 要注意的是,利用kmalloc或kzalloc来分配内存会带来一些潜在问题:
 * 因为初始化过程中,除了memory,driver会为probe的device分配各种资源
 * 例如IRQ号,io memory map、DMA等等,在初始化过程中需要管理这么多的资源分配
 * 以及释放时,很多驱动程序就会出现资源管理的问题,而且由于这些问题都是在异常路径
 * 上的问题,也不是很容易就可以找出问题点。
 * 因此,内核采取Devres模式,即devm(device resource management)模块。
 * 即devm_kzalloc(struct device * dev, size_t size, gfp_t gfp)函数
 * 核心思想是资源是设备的资源,资源的管理归device,不需要driver过多参与,当
 * device和driver detach时,device会自动释放其所有的资源,该函数返回值为
 * (void *),假如成功分配出内存会返回申请内存的地址指针,假如分配失败的话会返
 * 回NULL。
 * 参考:http://www.wowotech.net/linux_kenrel/pin-controller-driver.html
 */

    led = kzalloc(sizeof(struct s3c24xx_gpio_led), GFP_KERNEL);
    if (led == NULL) {
        dev_err(&dev->dev, "No memory for device\n");
        return -ENOMEM;
    }
/* led的值是申请到的内存的地址指针,dev是platform_device结构体的指针
 * platform_set_drvdata函数先把这个为驱动申请到的内存地址放到平台设备信息中
 * 然后再对led中的各个成员进行初始化。
 * Question1:
 * 此处有一个疑问,当我通过kzlloc函数申请到内存,将内存地址通过
 * platform_set_drvdata函数(实际上是dev_set_drvdata函数)存放起来,之后对
 * s3c24xx_gpio_led结构体中的各个成员进行设置,那什么时候用dev_get_drvdata
 * 函数调用那块内存?
 * Answer:
 * 会在remove中调用dev_get_drvdata函数
 */
    platform_set_drvdata(dev, led);
/* 对定义的struct s3c24xx_gpio_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 */
/* device_create函数跟class_create函数是配对使用的,class_create会在
 * sys/class目录生成相应的类,在本例中会生成leds目录,一旦创建好了这个类,
 * 再调用device_create(…)函数来在/dev目录下创建相应的设备节点。这样,加载模
 * 块的时候,用户空间中的udev会自动响应device_create(…)函数,去/sysfs下
 * 寻找对应的类从而创建设备节点。
 * Question2:
 * 既然led_classdev_register中调用了device_create,而device_create与
 * class_create是配对使用的,那class_create在哪里调用了?
 * Answer:
 * ????????????????(还没有用SourceInsight找到调用关系)
 */
    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;
}

至此probe函数已经调用完成,驱动已经加载完成。
当要卸载驱动的时候,我们会去调用s3c24xx_led_driver结构体中的remove函数,即:

static int s3c24xx_led_remove(struct platform_device *dev)
{
    struct s3c24xx_gpio_led *led = pdev_to_gpio(dev);

    led_classdev_unregister(&led->cdev);
    kfree(led);

    return 0;
}

首先pdev_to_gpio函数的定义是:

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

它实际上调用了platform_get_drvdata(dev)这个函数,就是与platform_set_drvdata函数对应的函数。为什么需要一开始就调用platform_get_drvdata这个函数呢?是因为在装载驱动的时候用kzalloc函数为结构体s3c24xx_gpio_led分配了内存,也通过led_classdev_register注册了类和设备。那么在remove函数中我们就需要unregister和释放内存。如果不知道当时分配的内存地址就无法进行unregister和释放内存操作,所以才需要在probe中将内存的地址存放在一个地方以供卸载时候使用。

猜你喜欢

转载自blog.csdn.net/weixin_41354745/article/details/82026449