RK3568笔记三十四:字符设备驱动

一、Linux设备分类

linux 是文件型系统,所有硬件都会在对应的目录 (/dev) 下面用相应的文件表示。直接读文件,写文件就可以向设备发送、接收数据。

按照读写存储数据方式,设备分为以下几种:字符设备、块设备和网络设备。

字符设备: 指应用程序按字节/字符来读写数据的设备。

块设备: 通常支持随机存取和寻址,并使用缓存器。

网络设备: 是一种特殊设备,它并不存在于/dev 下面,主要用于网络数据的收发。

二、字符设备驱动简介

字符设备是 Linux 驱动中最基本的一类设备驱动,字符设备就是一个一个字节,按照字节

流进行读写操作的设备,读写数据是分先后顺序的。

Linux 应用程序对驱动程序的调用如下图:

在这里插入图片描述

Linux 内核中将字符设备抽象成一个具体的数据结构 (struct cdev), 我们可以理解为字符设备对象,

cdev 记录了字符设备的相关信息 (设备号、内核对象),字符设备的打开、读写、关闭等操作接口

(file_operations),在我们想要添加一个字符设备时,就是将这个对象注册到内核中,通过创建一

个文件 (设备节点) 绑定对象的 cdev,当我们对这个文件进行读写操作时,就可以通过虚拟文件

系统,在内核中找到这个对象及其操作接口,从而控制设备。

在这里插入图片描述

三、字符设备驱动程序框架

在这里插入图片描述

驱动程序流程:

1、分配设备号,获取设备的唯一ID

2、实现 file_operation,保存到 cdev

3、实现 cdev 的初始化

4、使用cdev_add() 注册 cdev,告诉内核

5、创建设备节点,方便APP层调用 file_operation 接口

6、注销设备,注销设备需释放内核中的 cdev,归还申请的设备号,删除创建的设备节点。

四、驱动初始化和注销

1、设备号的申请和归还

1.1 定义字符设备

//第一种方式
static struct cdev chrdev;
//第二种方式
struct cdev *cdev_alloc(void);

1.2 移除某个字符设备

void cdev_del(struct cdev *p

1.3 静态字符设备申请

int register_chrdev_region(dev_t from, unsigned count, const char *name)

1.4 动态分配设备编号

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name)

1.5 注销的字符设备的设备编号

void unregister_chrdev_region(dev_t from, unsigned count)
static inline void unregister_chrdev(unsigned int major, const char *name)

1.6 分配设备号

不仅支持静态申请设备号,也支持动态申请设备号,并将主设备号返回

static inline** **int** register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)

1.7 初始化 cdev

void cdev_init(struct cdev *cdev, const struct file_operations *fops)

1.8 设备注册和注销

# 添加一个新的字符设备
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
# 注销
void cdev_del(struct cdev *p)

1.9 设备节点的创建和销毁

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

void device_destroy(struct class *class, dev_t devt)

这里注意,节点的创建可以使用代码,也可以使用mknod 命令创建设备节点

用法:mknod 设备名设备类型主设备号次设备号

如:mkmod /dev/test c 2 0

创建一个字符设备/dev/test,其主设备号为 2,次设备号为 0。

创建流程:创建了一个字符设备文件时,实际上就是创建了一个设备节点 inode 结构

体,并且将该设备的设备编号记录在成员 i_rdev,将成员 f_op 指针指向了 def_chr_fops 结构体。

在这里插入图片描述

五、open 函数到底做了什么

在这里插入图片描述

户空间使用 open() 系统调用函数打开一个字符设备时 (int fd = open(“dev/xxx”, O_RDWR)) 大致

有以下过程:

• 在虚拟文件系统 VFS 中的查找对应与字符设备对应 struct inode 节点

• 遍历散列表 cdev_map,根据 inod 节点中的 cdev_t 设备号找到 cdev 对象

• 创建 struct file 对象 (系统采用一个数组来管理一个进程中的多个被打开的设备,每个文件

秒速符作为数组下标标识了一个设备对象)

• 初始化 struct file 对象,将 struct file 对象中的 file_operations 成员指向 struct cdev 对象中的

file_operations 成员 (file->fops = cdev->fops)

• 回调 file->fops->open 函数

chrdev_open 函数代码实现:

在这里插入图片描述

总结整个过程,当使用 open 函数,打开设备文件时,会根据该设备的文件的设备号找到

相应的设备结构体,从而得到了操作该设备的方法。也就是说如果我们要添加一个新设备的话,

我们需要提供一个设备号,一个设备结构体以及操作该设备的方法 (file_operations 结构体)。

六、代码程序

1、驱动代码

创建文件chrdevbase.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#define DEV_NAME            "chrdevbase"
#define DEV_CNT                 (1)
#define BUFF_SIZE               128
//定义字符设备的设备号
static dev_t devno;
//定义字符设备结构体chr_dev
static struct cdev chr_dev;
//数据缓冲区
static char vbuf[BUFF_SIZE];
static int chr_dev_open(struct inode *inode, struct file *filp);
static int chr_dev_release(struct inode *inode, struct file *filp);
static ssize_t chr_dev_write(struct file *filp, const char __user * buf, size_t count, loff_t *ppos);
static ssize_t chr_dev_read(struct file *filp, char __user * buf, size_t count, loff_t *ppos);

