BSP开发学习1通用字符设备开发

Posted 与光同程

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了BSP开发学习1通用字符设备开发相关的知识,希望对你有一定的参考价值。

Linux字符驱动设备API

设备号申请API

动态申请:

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

静态申请:

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

参数 from 是要申请的起始设备号,也就是给定的设备号;参数 count 是要申请的数量,一般都是一个;参数 name 是设备名字。

释放:

void unregister_chrdev_region(dev_t from, unsigned count)

设备号申请示例:

1 int major; /* 主设备号 */
2 int minor; /* 次设备号 */
3 dev_t devid; /* 设备号 */
4
5 if (major)  /* 定义了主设备号 */
6 devid = MKDEV(major, 0); /* 大部分驱动次设备号都选择 0 */
7 register_chrdev_region(devid, 1, "test");
8  else  /* 没有定义设备号 */
9 alloc_chrdev_region(&devid, 0, 1, "test"); /* 申请设备号 */
10 major = MAJOR(devid); /* 获取分配号的主设备号 */
11 minor = MINOR(devid); /* 获取分配号的次设备号 */
12 

用户程序和内核数据交换

copy_to_user和copy_from_user就是在进行驱动相关程序设计的时候,要经常遇到的两个函数。由于内核空间与用户空间的内存不能直接互访,因此借助函数copy_to_user()完成用户空间到内核空间的复制

unsigned long copy_to_user(void __user *to, const void *from, unsigned long n)
unsigned long copy_from_user(void *to, const void __user *from, unsigned long n)  

cdev API

cdev 定义

1 struct cdev 
2 struct kobject kobj;
3 struct module *owner;
4 const struct file_operations *ops;
5 struct list_head list;
6 dev_t dev;
7 unsigned int count;
8 ;

在 cdev 中有两个重要的成员变量:ops 和 dev,这两个就是字符设备文件操作函数集合file_operations 以及设备号 dev_t。编写字符设备驱动之前需要定义一个 cdev 结构体变量,这个变量就表示一个字符设备

cdev_init 函数

定义好 cdev 变量以后就要使用 cdev_init 函数对其进行初始化

void cdev_init(struct cdev *cdev, const struct file_operations *fops)
参数 cdev 就是要初始化的 cdev 结构体变量,参数 fops 就是字符设备文件操作函数集合

cdev_add 函数

cdev_add 函数用于向 Linux 系统添加字符设备(cdev 结构体变量)

int cdev_add(struct cdev *p, dev_t dev, unsigned count)
参数 p 指向要添加的字符设备(cdev 结构体变量),参数 dev 就是设备所使用的设备号,参数 count 是要添加的设备数量。

cdev_del 函数

卸载驱动的时候一定要使用 cdev_del 函数从 Linux 内核中删除相应的字符设备

void cdev_del(struct cdev *p)
参数 p 就是要删除的字符设备。

自动创建设备节点

经过测试发现,在没有添加自动创建设备节点的代码之前,insmod 驱动之后并不会直接在/dev文件夹下创建设备节点,需要使用mknod 手动创建一个设备文件并且将这个文件和设备号关联

mkdnod /dev/global_mem c 250 0
这样有麻烦 busybox 提供类似于udev的简化版本 mdev 这个程序开机是不会自己启动的
所以需要在启动脚本中添加:

echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s

在完成上面的工作之后,我们还需要进行类和设备的创建

创建删除类

将宏 class_create 展开以后内容如下:

struct class *class_create (struct module *owner, const char *name)

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

卸载驱动程序的时候需要删除掉类,类删除函数为 class_destroy,函数原型如下:

void class_destroy(struct class *cls);
参数 cls 就是要删除的类。

创建删除设备

上一小节创建好类以后还不能实现自动创建设备节点,我们还需要在这个类下创建一个设
备。使用 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
这个设备文件。返回值就是创建好的设备。同样的,卸载驱动的时候需要删除掉创建的设备,设备删除函数为 device_destroy,函数原型如下:

