4412 字符类设备的设备号

一、静态申请字符类设备号

  • 字符类设备函数在文件"include/linux/fs.h"中
  • 内核提供了三个函数来注册一组字符设备编号,这三个函数分别是
    • register_chrdev_region()
    • alloc_chrdev_region()
    • register_chrdev()
  • register_chrdev_region()是提前知道设备的主次设备号,再去申请设备号
  • alloc_chrdev_region()是动态分配主次设备号
  • register_chrdev() 是老版本的设备号注册方式,只分配主设备号。从设备号在mknod的时候指定。
  • 宏定义MKDEV的头文件"include/linux/kdev.h"
    • 在kdev_t.h头文件中有一个系列设备号处理的宏命令,用于处理各种设备号相关的数据。本期视频只介绍MKDEV,后面使用了其他宏定义再介绍
  • include/linux/cdev.h
    • cdev类型是字符设备描述的结构
    • 其次的设备号必须用"dev_t"类型来描述,高12位为主设备号,低20位为此设备号

编写编译运行

  • 将视频"16_驱动模块传参数"中的文件"module_param.c"改成为"request_cdev_num.c",静态生成设备号
  • 编写,编译
  • 加载运行
    • 使用命令"cat /proc/devices"查看已经被注册的主设备,设备号9没有被注册
    • insmod /mnt/udisk/request_cdev_num.ko numdev_major=9 numdev_minor=0
    • 使用命令"cat /proc/devices"查看,设备号9被注册为scdev
    • rmmod request_cdev_num numdev_major=9 numdev_minor=0
#include <linux/init.h>
#include <linux/module.h>

/* define module_param module_param_array header file */
#include <linux/moduleparam.h>
/* define perm's head file*/
#include <linux/stat.h>
/* char device register head file */
#include <linux/fs.h>
/* MKDEV change device ID type */
#include <linux/kdev_t.h>
/* define char device struct */
#include <linux/cdev.h>

#define DEVICE_NAME "scdev"
#define DEVICE_MINOR_NUM 2
#define DEV_MAJOR 0
#define DEV_MINOR 0

MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("TOPEET");

int numdev_major = DEV_MAJOR;
int numdev_minor = DEV_MINOR;

/* input major device ID */
module_param(numdev_major, int, S_IRUSR);
/* input minor device ID */
module_param(numdev_minor, int, S_IRUSR);

