Linux-字符设备驱动

该文章参考了很多资料和博主,以及自己的一些体会,放在这里仅供参考学习,大家共同进步
若出现侵权,请告知博主本人,本人会立即删除相应的文章和代码

字符设备就是一个一个字节,按照字节流进行读写操作的设备,读写是分先后顺序的

1. 字符设备驱动结构

1.1 cdev结构体

路径:linux-5.10.186\include\linux\cdev.h
该结构体是Linux内核中用于表示字符设备的一个结构体

struct cdev {
	struct kobject kobj;   //内嵌的Kobject结构体
	struct module *owner;   //所属模块
	const struct file_operations *ops;  //文件操作结构体
	struct list_head list;   //链表
	dev_t dev;   //设备号
	unsigned int count;   //多少文件(或进程)打开了该字符设备
} __randomize_layout;

1.1.1 dev_t dev 设备号

dev 是一个设备号,用于Linux设备的唯一标识。在Linux中,设备号由主设备号(major)和次设备号(minjor)组成,主设备号用于区分设备驱动程序,而次设备号则用于区分同一驱动程序下不同的设备
在注册字符设备(cdev_add)之前,是需要向系统申请设备号的,调用函数:

int alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *)
int register_chrdev_region(dev_t from, unsigned count, const char *name)
static inline int register_chrdev(unsigned int major, const char *name,
				  const struct file_operations *fops)

alloc_chrdev_region:用于设备号未知,动态的申请未被占用的设备号的情况下
register_chrdev_region:已知起始设备的设备号的情况下
register_chrdev:老版本使用的,可忽略

在注销字符设备(cdev_del)之后,需要释放申请的设备号,调用函数
unregister_chrdev_region(dev_t, unsigned)

MAJOR(dev_t dev);获取设备的主设备号
MINJOR(dev_t dev);获取设别的次设备号
MKDEV(int major, int minor);该宏通过主次设备号生成dev_t

1.1.2 file_operations结构体

该结构体中的成员函数是字符设备驱动程序设计的主题内容,这些函数实际会在应用程序进行Linux的open()/close()/read()/write()等系统调用时最终被内核调用

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 *);
	ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
	ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
	int (*iopoll)(struct kiocb *kiocb, bool spin);
	int (*iterate) (struct file *, struct dir_context *);
	int (*iterate_shared) (struct file *, struct dir_context *);
	__poll_t (*poll) (struct file *, struct poll_table_struct *);
	long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
	long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
	int (*mmap) (struct file *, struct vm_area_struct *);
	unsigned long mmap_supported_flags;
	int (*open) (struct inode *, struct file *);
	int (*flush) (struct file *, fl_owner_t id);
	int (*release) (struct inode *, struct file *);
	int (*fsync) (struct file *, loff_t, loff_t, int datasync);
	int (*fasync) (int, struct file *, int);
	int (*lock) (struct file *, int, struct file_lock *);
	ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
	unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
	int (*check_flags)(int);
	int (*flock) (struct file *, int, struct file_lock *);
	ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
	ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
	int (*setlease)(struct file *, long, struct file_lock **, void **);
	long (*fallocate)(struct file *file, int mode, loff_t offset,
			  loff_t len);
	void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
	unsigned (*mmap_capabilities)(struct file *);
#endif
	ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,
			loff_t, size_t, unsigned int);
	loff_t (*remap_file_range)(struct file *file_in, loff_t pos_in,
				   struct file *file_out, loff_t pos_out,
				   loff_t len, unsigned int remap_flags);
	int (*fadvise)(struct file *, loff_t, loff_t, int);
	bool may_pollfree;
} __randomize_layout;

1.2 cdev结构体操作函数

1.2.1 cdev_init

该函数用于初始化cdev成员,并建立cdev和file_operations之间的连接