void device_destroy(struct class *class, dev_t devt)
参数 class 是要删除的设备所处的类,参数 devt 是要删除的设备号。

文件操作

cdev 中的file_operation变量是驱动中必须进行设置的
需要对其中几个函数指针进行赋值,常见函数原型如下:

    static int global_mem_open(struct inode *inode ,struct file *flip)
    static int global_mem_release(struct inode *inode ,struct file *flip)
    static ssize_t global_mem_read(struct file* flip, char __user *buf,size_t size ,loff_t *ppos)
    static ssize_t global_mem_write(struct file* flip, const char __user *buf,size_t size ,loff_t *ppos)
    static loff_t global_mem_llseek(struct file* flip ,loff_t offset,int orig)
    static long global_mem_ioctl(struct file* flip,unsigned int cmd,unsigned long arg)
static const struct file_operations global_mem_fops=
    .owner=THIS_MODULE,
    .open=global_mem_open,
    .release=global_mem_release,
    .read=global_mem_read,
    .write=global_mem_write,
    .llseek=global_mem_llseek,
    .unlocked_ioctl=global_mem_ioctl
;

字符设备驱动整体结构

#include <linux/module.h>//MOUDLE_XXX moudle_xxx
#include <linux/fs.h>    //file_operations register_chrdev_region alloc_chrdev_region
#include <linux/init.h>  //__init __exit
#include <linux/cdev.h>  //cdev
#include <linux/uaccess.h>//copy_to_user copy_from_user 
#include <linux/device.h> // device class 


typedef struct 
    //设备驱动变量
    struct cdev cdev;
    //设备号变量
    dev_t devid;
    int major;
    int minor;      
    //设备节点相关变量
    struct class *class; 
    struct device *device;
    //用户变量
    unsigned char mem[GLOBAL_MEM_SIZE];

xxx_t;
xxx_t xxx;

//操作实现
xxx_open
xxx_release
···
static const struct file_operations xxx_fops=
    .owner=THIS_MODULE,
    .open=xxx_open,
    .release=xxx_release,
    .read=xxx_read,
    .write=xxx_write,
    .llseek=xxx_llseek,
    .unlocked_ioctl=xxx_ioctl
;


/* 驱动入口函数 */
static int __init xxx_init(void)

    /*申请设备号*/
     xxx.devid=MKDEV(xxx.major,xxx.minor);
    if(global_mem.major!=0)//从devno 静态申请 一个设备号
        ret=register_chrdev_region(xxx.devid,1,"globalmem");
    else//从devno 动态申请一个设备号
        ret=alloc_chrdev_region(&xxx.devid,0,1,"globalmem");
        xxx.major=MAJOR(xxx.devid);
        xxx.minor=MINOR(xxx.devid);
       
    //初始化并且添加cdev
    int devno=MKDEV(xxx.major,minor_index);
    cdev_init(&xxx.cdev,&xxx_fops);
    xxx.cdev.owner=THIS_MODULE;
    ret=cdev_add(&xxx.cdev,devno,1);
    if(ret)printk("CDEV ADD ERROR:%d\\n",minor_index);   
    /* 创建类 */
    class = class_create(THIS_MODULE, "xxx");
    /* 创建设备 */
    device = device_create(class, NULL, devid, NULL, "xxx");
    return 0;


/* 驱动出口函数 */
static void __exit xxx_exit(void)

    //删除驱动和设备号
    cdev_del(&xxx.cdev);
    unregister_chrdev_region(xxx.devid,1);

    /* 删除设备 */
    device_destroy(xxx.class, xxx.devid);
    /* 删除类 */
    class_destroy(xxx.class);

//申明模块出口和入口
 module_init(xxx_init);
 module_exit(xxx_exit);
//申明作者 和 开源协议
MODULE_AUTHOR("YURI");
MODULE_LICENSE("GPL");

Linux 字符设备驱动编写

/*
 * @Copyright: 
 * @FileNames: 
 * @Description: 申请全局内存4096 并使用该内存进行 用户和内核之间的数据交换
 * @Author: 
 * @Date: 2022-07-29 09:03:02
 * @Version: V1.0
 * @LastEditTime: 2022-07-29 16:28:25
 */
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/device.h>


