3.字符设备驱动

来源:互联网 发布:网络平台开发预算 编辑:程序博客网 时间:2024/06/03 14:17

linux设备分为字符设备、块设备和网络设备三种,字符设备是最常见、最简单的一种。字符设备的访问是以字节流的形式来访问设备的,换句话说,应用程序对它的读取是以字节为单位,而且要按照先后顺序不能随机读取。串口是最常见的字符设备,它在进行收发数据时就是一个字节一个字节进行传输。

    cdev结构体

    struct cdev {

       structkobject kobj; /*内嵌的kobject对象*/

       structmodule *owner;    /*所属模块*/

       const structfile_operations *ops; /*文件操作结构体*/

       structlist_head list;  

       dev_t dev;        /*设备号*/

       unsigned intcount;

};

    cdev 结构体的 dev_t 成员定义了设备号,为 32位,其中高 12 位为主设备号,低20 位为次设备号。使用下列宏可以从dev_t 获得主设备号和次设备号。         MAJOR(dev_t dev)       MINOR(dev_t dev)

而使用下列宏则可以通过主设备号和设备号生成 dev_t

                MKDEV(int major, int minor)

Linux内核提供一组函数用于操作cdev结构体:

void cdev_init(struct cdev *, struct file_operations *);

用于初始化 cdev 的成员,并建立 cdev 和 file_operations 之间的连接

 

struct cdev *cdev_alloc(void);

动态申请一个 cdev 内存

void cdev_put(struct cdev *p);

int cdev_add(struct cdev *, dev_t, unsigned);

向系统添加一个 cdev,完成字符设备的注册

void cdev_del(struct cdev *);

向系统删除一个 cdev,完成字符设备的注销

 

file_operations 结构体

    structfile_operations

    {

       structmodule *owner;

       // 拥有该结构的模块的指针,一般为THIS_MODULES

       loff_t(*llseek)(structfile *, loff_t, int);

       // 用来修改文件当前的读写位置

       ssize_t(*read)(structfile *, char _ _user *, size_t, loff_t*);

       // 从设备中同步读取数据

       ssize_t(*write)(structfile *, const char _ _user *, size_t,

       loff_t*);

       // 向设备发送数据

       unsignedint(*poll)(struct file *, struct poll_table_struct*);

       // 轮询函数,判断目前是否可以进行非阻塞的读取或写入

       int(*ioctl)(structinode *, struct file *, unsigned int, unsigned

       long);

       // 执行设备 I/O 控制命令

       int(*mmap)(structfile *, struct vm_area_struct*);

       // 用于请求将设备内存映射到进程地址空间

       int(*open)(structinode *, struct file*);

       // 打开

       int(*release)(structinode *, struct file*);

       ……

};

内核空间数据和用户空间访问函数

unsigned long __must_check copy_from_user(void *to, constvoid __user *from, unsigned long n)

用户空间缓存区到内核空间的复制

to:目标地址,内核空间地址

from:源地址,用户空间地址

n:拷贝数据的字节数

unsigned long __must_check copy_to_user(void __user *to,const void *from, unsigned long n)

内核空间到用户空间缓存区的复制

to:目标地址,用户空间地址

from:源地址,内核空间地址

n:拷贝数据的字节数

3.1早期注册一个设备驱动程序的经典方式:

int register_chrdev(unsigned int major, const char *name,

           const struct file_operations *fops)

    major:设备的主设备号   0 内核会自动分配主设备号

    name:驱动程序的名词(出现在/proc/devices)

    fops:file_operations结构

对register_chrdev的调用谷歌将为给定的主设备号注册0-255作为次设备号,并为每个设备建立一个对应默认cdev结构。

 

移除设备函数

void unregister_chrdev(unsigned int major, const char *name)

    major和name必须与传递给register_chrdev函数的值保持一致,否则该调用会失败。

3.1.1 LED字符设备驱动示例

Led驱动:

