字符设备驱动开发的流程

目录

    • 1.字符设备驱动简介
    • 2.字符设备驱动开发步骤
      • 2.1驱动模块的加载和卸载
      • 2.2字符设备注册与注销
      • 2.3实现设备的具体操作函数
    • 3.linux设备号
      • 3.1设备号的组成
      • 3.2设备号的分配
        • 3.2.1静态分配设备号
        • 3.2.2动态分配设备号
    • 4.实例展示

说明:此博客为我自己对正点原子提供的资料学习的记录,内容大部分来源于正点原子资料,大家也可以去下载正点原子的官方资料学习,内容丰富。侵权删。

1.字符设备驱动简介

  字符设备是Linux 驱动中最基本的一类设备驱动,字符设备就是一个一个字节,按照字节流进行读写操作的设备,读写数据是分先后顺序的。比如我们最常见的点灯、按键、IIC、SPI,LCD 等等都是字符设备,这些设备的驱动就叫做字符设备驱动。在详细的学习字符设备驱动架构之前,我们先来简单的了解一下Linux 下的应用程序是如何调用驱动程序的,Linux 应用程序对驱动程序的调用如下图 所示:
字符设备驱动开发的流程_第1张图片
  其中关于 C库以及如何通过系统调用 “陷入 到内核空间这个我们不用去管,我们重点关注的是应用程序和具体的驱动,应用程序使用到的函数在具体驱动程序中都有与之对应的函数,比如应用程序中调用了 open这个函数,那么在驱动程序中也得有一个名为 open的函数。每一个系统调用,在驱动中都有与之对应的一个驱动函数,在 Linux内核文件 include/linux/fs.h中有个叫做 file_operations的结构体,此结构体就是 Linux内核驱动操作函数集合,内容如下所示:
字符设备驱动开发的流程_第2张图片字符设备驱动开发的流程_第3张图片

2.字符设备驱动开发步骤

2.1驱动模块的加载和卸载

module_init(xxx_init); //注册模块加载函数
module_exit(xxx_exit); //注册模块卸载函数

  module_init函数用来向 Linux内核注册一个模块加载函数,参数 xxx_init就是需要注册的具体函数,当使用“ insmod”命令加载驱动的时候 xxx_init这个函数就会被调 用。 module_exit()函数用来向 Linux内核注册一个模块卸载函数,参数 xxx_exit就是需要注册的具体函数,当使用“ rmmod”命令卸载具体驱动的时候 xxx_exit函数就会被调用。所以一般在xxx_init函数里进行一些驱动的初始化工作,在xxx_exit里面就需要对驱动程序的卸载做一些回收工作。

2.2字符设备注册与注销

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

register_chrdev函数用于注册字符设备,此函数共有三个参数:
major:主设备号,linux每个设备都有一个设备号,设备号分为主设备号和次设备号。
name:设备名字,指向一串字符串。
fops:结构体file_operations类型指针。
unregister_chrdev函数用那个与注销字符设备,此函数共有两个参数:
major:要注销的设备对应的主设备号。
name:要注销的设备对应的设备名。

2.3实现设备的具体操作函数

  对具体设备驱动功能实现需要分析其需求,也就是构造file_operation结构体,对file_operations结构体成员进行实例化。
  假设实现一个chttest驱动,打开open和关闭close是最基本的要求,几乎所有设备都得提供打开和关闭的功能,因此我们要实现file_operations结构体中的open和release两个函数。
  假设chrtest这个设备控制着一段缓冲区,应用程序需要哦通过read和write这两个函数来对缓冲区的进行读写操作,那么我们也得实现file_operations结构体中的read和write两个函数。
假设基本需求就这些,那么驱动框架如下:

/*1、实现file_operations结构体中的open、release、read、write函数*/
static int chrtest_open(struct inode *inode,struct file *filp)
{
	//具体内容根据具体需求实现
	return 0;
}
//从设备读取数据
static ssize_t chrtest_read(struct file *filp,char __user *buf,size_t cnt,loff_t * offt)
{
	//具体内容根据具体需求实现
	return 0;
}