#define GLOBAL_MEM_SIZE 0X1000
#define GLOBAL_MEM_DEBUG 1

#define GLOBAL_MEM_CLASS_NAME "global_mem" //创建的类名称
#define GLOBAL_MEM_NODE_NAME  "global_mem_0" //创建的节点名称


#define GLOBAL_MEM_MAJOR 250

//CMD
#define GLOBAL_MEM_CLEAR 0X01 //清理内存 命令


typedef struct 
    //设备驱动变量
    struct cdev cdev;
    //设备号变量
    dev_t devid;
    int major;
    int minor;      
    //设备节点相关变量
    struct class *class; 
    struct device *device;
    //用户变量
    unsigned char mem[GLOBAL_MEM_SIZE];

global_mem_t;

global_mem_t global_mem;

/**
 * @function: global_mem_open
 * @description: 打开设备 并将内存清零
 * @input: 
 * @output: 
 * @return *
 * @param inode *inode
 * @param file *flip
 */
static int global_mem_open(struct inode *inode ,struct file *flip)

    printk("OPEN GLOBAL MEM\\r\\n");
    flip->private_data=(void *)(&global_mem);
    return 0;

/**
 * @function: global_mem_release
 * @description: 释放设备节点
 * @input: 
 * @output: 
 * @return *
 * @param inode *inode
 * @param file *flip
 */
static int global_mem_release(struct inode *inode ,struct file *flip)

    printk("RELEASE GLOBAL MEM\\r\\n");
    return 0;


/**
 * @function: global_mem_read
 * @description: 拷贝数据到用户区
 * @input: 
 * @output: 
 * @return *
 * @param file* flip  文件结构体
 * @param char __user *buf 从用户区拷贝出来的数据
 * @param size_t size      传入数据大小
 * @param loff_t *ppos     当前数据位置
 */
static ssize_t global_mem_read(struct file* flip, char __user *buf,size_t size ,loff_t *ppos)

    int ret=0;
    printk("READ GLOBAL MEM\\r\\n");
    
    global_mem_t* dev=(global_mem_t*) flip->private_data;
    //当前文件指针所处于的位置
    unsigned long p=*ppos;
    //需要读出的个数
    unsigned int count=(unsigned int)size;
    //检查读取位置合法性
    if(p>=GLOBAL_MEM_SIZE)return 0;
    if(count+p>GLOBAL_MEM_SIZE)count=GLOBAL_MEM_SIZE-p;
    //拷贝数据到用户区
    ret=copy_to_user(buf,dev->mem+p,count);
    if (ret<0)
        ret=-EFAULT;
    else
        *ppos+=count;
        ret=count;
        if(GLOBAL_MEM_DEBUG)printk("READ %d BYTES FROM KERNEL At %d\\n",count,p);
    
    return ret;


/**
 * @function: global_mem_write
 * @description: 
 * @input: 
 * @output: 
 * @return *
 * @param file* flip
 * @param char __user *buf
 * @param size_t size
 * @param loff_t *ppos
 */
static ssize_t global_mem_write(struct file* flip, const char __user *buf,size_t size ,loff_t *ppos)

    int ret=0;
    printk("WRITE GLOBAL MEM\\r\\n");
    
    global_mem_t* dev=(global_mem_t*) flip->private_data;
    //当前文件指针所处于的位置
    unsigned long p=*ppos;
    //需要读出的个数
    unsigned int count=(unsigned int)size;
    //检查读取位置合法性
    if(p>=GLOBAL_MEM_SIZE)return 0;
    if(count+p>GLOBAL_MEM_SIZE)count=GLOBAL_MEM_SIZE-p;
    ret=copy_from_user(dev->mem+p,buf,count);
    if(ret<0)ret=-EFAULT;
    else
        *ppos+=count;
        ret=count;
        if(GLOBAL_MEM_DEBUG)printk("WRITE %d BYTES TO KERNEL AT %d\\n",(int)count,(int)p);
    
    return ret;