/** *4LED 1亮 0灭 *早期经典的字符设备注册方法 *register_chrdev *write read * */#include <linux/init.h>#include <linux/module.h>#include <linux/fs.h>#include <linux/io.h>#include <linux/uaccess.h>#include <linux/slab.h>#define LED_MAJOR 185#define GPM4BASE 0x11000000#define GPM4CON  0x2e0#define GPM4DAT  0x2e4static char *VGPM4BASE = NULL;#define VGPM4CON *((u32 *)(VGPM4BASE+GPM4CON))#define VGPM4DAT *((u32 *)(VGPM4BASE+GPM4DAT))static void led_init(void){VGPM4CON &= ~0xffff;VGPM4CON |= 0x1111;/*设置输出引脚*/VGPM4DAT |= 0x0f;/*led all off*/}static void led_stat(char *buf){u32 tmp;int i;tmp = VGPM4DAT;for(i = 0; i < 4; i++){if(tmp & (1<<i)){buf[i] = 0;}else{buf[i] = 1;}}}static void led_ctrl(int num, int stat){if(stat){VGPM4DAT &= ~(1 << (num - 1));}else{VGPM4DAT |= (1 << (num - 1));}}static intexynos4412_led_open(struct inode *inodp, struct file *filp){return 0;}static ssize_t exynos4412_led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *fpos){char stat[4] = {0};led_stat(stat);if(copy_to_user(buf,stat,sizeof(stat))){return -EINVAL;}return sizeof(stat);}static ssize_texynos4412_led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *fpos){u8 cmd;u8 num,stat;if(copy_from_user(&cmd,buf,cnt)){//get_user(cmd,buf);return -EINVAL;}num = cmd >> 4;stat = cmd & 0xf;//cmd 高4位放led编号 低4位放led状态if(( num < 1 ) || ( num > 4 ) ){        return -EINVAL;}if(( stat != 0 ) && ( stat != 1 )){        return -EINVAL;}led_ctrl(num, stat);return 1;}static int exynos4412_led_release(struct inode *inodp, struct file *filp){return 0;}/**字符驱动的核心 *当应用程序操作设备文件时所调用的open read write等函数 *最终会调用这个结构中对应的函数 */static struct file_operations exynos4412_led_fops = {.owner   = THIS_MODULE,/*内核使用这个字段以避免在模块的操作正在被使用时卸载该模块*/.open    = exynos4412_led_open,.read    = exynos4412_led_read,.write   = exynos4412_led_write,.release = exynos4412_led_release/*当file结构被释放时,将调用这个操作*/};static int __init exynos4412_led_init(void){VGPM4BASE = ioremap(GPM4BASE,SZ_4K);/*内核中都是在虚拟内存中运行,通过ioremap IO映射*/if(NULL == VGPM4BASE){return -ENOMEM;}led_init();return register_chrdev(LED_MAJOR,"LED",&exynos4412_led_fops);}module_init(exynos4412_led_init);static void __exit exynos4412_led_exit(void){unregister_chrdev(LED_MAJOR,"LED");printk("goodbye! led  driver");}module_exit(exynos4412_led_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("Songze Lee");MODULE_VERSION("verson 1.0");MODULE_DESCRIPTION("char driver for led");

Makefile:

obj-m:=led.oOUR_KERNEL :=/ARM/linux-3.5-songze/all:make -C $(OUR_KERNEL) M=$(shell pwd) modulesclean:make -C $(OUR_KERNEL) M=`pwd` clean

应用测试程序:

#include <sys/types.h>#include <stdlib.h>#include <string.h>#include <stdio.h>#include <sys/stat.h>#include <fcntl.h>void pri_usage(char *info){printf("-------- usage: ------\n");printf("usage1: %s stat\n", info);printf("usage2: %s num on/off\n", info);printf("           num: <1~4>\n");}//a.out led stat//./a.out led 1 onint main(int argc, char **argv){int i;int ret;int num;char buf[4];int fd;unsigned char cmd = 0;if((argc !=  3) && (argc != 4)){pri_usage(argv[0]);exit(1);}fd = open(argv[1], O_RDWR | O_NONBLOCK);if(fd < 0){perror("open");exit(1);}if(argc == 3){if(!strncmp("stat", argv[2], 4)){ret = read(fd, buf, sizeof(buf));if(ret != 4){perror("read");exit(1);}for(i = 0; i < sizeof(buf); i++){printf("led %d is %s\n", \i+1, buf[i]?"on":"off");}}else{pri_usage(argv[0]);exit(1);}}else{num = atoi(argv[2]);if(num >= 1 && num <= 4){cmd &= ~(0xf << 4);cmd = num << 4;}else{pri_usage(argv[0]);exit(1);}if(!strncmp("on", argv[3], 2)){cmd &= ~0xf;cmd |= 1;}else if(!strncmp("off", argv[3], 3)){cmd &= ~0xf;}else{pri_usage(argv[0]);exit(1);}ret = write(fd, &cmd,sizeof(cmd));if(ret != sizeof(cmd)){pri_usage(argv[0]);exit(1);}}return 0;}