/**
 * 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)
{
	memset(cdev, 0, sizeof *cdev);
	INIT_LIST_HEAD(&cdev->list);
	kobject_init(&cdev->kobj, &ktype_cdev_default);
	cdev->ops = fops;
}

1.2.2 cdev_alloc

该函数用于动态申请一个cdev内存

/**
 * cdev_alloc() - allocate a cdev structure
 *
 * Allocates and returns a cdev structure, or NULL on failure.
 */
struct cdev *cdev_alloc(void)
{
	struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);
	if (p) {
		INIT_LIST_HEAD(&p->list);
		kobject_init(&p->kobj, &ktype_cdev_dynamic);
	}
	return p;
}

1.2.3 cdev_add

该函数用于将一个字符设备添加到系统中,使其能够被访问,完成字符设备的注册。
该函数的调用常发生在字符设备驱动模块的加载函数**module_init()**中

cdev_add函数还会为每个指定的设备号初始化一个cdev结构体,并将其添加到内核的字符设备列表中,以便后续可以通过设备号来访问这些设备。如果cdev_add调用成功,它会返回0;如果失败,则返回一个负的错误码。

/**
 * 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)
{
	int error;

	p->dev = dev;
	p->count = count;

	if (WARN_ON(dev == WHITEOUT_DEV))
		return -EBUSY;

	error = kobj_map(cdev_map, dev, count, NULL,
			 exact_match, exact_lock, p);
	if (error)
		return error;

	kobject_get(p->kobj.parent);

	return 0;
}

1.2.4 cdev_del

和cdev_add刚好相反,从系统中删除一个字符设备,完成字符设备的注销
该函数的调用常发生在字符设备驱动模块的卸载函数module_exit()

/**
 * 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.
 *
 * NOTE: This guarantees that cdev device will no longer be able to be
 * opened, however any cdevs already open will remain and their fops will
 * still be callable even after cdev_del returns.
 */
void cdev_del(struct cdev *p)
{
	cdev_unmap(p->dev, p->count);
	kobject_put(&p->kobj);
}

1.2.1 cdev_put

void cdev_put(struct cdev *p)
{
	if (p) {
		struct module *owner = p->owner;
		kobject_put(&p->kobj);
		module_put(owner);
	}
}

1.3 设备节点

设备节点是用于文件系统中表示设备的特殊文件,设备节点位于 /dev 目录下,文件名由设备类型和设备号组成,通过设备节点,应用程序可以与设备进行交互,打开关闭/读取写入等操作
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

1.3.1 手动创建设备节点

手动创建节点时通过mknod指令
基本语法

mknod [OPTIONS] NAME TYPE [MAJOR MINOR]

OPTIONS:可选参数,如-m用于设置文件权限,-Z用于设置安全的上下文等。
NAME:要创建的设备文件或命名管道的名称。
TYPE:设备文件的类型,可以是b(块设备)、c(字符设备)或p(命名管道)。
MAJORMINOR:当TYPE为b或c时,需要指定主设备号和次设备号。这两个号码用于唯一标识设备。

如果需要创建一个名为/dev/mychar的字符设备文件,主设备号为230,次设备号为0,可以使用:

sudo mknod /dev/mychar c 230 0

1.3.2 自动创建设备节点

自动创建节点依赖于两个函数:class_create()/device_create()
节点的删除依赖与:device_destroy()/class_destroy();
需要注意使用的顺序:先创建类,再创建设备,先删除设备再删除类

#define class_create(owner, name)		\
({						\
	static struct lock_class_key __key;	\
	__class_create(owner, name, &__key);	\
})

//class:给哪个类创建设备
//parent:父设备,一般为NULL
//devt:设备号
//drvdata:设备可能会使用到的数据
//fmt:设备名字
struct device *device_create(struct class *class, struct device *parent,
			     dev_t devt, void *drvdata, const char *fmt, ...)
{
	va_list vargs;
	struct device *dev;

	va_start(vargs, fmt);
	dev = device_create_groups_vargs(class, parent, devt, drvdata, NULL,
					  fmt, vargs);
	va_end(vargs);
	return dev;
}

