字符设备

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,则使用次设备号6467进行区分。
电子硬盘设备:分区1分区7,则使用次设备号17进行区分。

身份证: 前六位数字,就代表是某个地区,用于区分不同地方的人。
后面的其他数字是具体到某个人。

2.字符设备驱动的设计过程

  1. 定义一个字符设备,struct cdev

  2. 申请设备号
    1)静态注册
    MKDEV
    register_chrdev_region

    2)动态注册
    alloc_chrdev_region

  3. 定义file_operations,初始化打开、关闭、读、写等函数接口。

  4. cdev初始化
    .cdev_init

  5. 将cdev加入到内核
    .cdev_add

6.创建设备文件
.手动创建,使用mknod命令去/dev目录进行创建
.自动创建
1)class_create
2)device_create

3.定义一个字符设备

  1. 描述字符设备的结构体——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;
失败,返回负数的错误码。

  1. 设备号的注销
#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
  • 3
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值