调试结果如下:

------------------------------PC机---------------------------------

[root@localhost led1]# arm-linux-gcc led_app.c -oled_test

[root@localhost led1]# make

make -C /ARM/linux-3.5-songze/M=/ARM/linux-3.5-songze/drivers/

songze_drivers/char/led/led1 modules

make[1]: Entering directory `/ARM/linux-3.5-songze'

  Building modules,stage 2.

  MODPOST 1modules

make[1]: Leaving directory `/ARM/linux-3.5-songze'

[root@localhost led1]# cp led_test /nfsroot

[root@localhost led1]# cp led.ko /nfsroot

--------------------------------ARM---------------------------------

[projct /]# insmod led.ko

[projct /]# cat /proc/devices

Character devices:

  1 mem

  2 pty

  3 ttyp

  4 /dev/vc/0

  4 tty

  4 ttyS

  5 /dev/tty

  5 /dev/console

  5 /dev/ptmx

  7 vcs

 10 misc

 13 input

 14 sound

 21 sg

 29 fb

 81 video4linux

 89 i2c

108 ppp

116 alsa

128 ptm

136 pts

180 usb

185 LED

188 ttyUSB

189 usb_device

204 ttySAC

216 rfcomm

251 ttyGS

252 watchdog

253 media

254 rtc

[projct /]# mknod led c 185 0

[projct /]# ls led -l

crw-r--r--    10        0         185,  0 Jan  5  2016 led

[projct /]# ./led_test

-------- usage: ------

usage1: ./led_test stat

usage2: ./led_test num on/off

           num:<1~4>

[projct /]# ./led_test led stat

led 1 is off

led 2 is off

led 3 is off

led 4 is off

[projct /]# ./led_test led 1 on

[projct /]# ./led_test led 3 on

[projct /]# ./led_test led stat

led 1 is on

led 2 is off

led 3 is on

led 4 is off

[projct /]# rmmod led

[ 3322.195000] goodbye! led  driver

rmmod: module 'led' not found


3.1.2 自动创建设备节点

       在上一个程序中通mknod创建字符设备,内核提供了一组函数来自动创建。如下所示:

1.class_create()创建类

 

#define class_create(owner, name)               \

({                                              \

    static structlock_class_key __key;     \

    __class_create(owner,name, &__key);    \

})

    宏class_create()用于动态创建设备的逻辑类,并完成部分字段的初始化,然后将其添加进linux内核系统中。

    此函数的执行效果就是在目录/sys/class/下创建一个新的文件夹,此文件夹的名字为此函数的第二个输入参数,文件夹是空的。

 

宏输入参数说明:

    宏owner是一个struct module结构体类型的指针,指向函数__class_create()即将创建的struct class类型对象的拥有者,

一般赋值为THIS_MODULE,详细查看:src/include/linux/module.h

       name是char类型的指针,代表即将创建的struct class变量的名字

 

返回参数:

    宏class_create()函数返回 struct class*的逻辑类

 

2.class_destroy()销毁结构体类

void class_destroy(struct class *cls)

cls: 指向要被销毁的结构体类,指针指向的必须时已经被创建的结构体

 

3.device_create()创建一个设备,并在sysfs中注册

struct  device*device_create(struct class *class, struct device *parent,

                  dev_t devt, void *drvdata, constchar *fmt, ...)

 

