字符设备驱动demo

一、简介

    如下是写一个字符驱动设备的一般步骤:

    1、为设备定义一个设备相关的结构体(包含设备涉及的cdev,私有数据等)

    2、初始化函数的定义

        ① 向系统申请设备号(register_chrdev_region()或alloc_chrdev_region())

        ② 使用kzalloc 申请设备内存(为1 中定义的结构体申请存储空间)

        ③ 调用cdev_init()初始化cdev

        ④ 调用cdev_add()向系统注册设备

    3、卸载函数的定义

        ① 释放设备号(unregister_chrdev_region())

        ② 调用cdev_del()注销设备

    4、定义file_operations,实现对应的open,read,write方法。

二、主要知识点介绍

    1、设备号

        一个字符设备或者块设备都有一个主设备号和次设备号。主设备号和次设备号统称为设备号。主设备
号用来表示一个特定的驱动程序。次设备号用来表示使用该驱动程序的各个设备。Linux 提供了一个名为
dev_t 的数据类型表示设备号,dev_t 定义在文件include/linux/types.h 里面,定义如下:

typedef __u32 __kernel_dev_t;
typedef __kernel_dev_t dev_t;

        dev_t 是个32 位的变量,其中12 位用来表示主设备号,20 位用来表示次设备号。因此Linux 系统中主
设备号范围为0~4095 , 所以大家在选择主设备号的时候一定不要超过这个范围。在文件
include/linux/kdev_t.h 中提供了几个操作设备号的宏定义,如下所示:

#define MINORBITS 20
#define MINORMASK ((1U << MINORBITS) - 1)

#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))

        宏MINORBITS 表示次设备号位数,一共20 位。
        宏MINORMASK 表示次设备号掩码。
        宏MAJOR 用于从dev_t 中获取主设备号,将dev_t 右移20 位即可。
        宏MINOR 用于从dev_t 中获取次设备号,取dev_t 的低20 位的值即可。
        宏MKDEV 用于将给定的主设备号和次设备号的值组合成dev_t 类型的设备号。

    2、设备号分配

        ① 静态分配:register_chrdev_region(dev_t from, unsigned count, const char *name)

            from:设备号(主设备号与此设备号组合)

            count:要申请的设备号数量

            name:设备名称

        ② 动态分配:alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)

            baseminor:次设备号起始地址,alloc_chrdev_region 可以申请一段连续的多个设备号,这些设备号的
主设备号一样,但是次设备号不同,次设备号以baseminor 为起始地址地址开始递增。一般baseminor 为0,
也就是说次设备号从0 开始。

            其他参数含义同register_chrdev_region

        当我们知道该主设备号没有被使用时,可使用静态分配方式。如果我们不清楚该主设备号是否被占用,最好使用动态分配方式,会自动进行分配这设备号。

    3、简化版的注册设备

        ① 注册 register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)

            major:主设备号,0 表示动态分配

            name: 设备名

            fops: 操作函数

        ② unregister_chrdev(unsigned int major, const char *name)

            major:主设备号

            name: 设备名

    4、自动创建设备节点

        class_create()一共有两个参数,参数owner 一般为THIS_MODULE,参数name 是类名字。返回值是个指
向结构体class 的指针,也就是创建的类。

        当使用class_create()创建类后,再使用device_create 函数在这个类下创建一个设备。device_create
函数原型如下:

struct device *device_create(struct class *class,
struct device *parent,
dev_t devt,
void *drvdata,
const char *fmt, ...)

        device_create 是个可变参数函数,参数class 就是设备要创建哪个类下面;参数parent 是父设备,一般
为NULL,也就是没有父设备;参数devt 是设备号;参数drvdata 是设备可能会使用的一些数据,一般为NULL;
参数fmt 是设备名字,如果设置fmt=xxx 的话,就会生成/dev/xxx 这个设备文件。返回值就是创建好的设备。

三、demo

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/ide.h>
#include <linux/delay.h>
#include <linux/cdev.h>

#define CHRDEV_MAJOR 200
#define CHRDEV_NAME "char_test"

dev_t devno;
struct class *class;
struct device *device;
struct cdev *cdev_t;
static char readbuf[100];
static char writebuf[100];
static char kerneldata[] = {"kernel data!"};

#define DEVICE_NUMBER 1

static int chartest_open (struct inode *inode, struct file *file)
{
    printk("chartest_open\n");
    return 0;
}

static ssize_t chartest_read (struct file *file, char __user *buf,
            size_t cnt, loff_t *offt)
{
    int value = 0;

    memcpy(readbuf, kerneldata, sizeof(kerneldata));
    value = copy_to_user(buf, readbuf, cnt);
    if(!value) {
        printk("kernel send data : %s\n", buf);
    } else {
        printk("kernel send data failed %d\n", value);
    }

    return 0;
}

static ssize_t chartest_write (struct file *file, const char __user *buf,
            size_t cnt, loff_t *offt)
{
    int value = 0;

    value = copy_from_user(writebuf, buf, cnt);
    if(!value) {
        printk("kernel recieve data : %s\n", writebuf);
    } else {
        printk("kernel send data failed %d\n", value);
    }

    return 0;
}

static int chartest_release (struct inode *inode, struct file *filp)
{
    printk("chartest_release\n");
    return 0;
}

static struct file_operations chartest_fops = {
    .owner = THIS_MODULE,
    .open = chartest_open,
    .read = chartest_read,
    .write = chartest_write,
    .release = chartest_release,
};

static int chartest_init(void)
{
    int ret = 0;

    if (CHRDEV_MAJOR) {
        devno = MKDEV(CHRDEV_MAJOR, 0);
        ret = register_chrdev_region(devno, DEVICE_NUMBER, CHRDEV_NAME);
        if (ret < 0) {
            printk("register_chrdev_region failed\n");
            return 0;
        }
    } else {
        ret = alloc_chrdev_region(&devno, 0, DEVICE_NUMBER, CHRDEV_NAME);
        if (ret < 0) {
            printk("alloc_chrdev_region failed\n");
            return 0;
        }

    }

    cdev_t = kzalloc(sizeof(struct cdev), GFP_KERNEL);

    cdev_init(cdev_t, &chartest_fops);
    cdev_t->owner = THIS_MODULE;

    cdev_add(cdev_t, devno, 1);

    class = class_create(THIS_MODULE, CHRDEV_NAME);

    device = device_create(class, NULL, devno, NULL, CHRDEV_NAME);

    printk("chartest_init !\n");

    return 0;
}

static void chartest_exit(void)
{
    unregister_chrdev_region(devno, DEVICE_NUMBER);
    cdev_del(cdev_t);

    device_destroy(class, devno);
    class_destroy(class);

    printk("chartest_exit\n");
}

module_init(chartest_init);
module_exit(chartest_exit);

MODULE_LICENSE("GPL");

参考:迅为linux驱动学习视频

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值