字符设备
0.设备驱动的分类
设备驱动分为三大类:字符设备、块设备、网络设备。
1.字符设备:
该设备对数据的处理按照
字节流
的形式进行的,支持顺序访问(是有时间的概念),也可以支持随机访问。
典型的字符设备:串口、键盘、触摸屏、摄像头、I2C、SPI、声卡、帧缓冲设备…
顺序访问的设备:串口、键盘、触摸屏
随机访问的设备:帧缓冲设备
应用程序,能够使用系统IO函数来就行访问:open、write、read、lseek、close…
crw--w---- 1 root tty 4, 58 Jan 1 1970 tty58
crw--w---- 1 root tty 4, 59 Jan 1 1970 tty59
crw--w---- 1 root tty 4, 6 Jan 1 1970 tty6
crw--w---- 1 root tty 4, 60 Jan 1 1970 tty60
crw--w---- 1 root tty 4, 61 Jan 1 1970 tty61
crw--w---- 1 root tty 4, 62 Jan 1 1970 tty62
crw--w---- 1 root tty 4, 63 Jan 1 1970 tty63
crw--w---- 1 root tty 4, 7 Jan 1 1970 tty7
crw--w---- 1 root tty 4, 8 Jan 1 1970 tty8
crw--w---- 1 root tty 4, 9 Jan 1 1970 tty9
crw------- 1 root tty 207, 16 Aug 14 12:44 ttymxc0
crw------- 1 root root 207, 18 Jan 1 1970 ttymxc2
crw------- 1 root root 10, 55 Jan 1 1970 ubi_ctrl
设备号方便操作系统进行管理,就例如进程,方便给系统管理,就有一个进程号,即pid,使用ps命令可以知道每个进程的id号;
线程,方便给系统管理,就有一个线程号,即tid,创建线程返回线程id;
就像每个人都会有一个名字,但是名字容易出现同名,这个时候可以使用身份证号码进行区分。
2.块设备:
该设备的处理按照若干个块来进行的。一个块的固定大小512字节、4096字节。这类设备支持随机访问,这种以块和随机访问能够提高数据存储效率。
块设备往往是面向于存储类的设备:nand flash、SD卡、U盘、eMMC、硬盘…。
块设备在/dev目录详细表现形式:
brw-rw---- 1 root disk 1, 0 Jan 1 1970 ram0
brw-rw---- 1 root disk 1, 1 Jan 1 1970 ram1
brw-rw---- 1 root disk 1, 10 Jan 1 1970 ram10
brw-rw---- 1 root disk 1, 11 Jan 1 1970 ram11
brw-rw---- 1 root disk 1, 12 Jan 1 1970 ram12
brw-rw---- 1 root disk 1, 13 Jan 1 1970 ram13
brw-rw---- 1 root disk 1, 14 Jan 1 1970 ram14
brw-rw---- 1 root disk 1, 15 Jan 1 1970 ram15
brw-rw---- 1 root disk 1, 2 Jan 1 1970 ram2
brw-rw---- 1 root disk 1, 3 Jan 1 1970 ram3
brw-rw---- 1 root disk 1, 4 Jan 1 1970 ram4
brw-rw---- 1 root disk 1, 5 Jan 1 1970 ram5
brw-rw---- 1 root disk 1, 6 Jan 1 1970 ram6
brw-rw---- 1 root disk 1, 7 Jan 1 1970 ram7
brw-rw---- 1 root disk 1, 8 Jan 1 1970 ram8
brw-rw---- 1 root disk 1, 9 Jan 1 1970 ram9
3.网络设备:
网络设备是比较特殊的,在/dev没有设备文件,它就是专门针对网络设备的一类驱动,其主要作用是进行网络的数据收发。
网络类设备:有线网卡、无线WiFi网卡(RT3070)、无线GPRS网卡、无线4G网卡…
应用程序:
socket套接字
1./dev目录下设备的区分
1.文件类型
- c:字符设备
- b:块设备
- d:目录
- l:符号链接
在/dev目录主要集中了字符设备和块设备,字符设备以c作为开头,块设备以b作为开头,详细如下:
crw-rw---- 1 root root 29, 0 Jan 1 1970 fb0
brw-rw---- 1 root root 179, 1 Jan 1 1970 mmcblk0p1
2.设备号
其实设备号等同于身份证号码,方便系统进行管理。设备文件跟普通文件区别在于比普通文件多出了两个数字,这两个数字分别是主设备号和次设备号。
普通文件:
root@ATK-IMX6U:~# ls -l 1.mp3
-rw-r--r-- 1 root root 8941241 Jan 1 2015 1.mp3
设备文件:
root@ATK-IMX6U:~# /dev]#ls -l fb0
crw-rw---- 1 root root 29, 0 Jan 1 1970 fb0
观察fb0设备文件,多出的数字为“29,0”,详细如下:
- 29:主设备号
- 0:次设备号
主设备号
: 区分某一类的设备。
ttySAC这是串口设备,主设备号为204;
mmcblk0这是电子硬盘属于块设备,主设备号为179。
次设备号
: 用于区分同一类设备的不同个体或不同分区。
串口设备:串口0
串口3,则使用次设备号64
67进行区分。
电子硬盘设备:分区1
分区7,则使用次设备号1
7进行区分。
身份证: 前六位数字,就代表是某个地区,用于区分不同地方的人。
后面的其他数字是具体到某个人。
2.字符设备驱动的设计过程
-
定义一个字符设备,struct cdev
-
申请设备号
1)静态注册
MKDEV
register_chrdev_region2)动态注册
alloc_chrdev_region -
定义file_operations,初始化打开、关闭、读、写等函数接口。
-
cdev初始化
.cdev_init -
将cdev加入到内核
.cdev_add
6.创建设备文件
.手动创建,使用mknod命令去/dev目录进行创建
.自动创建
1)class_create
2)device_create
3.定义一个字符设备
- 描述字符设备的结构体——cdev
#include <linux/cdev.h>
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
在linux内核当中,使用cdev来描述一个设备,每个字符设备都有自己一个cdev,设计字符设备的时候,首先定义一个cdev。
例如:
static struct cdev imx6ull_led_cdev;
2.cdev的成员
- struct kobject kobj,内核管理驱动的时候,使用到的一个object,自动调用。
-
struct module
owner,cdev是属于哪一个module,默认初始化为THIS_MODULE,指向当前模块,类似于C++ this指针,这个成员可阻止该模块还在被使用时被卸载。【
】 -
const struct file_operations
ops,字符设备文件操作集。【
】 - struct list_head list,内核管理字符设备的链表,自动调用。
- dev_t dev,设备号。【 】
- unsigned int count,次设备的数量。【*】
4.定义并初始化一个文件操作集
1.文件操作集
#include <linux/fs.h>
struct file_operations {
struct module *owner;
...........
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
int (*open) (struct inode *, struct file *);
int (*release) (struct inode *, struct file *);
...........
};
2.文件操作集的作用
每个字符设备都必须有一个文件操作集,文件操作集是驱动程序给应用程序访问硬件的一个接口,应用层与驱动程序函数接口的对应关系如下:
应用层 驱动层
open open
close release
write write
read read
lseek llseek
ioctl unlocked_ioctl
例子:
static int led_release(struct inode *inode, struct file *filp)
{
return 0;
}
/* 设备操作函数 */
static struct file_operations newchrled_fops = {
.owner = THIS_MODULE,
.open = led_open,
.read = led_read,
.write = led_write,
.release = led_release,
};
5.给字符设备申请设备号
(一)详细了解设备号
每个设备文件(字符设备 or 块设备)都有一个32位的设备号,相当于设备文件ID信息。
每个设备号 = 主设备号 + 次设备号
typedef __u32 __kernel_dev_t;
typedef __kernel_dev_t dev_t;
(二)设备号的运算函数
1.MKDEV
#define MINORBITS 20
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
设备号 = 高12位的主设备号[31:20] + 低20位的次设备号[19:0] 组成。
通过设备号来获取主设备号或次设备号
#define MINORMASK ((1U << MINORBITS) - 1)
//获取主设备号
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
//获取次设备号
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
(三)如何申请设备号
1.静态注册,register_chrdev_region
#include <linux/fs.h>
/**
* register_chrdev_region() - register a range of device numbers
* @from: the first in the desired range of device numbers; must include
* the major number.
* @count: the number of consecutive device numbers required
* @name: the name of the device or driver.
*
* Return value is zero on success, a negative error code on failure.
*/
int register_chrdev_region(dev_t from, unsigned count, const char *name)
参数说明:
from,注册的设备号,如果一次要注册多个设备,from就是注册设备号的开始值。
count,次设备的数量
name,设备名称,但是该名称不是/dev目录下设备文件的名字,而是在/proc/devices文件当中。
返回值:
成功,返回0;
失败,返回负数的错误码。
补充说明:
将该设备号注册到内核当中。如果该设备号在内核已经使用了,注册失败。参考内核源代码的设备号设置的文档\Documentation\devices.txt。
......................................................
29 char Universal frame buffer
0 = /dev/fb0 First frame buffer
1 = /dev/fb1 Second frame buffer
...
31 = /dev/fb31 32nd frame buffer
31 block ROM/flash memory card
0 = /dev/rom0 First ROM card (rw)
...
7 = /dev/rom7 Eighth ROM card (rw)
8 = /dev/rrom0 First ROM card (ro)
...
15 = /dev/rrom7 Eighth ROM card (ro)
16 = /dev/flash0 First flash memory card (rw)
...
23 = /dev/flash7 Eighth flash memory card (rw)
24 = /dev/rflash0 First flash memory card (ro)
...
31 = /dev/rflash7 Eighth flash memory card (ro)
The read-write (rw) devices support back-caching
written data in RAM, as well as writing to flash RAM
devices. The read-only devices (ro) support reading
only.
234-239 UNASSIGNED
240-254 char LOCAL/EXPERIMENTAL USE
............................
静态注册常用的主设备号可以从234开始进行尝试,在当前内核当中,没有分配登记的从234-239。谨记,不同的内核版本,主设备号占用的数值都会有所出入。
2.动态分配,就是由内核自动分配空闲的设备号
#include <linux/fs.h>
/**
* alloc_chrdev_region() - register a range of char device numbers
* @dev: output parameter for first assigned number
* @baseminor: first of the requested range of minor numbers
* @count: the number of minor numbers required
* @name: the name of the associated device or driver
*
* Allocates a range of char device numbers. The major number will be
* chosen dynamically, and returned (along with the first minor number)
* in @dev. Returns zero or a negative error code.
*/
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name)
参数说明:
dev,分配后的设备号
baseminor,次设备号的开始值
count,次设备的数量
name,设备名称,但是该名称不是/dev目录下设备文件的名字,而是在/proc/devices文件当中可以查阅
返回值:
成功,返回0;
失败,返回负数的错误码。
- 设备号的注销
#include <linux/fs.h>
/**
* unregister_chrdev_region() - return a range of device numbers
* @from: the first in the range of numbers to unregister
* @count: the number of device numbers to unregister
*
* This function will unregister a range of @count device numbers,
* starting with @from. The caller should normally be the one who
* allocated those numbers in the first place...
*/
void unregister_chrdev_region(dev_t from, unsigned count)
参数说明:
from,注销的设备号,如果一次要注销多个设备,from就是注销设备号的开始值。
count,次设备数量
返回值:
无。
6.初始化设备
(一)字符设备初始化
#include <linux/fs.h>
/**
* cdev_init() - initialize a cdev structure
* @cdev: the structure to initialize
* @fops: the file_operations for this device
*
* Initializes @cdev, remembering @fops, making it ready to add to the
* system with cdev_add().
*/
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
例子:
cdev_init(&imx6ull_led_cdev,&imx6ull_led_fops);
参数说明:
cdev,要初始化的字符设备结构体
fops,为该字符设备添加文件操作集
返回值:
无。
(二)字符设备的加入与删除
1.字符设备加载到内核
#include <linux/fs.h>
/**
* cdev_add() - add a char device to the system
* @p: the cdev structure for the device
* @dev: the first device number for which this device is responsible
* @count: the number of consecutive minor numbers corresponding to this
* device
*
* cdev_add() adds the device represented by @p to the system, making it
* live immediately. A negative error code is returned on failure.
*/
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
参数说明:
p,初始化好的字符设备
dev,设备号
count,次设备的数量
返回值:
成功,返回0;
失败,返回负数的错误码。
2.将字符设备从内核删除
#include <linux/fs.h>
/**
* cdev_del() - remove a cdev from the system
* @p: the cdev structure to be removed
*
* cdev_del() removes @p from the system, possibly freeing the structure
* itself.
*/
void cdev_del(struct cdev *p)
参数说明:
p,初始化好的字符设备
返回值:
无
#demo1操作演示:
1.将led_drv.ko加载到内核
[root@GEC6818 /]#insmod led_drv.ko
[ 0110.216000] gec6818 led init
2.使用lsmod命令查看模块加载状况,该命令等同于cat proc/modules操作。
[root@GEC6818 /]#lsmod
led_drv 2244 0 - Live 0xbf004000
第一列:模块的名称,led_drv
第二列:模块的大小,2244
第三列:表示依赖该模块的个数,当前为0;若有一个程序调用会自加1;若有一个模块依赖该模块,也会自加1。
第四列:模块的运行状态,live。
第五列:模块的加载地址,0xbf004000。
3.使用cat命令查看/proc/devices,能够找到注册成功的设备名字。
[root@GEC6818 /]#cat /proc/devices
Character devices:
......
239 gec6818_leds
......
4.为该设备在/dev目录下创建设备文件
[root@GEC6818 /]#mknod /dev/gec6818_leds c 239 0
说明:
mknod 设备文件 设备类型 主设备号 次设备号
补充说明:
1.手动创建设备文件的时候,主设备号和次设备号不能写错,否则应用程序打开设备文件的时候,会出现以下错误信息:
[root@GEC6818 /]#./led_test
open /dev/gec6818_leds:: No such device or address
2.若此前的设备文件已经存在,再次创建相同名字的设备文件(若该设备文件是设备号正确的),会出现以下错误:
[root@GEC6818 /]#mknod /dev/gec6818_leds c 244 0
mknod: /dev/gec6818_leds: File exists
解决方法:先使用rm命令删除原来的设备文件,再使用mknod命令重新创建。
[root@GEC6818 /]#rm /dev/gec6818_leds
[root@GEC6818 /]#mknod /dev/gec6818_leds c 244 0
3检查是否创建设备文件成功
[root@GEC6818 /]#ls -l /dev/gec6818_leds
crw-r--r-- 1 root root 239, 0 Jan 1 05:39 gec6818_leds
6. 若没有创建设备文件,会提示以下错误信息:
[root@GEC6818 /]#./led_test
open: No such file or directory
7.编写应用程序
8.使用动态分配设备号
使用静态注册设备号,有可能导致注册失败,因为linux内核不允许两个设备使用相同的设备号,为了杜绝该问题发生,可以由内核分配空闲的设备号给我们使用。
关键代码:
static int __init gec6818_led_init(void)
{
int rt=0;
//动态申请设备号
rt=alloc_chrdev_region(&led_num,0,1,"gec6818_leds");
if(rt < 0)
{
printk("alloc_chrdev_region fail\n");
return rt;
}
printk("led_major = %d\n",MAJOR(led_num));
printk("led_minor = %d\n",MINOR(led_num));
......
}
2.操作演示
[root@GEC6818 /]#insmod led_drv.ko
[10329.957000] led_major = 244
[10329.957000] led_minor = 0
[10329.957000] gec6818 led init
9.驱动程序与应用程序进行数据交换
在用户空间和内核空间,它们数据交换是不能直接访问的,必须通过内核提供的函数实现数据的交换。
#include <linux/uaccess.h>
1.copy_to_user
将内核空间的数据拷贝到用户空间
static inline long copy_to_user(void __user *to,
const void *from, unsigned long n)
{
might_sleep();
if (access_ok(VERIFY_WRITE, to, n))
return __copy_to_user(to, from, n);
else
return n;
}
参数说明:
to:用户空间数据的地址
from:内核空间数据的地址
n:要拷贝的字节数
返回值:
0:拷贝成功
非0值:不能被复制的字节数
2.copy_from_user
将用户空间的数据拷贝到内核空间
static inline unsigned long __must_check copy_from_user(void *to, const void __user *from, unsigned long n)
{
if (access_ok(VERIFY_READ, from, n))
n = __copy_from_user(to, from, n);
else /* security hole - plug it */
memset(to, 0, n);
return n;
}
参数说明:
to:内核空间数据的地址
from:用户空间数据的地址
n:要拷贝的字节数
返回值:
0:拷贝成功
非0值:不能被复制的字节数,也就是有多少个字节没有被成功拷贝
附:常见的错误码
#include <linux/errno.h>
#define EPERM 1 /* Operation not permitted */
#define ENOENT 2 /* No such file or directory */
#define ESRCH 3 /* No such process */
#define EINTR 4 /* Interrupted system call */
#define EIO 5 /* I/O error */
#define ENXIO 6 /* No such device or address */
#define E2BIG 7 /* Argument list too long */
#define ENOEXEC 8 /* Exec format error */
#define EBADF 9 /* Bad file number */
#define ECHILD 10 /* No child processes */
#define EAGAIN 11 /* Try again */
#define ENOMEM 12 /* Out of memory */
#define EACCES 13 /* Permission denied */
#define EFAULT 14 /* Bad address */
#define ENOTBLK 15 /* Block device required */
#define EBUSY 16 /* Device or resource busy */
#define EEXIST 17 /* File exists */
#define EXDEV 18 /* Cross-device link */
#define ENODEV 19 /* No such device */
#define ENOTDIR 20 /* Not a directory */
#define EISDIR 21 /* Is a directory */
#define EINVAL 22 /* Invalid argument */
#define ENFILE 23 /* File table overflow */
#define EMFILE 24 /* Too many open files */
#define ENOTTY 25 /* Not a typewriter */
#define ETXTBSY 26 /* Text file busy */
#define EFBIG 27 /* File too large */
#define ENOSPC 28 /* No space left on device */
#define ESPIPE 29 /* Illegal seek */
#define EROFS 30 /* Read-only file system */
#define EMLINK 31 /* Too many links */
#define EPIPE 32 /* Broken pipe */
#define EDOM 33 /* Math argument out of domain of func */
#define ERANGE 34 /* Math result not representable */
注:
若驱动层返回错误码,应用层则需要调用perror函数就能够识别到该错误码。
#demo3关键代码及操作演示
10.自动创建设备节点
(一)背景
Linux 2.6 引入了
动态设备管理
, 用 udev 作为设备管理器(应用在x86), 相比之前的静态设备管理,在使用上更加方便灵活。
udev 根据 sysfs 系统提供的设备信息实现对/dev 目录下设备节点的动态管理,包括设备节点的创建、删除等。
在当前开发板是使用udev的简化版——mdev,常用在嵌入式系统中,作用是在系统启动和热插拔或动态加载驱动程序时,自动创建设备节点。文件系统的/dev目录下的设备节点都是由mdev创建的。
举个例子:
insmod led_drv.ko时候,mdev自动帮我们在/dev目录下创建设备节点
rmmod led_drv时候,mdev自动帮我们在/dev目录下删除设备节点
若要编写一个能用 mdev 管理的设备驱动,需要在驱动代码中调用 class_create()为设备创建一个 class 类,再调用 device_create()为每个设备创建对应的设备。
(二)参考三星公司创建类与设备的方法
#include <linux/device.h>
static struct class *adb_dev_class;
static struct device *adb_dev_device;
static int __init adbdev_init(void)
{
...............
if (register_chrdev(ADB_MAJOR, "adb", &adb_fops)) {
printk(KERN_ERR "adb: unable to get major %d\n", ADB_MAJOR);
return;
}
adb_dev_class = class_create(THIS_MODULE, "adb");
if (IS_ERR(adb_dev_class))
return PTR_ERR(adb_dev_class);
adb_dev_device=device_create(adb_dev_class, NULL, MKDEV(ADB_MAJOR, 0), NULL, "adb");
if (IS_ERR(adb_dev_device))
{
class_destroy(adb_dev_class);
return PTR_ERR(adb_dev_device);
}
return 0;
}
(三)创建一个类
#include <linux/device.h>
..............................
/* This is a #define to keep the compiler from merging different
* instances of the __key variable */
#define class_create(owner, name) \
({ \
static struct lock_class_key __key; \
__class_create(owner, name, &__key); \
})
..............................
/**
* class_create - create a struct class structure
* @owner: pointer to the module that is to "own" this struct class
* @name: pointer to a string for the name of this class.
* @key: the lock_class_key for this class; used by mutex lock debugging
*
* This is used to create a struct class pointer that can then be used
* in calls to device_create().
*
* Returns &struct class pointer on success, or ERR_PTR() on error.
*
* Note, the pointer created here is to be destroyed when finished by
* making a call to class_destroy().
*/
struct class *__class_create(struct module *owner, const char *name,
struct lock_class_key *key)
参数:
owner:class的所有者,默认写THIS_MODULE
name:自定义class的名字,会显示在/sys/class目录当中
返回值:
成功:就返回创建好的class指针
失败:就返回错误码指针
如何判断当前的指针为错误码,使用IS_ERR函数进行判断。
static inline long __must_check IS_ERR(const void *ptr)
{
return IS_ERR_VALUE((unsigned long)ptr);
}
如果IS_ERR返回值大于0该指针就为错误码指针。如果返回0值,就是正确的指针。
2.创建属于class的设备
/**
* device_create - creates a device and registers it with sysfs
* @class: pointer to the struct class that this device should be registered to
* @parent: pointer to the parent struct device of this new device, if any
* @devt: the dev_t for the char device to be added
* @drvdata: the data to be added to the device for callbacks
* @fmt: string for the device's name
*
* This function can be used by char device classes. A struct device
* will be created in sysfs, registered to the specified class.
*
* A "dev" file will be created, showing the dev_t for the device, if
* the dev_t is not 0,0.
* If a pointer to a parent struct device is passed in, the newly created
* struct device will be a child of that device in sysfs.
* The pointer to the struct device will be returned from the call.
* Any further sysfs files that might be required can be created using this
* pointer.
*
* Returns &struct device pointer on success, or ERR_PTR() on error.
*
* Note: the struct class passed to this function must have previously
* been created with a call to class_create().
*/
struct device *device_create(struct class *class, struct device *parent,
dev_t devt, void *drvdata, const char *fmt, ...)
参数:
class:创建device是属于哪个类
parent:默认为NULL
devt:设备号,设备号必须正确,因为这个函数会在/dev目录下帮我们自动创建设备文件
drvdata:默认为NULL
fmt:设备的名字,如果创建成功,就可以在/dev目录看到该设备的名字
返回值:
成功:就返回创建好的设备指针
失败:就返回错误码指针
(五)类的销毁
/**
* class_destroy - destroys a struct class structure
* @cls: pointer to the struct class that is to be destroyed
*
* Note, the pointer to be destroyed must have been created with a call
* to class_create().
*/
void class_destroy(struct class *cls)
参数:
class:创建device是属于哪个类
(六)设备的销毁
/**
* device_destroy - removes a device that was created with device_create()
* @class: pointer to the struct class that this device was registered with
* @devt: the dev_t of the device that was previously registered
*
* This call unregisters and cleans up a device that was created with a
* call to device_create().
*/
void device_destroy(struct class *class, dev_t devt)
参数:
class:创建device是属于哪个类
devt:设备号
(七)错误码指针的判断
static inline long __must_check IS_ERR(const void *ptr)
{
return IS_ERR_VALUE((unsigned long)ptr);
}
(八)将错误码指针转换为数值(即错误码)
static inline long __must_check PTR_ERR(const void *ptr)
{
return (long) ptr;
}
附:关键代码
static struct class *leds_class;
static struct device *leds_device;
//入口函数
static int __init gec6818_led_init(void)
{
..................
//创建类
leds_class=class_create(THIS_MODULE, "myled");
if (IS_ERR(leds_class))
{
rt = PTR_ERR(leds_class);
printk("class_create gec6818_leds fail\n");
goto fail_class_create;
}
//创建设备
leds_device=device_create(leds_class, NULL, led_num, NULL, "gec6818_leds");
if (IS_ERR(leds_device))
{
rt = PTR_ERR(leds_device);
printk("device_create gec6818_leds fail\n");
goto fail_device_create;
}
..................
}
//出口函数
static void __exit gec6818_led_exit(void)
{
..................
device_destroy(leds_class,led_num);
class_destroy(leds_class);
..................
}
检查是否创建myled设备类型成功。
[root@GEC6818 /sys/class]#ls
myled
检查是否在gec6818_leds类,创建gec6818_leds设备成功
[root@GEC6818 /sys/devices/virtual/myled/gec6818_leds]#cat uevent
MAJOR=244
MINOR=0
DEVNAME=gec6818_leds
检查是否创建gec6818_leds设备文件成功。
[root@GEC6818 /]#ls /dev
gec6818_leds