函数功能:

        函数device_create()用于动态的建立逻辑设备,并对新的逻辑设备类进行相应初始化,将其与函数的第一个参数所代表的逻辑类关联起来,然后将此逻辑设备加到linux内核系统的设备驱动程序模型中。函数能够自动在/sys/devices/virtual目录下创建新的逻辑设备目录,在/dev目录下创建于逻辑类对应的设备文件

参数说明:

   struct class *class:即将创建逻辑设备相关的逻辑类

   dev_t dev:设备号

   void *drvdata: void类型的指针,代表回调函数的输入参数

   const char *fmt: 逻辑设备的设备名,即在目录/sys/devices/virtual创建的逻辑设备目录的目录名。

 

4.函数device_unregister()移除设备

void  device_unregister(struct device *dev)


class_create() 和 device_create()关系

很多时候都是利用mknod命令手动创建设备节点,实际上Linux内核为我们提供了一组函数,可以用来在模块加载的时候自动在/dev目录下创建相应设备节点,并在卸载模块时删除该节点,当然前提条件是用户空间移植了udev。

内核中定义了structclass结构体,顾名思义,一个struct class结构体类型变量对应一个类,内核同时提供了class_create(…)函数,可以用它来创建一个类,这个类存放于sysfs下面,一旦创建好了这个类,再调用device_create(…)函数来在/dev目录下创建相应的设备节点。这样,加载模块的时候,用户空间中的udev会自动响应device_create(…)函数,去/sysfs下寻找对应的类从而创建设备节点。

    修改上一个程序部分代码如下,insmod led.ko可在/dev/看见相应的设备节点

static struct class *led_class = NULL;static struct device *led_device = NULL; static int __init exynos4412_led_init(void){int ret = 0;VGPM4BASE = ioremap(GPM4BASE,SZ_4K);if(NULL == VGPM4BASE){return -ENOMEM;}led_init();ret = register_chrdev(LED_MAJOR,"LED",&exynos4412_led_fops);/*自动创建设备文件节点*//*1.创建LED类*/led_class =  class_create(THIS_MODULE,"LED");/*2.创建一个设备,并在sysfs中注册*/led_device = device_create(led_class,NULL,MKDEV(LED_MAJOR,0),NULL,"led");return ret;}module_init(exynos4412_led_init);static void __exit exynos4412_led_exit(void){unregister_chrdev(LED_MAJOR,"LED");/*卸载类下的设备*/device_unregister(led_device);/*卸载类*/class_destroy(led_class);/*解除映射*/iounmap(VGPM4BASE);printk("goodbye! led  driver\n");

3.2注册设备和释放设备号

静态注册设备:

int  register_chrdev_region(dev_t from, unsigned count, const char *name)

from:要分配设备编号范围的起始值

count:连续设备编号的个数

name:设备名称

动态注册设备:

int  alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,

              const char *name)

dev:仅用于输出的参数,在成功完成调用后将保存已分配范围的第一个编号

baseminor:要使用的被请求的第一个次设备号

count:连续设备编号的个数

name:设备名称

注销设备

void  unregister_chrdev_region(dev_t from, unsigned count)

from:要分配设备编号范围的起始值

count:连续设备编号的个数

3.2.1 Led字符设备驱动示例

Led驱动:

