本章目标:在第19章的基础上,给cdev字符类设备添加设备节点。
1. 代码设计思路
1.1 字符设备驱动设计流程
(1)class类型结构体
模块内部新建class类型结构体变量,使用class_creat来完成初始化:
static struct class *my_class;
my_class = class_create(THIS_MODULE,DEVICE_NAME);
(2)申请设备编号
手动带参数申请:
ret = register_chrdev_region(num_dev,DEVICE_MINOR_NUM,DEVICE_NAME);
系统自动申请:
ret = alloc_chrdev_region(&num_dev,baseminor,DEVICE_MINOR_NUM,DEVICE_NAME);
(3)cdev设备结构体的初始化
struct file_operations my_fops = {
.owner = THIS_MODULE,
};
cdev_init(&dev->char_dev,&my_fops);
dev->char_dev.owner = THIS_MODULE;
dev->char_dev.ops = &my_fops;
err = cdev_add(&dev->char_dev,devno,1);
(4)创建设备节点
创建设备节点函数device_create格式如下:
device_create(my_class,NULL,MKDEV(basemajor,baseminor+i),NULL,DEVICE_NAME"%d",i);
-- my_class :class类型结构体
-- MKDEV(basemajor,baseminor+i):设备号
-- DEVICE_NAME"%d",i :第i个设备号节点名称,例如char_dev_node0、char_dev_node1
(5)设备销毁
删除设备号:
unregister_chrdev_region(MKDEV(basemajor,baseminor),DEVICE_MINOR_NUM);
内核删除设备:
cdev_del(&(my_devices[i].char_dev));
删除设备节点:
device_destroy(my_class,MKDEV(basemajor,baseminor+i));
删除class结构体:
class_destroy(my_class);
1.2 手动创建设备节点
mknod 的标准形式为: mknod DEVNAME {b | c} MAJOR MINOR
-- DEVNAME :是要创建的设备文件名,如果想将设备文件放在一个特定的文件夹下,
就需要先用mkdir在dev目录下新建一个目录;
-- b和c : 分别表示块设备和字符设备
-- MAJOR和MINOR:分别表示主设备号和次设备号
root@ubuntu:/home/minilinux/system/01_test/19_cdev# mknod /dev/test0 c 230 0
root@ubuntu:/home/minilinux/system/01_test/19_cdev# ls /dev/test*
/dev/test0
设备节点的删除:
root@ubuntu:/home/minilinux/system/01_test/19_cdev# rm /dev/test0
root@ubuntu:/home/minilinux/system/01_test/19_cdev# ls /dev/test*
ls: cannot access /dev/test*: No such file or directory
2. 头文件与函数
2.1 class相关
头文件include/linux/device.h
(1)class_create(owner, name)
创建一个设备类,用于设备节点文件的创建,返回一个class结构体变量。
-- 参数1:一般是THIS_MODULE
-- 参数2:设备名称
(2)class_destroy(struct class *cls)
删除设备类。
-- 参数1:class结构体变量
truct class {
const char *name;
struct module *owner;
struct class_attribute *class_attrs;
struct device_attribute *dev_attrs;
struct bin_attribute *dev_bin_attrs;
struct kobject *dev_kobj;
int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);
char *(*devnode)(struct device *dev, mode_t *mode);
void (*class_release)(struct class *class);
void (*dev_release)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
const struct kobj_ns_type_operations *ns_type;
const void *(*namespace)(struct device *dev);
const struct dev_pm_ops *pm;
struct subsys_private *p;
};
extern void class_destroy(struct class *cls);
#define class_create(owner, name) \
({ \
static struct lock_class_key __key; \
__class_create(owner, name, &__key); \
})
2.2 设备节点相关
头文件include/linux/device.h
(1)创建设备节点函数device_create
device_create(struct class *cls, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...)
-- 参数1:设备所属于的类
-- 参数2:设备的父设备,NULL
-- 参数3:设备号
-- 参数4:设备数据,NULL
-- 参数4:设备名称
备注:老版本对应函数 class_device_create,参数类似。
(2)摧毁设备节点函数device_destroy
device_destroy(struct class *cls, dev_t devt);
-- 参数1:设备所属于的类
-- 参数2:设备号
extern struct device *device_create(struct class *cls, struct device *parent,
dev_t devt, void *drvdata,
const char *fmt, ...)
extern void device_destroy(struct class *cls, dev_t devt);
3. 驱动代码
3.1 create_node.c
/*包含初始化宏定义的头文件,代码中的module_init和module_exit在此文件中*/
#include <linux/init.h>
/*包含初始化加载模块的头文件,代码中的MODULE_LICENSE在此头文件中*/
#include <linux/module.h>
/*定义module_param module_param_array的头文件*/
#include <linux/moduleparam.h>
/*定义module_param module_param_array中perm的头文件*/
#include <linux/stat.h>
/*三个字符设备函数*/
#include <linux/fs.h>
/*MKDEV转换设备号数据类型的宏定义*/
#include <linux/kdev_t.h>
/*定义字符设备的结构体*/
#include <linux/cdev.h>
/*分配内存空间函数头文件*/
#include <linux/slab.h>
/*创建class类以及生成字符设备节点*/
#include <linux/device.h>
#define DEVICE_NAME "char_dev_node"
#define DEVICE_MINOR_NUM 2
#define DEV_MAJOR 0
#define DEV_MINOR 0
#define REGDEV_SIZE 10
MODULE_LICENSE("Dual BSD/GPL");
/*声明是开源的,没有内核版本限制*/
MODULE_AUTHOR("iTOPEET_dz");
/*声明作者*/
int basemajor = DEV_MAJOR;
int baseminor = DEV_MINOR;
static struct class *my_class;
/*输入主设备号*/
module_param(basemajor,int,S_IRUSR);
/*输入次设备号*/
module_param(baseminor,int,S_IRUSR);
struct reg_dev
{
char *data;
unsigned long size;
struct cdev char_dev;
};
struct reg_dev *my_devices;
struct file_operations my_fops = {
.owner = THIS_MODULE,
};
/*设备注册到系统*/
static void reg_init_cdev(struct reg_dev *dev,int index){
int err;
int devno = MKDEV(basemajor,baseminor+index);
/*数据初始化*/
cdev_init(&dev->char_dev,&my_fops);
dev->char_dev.owner = THIS_MODULE;
dev->char_dev.ops = &my_fops;
/*注册到系统*/
err = cdev_add(&dev->char_dev,devno,1);
if(err){
printk(KERN_EMERG "cdev_add %d is fail! %d\n",index,err);
}
else{
printk(KERN_EMERG "cdev_add %d is success!\n",index);
}
}
static int scdev_init(void)
{
int ret = 0,i;
dev_t num_dev;
printk(KERN_EMERG "basemajor is %d!\n",basemajor);
printk(KERN_EMERG "baseminor is %d!\n",baseminor);
if(basemajor){
num_dev = MKDEV(basemajor,baseminor);
ret = register_chrdev_region(num_dev,DEVICE_MINOR_NUM,DEVICE_NAME);
}
else{
/*动态注册设备号*/
ret = alloc_chrdev_region(&num_dev,baseminor,DEVICE_MINOR_NUM,DEVICE_NAME);
/*获得主设备号*/
basemajor = MAJOR(num_dev);
printk(KERN_EMERG "adev_region req %d !\n",basemajor);
}
if(ret<0){
printk(KERN_EMERG "register_chrdev_region req %d is failed!\n",basemajor);
}
my_class = class_create(THIS_MODULE,DEVICE_NAME);
my_devices = kmalloc(DEVICE_MINOR_NUM * sizeof(struct reg_dev),GFP_KERNEL);
if(!my_devices){
ret = -ENOMEM;
goto fail;
}
memset(my_devices,0,DEVICE_MINOR_NUM * sizeof(struct reg_dev));
/*设备初始化*/
for(i=0;i<DEVICE_MINOR_NUM;i++){
my_devices[i].data = kmalloc(REGDEV_SIZE,GFP_KERNEL);
memset(my_devices[i].data,'0',REGDEV_SIZE);
/*设备注册到系统*/
reg_init_cdev(&my_devices[i],i);
device_create(my_class,NULL,MKDEV(basemajor,baseminor+i),NULL,DEVICE_NAME"%d",i);
}
my_devices[0].data[10]='\0';
my_devices[1].data[10]='\0';
printk(KERN_EMERG"my_devices[0].data: %s \n",my_devices[0].data);
printk(KERN_EMERG"my_devices[1].data: %s \n",my_devices[1].data);
printk(KERN_EMERG "scdev_init!\n");
/*打印信息,KERN_EMERG表示紧急信息*/
return 0;
fail:
/*注销设备号*/
unregister_chrdev_region(MKDEV(basemajor,baseminor),DEVICE_MINOR_NUM);
printk(KERN_EMERG "kmalloc is fail!\n");
kfree(my_devices[0].data);
kfree(my_devices[1].data);
kfree(my_devices);
printk(KERN_EMERG"my_devices[1].data: %s \n",my_devices[1].data);
return ret;
}
static void scdev_exit(void)
{
int i;
printk(KERN_EMERG "scdev_exit!\n");
/*除去字符设备*/
for(i=0;i<DEVICE_MINOR_NUM;i++){
cdev_del(&(my_devices[i].char_dev));
device_destroy(my_class,MKDEV(basemajor,baseminor+i));
}
class_destroy(my_class);
unregister_chrdev_region(MKDEV(basemajor,baseminor),DEVICE_MINOR_NUM);
kfree(my_devices[0].data);
kfree(my_devices[1].data);
kfree(my_devices);
printk(KERN_EMERG"my_devices[0].data: %s \n",my_devices[0].data);
printk(KERN_EMERG"my_devices[1].data: %s \n",my_devices[1].data);
}
module_init(scdev_init);
/*初始化函数*/
module_exit(scdev_exit);
/*卸载函数*/
3.2 Makefile
#!/bin/bash
#通知编译器我们要编译模块的哪些源码
#这里是编译itop4412_hello.c这个文件编译成中间文件mini_linux_module.o
obj-m += create_node.o
#源码目录变量,这里用户需要根据实际情况选择路径
#作者是将Linux的源码拷贝到目录/home/topeet/android4.0下并解压的
KDIR := /home/topeet/iTop4412_Kernel_3.0
#当前目录变量
PWD ?= $(shell pwd)
#make命名默认寻找第一个目标
#make -C就是指调用执行的路径
#$(KDIR)Linux源码目录,作者这里指的是/home/topeet/android4.0/iTop4412_Kernel_3.0
#$(PWD)当前目录变量
#modules要执行的操作
all:
make -C $(KDIR) M=$(PWD) modules
#make clean执行的操作是删除后缀为o的文件
clean:
rm -rf *.mod.c *.o *.order *.ko *.mod.o *.symvers
4. 驱动调试
(1)装载驱动creat_node.ko
[root@iTOP-4412]# cd /01_test/20_creat_node/
[root@iTOP-4412]# ls
Makefile creat_node.c creat_node.mod.c creat_node.o
Module.symvers creat_node.ko creat_node.mod.o modules.order
[root@iTOP-4412]# insmod creat_node.ko
[10387.290606] basemajor is 0!
[10387.291956] baseminor is 0!
[10387.294739] adev_region req 247 !
[10387.310895] cdev_add 0 is success!
[10387.325825] cdev_add 1 is success!
[10387.340435] my_devices[0].data: 0000000000
[10387.343178] my_devices[1].data: 0000000000
[10387.350323] scdev_init!
(2)查看设备编号为247的字符设备 char_dev_node
[root@iTOP-4412]# cat /proc/devices
Character devices:
1 mem
4 ttyS
5 /dev/tty
5 /dev/console
5 /dev/ptmx
10 misc
13 input
21 sg
29 fb
81 video4linux
89 i2c
108 ppp
116 alsa
128 ptm
136 pts
153 rc522_test
166 ttyACM
180 usb
188 ttyUSB
189 usb_device
204 ttySAC
216 rfcomm
243 ump
244 mali
247 char_dev_node
248 ascdev
249 mt3326-gps
250 roccat
251 BaseRemoteCtl
252 media
253 ttyGS
254 rtc
Block devices:
1 ramdisk
259 blkext
7 loop
8 sd
65 sd
66 sd
67 sd
68 sd
69 sd
70 sd
71 sd
128 sd
129 sd
130 sd
131 sd
132 sd
133 sd
134 sd
135 sd
179 mmc
254 device-mapper
(3)查看设备节点 char_dev_node0和char_dev_node1
[root@iTOP-4412]# ls /dev
AGPS i2c-5 ptmx tty3
HPD i2c-7 pts tty4
adc input ram0 ttyGS0
alarm ion ram1 ttyGS1
android_adb keychord ram10 ttyGS2
ashmem kmem ram11 ttyGS3
buzzer_ctl kmsg ram12 ttyS0
char_dev_node0 log ram13 ttyS1
char_dev_node1 loop0 ram14 ttyS2
console loop1 ram15 ttyS3
cpu_dma_latency loop2 ram2 ttySAC0
exynos-mem loop3 ram3 ttySAC1
fb0 loop4 ram4 ttySAC2
fb1 loop5 ram5 ttySAC3
fb10 loop6 ram6 uinput
fb11 loop7 ram7 ump
fb2 mali ram8 urandom
fb3 mapper ram9 usb_accessory
fb4 max485_ctl_pin random usbdev1.1
fb5 mem rc522 usbdev1.2
fb6 mmcblk0 relay_ctl usbdev1.3
fb7 mmcblk0p1 rtc0 video0
fb8 mmcblk0p2 rtc1 video1
fb9 mmcblk0p3 s3c-mem video11
fimg2d mmcblk0p4 s3c-mfc video12
full mtp_usb shm video16
fuse network_latency snd video2
gps network_throughput srp video20
i2c-0 null srp_ctrl video3
i2c-1 pmem tty watchdog
i2c-3 pmem_gpu1 tty1 xt_qtaguid
i2c-4 ppp tty2 zero
(4)查看新建的class结构体char_dev_node
[root@iTOP-4412]# ls /sys/class
android_usb graphics lirc pyra sound
arvo i2c-adapter mali rc spi_master
backlight i2c-dev mdio_bus rc522 switch
bdi ieee80211 mem regulator tty
block input misc rtc ump
bluetooth kone mmc_host scsi_device usb_device
char_dev_node koneplus net scsi_disk video4linux
firmware kovaplus power_supply scsi_generic
gpsdrv lcd ppp scsi_host