2. 字符设备驱动的组成

Linux编码习惯是位设别定义一个设备相关的结构体,该结构体包含设备的cdev、私有数据、锁等

struct chrtest_dev {
    struct cdev cdev;
    struct class * class;
    struct device * device;
    dev_t devid;
    int major;
    int minjor;
}test_dev;

2.1 字符设备驱动模块的加载和卸载

  • 在加载函数中应实现设备号的申请和cdev注册
  • 在卸载函数中应实现设备号的释放和cdev注销
//设备驱动加载函数
static int __init test_init(void) {
	...
	cdev_init(&test_dev.cdev, &test_fops);
	test_dev.cdev.owner = THIS_MODULE;
	if (test_major)  {
		register_chrdev_region(test_dev.cdev.dev, 1, DEV_NAME);
	}
	else {
		alloc_chrdev_region(&test_dev.cdev.dev, 0, 1, DEV_NAME);
	}
	ret = cdev_add(&test_dev.cdev, &test.cdev.dev, count)
	return ret;
	...
}

module_init(test_init);

//驱动模块卸载函数
static void __exit test_exit(void) {
	...
	cdev_del(&test_dev.cdev);   //注销设备
	unregister_chrdev_region(&test_dev.cdev.dev);//注销设备号
	...
}
module_exit(test_exit);

2.2 file_operations成员实现

该结构体中的成员函数的字符设备驱动与内核虚拟文件系统的接口,是用户空间对Linux进行系统调用最终的落实者,大多数驱动都会实现read()/write()/open()/release()/ioctl()
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

static int chr_open(struct inode *inode, struct file *file);
static int chr_release(struct inode *, struct file *);
static ssize_t chr_read(struct file *file, char __user *buf, size_t count, loff_t *offt);
static ssize_t chr_write(struct file *file, const char __user *buf, size_t count, loff_t *offt);

3. 代码示例

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/kobject.h>
#include <linux/kdev_t.h>
#include <linux/list.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <asm/io.h>


#define   CHR_TEST_NAME   "chr_test"
#define   CHR_DEV_COUNT   1
#define   CHEDEV_TASK_0   0
#define   CHEDEV_TASK_1   1



/* 寄存器物理地址 */
#define CCM_CCGR1_BASE (0X020C406C)
#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)
#define GPIO1_DR_BASE (0X0209C000)
#define GPIO1_GDIR_BASE (0X0209C004)

/* 映射后的寄存器虚拟地址指针 */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;


static char readbuf[100]; //读缓冲区
static char writebuf[100]; //写缓冲区



static int __init test_init(void);
static void __exit test_exit(void);
static int chr_open(struct inode *inode, struct file *file);
static int chr_release(struct inode *, struct file *);
static ssize_t chr_read(struct file *file, char __user *buf, size_t count, loff_t *offt);
static ssize_t chr_write(struct file *file, const char __user *buf, size_t count, loff_t *offt);


struct chrtest_dev {
    struct cdev cdev;
    struct class * class;
    struct device * device;
    dev_t devid;
    int major;
    int minjor;
}chrtest;

static const struct file_operations test_fops = {
    .owner = THIS_MODULE,
    .read = chr_read,
    .write = chr_write,
    .open = chr_open,
    .release = chr_release,
};