/** *4LED 1亮 0灭 *字符设备注册方法 *register_chrdev_region *write read * */#include <linux/init.h>#include <linux/module.h>#include <linux/fs.h>#include <linux/io.h>#include <linux/uaccess.h>#include <linux/slab.h>#include <linux/cdev.h>struct cdev cdev;static dev_t devnum;//static int major = 0;static int LED_MAJOR = 0;#define GPM4BASE 0x11000000#define GPM4CON  0x2e0#define GPM4DAT  0x2e4static char *VGPM4BASE = NULL;#define VGPM4CON *((u32 *)(VGPM4BASE+GPM4CON))#define VGPM4DAT *((u32 *)(VGPM4BASE+GPM4DAT))static void led_init(void){VGPM4CON &= ~0xffff;VGPM4CON |= 0x1111;/*设置输出引脚*/VGPM4DAT |= 0x0f;/*led all off*/}static void led_stat(char *buf){u32 tmp;int i;tmp = VGPM4DAT;for(i = 0; i < 4; i++){if(tmp & (1<<i)){buf[i] = 0;}else{buf[i] = 1;}}}static void led_ctrl(int num, int stat){if(stat){VGPM4DAT &= ~(1 << (num - 1));}else{VGPM4DAT |= (1 << (num - 1));}}static intexynos4412_led_open(struct inode *inodp, struct file *filp){return 0;}static ssize_t exynos4412_led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *fpos){char stat[4] = {0};led_stat(stat);if(copy_to_user(buf,stat,sizeof(stat))){return -EINVAL;}return sizeof(stat);}static ssize_texynos4412_led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *fpos){u8 cmd;u8 num,stat;if(copy_from_user(&cmd,buf,cnt)){//get_user(cmd,buf);return -EINVAL;}num = cmd >> 4;stat = cmd & 0xf;//cmd 高4位放led编号 低4位放led状态if(( num < 1 ) || ( num > 4 ) ){        return -EINVAL;}if(( stat != 0 ) && ( stat != 1 )){        return -EINVAL;}led_ctrl(num, stat);return 1;}static int exynos4412_led_release(struct inode *inodp, struct file *filp){return 0;}/**字符驱动的核心 *当应用程序操作设备文件时所调用的open read write等函数 *最终会调用这个结构中对应的函数 */static struct file_operations exynos4412_led_fops = {.owner   = THIS_MODULE,/*内核使用这个字段以避免在模块的操作正在被使用时卸载该模块*/.open    = exynos4412_led_open,.read    = exynos4412_led_read,.write   = exynos4412_led_write,.release = exynos4412_led_release/*当file结构被释放时,将调用这个操作*/};static int __init exynos4412_led_init(void){int ret = 0;VGPM4BASE = ioremap(GPM4BASE,SZ_4K);if(NULL == VGPM4BASE){return -ENOMEM;}led_init();/*申请设备号*/devnum = MKDEV(LED_MAJOR,0);/*注册设备号*/if(LED_MAJOR){/*静态注册*/ret = register_chrdev_region(devnum,1,"LED");} else{/*动态注册*/ret = alloc_chrdev_region(&devnum,0,1,"LED");LED_MAJOR = MAJOR(devnum);if(ret < 0) {printk("register_chrdev_region failed!");return ret;}}/*初始化 cdev*/cdev_init(&cdev, &exynos4412_led_fops);/*将cdev添加到系统中*/ret = cdev_add(&cdev, devnum, 1);if(ret < 0){return ret;}return 0;}module_init(exynos4412_led_init);static void __exit exynos4412_led_exit(void){cdev_del(&cdev);unregister_chrdev_region(devnum, 1);printk("goodbye! led  driver");}module_exit(exynos4412_led_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("Songze Lee");MODULE_VERSION("verson 1.0");MODULE_DESCRIPTION("char driver for led");

Makefile、用户测试程序同上。

调试结果如下:

[projct /]# insmod led.ko

[projct /]# mknod led2 c 250 0   250为自动分配的设备号可cat /proc/devices

[projct /]# ./led_test led2 stat

led 1 is off

led 2 is off

led 3 is off

led 4 is off

[projct /]# ./led_test led2 2 on

[projct /]# ./led_test led2 4 on

[projct /]# ./led_test led2 stat

led 1 is off

led 2 is on

led 3 is off

led 4 is on

[projct /]# rmmod led

[ 3520.355000] goodbye! led  driver

rmmod: module 'led' not found

3.3混杂设备

    Misc驱动是一些拥有着共同特性的简单字符设备驱动。内核抽象出这些特性而形成了一些API(在文件drivers/char/misc.c中实现),以简化这些设备驱动程序的初始化。所有misc设备被分配同一个主设备号MISC_MAJOR(10),但每次可以选择一个单独的次设备号。如果一个字符设备驱动要驱动多个设备,那么它就不应给用misc设备来实现。

3.3.1 miscdevice 结构体

struct miscdevice {

    int minor;                              /* 次设备号*/

    const char*name;                       /*设备名称*/

    const structfile_operations *fops;     /*文件操作结构体*/

