本文共 19007 字,大约阅读时间需要 63 分钟。
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:拷贝数据的字节数
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函数的值保持一致,否则该调用会失败。
Led驱动:
/** *4LED 1亮 0灭 *早期经典的字符设备注册方法 *register_chrdev *write read * */#include#include #include #include #include #include #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< << (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#include #include #include #include #include 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
在上一个程序中通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");
静态注册设备:
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:连续设备编号的个数
Led驱动:
/** *4LED 1亮 0灭 *字符设备注册方法 *register_chrdev_region *write read * */#include#include #include #include #include #include #include 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< << (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
Misc驱动是一些拥有着共同特性的简单字符设备驱动。内核抽象出这些特性而形成了一些API(在文件drivers/char/misc.c中实现),以简化这些设备驱动程序的初始化。所有misc设备被分配同一个主设备号MISC_MAJOR(10),但每次可以选择一个单独的次设备号。如果一个字符设备驱动要驱动多个设备,那么它就不应给用misc设备来实现。
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;
};
字符驱动程序完成初始化顺序如下:
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,自动创建设备节点的字符设备,是一种节约主设备号,为驱动程序员简化的字符设备驱动注册方式。
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->删除设备文件。)
在Documentation/gpio.txt中有内核中gpio的使用详细说明,下面先介绍比较常用的函数,如下:
对于一个GPIO,系统应该做的第一件事情就是通过 gpio_request() 函数声明申请分配他
intgpio_request(unsigned gpio, const char *label)
/*设置为输入或输出, 成功返回 0 失败返回负的错误代码*/
int gpio_direction_input(unsigned gpio);
int gpio_direction_output(unsigned gpio, intvalue);
/* GPIO 读取:返回零或非零 */
int gpio_get_value(unsigned gpio);
返回值是布尔值,零表示低电平,非零表示高电平
/* GPIO 设置 */
void gpio_set_value(unsigned gpio, intvalue);
void gpio_free(unsigned gpio);
应用层调试程序,Makefile同上。
/** *混杂设备misc *主设备号为10的特殊的字符设备 * */#include调试结果如下:#include #include #include #include #include #include #include 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; } return 0;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
转载地址:http://yglpi.baihongyu.com/