static struct file_operations  chr_dev_fops = 
{
    .owner = THIS_MODULE,
    .open = chr_dev_open,
    .release = chr_dev_release,
    .write = chr_dev_write,
    .read = chr_dev_read,
};

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

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

static ssize_t chr_dev_write(struct file *filp, const char __user * buf, size_t count, loff_t *ppos)
{
    unsigned long p = *ppos;
    int ret;
    int tmp = count ;
    if(p > BUFF_SIZE)
        return 0;
    if(tmp > BUFF_SIZE - p)
        tmp = BUFF_SIZE - p;
    ret = copy_from_user(vbuf, buf, tmp);
    *ppos += tmp;
    return tmp;
}

static ssize_t chr_dev_read(struct file *filp, char __user * buf, size_t count, loff_t *ppos)
{
    unsigned long p = *ppos;
    int ret;
    int tmp = count ;
    if(p >= BUFF_SIZE)
        return 0;
    if(tmp > BUFF_SIZE - p)
        tmp = BUFF_SIZE - p;
    ret = copy_to_user(buf, vbuf+p, tmp);
    *ppos +=tmp;
    return tmp;
}

static int __init chrdev_init(void)
{
    int ret = 0;
    printk("chrdev init\n");
    //第一步
    //采用动态分配的方式,获取设备编号,次设备号为0,
    //设备名称为EmbedCharDev,可通过命令cat  /proc/devices查看
    //DEV_CNT为1,当前只申请一个设备编号
    ret = alloc_chrdev_region(&devno, 0, DEV_CNT, DEV_NAME);
    if(ret < 0){
        printk("fail to alloc devno\n");
        goto alloc_err;
    }
    //第二步
    //关联字符设备结构体cdev与文件操作结构体file_operations
    cdev_init(&chr_dev, &chr_dev_fops);
    //第三步
    //添加设备至cdev_map散列表中
    ret = cdev_add(&chr_dev, devno, DEV_CNT);
    if(ret < 0)
    {
        printk("fail to add cdev\n");
        goto add_err;
    }
    return 0;

add_err:
    //添加设备失败时,需要注销设备号
    unregister_chrdev_region(devno, DEV_CNT);
alloc_err:
    return ret;
}


static void __exit chrdev_exit(void)
{
    printk("chrdev exit\n");
    unregister_chrdev_region(devno, DEV_CNT);

    cdev_del(&chr_dev);
}

module_init(chrdev_init);
module_exit(chrdev_exit);
MODULE_LICENSE("GPL");


makefile

# 内核目录
KERNELDIR := /home/alientek/rk3568_linux_sdk/kernel
# rk3568是arm64架构
ARCH=arm64
# 交叉工具链
CROSS_COMPILE=/opt/atk-dlrk356x-toolchain/usr/bin/aarch64-buildroot-linux-gnu-
export  ARCH  CROSS_COMPILE

CURRENT_PATH := $(shell pwd)
obj-m := chrdevbase.o

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean


2、测试 APP

创建文件chrdevbaseApp.c

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>

char *wbuf = "chrdevbase test\n";

char rbuf[128];
int main(void)
{
    printf("chrdevbase test\n");
    //打开文件
    int fd = open("/dev/chrdevbase", O_RDWR);
    //写入数据
    write(fd, wbuf, strlen(wbuf));
    //写入完毕,关闭文件
    close(fd);
    //打开文件
     fd = open("/dev/chrdevbase", O_RDWR);
    //读取文件内容
    read(fd, rbuf, 128);
    //打印读取的内容
    printf("The content : %s \n", rbuf);
    //读取完毕,关闭文件
    close(fd);
    return 0;
}

编译:

/opt/atk-dlrk356x-toolchain/bin/aarch64-buildroot-linux-gnu-gcc chrdevbaseApp.c -o 
chrdevbaseApp

3、测试

把生成的ko文件和chrdevbaseApp文件通过tftp等方式上传到开发板上测试。

相关推荐

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-11 13:34:04       66 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-11 13:34:04       70 阅读
  3. 在Django里面运行非项目文件

    2024-07-11 13:34:04       57 阅读
  4. Python语言-面向对象

    2024-07-11 13:34:04       68 阅读

热门阅读

  1. 数据结构笔记之线索二叉树找前驱后继

    2024-07-11 13:34:04       21 阅读
  2. Mybatis之动态sql、缓存、分页、配置数据源

    2024-07-11 13:34:04       17 阅读
  3. python的入门知识(下)

    2024-07-11 13:34:04       23 阅读
  4. 网络协议 | 计算机网络基础学习笔记

    2024-07-11 13:34:04       18 阅读
  5. 【Axure高保真原型】输入表单——回车键切换

    2024-07-11 13:34:04       21 阅读
  6. c与c++ 常用的字符与字符串处理的接口介绍:

    2024-07-11 13:34:04       25 阅读
  7. AT32单片机踩坑记录

    2024-07-11 13:34:04       23 阅读
  8. 西门子总线插头6ES7972-0BB41-0XA0

    2024-07-11 13:34:04       19 阅读
  9. ActiViz中的过滤器vtkLinearExtrusionFilter

    2024-07-11 13:34:04       24 阅读
  10. R 数据重塑

    2024-07-11 13:34:04       20 阅读
  11. MySQL InnoDB存储引擎

    2024-07-11 13:34:04       24 阅读