    structlist_head list;                 /*misc_list的链表头*/

    struct device*parent;                  /*父设备*/

    struct device*this_device;             /*当前设备*/

    const char*nodename;

    umode_t mode;

    };

3.3.2字符设备驱动和混杂设备驱动的使用区别

字符驱动程序完成初始化顺序如下:

    1.register_chrdev_region()/register_chrdev_region()注册分配主次设备号

    2.class_create()和device_create()创建/dev和/sys设备节点

    3.使用cdev_init()和cdev_add()将自身注册为字符设备驱动

字符驱动程序注销顺序如下:

    1.unregister_chrdev()和device_unregister()卸载类下的设备和类

    2.cdev_del(&cdev);删除一个 cdev,完成字符设备的注销

    3.unregister_chrdev_region()注销字符设备

混杂设备只需调用misc_register()和misc_deregister()完成初始化和注销设备。

static struct miscdevice miscdev = {

    .minor =MISC_DYNAMIC_MINOR,

    .name = "LED",

    .fops =&fops,

};

misc_register(&miscdev);

misc_deregister(&miscdev);

 

int misc_register(struct miscdevice * misc)

    misc_register()函数用于注册一个混杂设备,其主设备号为10,如果次设备号指定为MISC_DYNAMIC_MINOR将由系统去指定一个次设备号,调用device_cr

eate创建设备节点。

misc_deregister(struct miscdevice * misc)

    misc_deregister()用于注销这个混杂设备,其中调用了device_destroy

删除设备节点。

    混杂设备就是主设备号为10,所有的混杂设备形成一个链表,次设备号由注册时指定,类名为misc,自动创建设备节点的字符设备,是一种节约主设备号,为驱动程序员简化的字符设备驱动注册方式。

3.3.3混杂设备实现机制

    misc_init()通过class_create(THIS_MODULE, "misc")在/sys/class/目录下创建一个名为misc,调用register_chrdev(MISC_MAJOR,"misc",&misc_fops)注册设备,其中设备的主设备号为MISC_MAJOR,为10。设备名为misc,misc_fops是操作函数的集合。通过subsys_initcall(misc_init)函数将misc作为一个子系统被注册到linux内核中。

    misc_register(&miscdev)中先初始化misc_list链表,遍历misc_list链表,看这个次设备号是否已被使用如果次设备号已被占有则退出。然后 判断misc->minor== MISC_DYNAMIC_MINOR,如果相等,系统动态分配次设备号,MKDEV(MISC_MAJOR,misc->minor)计算出设备号,device_create(misc_class,misc->parent,dev, NULL, "%s", misc->name)在/dev下创建设备节点,list_add(&misc->list,&misc_list)将这个miscdevice添加到misc_list链中。(初始化和遍历misc_list链表->匹配次设备号->找到一个没有占用的次设备号(如果需要动态分配的话)->计算设备号->创建设备节点->miscdevice结构体添加到misc_list链表中。)

    misc_deregister list_del(&misc->list)在misc_list链表中删除miscdevice设备,device_destroy(misc_class, MKDEV(MISC_MAJOR,misc->min

or))删除设备节点(从mist_list中删miscdevice->删除设备文件。)

 

3.3.4内核中的gpio口

    在Documentation/gpio.txt中有内核中gpio的使用详细说明,下面先介绍比较常用的函数,如下:

使用 GPIO

对于一个GPIO,系统应该做的第一件事情就是通过 gpio_request() 函数声明申请分配他

intgpio_request(unsigned gpio, const char *label)

设置GPIO的方向

    /*设置为输入或输出, 成功返回 0 失败返回负的错误代码*/

    int gpio_direction_input(unsigned gpio);

    int gpio_direction_output(unsigned gpio, intvalue);

访问自旋锁安全的GPIO

    /* GPIO 读取:返回零或非零 */

    int gpio_get_value(unsigned gpio);

    返回值是布尔值,零表示低电平,非零表示高电平

    /* GPIO 设置 */

    void gpio_set_value(unsigned gpio, intvalue);

释放之前声明的GPIO

    void gpio_free(unsigned gpio);