//向设备写入数据
static ssize_t chrtest_write(struct file *filp,const char __user *buf,
							size_t cnt,loff_t *offt)
{
	//具体内容根据具体需求实现
	return 0;
}

//关闭和释放设备
static int chrtest_release(struct inode *indoe,struct file *filp)
{
	//具体内容根据具体需求实现
	return 0;
}

//初始化file_operations结构体
static struct file_operations test_fops=(void)
{
	.owner = THIS_MODULE,
	.open = chrtest_open,
	.read = chrtest_read,
	.write = chrtest_write,
	.release = chrtest_release,
};

//驱动入口函数
static int __init xxx_init(void)
{
	int ret=0;
	//
	ret = register_chrtest(200,"chrtest",&test_fops);
	if(ret<0)
	{
		//出错处理
	}
	return 0}

//驱动出口函数
static void __exit xxx_exit(void)
{
	//注销字符设备驱动
	unregister_chrdev(200,"chrtest");
}

//指定驱动出入口函数
module_init(xxx_init);
module_exit(xxx_exit);

//添加LICENSE和作者信息
MODULE_LICENSE() //添加模块 LICENSE信息
MODULE_AUTHOR() //添加模块作者信息

3.linux设备号

3.1设备号的组成

  为了方便管理, Linux中每个设备都有一个设备号,设备号由主设备号和次设备号两部分组成,主设备号表示某一个具体的驱动,次设备号表示使用这个驱动的各个设备。 Linux提供了一个名为 dev_t的数据类型表示设备号, dev_t定义在文件 include/linux/types.h里面,定义如下:

typedef __u32 __kernel_dev_t;
typedef __kernel_dev_t dev_t;

可以看出 dev_t是 __u32类型的,而 __u32定义在文件 include/uapi/asm-generic/int-ll64.h里面,定义如下:

typedef unsigned int __u32;

综上所述, dev_t其实就是 unsigned int类型,是一个 32位的数据类型。这 32位的数据构成了主设备号和次设备号两部分,其中高12位为主设备号, 低 20位为次设备号。因此 Linux系统中主设备号范围为 0~4095,所以大家在选择主设备号的时候一定不要超过这个范围。

3.2设备号的分配

3.2.1静态分配设备号

静态分配号,可以由开发者自己确定设备号,但是会有可能指定到一个正在使用的设备号,这是静态分配设备号的缺点。在确定设备号前,可以先查看设备号是否被使用,可以用cat /proc/devices查看。

3.2.2动态分配设备号

动态分配设备号有系统分配一个未被使用的设备号,这样也就不会造成冲突了,但是缺点就是分配之后的设备号到底是多少,是不确定的。

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

dev: 保存申请到的设备号
baseminor:此设备号的起始地址,alloc_chrdev_region可以申请到一端连续的多个设备号,这些设备号的主设备号都一样,但是次设备号不同,次设备号以baseminor为起始地址开始递增。一般baseminor为0,所以说此设备号从0开始。
count:要申请的设备号数量。
name:设备名字

void unregister_chrdev_region(dev_t from,unsigned count)

from:要释放的设备号
count:表示从from开始,要释放的设备号数量

4.实例展示

实现对设备缓冲区数据的读取和写入

#include 
#include 
#include 
#include 
#include 

/*test file*/

#define CHRDEVBASE_MAJOR    200             /*Main equipment num*/
#define CHRDEVBASE_NAME     "chrdevbase"    /*Secondary equipment num*/

