Linux——平台设备及其驱动

目录

前言

一、平台设备

二、平台驱动

三、平台驱动简单实例

四、 电源管理

五、udev 和驱动的自动加载

六、使用平台设备的LED 驱动

七、自动创建设备节点


前言


        要满足 Linux 设备模型,就必须有总线、设备和驱动。但是有的设备并没有对应的物理总线,比如 LED、RTC 和蜂鸣器等。为此,内核专门开发了一种虚拟总线一-platfomm总线,用来连接这些没有物理总线的设备或者一些不支持热插拔的设备,DM9000 网卡
设备就是挂接在这条总线上的。

一、平台设备

平台设备是用structplatform device 结构来表示的,它的定义如下

struct platform_device {
    const char *name;
    int id;
    bool id auto;
    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;
};


        驱动开发者关心的主要成员如下。

        name: 设备的名字,在平台总线的 match 函数中可用于同平台驱动的匹配

        id:设备的ID 号,用于区别同类型的不同平台设备。
        dev:内的 struct device。
        num_resources:平台设备使用的资源个数。
        resource: 平台设备的资源列表 (数组),指向资源数组中的首元素.

        id_entry:用于同平台驱动匹配的 ID,在平台总线的 match 函数中首先尝试匹配该 ID,如果不成功再尝试用 name 成员来匹配。

        在平台设备中,最关键的就是设备使用的资源信息的描述,这是实现设备和驱动分离的关键。struct resource 的定义如下

struct resource {
    resource_size_t start;
    resource_size_t end;
    const char *name;
    unsigned long flags;
    struct resource *parent,*sibling,*child;
};


驱动开发者关心的主要成员如下。
        start: 资源的开始,对于 IO 内存来说就是起始的内存地址,对于中断资源来说就是起始的中断号,对于 DMA 资源来说就是起始的 DMA 通道号。
        end:资源的结束。
        flags:资源的标志,定义在“include/linux/ioport.h”文件中,最常见的有如下几种.

        IORESOURCE_MEM:资源的类型是内存资源,也包括I/O内存。
        IORESOURCE_IRO:资源的类型是中断资源。
        IORESOURCE_DMA:资源的类型是 DMA 通道资源。
        资源可以组成一个树形结构,由成员parent、sibling和child来完成.

        平台设备及其资源通常存在于BSP(Board Support Package,板级支持包)文件中,该文件通常包含和目标板相关的一些代码。例如对于 QT2410 目标板,其对应的 BSP文件为arch/arm/mach-s3c24xx/mach-gt2410.c,现将其描述CS8900网卡的平台设备摘录如下。

183 static struct resource qt2410_cs89x0_resources[]={
184     [0] = DEFINE_RES_MEM(0X19000000,17),
185     [1] = DEFINE_RES_IRQ(IRQ_EINT9),
186 };
187
188 static struct platform_device qt2410_cs89x0 = {
189     .name            = "cirrus-cs89x0",
190     .num_resources   = ARRAY_SIZE(qt2410_cs89x0_resources),
191     .resource        = qt2410_cs89x0_resources,
192 };


        CS8900 平台设备有两个资源,分别是 IORESOURCE_MEM 和 IORESOURCE_IRQ两种类型的,并用宏 DEFINE_RES_MEM 和 DEFINE_RES_IRQ 来定义。对于 DEFINERES_MEM 宏,里面的两个参数分别是内存的起始地址和大小:对于 DEFINE_RES_IRQ宏,里面的参数则是中断号。读者可以自行查看这两个宏的定义,最终是对 start、end和flags成员进行了赋值。最终定义的平台设备是 qt2410_cs89x0,ARRAY_SIZE 是用于获取数组元素个数的宏。

向平台总线注册和注销的平台设备的主要函数如下。

int platform_add_devices(struct platform_device **devs, int num);
int platform_device_register(struct platform_device *pdev);
void platform_device_unregister(struct platform_device *pdev);


        platform_add_devices用于一次注册多个平台设备,platform_device_register 一次只注册一个平台设备。其实,platform_add_devices 是通过多次调用 platform_device_register来实现的。platform_device_unregister 用于注销平台设备。
        当平台总线发现有和平台设备匹配的驱动时,就会调用平台驱动内的一个函数,并传递匹配的平台设备结构地址,平台驱动就可以从中获取设备的资源信息。关于资源操作的主要函数如下。
 

struct resource *platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num);
resource_size_t resource_size(const struct resource *res);


        platform_get_resource:从平台设备 dev 中获取类型为type、序号为num的资源.

        resource_size:返回资源的大小,其值为 end-start +1。

        例如,在 CS8900 网卡驱动中就有如下的代码来获取资源及其大小。

1857     mem_res  = platform_get_resource(pdev,IORESOURCE_MEM,0);
1858     dev->irq = platform_get_irq(pdev,0);
......
1865     lp->size = resource_size(mem_res);
......
1872     virt_addr = ioremap(mem_res->start,lp->size);


        代码第1857行获取了IORESOURCE_MEM资源,序号为0。代码第1858 行获取了IORESOURCE_IRQ 资源,序号也为0。所以,当资源类型不同后,序号重新开始编号.代码第1865行获取了内存资源的大小。代码第1872行使用ioremap将内存资源进行映射得到映射后的虚拟地址。