3.3.5混杂设备led驱动示例

    应用层调试程序,Makefile同上。

/** *混杂设备misc  *主设备号为10的特殊的字符设备 * */#include <linux/init.h>#include <linux/module.h>#include <linux/fs.h>#include <linux/uaccess.h>#include <linux/slab.h>#include <linux/io.h>#include <linux/gpio.h>#include <linux/miscdevice.h>enum{ON,OFF};static int ledgpio[] = {EXYNOS4X12_GPM4(0),EXYNOS4X12_GPM4(1),EXYNOS4X12_GPM4(2),EXYNOS4X12_GPM4(3),};static void led_ctrl(int num, int stat){if(stat){ gpio_set_value(ledgpio[num - 1], ON);}else{gpio_set_value(ledgpio[num - 1], OFF);}}static void led_init(void){int i;for(i = 0; i < ARRAY_SIZE(ledgpio); i++){/*设置gpio方向为输出,电平为1*/gpio_direction_output(ledgpio[i], OFF);}}static void led_stat(char *buf){int i;for(i = 0; i < ARRAY_SIZE(ledgpio); i++){/*读取gpio端口的值*/if(gpio_get_value(ledgpio[i]) == ON){buf[i] = !ON;}else{buf[i] = !OFF;}}}static int exynos4412_led_open (struct inode *inodp, struct file *filp){return 0;}static ssize_t exynos4412_led_read (struct file *filp, char __user *buf, size_t cnt, loff_t *fpos){char stat[4] = {0};led_stat(stat);if(copy_to_user(buf, stat, sizeof(stat))){return -EINVAL;}return sizeof(stat);}static ssize_t exynos4412_led_write (struct file *filp, const char __user *buf, size_t cnt, loff_t *fpos){u8 cmd;u8 num, stat;get_user(cmd, buf);num = cmd >> 4; stat = cmd & 0xf;if(( num < 1 ) || ( num > 4 ) ){return -EINVAL;}if(( stat != 0 ) && ( stat != 1 )){return -EINVAL;}led_ctrl(num, stat);return 1;}static int     exynos4412_led_release (struct inode *inodp, struct file *filp){return 0;}static struct file_operations fops = {.owner = THIS_MODULE,.open = exynos4412_led_open,.read = exynos4412_led_read,.write = exynos4412_led_write,.release = exynos4412_led_release,};static struct miscdevice miscdev = {.minor = MISC_DYNAMIC_MINOR,/*动态分配次设备号*/.name = "LED",/*设备名 在/dev/可见*/.fops = &fops, /*操作函数集*/};static void led_exit(void){int i;for(i = 0; i < ARRAY_SIZE(ledgpio); i++){/*设置gpio端口为1*/gpio_set_value(ledgpio[i], OFF);/*释放之前声明申请的gpio*/gpio_free(ledgpio[i]);}}static int __init exynos4412_led_init(void){int ret;int i;for(i = 0; i < ARRAY_SIZE(ledgpio); i++){/*申请分配gpio*/ret = gpio_request(ledgpio[i], "led");if(ret < 0){goto error0;}}led_init();ret = misc_register(&miscdev);if(ret < 0){led_exit();return ret;}return0;error0:while(i--){gpio_free(ledgpio[i]);}return ret;}module_init(exynos4412_led_init);static void __exit exynos4412_led_exit(void){misc_deregister(&miscdev);led_exit();} module_exit(exynos4412_led_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("Songze Lee");MODULE_VERSION("verson 1.0");MODULE_DESCRIPTION("misc for led driver");
调试结果如下:

[projct /]# insmod led.ko

[projct /]# ./led_test /dev/led stat

led 1 is off

led 2 is off

led 3 is off

led 4 is off

[projct /]# ./led_test /dev/led 2 on

[projct /]# ./led_test /dev/led 4 on

[projct /]# ./led_test /dev/led stat

led 1 is off

led 2 is on

led 3 is off

led 4 is on

[projct /]# rmmod led

[ 3520.355000] goodbye! led  driver

rmmod: module 'led' not found


获取源码:git clone https://www.github.com/lisongze2016/Tiny4412_kernel.git