static int hello_init(void)
{
        int ret;
        dev_t num_dev;

        printk(KERN_EMERG "numdev_major is %d!
", numdev_major);
        printk(KERN_EMERG "numdev_minor is %d!
", numdev_minor);

        if(numdev_major) {
                num_dev = MKDEV(numdev_major, numdev_minor);
                ret = register_chrdev_region(num_dev, DEVICE_MINOR_NUM, DEVICE_NAME);
        } else {
                printk(KERN_EMERG "numdev_major %d is failed
", numdev_major);
        }

        if(ret < 0) {
                printk(KERN_EMERG "register_chrdev_region req %d is failed
", numdev_major);
                unregister_chrdev_region(num_dev, DEVICE_MINOR_NUM);
                return ret;
        }

        printk(KERN_EMERG "Hello World enter!
");
        return 0;
}

static void hello_exit(void)
{
        dev_t num_dev = MKDEV(numdev_major, numdev_minor);
        unregister_chrdev_region(num_dev, DEVICE_MINOR_NUM);
        printk(KERN_EMERG "Hello World exit!
");
}

module_init(hello_init);
module_exit(hello_exit);
register_chrdev_region

测试结果:

[root@iTOP-4412]# insmod request_cdev_num.ko numdev_major=9 numdev_minor=0                                 
[  135.652085] numdev_major is 9!
[  135.653710] numdev_minor is 0!
[  135.656810] Hello World enter!
[root@iTOP-4412]# cat /proc/devices                                                                        
Character devices:
  1 mem
  4 ttyS
  5 /dev/tty
  5 /dev/console
  5 /dev/ptmx
  9 scdev
 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
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
[root@iTOP-4412]# rmmod request_cdev_num numdev_major=9 numdev_minor=0                                     
[  152.245805] Hello World exit!
[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
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
[root@iTOP-4412]
测试结果

二、动态申请字符类设备号

  • 字符设备函数在文件"include/linux/fs.h"中
  • alloc_chrdev_region()是动态分配主次设备号
  • 宏定义MAJOR提取dev_t数据中的主设备号

编写编译运行

  • 将视频"17"中的文件"request_cdev_num.c改写为"request_ascdev_num.c"动态生成字符设备号
  • 编写,编译
  • 加载运行
    • 使用命令"cat /proc/device"查看
    • 动态加载模块之后再查看设备号

 修改后的代码:

#include <linux/init.h>
#include <linux/module.h>

/* define module_param module_param_array header file */
#include <linux/moduleparam.h>
/* define perm's head file*/
#include <linux/stat.h>
/* char device register head file */
#include <linux/fs.h>
/* MKDEV change device ID type */
#include <linux/kdev_t.h>
/* define char device struct */
#include <linux/cdev.h>

#define DEVICE_NAME "scdev"
#define DEVICE_MINOR_NUM 2
#define DEV_MAJOR 0
#define DEV_MINOR 0

MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("TOPEET");

int numdev_major = DEV_MAJOR;
int numdev_minor = DEV_MINOR;

/* input major device ID */
module_param(numdev_major, int, S_IRUSR);
/* input minor device ID */
module_param(numdev_minor, int, S_IRUSR);

static int hello_init(void)
{
        int ret;
        dev_t num_dev;

        printk(KERN_EMERG "numdev_major is %d!
", numdev_major);
        printk(KERN_EMERG "numdev_minor is %d!
", numdev_minor);

        if(numdev_major) {
                num_dev = MKDEV(numdev_major, numdev_minor);
                ret = alloc_chrdev_region(&num_dev, numdev_minor, DEVICE_MINOR_NUM, DEVICE_NAME);
        } else {
                ret = alloc_chrdev_region(&num_dev, numdev_minor, DEVICE_MINOR_NUM, DEVICE_NAME);
                numdev_major = MAJOR(num_dev);
                printk(KERN_EMERG "register req major number is %d
", numdev_major);
        }

        if(ret < 0) {
                printk(KERN_EMERG "register_chrdev_region req %d is failed
", numdev_major);
                unregister_chrdev_region(num_dev, DEVICE_MINOR_NUM);
                return ret;
        }

        printk(KERN_EMERG "Hello World enter!
");
        return 0;
}

static void hello_exit(void)
{
        dev_t num_dev = MKDEV(numdev_major, numdev_minor);
        unregister_chrdev_region(num_dev, DEVICE_MINOR_NUM);
        printk(KERN_EMERG "Hello World exit!
");
}

module_init(hello_init);
module_exit(hello_exit);
alloc_chrdev_region代码

测试运行后:

[root@iTOP-4412]# insmod request_ascdev_num.ko                                                             
[ 1335.710205] numdev_major is 0!
[ 1335.711817] numdev_minor is 0!
[ 1335.714861] register req major number is 248
[ 1335.727258] Hello World enter!
[root@iTOP-4412]# cat /proc/                                                                               
1/               670/             bus/             mfc/
11907/           714/             cgroups          misc
12924/           726/             cmdline          modules
12925/           731/             consoles         mounts
12926/           734/             cpu/             net/
16/              744/             cpuinfo          pagetypeinfo
2/               745/             crypto           panic_info_dump
3/               763/             devices          partitions
339/             767/             diskstats        sched_debug
341/             838/             driver/          scsi/
343/             866/             execdomains      self/
354/             892/             fb               softirqs
365/             894/             filesystems      stat
376/             896/             fs/              sys/
429/             898/             interrupts       sysrq-trigger
445/             900/             iomem            sysvipc/
455/             911/             ioports          timer_list
456/             918/             irq/             tty/
5/               919/             kallsyms         uid_stat/
508/             9192/            key-users        uptime
522/             940/             kmsg             version
528/             943/             kpagecount       vmallocinfo
6/               956/             kpageflags       vmstat
6437/            994/             loadavg          wakelocks
657/             asound/          locks            zoneinfo
662/             buddyinfo        meminfo
[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
248 scdev
249 mt3326-gps
250 roccat
251 BaseRemoteCtl
252 media
253 ttyGS
254 rtc
测试

三、注册字符类设备

  • 分配内存空间函数kmalloc
    • 分配连续的虚拟地址,用于小内存分配。在include/linux/slab.h文件中
    • 参数1:申请的内存大小(最大128K)
    • 参数2:GFP_KERNEL,代表优先权,内存不够可以延迟分配
  • 清空内存空间的数据函数memset
    • 可以清空内存空间,也就是全部写为0
    • 参数1:内存地址
    • 参数2:0
    • 参数3:内存长度
  • 字符设备初始化函数cdev_init
    • 在头文件include/linux/cdev.h中
    • 参数1:cdev字符设备文件结构体
    • 参数2:file_operations结构体
    • 注册设备本质是向linux设备文件中添加数据,这些数据需要初始化
  • 字符设备注册函数cdev_add
    • 在头文件include/linux/cdev.h中
    • 参数1:cdev字符设备文件结构体
    • 参数2:设备号
    • 参数3:设备范围大小
    • 向系统注册设备,也就是向linux系统添加数据
  • 卸载设备函数cdev_del
    • 参数1:cdev结构体
    • 移除字符设备
  • 将"18_动态申请字符类设备号"中的文件"request_ascdev_num.c"改写为"register_cdev.c"
  • 编译
  • 测试
    • 通过加载模块后的打印信息,可以观察到驱动加载的过程以及注册设备的反馈信息

 代码:

#include <linux/init.h>
#include <linux/module.h>

/* define module_param module_param_array header file */
#include <linux/moduleparam.h>
/* define perm's head file*/
#include <linux/stat.h>
/* char device register head file */
#include <linux/fs.h>
/* MKDEV change device ID type */
#include <linux/kdev_t.h>
/* define char device struct */
#include <linux/cdev.h>
/* define memroy sapce */
#include <linux/slab.h>

#define DEVICE_NAME "scdev"
#define DEVICE_MINOR_NUM 2
#define DEV_MAJOR 0
#define DEV_MINOR 0
#define REGDEV_SIZE 3000

MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("TOPEET");

int numdev_major = DEV_MAJOR;
int numdev_minor = DEV_MINOR;

/* input major device ID */
module_param(numdev_major, int, S_IRUSR);
/* input minor device ID */
module_param(numdev_minor, int, S_IRUSR);

struct reg_dev {
        char *data;
        unsigned long size;
        struct cdev cdev;
};
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(numdev_major, numdev_minor+index);

        cdev_init(&dev->cdev, &my_fops);
        dev->cdev.owner = THIS_MODULE;
        dev->cdev.ops = &my_fops;

        err = cdev_add(&dev->cdev, devno, 1);
        if(err) {
                printk(KERN_EMERG "cdev_add %d is fail! %d
", index, err);
        } else {
                printk(KERN_EMERG "cdev_add %d is success!
", index);
        }
}

static int hello_init(void)
{
        int ret, i;
        dev_t num_dev;

        printk(KERN_EMERG "numdev_major is %d!
", numdev_major);
        printk(KERN_EMERG "numdev_minor is %d!
", numdev_minor);

        if(numdev_major) {
                num_dev = MKDEV(numdev_major, numdev_minor);
                ret = alloc_chrdev_region(&num_dev, numdev_minor, DEVICE_MINOR_NUM, DEVICE_NAME);
        } else {
                ret = alloc_chrdev_region(&num_dev, numdev_minor, DEVICE_MINOR_NUM, DEVICE_NAME);
                numdev_major = MAJOR(num_dev);
                printk(KERN_EMERG "register req major number is %d
", numdev_major);
        }

        if(ret < 0) {
                printk(KERN_EMERG "register_chrdev_region req %d is failed
", numdev_major);
                unregister_chrdev_region(num_dev, DEVICE_MINOR_NUM);
                return ret;
        }

        my_devices = kmalloc(DEVICE_MINOR_NUM*sizeof(struct reg_dev), GFP_KERNEL);
        if(!my_devices) {
                ret = -ENOMEM;
                printk(KERN_EMERG "kamlloc fialed!
");
                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);          /* data address */
                reg_init_cdev(&my_devices[i], i);
        }

        printk(KERN_EMERG "Hello World enter!
");
        return 0;

fail:
        unregister_chrdev_region(num_dev, DEVICE_MINOR_NUM);
        return ret;
}

static void hello_exit(void)
{
        int i;
        dev_t num_dev = MKDEV(numdev_major, numdev_minor);
        printk(KERN_EMERG "Hello World exit!
");
        for(i=0;i<DEVICE_MINOR_NUM;i++) {
                cdev_del(&my_devices[i].cdev);
        }
        unregister_chrdev_region(num_dev, DEVICE_MINOR_NUM);
}

module_init(hello_init);
module_exit(hello_exit);
代码

测试结果:

[root@iTOP-4412]# insmod register_cdev.ko                                                                  
[ 1065.781646] numdev_major is 0!
[ 1065.783265] numdev_minor is 0!
[ 1065.786377] register req major number is 248
[ 1065.790637] cdev_add 0 is success!
[ 1065.793943] cdev_add 1 is success!
[ 1065.797392] Hello World enter!
[root@iTOP-4412]# rmmod register_cdev                                                                      
[ 1082.904729] Hello World exit
测试结果

四、生成字符类设备节点

前面介绍设备中的模型:bus,device,driver,都是有比较明显确定的定义。bus代表总线,device代表实际的设备和接口,driver代表驱动。

class是设备类,它是一个抽象的概念,没有对应的实体。它是提供给用户接口相似的一类设备的集合。常见的输入子系统input、usb、串口tty‘块设备block等。

  • 函数class_create创建class类文件
    • 参数1:一般是THIS_MODULE
    • 参数2:设备名称
    • 创建一个设备类,用于设备节点文件的创建
    • 返回一个class结构体变量
  • class结构体变量
    • class是设备驱动模型中通用的设备结构
    • 在头文件include/linux/device.h的280行
  • 老版本:创建设备class函数class_device_create
    • 头文件include/linux/device.h中
    • 参数1:class结构体变量
    • 参数2:父设备NULL
    • 参数3:dev_t设备号
    • 参数4:数据NULL
    • 参数5:设备节点名称
  • 释放设备class函数class_destroy
    • 参数1:myclass
  • 创建设备节点函数device_create
    • 头文件include/linux/device.h中
    • 参数1:设备诶所属于的类
    • 参数2:设备的浮设备,NULL
    • 参数3:设备号
    • 参数4:设备数据,NULL
    • 参数5:设备名称
  • 摧毁设备节点函数device_destroy
    • 参数1:设备所属于的类
    • 参数2:设备号
  • 释放内存函数kfree
    • 参数1:数据指针

 编写编译运行测试

  • 将"19注册字符类设备"中的"register_cdev.c"文件为"create_cnode.c"
  • 编译
  • 加载模块"create_cnode.ko"
    • 使用命令"ls /sys/class/"可以查看到生成的class
    • 使用命令"ls /dev"可以查看到生成的两个设备节点
  • 加载模块的时候还可以使用命令生成设备节点命令,列如
    • mknod dev/test0 c 249 0
    • mknod dev/test1 c 249 1

 运行代码:

#include <linux/init.h>
#include <linux/module.h>

/* define module_param module_param_array header file */
#include <linux/moduleparam.h>
/* define perm's head file*/
#include <linux/stat.h>
/* char device register head file */
#include <linux/fs.h>
/* MKDEV change device ID type */
#include <linux/kdev_t.h>
/* define char device struct */
#include <linux/cdev.h>
/* define memroy sapce */
#include <linux/slab.h>

/* include device_create class file */
#include <linux/device.h>

#define DEVICE_NAME "chardevnode"
#define DEVICE_MINOR_NUM 2
#define DEV_MAJOR 0
#define DEV_MINOR 0
#define REGDEV_SIZE 3000

MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("TOPEET");

int numdev_major = DEV_MAJOR;
int numdev_minor = DEV_MINOR;

/* input major device ID */
module_param(numdev_major, int, S_IRUSR);
/* input minor device ID */
module_param(numdev_minor, int, S_IRUSR);

static struct class *my_class;

struct reg_dev {
        char *data;
        unsigned long size;
        struct cdev cdev;
};
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(numdev_major, numdev_minor+index);

        cdev_init(&dev->cdev, &my_fops);
        dev->cdev.owner = THIS_MODULE;
        dev->cdev.ops = &my_fops;

        err = cdev_add(&dev->cdev, devno, 1);
        if(err) {
                printk(KERN_EMERG "cdev_add %d is fail! %d
", index, err);
        } else {
                printk(KERN_EMERG "cdev_add %d is success!
", (numdev_minor+index));
        }
}

static int hello_init(void)
{
        int ret, i;
        dev_t num_dev;

        printk(KERN_EMERG "numdev_major is %d!
", numdev_major);
        printk(KERN_EMERG "numdev_minor is %d!
", numdev_minor);

        if(numdev_major) {
                num_dev = MKDEV(numdev_major, numdev_minor);
                ret = alloc_chrdev_region(&num_dev, numdev_minor, DEVICE_MINOR_NUM, DEVICE_NAME);
        } else {
                ret = alloc_chrdev_region(&num_dev, numdev_minor, DEVICE_MINOR_NUM, DEVICE_NAME);
                numdev_major = MAJOR(num_dev);
                printk(KERN_EMERG "register req major number is %d
", numdev_major);
        }

        if(ret < 0) {
                printk(KERN_EMERG "register_chrdev_region req %d is failed
", numdev_major);
                unregister_chrdev_region(num_dev, DEVICE_MINOR_NUM);
                return ret;
        }

        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;
                printk(KERN_EMERG "kmalloc fialed!
");
                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);          /* data address */
                /* register device to system */
                reg_init_cdev(&my_devices[i], i);
                /* create device node */
                device_create(my_class, NULL, MKDEV(numdev_major, numdev_minor+i), "NULL", DEVICE_NAME"%d", i);
        }

        printk(KERN_EMERG "Hello World enter!
");
        return 0;

fail:
        unregister_chrdev_region(num_dev, DEVICE_MINOR_NUM);
        return ret;
}

static void hello_exit(void)
{
        int i;
        dev_t num_dev = MKDEV(numdev_major, numdev_minor);
        printk(KERN_EMERG "Hello World exit!
");
        for(i=0;i<DEVICE_MINOR_NUM;i++) {
                cdev_del(&my_devices[i].cdev);
                /* release memory*/
                device_destroy(my_class, MKDEV(numdev_major, numdev_minor+i));
        }

        /* release my class*/
        class_destroy(my_class);
        /* release kfre */
        kfree(my_devices);
        unregister_chrdev_region(num_dev, DEVICE_MINOR_NUM);
}

module_init(hello_init);
module_exit(hello_exit);
class_create

测试结果:

[root@iTOP-4412]# insmod create_cnode.ko                                                                   
[12828.419006] numdev_major is 0!
[12828.420699] numdev_minor is 0!
[12828.423674] register req major number is 248
[12828.429116] cdev_add 0 is success!
[12828.432882] cdev_add 1 is success!
[12828.436403] Hello World enter!

[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
248 chardevnode
249 mt3326-gps
250 roccat
251 BaseRemoteCtl
252 media
253 ttyGS
254 rtc

[root@iTOP-4412]# rmmod create_cnode                                                                       
[12439.491484] Hello World exit!
测试结果

五、字符驱动

  • file_operations中的函数比较多,选取用的比较多的函数简单介绍,后面的驱动教程中调用了对应的函数,再详细介绍
  • int (*open)(struct inode *, struct file *)
    • 打开函数
  • int (*release)(struct inode *, struct file *)
    • 释放close函数
  • long (*unlocked_ioctl)(struct file *, unsigned int, unsigned long)
    • io控制函数
  • ssize_t (*read)(struct file *, char __user *, size_t, loff_t *)
    • 读函数
  • ssize_t (*write)(struct file *, const char __user *, size_t, loff_t *)
    • 写函数
  • loff_t (*llseek)(struct file *, loff_t, int)
    • 定位函数
  • 如果需要不同的设备节点有不同的功能,只需要在注册设备的时候添加不同的file_operations结构体即可
  • 编写编译运行测试
  • 将驱动视频教程20中的"create_cnode.c"改为“char_driver.c”
  • 修改编译文件Makefile
  • 将驱动视频教程09中"invoke_hello.c"改为"invoke_char_driver.c",编译应用命令如下
    • arm-none-linux-gnueabi-gcc -o invoke_char_driver invoke_char_driver.c -static

 编写代码:

#include <linux/init.h>
#include <linux/module.h>

/* define module_param module_param_array header file */
#include <linux/moduleparam.h>
/* define perm's head file*/
#include <linux/stat.h>
/* char device register head file */
#include <linux/fs.h>
/* MKDEV change device ID type */
#include <linux/kdev_t.h>
/* define char device struct */
#include <linux/cdev.h>
/* define memroy sapce */
#include <linux/slab.h>

/* include device_create class file */
#include <linux/device.h>

#define DEVICE_NAME "chardevnode"
#define DEVICE_MINOR_NUM 2
#define DEV_MAJOR 0
#define DEV_MINOR 0
#define REGDEV_SIZE 3000

MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("TOPEET");

int numdev_major = DEV_MAJOR;
int numdev_minor = DEV_MINOR;

/* input major device ID */
module_param(numdev_major, int, S_IRUSR);
/* input minor device ID */
module_param(numdev_minor, int, S_IRUSR);

static struct class *my_class;

struct reg_dev {
        char *data;
        unsigned long size;
        struct cdev cdev;
};
struct reg_dev *my_devices;

/* open */
static int chardevnode_open(struct inode *inode, struct file *file)
{
        printk(KERN_EMERG "chardevnode open is success!
");
        return 0;
}

/* close */
static int chardevnode_release(struct inode *indoe, struct file *file)
{
        printk(KERN_EMERG "chardevnode release is success!
");
        return 0;
}

/* io control */
static long chardevnode_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
        printk(KERN_EMERG "chardevnode release is success!cmd is %d,arg is %d
", cmd, arg);
        return 0;
}

/* read */
static ssize_t chardevnode_read(struct file *file, char __user *buf, size_t size, loff_t *f_ops)
{
        return 0;
}

/* write */
static ssize_t chardevnode_write(struct file *file, const char __user *buf, size_t size, loff_t *ops)
{
        return 0;
}

/* lseek */
static loff_t chardevnode_llseek(struct file *file, loff_t offset, int whence)
{
        return 0;
}

struct file_operations my_fops = {
        .owner = THIS_MODULE,
        .open  = chardevnode_open,
        .release = chardevnode_release,
        .unlocked_ioctl = chardevnode_ioctl,
        .read = chardevnode_read,
        .write = chardevnode_write,
        .llseek = chardevnode_llseek,
};

static void reg_init_cdev(struct reg_dev *dev, int index)
{
        int err;
        int devno = MKDEV(numdev_major, numdev_minor+index);

        cdev_init(&dev->cdev, &my_fops);
        dev->cdev.owner = THIS_MODULE;
        dev->cdev.ops = &my_fops;

        err = cdev_add(&dev->cdev, devno, 1);
        if(err) {
                printk(KERN_EMERG "cdev_add %d is fail! %d
", index, err);
        } else {
                printk(KERN_EMERG "cdev_add %d is success!
", (numdev_minor+index));
        }
}

static int hello_init(void)
{
        int ret, i;
        dev_t num_dev;

        printk(KERN_EMERG "numdev_major is %d!
", numdev_major);
        printk(KERN_EMERG "numdev_minor is %d!
", numdev_minor);

        if(numdev_major) {
                num_dev = MKDEV(numdev_major, numdev_minor);
                ret = alloc_chrdev_region(&num_dev, numdev_minor, DEVICE_MINOR_NUM, DEVICE_NAME);
        } else {
                ret = alloc_chrdev_region(&num_dev, numdev_minor, DEVICE_MINOR_NUM, DEVICE_NAME);
                numdev_major = MAJOR(num_dev);
                printk(KERN_EMERG "register req major number is %d
", numdev_major);
        }

        if(ret < 0) {
                printk(KERN_EMERG "register_chrdev_region req %d is failed
", numdev_major);
                unregister_chrdev_region(num_dev, DEVICE_MINOR_NUM);
                return ret;
        }

        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;
                printk(KERN_EMERG "kmalloc fialed!
");
                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);          /* data address */
                /* register device to system */
                reg_init_cdev(&my_devices[i], i);
                /* create device node */
                device_create(my_class, NULL, MKDEV(numdev_major, numdev_minor+i), "NULL", DEVICE_NAME"%d", i);
        }

        printk(KERN_EMERG "Hello World enter!
");
        return 0;

fail:
        unregister_chrdev_region(num_dev, DEVICE_MINOR_NUM);
        return ret;
}

static void hello_exit(void)
{
        int i;
        dev_t num_dev = MKDEV(numdev_major, numdev_minor);
        printk(KERN_EMERG "Hello World exit!
");
        for(i=0;i<DEVICE_MINOR_NUM;i++) {
                cdev_del(&my_devices[i].cdev);
                /* release memory*/
                device_destroy(my_class, MKDEV(numdev_major, numdev_minor+i));
        }

        /* release my class*/
        class_destroy(my_class);
        /* release kfre */
        kfree(my_devices);
        unregister_chrdev_region(num_dev, DEVICE_MINOR_NUM);
}

module_init(hello_init);
module_exit(hello_exit);
代码

测试结果:

[root@iTOP-4412]# insmod char_driver.ko                                                                    
[ 2135.474056] numdev_major is 0!
[ 2135.475744] numdev_minor is 0!
[ 2135.478724] register req major number is 248
[ 2135.484220] cdev_add 0 is success!
[ 2135.487909] cdev_add 1 is success!
[ 2135.500246] Hello World enter!
[root@iTOP-4412]# ./invok_char                                                                             
[ 2174.171640] chardevnode open is success!
[ 2174.174452] chardevnode release is success!cmd is 1,arg is 6
[ 2174.179828] chardevnode open is success!
[ 2174.183737] chardevnode release is success!cmd is 1,arg is 6
[ 2174.189346] chardevnode release is success!
[ 2174.193511] chardevnode release is success!
APP open /dev/chardevnode0 success
APP open /dev/chardevnode0 success
[root@iTOP-4412]# rmmod char_driver                                                                        
[ 2199.758801] Hello World exit!
测试结果

六、字符类GPIOS,LED驱动编写

  • 将"21_字符驱动"中的文件“char_driver.c”改为“char_driver_leds.c”,添加gpio的初始化和操作函数,卸载模块的时候释放GPIO,将宏定义部分添加到头文件中,简单修改Makefile文件
  • 将“21_字符驱动”中的文件"incoke_char_driver.c"改为文件"invoke_char_gpios.c",并使用main参数传参数操作gpio
  • 应用编译命令
    • arm-none-linux-gnueabi-gcc -o invoke_char_gpios invoke_char_gpios.c -static
  • 操作命令
    • 类似"./invoke_char_gpios 0 1",参数1为命令,参数2为GPIO

字符驱动程序:

#include <linux/init.h>
#include <linux/module.h>

/* define module_param module_param_array header file */
#include <linux/moduleparam.h>
/* define perm's head file*/
#include <linux/stat.h>
/* char device register head file */
#include <linux/fs.h>
/* MKDEV change device ID type */
#include <linux/kdev_t.h>
/* define char device struct */
#include <linux/cdev.h>
/* define memroy sapce */
#include <linux/slab.h>

/* include device_create class file */
#include <linux/device.h>

#include <linux/gpio.h>
#include <plat/gpio-cfg.h>
#include <mach/gpio.h>
#include <mach/gpio-exynos4.h>

#define DEVICE_NAME "chardevnode"
#define DEVICE_MINOR_NUM 2
#define DEV_MAJOR 0
#define DEV_MINOR 0
#define REGDEV_SIZE 3000

MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("TOPEET");

int numdev_major = DEV_MAJOR;
int numdev_minor = DEV_MINOR;

/* input major device ID */
module_param(numdev_major, int, S_IRUSR);
/* input minor device ID */
module_param(numdev_minor, int, S_IRUSR);

static struct class *my_class;

struct reg_dev {
    char *data;
    unsigned long size;
    struct cdev cdev;
};
struct reg_dev *my_devices;

/* open */
static int chardevnode_open(struct inode *inode, struct file *file)
{
    printk(KERN_EMERG "chardevnode open is success!
");
    return 0;
}

/* close */
static int chardevnode_release(struct inode *indoe, struct file *file)
{
    printk(KERN_EMERG "chardevnode release is success!
");
    return 0;
}

/* io control */
static long chardevnode_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    printk(KERN_EMERG "chardevnode release is success!cmd is %d,arg is %d
", cmd, arg);

    if(cmd >1 || arg> 1) {
        printk(KERN_EMERG "cmd and arg is 0 or 1");
        return 0;
    }
    switch(arg) {
        case 0:
            gpio_set_value(EXYNOS4_GPL2(0), cmd);
            break;
        case 1:
            gpio_set_value(EXYNOS4_GPK1(1), cmd);
            break;
        default:
            printk(KERN_EMERG "cmd and arg is 0 or 1");
            break;
    }

    return 0;
}

/* read */
static ssize_t chardevnode_read(struct file *file, char __user *buf, size_t size, loff_t *f_ops)
{
    return 0;
}

/* write */
static ssize_t chardevnode_write(struct file *file, const char __user *buf, size_t size, loff_t *ops)
{
    return 0;
}

/* lseek */
static loff_t chardevnode_llseek(struct file *file, loff_t offset, int whence)
{
    return 0;
}

struct file_operations my_fops = {
    .owner = THIS_MODULE,
    .open  = chardevnode_open,
    .release = chardevnode_release,
    .unlocked_ioctl = chardevnode_ioctl,
    .read = chardevnode_read,
    .write = chardevnode_write,
    .llseek = chardevnode_llseek,
};

/* GPL2_0 */
static int led1_init(void)
{
    int ret;
    printk(KERN_EMERG "Gpio led 1 init
");

    ret = gpio_request(EXYNOS4_GPL2(0), "LEDS");
    if(ret < 0) {
        printk(KERN_EMERG "gpio_request EXYNOS4_GPL2(0) failed
");
        return ret;
    }

    s3c_gpio_cfgpin(EXYNOS4_GPL2(0), S3C_GPIO_OUTPUT);

    gpio_set_value(EXYNOS4_GPL2(0), 0);

    return 0; 
}

/* GPK1_1 */
static int led2_init(void)
{
    int ret;
    printk(KERN_EMERG "GPIO led 2 init
");

    ret = gpio_request(EXYNOS4_GPK1(1), "LEDS2");
    if(ret < 0) {
        printk(KERN_EMERG "gpio_request EXYNOS4_GPK1(1) fialed
");
        return ret;
    }
    s3c_gpio_cfgpin(EXYNOS4_GPK1(1), S3C_GPIO_OUTPUT);

    gpio_set_value(EXYNOS4_GPK1(1), 0);

    return 0;
}

static void reg_init_cdev(struct reg_dev *dev, int index)
{
    int err;
    int devno = MKDEV(numdev_major, numdev_minor+index);

    cdev_init(&dev->cdev, &my_fops);
    dev->cdev.owner = THIS_MODULE;
    dev->cdev.ops = &my_fops;

    err = cdev_add(&dev->cdev, devno, 1);
    if(err) {
        printk(KERN_EMERG "cdev_add %d is fail! %d
", index, err);
    } else {
        printk(KERN_EMERG "cdev_add %d is success!
", (numdev_minor+index));
    }
}

static int hello_init(void)
{
    int ret, i;
    dev_t num_dev;
    
    printk(KERN_EMERG "numdev_major is %d!
", numdev_major);
    printk(KERN_EMERG "numdev_minor is %d!
", numdev_minor);

    if(numdev_major) {
        num_dev = MKDEV(numdev_major, numdev_minor);
        ret = alloc_chrdev_region(&num_dev, numdev_minor, DEVICE_MINOR_NUM, DEVICE_NAME);
    } else {
        ret = alloc_chrdev_region(&num_dev, numdev_minor, DEVICE_MINOR_NUM, DEVICE_NAME);
        numdev_major = MAJOR(num_dev);
        printk(KERN_EMERG "register req major number is %d
", numdev_major);
    }

    if(ret < 0) {
        printk(KERN_EMERG "register_chrdev_region req %d is failed
", numdev_major);
        unregister_chrdev_region(num_dev, DEVICE_MINOR_NUM);
        return ret;
    }

    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;
        printk(KERN_EMERG "kmalloc fialed!
");
        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);        /* data address */
        /* register device to system */
        reg_init_cdev(&my_devices[i], i);
        /* create device node */
        device_create(my_class, NULL, MKDEV(numdev_major, numdev_minor+i), "NULL", DEVICE_NAME"%d", i);
    }

    led1_init();
    led2_init();
    printk(KERN_EMERG "Hello World enter!
");
    return 0;

fail:
    unregister_chrdev_region(num_dev, DEVICE_MINOR_NUM);
    return ret;
}

static void hello_exit(void)
{
    int i;
    dev_t num_dev = MKDEV(numdev_major, numdev_minor);
    printk(KERN_EMERG "Hello World exit!
");
    for(i=0;i<DEVICE_MINOR_NUM;i++) {
        cdev_del(&my_devices[i].cdev);
        /* release memory*/
        device_destroy(my_class, MKDEV(numdev_major, numdev_minor+i));
    }
    
    /* release my class*/
    class_destroy(my_class);
    /* release kfre */
    kfree(my_devices);
    unregister_chrdev_region(num_dev, DEVICE_MINOR_NUM);
}

module_init(hello_init);
module_exit(hello_exit);
char_driver_leds.c

然后是应用程序:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
        int fd0, fd1;
        char *hello_node0 = "/dev/chardevnode0";
        char *hello_node1 = "/dev/chardevnode1";
        if(argc > 2) {
                printf("please input cmd and arg
");
        }
        /* O_RDWR只读打开, O_NDELAY非阻塞方式 */
        fd0 = open(hello_node0, O_RDWR|O_NDELAY);
        if(fd0 < 0) {
                printf("APP open %s failed
", hello_node0);
                exit(EXIT_FAILURE);
        } else {
                printf("APP open %s success
", hello_node0);
                ioctl(fd0, atoi(argv[1]), atoi(argv[2]));
        }
        /* O_RDWR只读打开, O_NDELAY非阻塞方式 */
/*
        fd1 = open(hello_node0, O_RDWR|O_NDELAY);
        if(fd1 < 0) {
                printf("APP open %s failed
", hello_node0);
                exit(EXIT_FAILURE);
        } else {
                printf("APP open %s success
", hello_node0);
                ioctl(fd1, 1, 6);
        }
*/
        close(fd0);
        close(fd1);
}
invoke_char_driver.c

然后是makefile:

TARGET_NAME = char_driver_leds
APP_NAME = invoke_char_gpios
obj-m += $(TARGET_NAME).o

KDIR := /home/topeet/chen/kernel-3.0/iTop4412_Kernel_3.0

PWD ?= $(shell pwd)

all:app
        make -C $(KDIR) M=$(PWD) modules

app:$(APP_NAME)
        arm-none-linux-gnueabi-gcc $(APP_NAME).c -o $(APP_NAME) -static

clean:
        rm -rf *.o *.ko *.mod.c *.symvers *.order *.cmd .$(TARGET_NAME)* $(APP_NAME)
Makefile

测试结果:

[root@iTOP-4412]# insmod char_driver_leds.ko                                                               
[  420.107938] numdev_major is 0!
[  420.109549] numdev_minor is 0!
[  420.112677] register req major number is 248
[  420.125765] cdev_add 0 is success!
[  420.137424] cdev_add 1 is success!
[  420.148881] Gpio led 1 init
[  420.150342] gpio_request EXYNOS4_GPL2(0) failed
[  420.154743] GPIO led 2 init
[  420.165167] Hello World enter!
[root@iTOP-4412]# ./invoke_char_gpios 1 0                                                                  
please input cmd [  431.050669] chardevnode open is success!
[  431.054691] chardevnode release is success!cmd is 1,arg is 0
[  431.060238] chardevnode release is success!
and arg
APP open /dev/chardevnode0 success
[root@iTOP-4412]# ./invoke_char_gpios 1 1                                                                  
please input cmd [  435.289936] chardevnode open is success!
[  435.294047] chardevnode release is success!cmd is 1,arg is 1
[  435.299498] chardevnode release is success!
and arg
APP open /dev/chardevnode0 success
[root@iTOP-4412]# ./invoke_char_gpios 0 0                                                                  
please input cmd [  440.595232] chardevnode open is success!
[  440.599237] chardevnode release is success!cmd is 0,arg is 0
and arg
APP open /dev/chardevnode0 success
[  440.609648] chardevnode release is success!
[root@iTOP-4412]# ./invoke_char_gpios 0 1                                                                  
please input cmd [  443.313565] chardevnode open is success!
[  443.317679] chardevnode release is success!cmd is 0,arg is 1
[  443.323129] chardevnode release is success!
and arg
APP open /dev/chardevnode0 success

[root@iTOP-4412]# rmmod char_driver_leds                                                                   
[  468.722834] Hello World exit!
测试结果

不知道为什么要设两个驱动设备,按我的写法应该,这两个驱动设备没什么区别。

原文地址:https://www.cnblogs.com/ch122633/p/9459904.html