二、平台驱动


平台驱动是用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;
    bool prevent_deferred_probe;
};


驱动开发者关心的主要成员如下。
        probe: 总线发现有匹配的平台设备时调用。

        remove:所驱动的平台设备被移除时或平台驱动注销时调用。

        shutdown、suspend 和 resume: 电源管理函数,在要求设备电、挂起和恢复时被调用。内嵌的 struct device_driver 的 pm 成员也有对应的电源管理函数。

        id_table: 平台驱动可以驱动的平台设备ID 列表,可用于和平台设备匹配。

        向平台总线注册和注销的平台驱动的主要函数如下。
 

platform_driver_register(drv)
void platform_driver_unregister(struct platform_driver *);

        因为在驱动中,经常在模块初始化函数中注册一个平台驱动,在清除函数中注销一个平台驱动,所以内核定义了一个宏来简化这些代码,宏的定义如下。

#define module_platform_driver( __platform_driver) \
    module_driver(__platform_driver, platform_driver_register,\
                platform_driver_unregister)

#define module_driver(__driver, __register, __unregister, ...) \
static int __init_driver##_init(void) \
{ \
    return __register(&(__driver),##__VA_ARGS__); \
} \
module_init(__driver##_init);
static void  __exit __driver##_exit(void) \
{ \
    __unregister(&(_driver),##_VA_ARGS__); \
} \
module_exit(__driver##_exit);


三、平台驱动简单实例


        在前面的基础之上,我们可以先来编写一个简单的平台驱动,再编写一个模块来注册两个设备,代码如下
 

#include 
#include 
#include 

#include 

static void pdev_release(struct device *dev)
{
}

struct platform_device pdev0 = {
	.name = "pdev",
	.id = 0,
	.num_resources = 0,
	.resource = NULL,
	.dev = {
		.release = pdev_release,
	},
};

struct platform_device pdev1 = {
	.name = "pdev",
	.id = 1,
	.num_resources = 0,
	.resource = NULL,
	.dev = {
		.release = pdev_release,
	},
};

static int __init pltdev_init(void)
{
	platform_device_register(&pdev0);
	platform_device_register(&pdev1);

	return 0;
}

static void __exit pltdev_exit(void)
{
	platform_device_unregister(&pdev1);
	platform_device_unregister(&pdev0);
}

module_init(pltdev_init);
module_exit(pltdev_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("name ");
MODULE_DESCRIPTION("register a platfom device");
#include 
#include 
#include 

#include 

static int pdrv_suspend(struct device *dev)
{
	printk("pdev: suspend\n");
	return 0;
}

static int pdrv_resume(struct device *dev)
{
	printk("pdev: resume\n");
	return 0;
}

static const struct dev_pm_ops pdrv_pm_ops = {
	.suspend = pdrv_suspend,
	.resume  = pdrv_resume,
};

static int pdrv_probe(struct platform_device *pdev)
{
	return 0;
}

static int pdrv_remove(struct platform_device *pdev)
{
	return 0;
}

struct platform_driver pdrv = {
	.driver = {
		.name    = "pdev",
		.owner   = THIS_MODULE,
		.pm      = &pdrv_pm_ops,
	},
	.probe   = pdrv_probe,
	.remove  = pdrv_remove,
};

module_platform_driver(pdrv);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("name ");
MODULE_DESCRIPTION("A simple platform driver");
MODULE_ALIAS("platform:pdev");

        在 pltdev.c 文件中,代码第 7 行至第 29 行分别定义了两个平台设备,id为0和1以示区别,名字都为 pdev,没有使用任何资源。在模块的初始化函数和清除函数中分别注册和注销了这两个平台设备。
        在 pltdrv.c 文件中,代码第 34 行至第42 行定义了一个平台驱动,名字也为pdev,这样才能和平台设备匹配。pm 是电源管理函数的集合,实现了挂起和恢复两个电源管理操作。因为是虚拟设备,所以并没有做任何电源管理相关的操作。为了简单,probe 和remove函数也只是返回成功而已。代码第44行使用module_platform_driver这个宏来简化模块初始化函数和卸载函数的编写。
        编译和测试的命令如下
Linux——平台设备及其驱动_第1张图片

 Linux——平台设备及其驱动_第2张图片


从上面的测试结果可以看到,平台驱动驱动了两个设备 pdev.0 和 pdev.1,这是设备名字加过构成的名字。


四、 电源管理


        在平台驱动里面实现了挂起和恢复两个电源管理函数,从而可以管理设备的电源状态。 /sys/devices/platform/pdev.0/power/control 和 /sys/devices/platform/pdev.1/power/control两个文件可以用来管理两个设备的电源控制方式,如果文件的内容为 auto,那么设备的电源会根据系统的状态自动进行管理,为on则表示打开。我们首先确定电源控制方式为自动,可以使用下面的命令进行确认。

Linux——平台设备及其驱动_第3张图片

貌似只跑一个linux的内核不行捏

Linux——平台设备及其驱动_第4张图片

没有挂起命令,只使用串口貌似也不能挂起,那这样的话就还是用ubuntu吧

 Linux——平台设备及其驱动_第5张图片

 

接下来将 Ubuntu 系统挂起

Linux——平台设备及其驱动_第6张图片

喵的恢复不了了

Linux——平台设备及其驱动_第7张图片 

试试用vm的挂起能不能起到效果

还是不行下面我修改一下驱动程序看看能不能在挂起后立刻自动恢复
 

 

Linux——平台设备及其驱动_第8张图片

 看看上面的选项是不是y并且确定他有唤醒方式

Linux——平台设备及其驱动_第9张图片

Linux——平台设备及其驱动_第10张图片 

 Linux——平台设备及其驱动_第11张图片

API和版本貌似对不上换一下试试

Linux——平台设备及其驱动_第12张图片 

 

#include 
#include 
#include 

#include 
#include 

static int pdrv_suspend(struct device *dev)
{
    printk("pdev: suspend\n");
    // 在这里添加自动唤醒的代码
    pm_wakeup_event(dev, 0);
    printk("pdev: resume\n");
    return 0;
}

static int pdrv_resume(struct device *dev)
{
    printk("pdev: resume\n");
    return 0;
}

static const struct dev_pm_ops pdrv_pm_ops = {
    .suspend = pdrv_suspend,
    .resume  = pdrv_resume,
};

static int pdrv_probe(struct platform_device *pdev)
{
    return 0;
}

static int pdrv_remove(struct platform_device *pdev)
{
    return 0;
}

struct platform_driver pdrv = {
    .driver = {
        .name    = "pdev",
        .owner   = THIS_MODULE,
        .pm      = &pdrv_pm_ops,
    },
    .probe   = pdrv_probe,
    .remove  = pdrv_remove,
};

module_platform_driver(pdrv);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("name ");
MODULE_DESCRIPTION("A simple platform driver");
MODULE_ALIAS("platform:pdev");

我又修改了一下代码

Linux——平台设备及其驱动_第13张图片

 这次没挂起直接关机了

使用命令后挂起成功了但是又是黑屏状态估计哪没配置好

服了,这里我环境不行就当成功验证了吧,后面我看看怎么修改一下环境或者直接在板子上跑这样很容易恢复。

系统挂起后,再重新恢复系统,使用 dmesg 命令可以看到,驱动中的 suspend 和 resume函数先后都被调用了两次。
# dmesg
[ 171.396323] pdev: suspend
[ 171.396325] pdev; suspend
......

176.699954] pdev:resume

[ 176.699959] pdev: resume


五、udev 和驱动的自动加载


        在上面的例子中,我们可以通过加载模块来向系统添加两个设备,也可以通过移除模块来删除这两个设备。对于这样的操作,我们想使设备被添加到系统后,其驱动能够自动被加载,这对于实际的可支持热插拔的硬件来说更有必要。比如,我们插入一个USB无线网卡,那么对应的驱动就应该自动加载,而不是由用户来手动加载。要做到这一点,就必须利用到一个工具-udev,在入式系统中通常使用 mdev,其功能比 udev 要弱很多,但也可以移植 udev 到嵌入式系统上。
        使用了 Linux 设备模型后,任何设备的添加、删除或状态修改都会导致内核向用户空间发送相应的事件,这个事件叫 uevent,和 kobiect 密切关联。这样用户空间就可以捕获这些事件来自动完成某些操作,如自动加载驱动、自动创建和删除设备节点、修改权限、创建软链接、修改网络设备的名字等。目前实现这个功能的工具就是 udev(或 mdev)这是一个用户空间的应用程序,捕获来自内核空间发来的事件,然后根据其规则文件进行操作。udev的规则文件为/etc/udev/rules.d 目录下后缀为.rules 的文件。
        udev 规则文件用#来注释,除此之外的就是一条一条的规则。每条规则至少包含一个键值对,键分为匹配和赋值两种类型。如果内核发来的事件匹配了规则中的所有匹配键的值,那么这条规则就可以得到应用,并且赋值键被赋予指定的值。一条规则包含了一个或多个键值对,这些键值对用逗号隔开,每个键由操作符规定一个操作,合法的操作符如下。
        ==和!=       :判等,用于匹配键。
        =、+=和:= : 赋值,用于赋值键,=和:=的区别是前者允许用新值来覆盖原来的值后者则不允许。+=则是追加赋值。
        常见的键如下。
        ACTION:事件动作的名字,如add 表示添加
        DEVPATH:事件设备的路径。
        KERNEL:事件设备的名字。
        NAME:节点或网络接口的名字
        SUBSYSTEM:事件设备子系统
        DRIVER:事件设备驱动的名字。
        ENV{key}:设备的属性。
        OWNER、GROUP、MODE:设备节点的权限。
        RUN:添加一个和设备相关的命令到一个命令列表中。

        IMPORT{type):导入一组设备属性的变量,依赖于类型 type。

        上面的键有的是匹配键,有的是赋值键,还有的既是匹配键又是赋值键。另外,还有很多其他的键,在此不一一罗列,详细信息请参见 udev 的 man 手册。

Linux——平台设备及其驱动_第14张图片
    Linux——平台设备及其驱动_第15张图片  Linux——平台设备及其驱动_第16张图片  Linux——平台设备及其驱动_第17张图片

Linux——平台设备及其驱动_第18张图片 

        值还可以使用?、*和来[]进行通配,这和正则表达式中的含义是一样的。接下来来看一个例子。

ACTION--"add", SUBSYSTEM=="scsi_device", RUN+="/sbin/modprobe sg"


        它表示当向 SCSI子系统添加任意设备后都要添加一个命令“/sbin/modprobe sg”到命令列表中,这个命令就是为相应的设备加载 sg 驱动模块。
        在 Ubuntu 中自动加载驱动的规则如下,请将这条规则添加到/etc/udev/rules.d/40-modprobe.rules 文件中,如果没有这个文件请新建一个。

ENV{MODALIAS}=="?*",RUN+="/sbin/modprobe $env(MODALIAS}"


        它表示根据模块的别名信息,用 modprobe 命令加载对应的内核模块。为此,我们要给平台驱动一个别名,如 pltdrv.c 文件中代码的第 49 行。pdev 要和驱动中用于匹配平台设备的名字保持一致。

49 MODULE_ALIAS("platform:pdev");


        添加了这一条规则后,加载 pltdev 模块就可以自动加载平台 pltdrv 驱动
#IsmodI grep plt

# modprobe pltdev

#lsmod I grep plt
pltdrvpltdev

但是我的ubuntu的modprobe不能用,并且开发板还不支持udev所以后面再说。


六、使用平台设备的LED 驱动


        前面我们说过,之前的驱动最大的问题就是没有把设备和驱动分离开,这使得驱动的通用性很差。只要硬件有任何改动(比如换一个管脚,增加或删除 LED 灯),都会导致驱动代码的修改。有了 Linux 设备模型以及平台总线后,我们可以把设备的信息用平台设备来实现,这就大大提高了驱动的通用性。接下来的任务就是把前面的 LED 驱动改造成基于平台总线的设备和驱动。首先是平台设备,代码如下
 

/*fsdev.c*/
#include 
#include 
#include 

#include 

static void fsdev_release(struct device *dev)
{
}

static struct resource led2_resources[] = {
	[0] = DEFINE_RES_MEM(0x11000C40, 4),
};

static struct resource led3_resources[] = {
	[0] = DEFINE_RES_MEM(0x11000C20, 4),
};

static struct resource led4_resources[] = {
	[0] = DEFINE_RES_MEM(0x114001E0, 4),
};

static struct resource led5_resources[] = {
	[0] = DEFINE_RES_MEM(0x114001E0, 4),
};

unsigned int led2pin = 7;
unsigned int led3pin = 0;
unsigned int led4pin = 4;
unsigned int led5pin = 5;

struct platform_device fsled2 = {
	.name = "fsled",
	.id = 2,
	.num_resources = ARRAY_SIZE(led2_resources),
	.resource = led2_resources,
	.dev = {
		.release = fsdev_release,
		.platform_data = &led2pin,
	},
};

struct platform_device fsled3 = {
	.name = "fsled",
	.id = 3,
	.num_resources = ARRAY_SIZE(led3_resources),
	.resource = led3_resources,
	.dev = {
		.release = fsdev_release,
		.platform_data = &led3pin,
	},
};

struct platform_device fsled4 = {
	.name = "fsled",
	.id = 4,
	.num_resources = ARRAY_SIZE(led4_resources),
	.resource = led4_resources,
	.dev = {
		.release = fsdev_release,
		.platform_data = &led4pin,
	},
};

struct platform_device fsled5 = {
	.name = "fsled",
	.id = 5,
	.num_resources = ARRAY_SIZE(led5_resources),
	.resource = led5_resources,
	.dev = {
		.release = fsdev_release,
		.platform_data = &led5pin,
	},
};

static struct platform_device *fsled_devices[]  = {
	&fsled2,
	&fsled3,
	&fsled4,
	&fsled5,
};

static int __init fsdev_init(void)
{
	return platform_add_devices(fsled_devices, ARRAY_SIZE(fsled_devices));
}

static void __exit fsdev_exit(void)
{
	platform_device_unregister(&fsled5);
	platform_device_unregister(&fsled4);
	platform_device_unregister(&fsled3);
	platform_device_unregister(&fsled2);
}

module_init(fsdev_init);
module_exit(fsdev_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("name ");
MODULE_DESCRIPTION("register LED devices");

         由上可知,我们分别定义了 4 个平台设备,每一个平台设备代表一个LED 灯,之所以要这样做,是因为可以任意增加或删除一个 LED 灯。4 个平台设备都有一个IORESOURCE_MEM资源,用来描述2个寄存器所占用的内存空间;名字都为 fsled,用来和平台驱动匹配;id 分别为 2、3、4、5,用来区别不同的设备。还给每个平台设备的platform_data成员赋了值,platform_data 的类型是 void*,用来向驱动传递更多的信息,在这里传递的是每个LED 灯使用的管脚号,因为只有I/O内存是不能够控制一个具体的管脚的。这些平台设备放在 fsled_devices 数组中,在模块初始化函数中使用platform_add_devices 一次注册到平台总线上。在模块的清除函数中,则使用 platform_device_unregister 来注销。
        再来看看平台驱动。

fsled.c

#include 
#include 
#include 

#include 
#include 

#include 
#include 
#include 

#include 
#include 
#include 

#include "fsled.h"

#define FSLED_MAJOR	256
#define FSLED_DEV_NAME	"fsled"

struct fsled_dev {
	unsigned int __iomem *con;
	unsigned int __iomem *dat;
	unsigned int pin;
	atomic_t available;
	struct cdev cdev;
};

static int fsled_open(struct inode *inode, struct file *filp)
{
	struct fsled_dev *fsled = container_of(inode->i_cdev, struct fsled_dev, cdev);

	filp->private_data = fsled;
	if (atomic_dec_and_test(&fsled->available))
		return 0;
	else {
		atomic_inc(&fsled->available);
		return -EBUSY;
	}
}

static int fsled_release(struct inode *inode, struct file *filp)
{
	struct fsled_dev *fsled = filp->private_data;

	writel(readl(fsled->dat) & ~(0x1 << fsled->pin), fsled->dat);

	atomic_inc(&fsled->available);
	return 0;
}

static long fsled_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	struct fsled_dev *fsled = filp->private_data;

	if (_IOC_TYPE(cmd) != FSLED_MAGIC)
		return -ENOTTY;

	switch (cmd) {
	case FSLED_ON:
		writel(readl(fsled->dat) | (0x1 << fsled->pin), fsled->dat);
		break;
	case FSLED_OFF:
		writel(readl(fsled->dat) & ~(0x1 << fsled->pin), fsled->dat);
		break;
	default:
		return -ENOTTY;
	}

	return 0;
}

static struct file_operations fsled_ops = {
	.owner = THIS_MODULE,
	.open = fsled_open,
	.release = fsled_release,
	.unlocked_ioctl = fsled_ioctl,
};

static int fsled_probe(struct platform_device *pdev)
{
	int ret;
	dev_t dev;
	struct fsled_dev *fsled;
	struct resource *res;
	unsigned int pin = *(unsigned int*)pdev->dev.platform_data;

	dev = MKDEV(FSLED_MAJOR, pdev->id);
	ret = register_chrdev_region(dev, 1, FSLED_DEV_NAME);
	if (ret)
		goto reg_err;

	fsled = kzalloc(sizeof(struct fsled_dev), GFP_KERNEL);
	if (!fsled) {
		ret = -ENOMEM;
		goto mem_err;
	}

	cdev_init(&fsled->cdev, &fsled_ops);
	fsled->cdev.owner = THIS_MODULE;
	ret = cdev_add(&fsled->cdev, dev, 1);
	if (ret)
		goto add_err;

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!res) {
		ret = -ENOENT;
		goto res_err;
	}

	fsled->con = ioremap(res->start, resource_size(res));
	if (!fsled->con) {
		ret = -EBUSY;
		goto map_err;
	}
	fsled->dat = fsled->con + 1;

	fsled->pin = pin;
	atomic_set(&fsled->available, 1);
	writel((readl(fsled->con) & ~(0xF  << 4 * fsled->pin)) | (0x1  << 4 * fsled->pin), fsled->con);
	writel(readl(fsled->dat) & ~(0x1 << fsled->pin), fsled->dat);
	platform_set_drvdata(pdev, fsled);

	return 0;

map_err:
res_err:
	cdev_del(&fsled->cdev);
add_err:
	kfree(fsled);
mem_err:
	unregister_chrdev_region(dev, 1);
reg_err:
	return ret;
}

static int fsled_remove(struct platform_device *pdev)
{
	dev_t dev;
	struct fsled_dev *fsled = platform_get_drvdata(pdev);

	dev = MKDEV(FSLED_MAJOR, pdev->id);

	iounmap(fsled->con);
	cdev_del(&fsled->cdev);
	kfree(fsled);
	unregister_chrdev_region(dev, 1);

	return 0;
}

struct platform_driver fsled_drv = { 
	.driver = { 
		.name    = "fsled",
		.owner   = THIS_MODULE,
	},  
	.probe   = fsled_probe,
	.remove  = fsled_remove,
};

module_platform_driver(fsled_drv);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("name ");
MODULE_DESCRIPTION("A simple character device driver for LEDs on FS4412 board");

fsled.h

#ifndef _FSLED_H
#define _FSLED_H

#define FSLED_MAGIC	'f'

#define FSLED_ON	_IO(FSLED_MAGIC, 0)
#define FSLED_OFF	_IO(FSLED_MAGIC, 1)

#endif


        代码第 152 行至第 159 行定义了一个平台驱动 fsled_drv,名字叫 fsled,和平台设备匹配。代码第 161 行是平台驱动注册和注销的简化宏。
        在 fsled_probe 函数中,代码第 86 行首先通过 platform_data 取了管脚号。代码第88 行以平台设备中的 id 为次设备号。代码第 93 行动态分配了 struct fsled_dev 结构对象代码第 105行使用 platform_get_resource 获取了I/O内存的资源,这样要操作 GPIO管脚的两个信息就都获得了,一个是管脚号,一个是 I/O 内存地址。代码第 122 行使用platform set_drvdata 将动态分配得到的 fsled 保存到了平台设备中,便于之后的代码能从平台设备中获取 struct fsled_dev 结构对象的地址,是经常会使用到的一种技巧,也是个驱动支持多个设备的关键。
        函数 fsled_remove 中使用了 platform_get_drvdata 得到了对应的 struct fsled_dev 结构对象的地址,其他操作则是函数 fsled_probe 的反操作。
        函数fsled_open 也使用了 container_of宏得到了对应的struct fsled_dev 结构对象的地址,并保存在 filp->private_data 中,这也是我前面谈到的一个驱动支持多个设备的技巧。
        函数 fsled_ioctl 相比于以前则要简单一些,因为只控制一个对应的LED灯。
        测试的应用代码则是分别打开了 4个 LED 设备文件,然后再分别控制,代码比较简单,这里就不再赘述。测试方法和前面基本一致,只是要创建 4 个设备文件,用到 4个不同的次设备号 2、3、4、5。


七、自动创建设备节点


        前面谈到,内核中设备的添加、删除或修改都会向应用层发送热插拔事件,应用程序可以捕获这些事件来自动完成某些操作,如自动加载驱动、自动创建设备节点等。接下来以mdev为例,来说明如何自动创建设备节点。
        mdev 创建设备节点有两种方法,一种是运行 mdev -s 命令,一种是实时捕获热插拔事件。mdev-s 命令通常在根文件系统挂载完成后运行一次,它将递归扫描/sys/block 目录和/sys/class 目录下的文件,根据文件的内容来调用 make device 自动创建设备文件,这在busybox中的 mdev 源码中展现得非常清楚。

int mdev_main(int argc UNUSED_PARAM,char **argv)
{
......
    if (argv[1] && strcmp(argv[1],"-s") == 0) {
        /*
        * Scan:mdev-s
        */
......
        recursive_action("/sys/block",
            ACTION_RECURSE | ACTION_FOLLOWLINKS | ACTION_QUIET,
            fileAction,dirAction, temp,0);
    }
    recursive_action("/sys/class",
        ACTION_RECURSE | ACTION_FOLLOWLINKS,
        fileAction,dirAction,temp,0);
......


        另外一种情况则是当内核发生了热插拔事件后,mdev会自动被调用,这体现在根文件系统中的/etc/init.d/reS 初始化脚本文件中。

        echo /sbin/mdev > /proc/sys/kernel/hotplug


        内核有一种在发生热插拔事件后调用应用程序的方式,那就是执行/proc/sys/kernel/hotplug 文件中的程序,因为这种方式比较简单,所以常用在嵌入式系统之中。而之前说的udev 使用的则是 netlink 机制。发生热插拔事件时,调用 mdev 程序会将热插拔信息放在环境变量和参数当中,mdev 程序利用这些信息就可以自动创建设备节点,在 mdev 的源码中也有清晰的体现。

int mdev_main(int argc UNUSED_PARAM,char **argv)
{
......
    env_devname = getenv("DEVNAME");/* can be NULL */
    G.subsystem = getenv("SUBSYSTEM");
    action = getenv("ACTION");
    env_devpath = getenv("DEVPATH");
......
    op =index in strings(keywords,action);
......

    snprintf(temp,PATH_MAX,"/sys%s", env_devpath);
    if (op == OP_remove) {
......
        if (!fw)
            make_device(env_devname, temp,op);
        }
        else {
            make_device(env_devname, temp,op);
            if (ENABLE_FEATURE_MDEV_LOAD_FIRMWARE) {
                if (op== OP_add && fw)
                    load_firmware(fw, temp);
            }
        }
......


        上面的代码的总体思路是根据 ACTION 键的值来决定 op 是增加还是移除操作,最终调用 make_device 来自动创建或删除设备节点。

        了解了应用层自动创建设备节点的方式后,接下来就需要讨论在驱动中如何实现了.既然自动设备节点的创建要依靠热插拔事件和 sysfs 文件系统,那这和我们之前讨论的kobjet 就是分不开的,mdev 扫描/sys/class 目录暗示我们要创建类,并且在类下面应该有具体的设备。为此,内核提供了相应的 API。

    class_create(owner,name)
    void class_destroy(struct class *cls);
    struct device *device_create(struct class *class, struct device *parent, dev_t
devt, void *drvdata, const char *fmt,...);
    void device_destroy(struct class *class, dev_t devt);


        class_create: 创建类,owner 是所属的模块对象指针,name 是类的名字,返回 struct
class 对象指针,返回值通过IS_ERR 宏来判断是否失败,通过 PTR_ERR 宏来获得错误码。
        class_destroy:销毁cls 类。
        device_create:在类class 下创建设备,parent 是父设备,没有则为 NULL。devt 是设备的主次设备号,drvdata 是驱动数据,没有则为 NULL。fmt 是格式化字符串,使用方法类似于printk。
        device_destroy:销毁 class 类下面主次设备号为 devt 的设备。返回值的检查方式同class_create。
        添加了自动创建设备的驱动的主要代码如下

#include 
#include 
#include 

#include 
#include 

#include 
#include 
#include 

#include 
#include 
#include 

#include "fsled.h"

#define FSLED_MAJOR	256
#define FSLED_DEV_NAME	"fsled"

struct fsled_dev {
	unsigned int __iomem *con;
	unsigned int __iomem *dat;
	unsigned int pin;
	atomic_t available;
	struct cdev cdev;
	struct device *dev;
};

struct class *fsled_cls;

static int fsled_open(struct inode *inode, struct file *filp)
{
	struct fsled_dev *fsled = container_of(inode->i_cdev, struct fsled_dev, cdev);

	filp->private_data = fsled;
	if (atomic_dec_and_test(&fsled->available))
		return 0;
	else {
		atomic_inc(&fsled->available);
		return -EBUSY;
	}
}

static int fsled_release(struct inode *inode, struct file *filp)
{
	struct fsled_dev *fsled = filp->private_data;

	writel(readl(fsled->dat) & ~(0x1 << fsled->pin), fsled->dat);

	atomic_inc(&fsled->available);
	return 0;
}

static long fsled_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	struct fsled_dev *fsled = filp->private_data;

	if (_IOC_TYPE(cmd) != FSLED_MAGIC)
		return -ENOTTY;

	switch (cmd) {
	case FSLED_ON:
		writel(readl(fsled->dat) | (0x1 << fsled->pin), fsled->dat);
		break;
	case FSLED_OFF:
		writel(readl(fsled->dat) & ~(0x1 << fsled->pin), fsled->dat);
		break;
	default:
		return -ENOTTY;
	}

	return 0;
}

static struct file_operations fsled_ops = {
	.owner = THIS_MODULE,
	.open = fsled_open,
	.release = fsled_release,
	.unlocked_ioctl = fsled_ioctl,
};

static int fsled_probe(struct platform_device *pdev)
{
	int ret;
	dev_t dev;
	struct fsled_dev *fsled;
	struct resource *res;
	unsigned int pin = *(unsigned int*)pdev->dev.platform_data;

	dev = MKDEV(FSLED_MAJOR, pdev->id);
	ret = register_chrdev_region(dev, 1, FSLED_DEV_NAME);
	if (ret)
		goto reg_err;

	fsled = kzalloc(sizeof(struct fsled_dev), GFP_KERNEL);
	if (!fsled) {
		ret = -ENOMEM;
		goto mem_err;
	}

	cdev_init(&fsled->cdev, &fsled_ops);
	fsled->cdev.owner = THIS_MODULE;
	ret = cdev_add(&fsled->cdev, dev, 1);
	if (ret)
		goto add_err;

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!res) {
		ret = -ENOENT;
		goto res_err;
	}

	fsled->con = ioremap(res->start, resource_size(res));
	if (!fsled->con) {
		ret = -EBUSY;
		goto map_err;
	}
	fsled->dat = fsled->con + 1;

	fsled->pin = pin;
	atomic_set(&fsled->available, 1);
	writel((readl(fsled->con) & ~(0xF  << 4 * fsled->pin)) | (0x1  << 4 * fsled->pin), fsled->con);
	writel(readl(fsled->dat) & ~(0x1 << fsled->pin), fsled->dat);
	platform_set_drvdata(pdev, fsled);

	fsled->dev = device_create(fsled_cls, NULL, dev, NULL, "led%d", pdev->id);
	if (IS_ERR(fsled->dev)) {
		ret = PTR_ERR(fsled->dev);
		goto dev_err;
	}

	return 0;

dev_err:
	iounmap(fsled->con);
map_err:
res_err:
	cdev_del(&fsled->cdev);
add_err:
	kfree(fsled);
mem_err:
	unregister_chrdev_region(dev, 1);
reg_err:
	return ret;
}

static int fsled_remove(struct platform_device *pdev)
{
	dev_t dev;
	struct fsled_dev *fsled = platform_get_drvdata(pdev);

	dev = MKDEV(FSLED_MAJOR, pdev->id);

	device_destroy(fsled_cls, dev);
	iounmap(fsled->con);
	cdev_del(&fsled->cdev);
	kfree(fsled);
	unregister_chrdev_region(dev, 1);

	return 0;
}

struct platform_driver fsled_drv = { 
	.driver = { 
		.name    = "fsled",
		.owner   = THIS_MODULE,
	},  
	.probe   = fsled_probe,
	.remove  = fsled_remove,
};

static int __init fsled_init(void)
{
	int ret;

	fsled_cls = class_create(THIS_MODULE, "fsled");
	if (IS_ERR(fsled_cls))
		return PTR_ERR(fsled_cls);

	ret = platform_driver_register(&fsled_drv);
	if (ret)
		class_destroy(fsled_cls);

	return ret;
}

static void __exit fsled_exit(void)
{
	platform_driver_unregister(&fsled_drv);
	class_destroy(fsled_cls);
}

module_init(fsled_init);
module_exit(fsled_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("name ");
MODULE_DESCRIPTION("A simple character device driver for LEDs on FS4412 board");

        代码第177行使用class_create 创建了名叫 fsled的类。代码第127行使用device_create在 fsled 类下面创建了 led%d 的设备,%d 用平台设备的id 来替代。在创建过程中,内核会发送热插拔事件给 mdev,mdev 利用这些信息就可以创建设备节点,因为设备的名字和设备号都传递给了 device_create,而内核又会利用这些参数生成热插拔信息。

        使用上面的驱动且驱动加载成功后,设备节点就自动被创建了,不需要再手动创建.整个测试过程和前面的例子类似,这里就不再重复了     

Linux——平台设备及其驱动_第19张图片   

#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include "fsled.h"

int main(int argc, char *argv[])
{
	int fd[4];
	int ret;
	int num = 0;

	fd[0] = open("/dev/led2", O_RDWR);
	if (fd[0] == -1)
		goto fail;
	fd[1] = open("/dev/led3", O_RDWR);
	if (fd[1] == -1)
		goto fail;
	fd[2] = open("/dev/led4", O_RDWR);
	if (fd[2] == -1)
		goto fail;
	fd[3] = open("/dev/led5", O_RDWR);
	if (fd[3] == -1)
		goto fail;

	while (1) {
		ret = ioctl(fd[num], FSLED_ON);
		if (ret == -1)
			goto fail;
		usleep(500000);
		ret = ioctl(fd[num], FSLED_OFF);
		if (ret == -1)
			goto fail;
		usleep(500000);

		num = (num + 1) % 4;
	}
fail:
	perror("led test");
	exit(EXIT_FAILURE);
}

测试程序是个流水灯不多说了。

---------------------------------------------------------------------------------------------------------------------------------

        这周感觉更累了,已经没有太多的精力继续学习驱动了,但是周末我们软件部门聚餐时听着那些四五十的大佬说着人生经历讲着做人道理时我幡然醒悟。一时的强弱代表不了什么态度才是最重要的。只要你有终身学习的态度不会成为不了强者的,在公司做工作也是一种学习,即便有时做起来不是很愉快,但往往向着难受的方向学习,才能发展的更加全面。现在来公司已经三周了,我刚刚敢叫大家的名字,因为大家都是英文花名,我是农村的从小英语就不好,一直不敢张口叫大家的名字,只能尴尬的这个同学那个同学,后来发现没人在意你的,大概意思对就行了。

        以前在学校听说能画四层板的就能找到工作能画六层板就是大佬,偶然和硬件部的大佬交流了一下,喵的我们公司底板28层,我丢啊,玩个锤子。晚上吃饭的时候,听着大家都是各大名牌大学的研究生。其中不乏浙大这种顶尖学府的人才。顿时一个二本还没毕业的大三学生的我自卑感油然而生。突然萌生了考研的念头。可是真的很讨厌英语。如果以后有机会一定要试试另一种方式读研。去国外读研只需要一封介绍信。但是需要你是某个领域的顶尖人才。我们公司有个华三挖来的大佬。什么都会,整个FPGA都是他做的,这么多年问题不超过7个,或许大家绝对7个很多,但是真正工作时你就知道这个含金量了,我们软件的问题单貌似有70多个还是10多个人哦。重要的是他还精通python、通信、uboot。其它的我就不了解了,一个人可以全方位都牛,成为一个架构师我想这是每个专心搞技术的人的最高成就了吧。

        我们组有个很阳光的大哥,人巨好,我们老大太严厉了有时我都不敢问,但是这个邻家哥哥一样的同事每次问他东西都给我耐心解决。这周刚好赶上版本验证,第一次经历这种大规模代码发布新版本前的验证工作。挺有意思的,因为我是实习生可能活少吧,大半天就完事了,他们几个忙的都见不到人了,过了巨无聊的一天,第二天他们还在验证,我就自己去解决一些新的问题单。其实看着那些署了自己名字的代码进入公司的版本成为其中的一部分,还是很有成就感的,以后可以吹牛说你用的手机芯片是我写的程序验证出来的啦哈哈。我的老大之前有点不理解他,后来吃饭才知道他原来已经结婚了,怪不得赚那么多,还每天那么节俭,在领导面前被迫改变自己的性格。结婚后要学会的第一件事或许就是责任吧。

        我还发现了一种名叫领导力和人格魅力的东西。曾经在学校时我在一个大我两届的学长身上见到过。如今在这家公司我又见到了。一个长者,大概是公司的二号人物。真的很有人格魅力。有种即便做错了也想跟着他的感觉。

        吃完饭去唱歌时玩游戏输了,不得不喝了两口酒,我根本就不能喝酒,吃饭时领导敬酒我都是喝水的,有点不好意思哈哈。但是没办法输了就要接受惩罚。有个也是东北的姐姐问到我为什么会来杭州。我楞了一下,回了两个字,舔狗。是啊,北京一个月实习期能拿一万多的不去,深圳一个月5000供吃住,常州4500管住都没去,来了杭州一个月也就4000块还不管吃住。最后呢,人家还不是很愿意搭理你,不是舔狗又是什么呢。

        她并不是很惊艳的那种美丽,也没有很好的身材。但是就有一种奇特的魔力吸引我,或许是种别样的人格魅力吧。智慧型吸引?又或许是因为曾在我最需要帮助时帮了我。呼,累。但是说出来舒服了很多。喵的在CSDN写日常不会被和谐吧哈哈。后面技术更扎实了解决问题速度提高后就开始写写小说,现在已经看完两千多本了,感觉能看下去的很少了,所以以后自己写哈哈。就到这里咯,以后尽量保证每周更新一篇博客,学习些新东西,顺便记录一下一周的故事,就当是日记了。以后也有可以翻一翻的东西。

你可能感兴趣的:(驱动开发,linux,驱动开发,平台设备,嵌入式)