static loff_t global_mem_llseek(struct file* flip ,loff_t offset,int orig)

    loff_t ret=0;
    if(orig==0)//从头开始偏移
    
        if(offset<0)
            ret=-EINVAL;
            goto end;
        
        if((unsigned int) offset>GLOBAL_MEM_SIZE)
            ret=-EINVAL;
            goto end;
        
        flip->f_pos=(unsigned int) offset;
        ret=flip->f_pos;
    else if(orig==1)//从当前位置开始偏移
    
        if(flip->f_pos+offset>GLOBAL_MEM_SIZE)
            ret=-EINVAL;
            goto end;
        
        flip->f_pos+=offset;
        ret=flip->f_pos;
    else
        ret=-EINVAL;
    
    end:
        return ret;


/**
 * @function: global_mem_ioctl
 * @description: 
 * @input: 
 * @output: 
 * @return *
 * @param file* flip
 * @param unsigned int cmd
 * @param unsigned long arg
 */
static long global_mem_ioctl(struct file* flip,unsigned int cmd,unsigned long arg)

    global_mem_t *dev =(global_mem_t *) flip->private_data;
    switch(cmd)
        case GLOBAL_MEM_CLEAR:
            memset(dev->mem,0,sizeof(dev->mem));
            break;
        default:
            return -EINVAL;
    
    return 0;


static const struct file_operations global_mem_fops=
    .owner=THIS_MODULE,
    .open=global_mem_open,
    .release=global_mem_release,
    .read=global_mem_read,
    .write=global_mem_write,
    .llseek=global_mem_llseek,
    .unlocked_ioctl=global_mem_ioctl
;

/**
 * @function: static void global_mem_setup_cdev(int minor_index)
 * @description: 根据一个从设备号生成cdev
 * @input: 
 * @output: 
 * @return *
 */
static void global_mem_setup_cdev(int minor_index)

    int ret;
    //得到设备号
    int devno=MKDEV(global_mem.major,minor_index);
    cdev_init(&global_mem.cdev,&global_mem_fops);
    global_mem.cdev.owner=THIS_MODULE;
    ret=cdev_add(&global_mem.cdev,devno,1);
    if(ret)printk("CDEV ADD ERROR:%d\\n",minor_index);


/**
 * @function: global_mem_init
 * @description: 申请设备号 并且注册cdev 
 * @input: void
 * @output: 
 * @return *
 */
static int __init global_mem_init(void)

    int ret;
    //如果定义了静态主设备号 就采用静态申请的方式
    global_mem.major=GLOBAL_MEM_MAJOR;

    //得到需要注册的设备号
    global_mem.devid=MKDEV(global_mem.major,global_mem.minor);
    if(global_mem.major!=0)//从devno 静态申请 一个设备号
        ret=register_chrdev_region(global_mem.devid,1,"globalmem");
    else//从devno 动态申请一个设备号
        ret=alloc_chrdev_region(&global_mem.devid,0,1,"globalmem");
        global_mem.major=MAJOR(global_mem.devid);
        global_mem.minor=MINOR(global_mem.devid);
    
    memset(global_mem.mem,0,sizeof(global_mem.mem));
    if(GLOBAL_MEM_DEBUG)
        printk("主设备号:%d\\n",global_mem.major);
        printk("从设备号:%d\\n",global_mem.minor);   
    
    
    global_mem_setup_cdev(global_mem.minor);

    //创建设备 创建类
    global_mem.class = class_create(THIS_MODULE, GLOBAL_MEM_CLASS_NAME);
    global_mem.device = device_create(global_mem.class, NULL, global_mem.devid, NULL, GLOBAL_MEM_NODE_NAME);

    return 0;

module_init(global_mem_init);

static BSP开发学习2平台设备驱动

BSP开发学习2平台设备驱动

bsp开发之驱动开发

Linux驱动学习记录-新字符设备

linux驱动学习——字符设备驱动开发

linux驱动开发之misc设备与蜂鸣器驱动