static int __init test_init(void) {
//     u32 val = 0;

// /* 初始化 LED */
// /* 1、寄存器地址映射 */
//     IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);
//     SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);
//     SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);
//     GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);
//     GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);

// /* 2、使能 GPIO1 时钟 */
//     val = readl(IMX6U_CCM_CCGR1);
//     val &= ~(3 << 26); /* 清楚以前的设置 */
//     val |= (3 << 26); /* 设置新值 */
//     writel(val, IMX6U_CCM_CCGR1);

// /* 3、设置 GPIO1_IO03 的复用功能,将其复用为GPIO1_IO03,最后设置 IO 属性*/
//     writel(5, SW_MUX_GPIO1_IO03);

// /* 寄存器 SW_PAD_GPIO1_IO03 设置 IO 属性 */
//     writel(0x10B0, SW_PAD_GPIO1_IO03);

// /* 4、设置 GPIO1_IO03 为输出功能 */
//     val = readl(GPIO1_GDIR);
//     val &= ~(1 << 3); /* 清除以前的设置 */
//     val |= (1 << 3); /* 设置为输出 */
//     writel(val, GPIO1_GDIR);

// /* 5、默认关闭 LED */
//     val = readl(GPIO1_DR);
//     val |= (1 << 3);
//     writel(val, GPIO1_DR);

    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);


/* 注册字符设备驱动 */
/* 1、创建设备号 */
    if (chrtest.major) {
        chrtest.devid = MKDEV(chrtest.major, 0);
        register_chrdev_region(chrtest.devid, CHR_DEV_COUNT, CHR_TEST_NAME);
    } else {
        alloc_chrdev_region(&chrtest.devid, 0, CHR_DEV_COUNT, CHR_TEST_NAME);
    }
    
/* 2、初始化 cdev */
    cdev_init(&chrtest.cdev, &test_fops);
    chrtest.cdev.owner = THIS_MODULE;
/* 3、添加一个 cdev */
    cdev_add(&chrtest.cdev, chrtest.devid, CHR_DEV_COUNT);
/* 5、创建类和设备 */
    chrtest.class = class_create(THIS_MODULE, CHR_TEST_NAME);
    chrtest.device = device_create(chrtest.class, NULL, chrtest.devid, NULL, CHR_TEST_NAME);

    return 0;
}

static void __exit test_exit(void) {
/* 取消映射 */
    iounmap(IMX6U_CCM_CCGR1);
    iounmap(SW_MUX_GPIO1_IO03);
    iounmap(SW_PAD_GPIO1_IO03);
    iounmap(GPIO1_DR);
    iounmap(GPIO1_GDIR);

/* 注销字符设备 */
    cdev_del(&chrtest.cdev);/* 删除 cdev */
    unregister_chrdev_region(chrtest.devid, CHR_DEV_COUNT);

    device_destroy(chrtest.class, chrtest.devid);
    class_destroy(chrtest.class);
}

static int chr_open(struct inode *inode, struct file *file) {
    file -> private_data = &chrtest;
    return 0;
}

static int chr_release(struct inode *, struct file *) {
    return 0;
}

/*
* @description : 从设备读取数据
* @param - file : 要打开的设备文件(文件描述符)
* @param - buf : 返回给用户空间的数据缓冲区
* @param - count : 要读取的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
*/
static ssize_t chr_read(struct file *file, char __user *buf, size_t count, loff_t *offt) {
    int ret = 0;
    memcpy(readbuf, CHR_TEST_NAME, sizeof(CHR_TEST_NAME));
    ret = copy_to_user(buf, readbuf, count);
    if(ret == 0) {
        printk("from kernel read ok!!!\r\n");
    } else {
        printk("from kernel read err!!!\r\n");
        return -1;
    }
    return count;
}


static ssize_t chr_write(struct file *file, const char __user *buf, size_t count, loff_t *offt) {
    int ret = 0;
    long int num;
    ret = copy_from_user(writebuf, buf, count);
    if(ret == 0) {
        printk("write to kernel ok == %s!!!\r\n", writebuf);
    } else {
        printk("from kernel read err!!!\r\n");
        return -1;
    }
    num = simple_strtol(writebuf, NULL, 10);

    switch (num)
    {
    case CHEDEV_TASK_0:
        printk("CHEDEV_TASK_0\r\n");
        break;
    case CHEDEV_TASK_1:
        printk("CHEDEV_TASK_0\r\n");
        break;
    default:
        printk("default\r\n");
        break;
    }
    return count;
}

module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("qiao");
  • 21
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值