static char readbuf[100];                   /*read buffer*/
static char writebuf[100];                  /*write buffer*/
static char kerneldata[] = {"kernel data!"};

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

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

    memcpy(readbuf,kerneldata,sizeof(kerneldata));
    rv = copy_to_user(buf,readbuf,cnt);
    if(0==rv)
    {
        printk("kernel senddata ok !\r\n");
    }
    else
    {
        printk("kernel senddata failure!\r\n");
    }

    return 0;
}

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

    rv = copy_from_user(writebuf,buf,cnt);
    if(0==rv)
    {
        printk("kernel recevdata:%s\r\n",writebuf);
    }
    else
    {
        printk("kernel recevdata failure\r\n");
    }

    return 0;
}

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

static struct file_operations chrdevbase_fops = {
    .owner = THIS_MODULE,
    .open = chrdevbase_open,
    .read = chrdevbase_read,
    .write = chrdevbase_write,
    .release = chrdevbase_release,
};

static int __init chrdevbase_init(void)
{
    int rv=0;

    rv = register_chrdev(CHRDEVBASE_MAJOR,CHRDEVBASE_NAME,
                        &chrdevbase_fops);
    if(rv<0)
    {
        printk("chrdevbase driver register failure\r\n");
    }
    printk("chrdevbase_init()\r\n");
    return 0;
}

static void __exit chrdevbase_exit(void)
{
    unregister_chrdev(CHRDEVBASE_MAJOR,CHRDEVBASE_NAME);
    printk("chrdevbase_exit()\r\n");
}

module_init(chrdevbase_init);
module_exit(chrdevbase_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("dairongan");

Makefile

#编译环境,提供一些内核需要的API等
KERNELDIR := /home/dra/imx6ull_file/bsp/kernel/linux-imx

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
#-C表示将当前的工作目录切换到指定的目录中
#M表示模块源码目录

应用层测试程序

/*************************************
*文件名  :chrdevbaseApp.c
*作者   :dra
*版本   :V1.0
*描述   :chrdevbase驱动测试APP
*其他   :使用方法:./chrdevbaseApp /dev/chrdevbase <1>|<2>
        argv[2] 1:读文件
        argv[2] 2:写文件
**************************************/
#include 
#include 
#include 
#include 
#include 
#include 
#include 

static char usrdata[]={"usr data!"};

int main(int argc,char **argv)
{
    int fd,rv;
    char *filename;
    char readbuf[100],writebuf[100];

    if(argc != 3)
    {
        printf("Error Usage!\r\n");
        printf("exemple:./chrdevbaseApp /dev/chrdevbase <1>|<2>\r\n");
        return -1;
    }

    filename = argv[1];

    fd = open(filename,O_RDWR);
    if(fd<0)
    {
        printf("Can't open file %s\r\n",filename);
        return -1;
    }

    if(atoi(argv[2]) == 1)
    {
        rv = read(fd,readbuf,50);
        if(rv<0)
        {
            printf("read file %s failure\r\n",filename);
        }
        else
        {
            printf("read data:%s\r\n",readbuf);
        }
    }
    if(atoi(argv[2]) == 2)
    {
        memcpy(writebuf,usrdata,sizeof(usrdata));
        rv = write(fd,writebuf,50);
        if(rv<0)
        {
            printf("write file %s failure\r\n",filename);
        }
        else
        {
            printf("write data:%s\r\n",writebuf);
        }
    }

    rv = close(fd);
    if(rv<0)
    {
        printf("Can't close file %s\r\n",filename);
        return -1;
    }

    return 0;
}

编译生成ko文件后,将其放到开发板合适的位置上后:
1、insmod chrdevbase.ko加载驱动
在这里插入图片描述
2、lsmod 检查驱动是否加载成功
字符设备驱动开发的流程_第4张图片
3、cat /proc/devices查看设备号
字符设备驱动开发的流程_第5张图片
4、mknod /dev/chrdevbase c 200 0创建设备节点 c表示字符设备,200为主设备号,0为次设备号。
在这里插入图片描述
5、运行测试文件
在这里插入图片描述

你可能感兴趣的:(驱动开发bsp,驱动开发)