新字符设备驱动原理和框架源码分析


一、分配和释放设备号

动态申请设备号:

/*
 dev:设备号--dev_t devid;
 count:是要申请的数量,一般都是一个;
 name:是设备名字
*/
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)

静态申请设备号:

/*
 from:是要申请的起始设备号,也就是给定的设备号;
 count:是要申请的数量,一般都是一个;
 name:是设备名字
*/
int register_chrdev_region(dev_t from, unsigned count, const char *name)

释放(删除)设备号:

/*
 from:是要申请的起始设备号,也就是给定的设备号;
 count:是要申请的数量,一般都是一个;
*/
void unregister_chrdev_region(dev_t from, unsigned count)

【文章福利】小编推荐自己的Linux内核源码交流群:【点击链接加入群聊869634926】整理了一些个人觉得比较好的学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!

新字符设备驱动原理和框架源码分析

 

 

学习直通车:Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈


二、驱动模块的加载和卸载

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

当在终端使用“insmod”命令加载驱动的时候,xxx_init 这个函数就会被调用。
当在终端使用“rmmod”命令卸载具体驱动的时候 xxx_exit 函数就会被调用。

字符驱动模块:

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.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 <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>


#define NEWCHR_CNT            1              /* 设备号个数 */
#define NEWCHR_NAME            "newchr"    /* 名字 */



/* newchr设备结构体 */
struct newchr_dev
{
    dev_t  devid;            /* 设备号      */
    struct cdev cdev;        /* cdev      */
    struct class *class;    /* 类          */
    struct device *device;    /* 设备      */
    int    major;            /* 主设备号     */
    int    minor;            /* 次设备号  */
};

struct newchr_dev newchr;    /* 设备 */

/*
 * @description        : 打开设备
 * @param - inode     : 传递给驱动的inode
 * @param - filp     : 设备文件,file结构体有个叫做private_data的成员变量
 *                       一般在open的时候将private_data指向设备结构体。
 * @return             : 0 成功;其他 失败
 */
static int chr_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &newchr; /* 设置私有数据 */
    return 0;
}

/*
 * @description        : 从设备读取数据
 * @param - filp     : 要打开的设备文件(文件描述符)
 * @param - buf     : 返回给用户空间的数据缓冲区
 * @param - cnt     : 要读取的数据长度
 * @param - offt     : 相对于文件首地址的偏移
 * @return             : 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t chr_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    return 0;
}

/*
 * @description        : 向设备写数据
 * @param - filp     : 设备文件,表示打开的文件描述符
 * @param - buf     : 要写给设备写入的数据
 * @param - cnt     : 要写入的数据长度
 * @param - offt     : 相对于文件首地址的偏移
 * @return             : 写入的字节数,如果为负值,表示写入失败
 */
static ssize_t chr_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    int retvalue;
    unsigned char databuf[1];
    unsigned char state;

    retvalue = copy_from_user(databuf, buf, cnt);//将用户传递的参数转移到内核中
    if(retvalue < 0)
    {
        printk("kernel write fail!/r/n");
        return -EFAULT;
    }

    state = databuf[0];        /* 获取状态值 */
    /*处理....*/
    return 0;
}

/*
 * @description        : 关闭/释放设备
 * @param - filp     : 要关闭的设备文件(文件描述符)
 * @return             : 0 成功;其他 失败
 */
static int chr_release(struct inode *inode, struct file *filp)
{
    return 0;
}

/* 设备操作函数 */
static struct file_operations newchr_fops =
{
    .owner   = THIS_MODULE,
    .open    = chr_open,
    .read    = chr_read,
    .write   = chr_write,
    .release = chr_release,
};

/*
 * @description    : 驱动出口函数
 * @param         : 无
 * @return         : 无
 */
static int __init chr_init(void)
{
    u32 val = 0;

    /* 注册字符设备驱动 */
    newchr.major = 0;  //将设备号定义为0,则每次都动态申请设备号
    /* 1、创建设备号 */
    if (newchr.major)          /*  定义了设备号 */
    {
        newchr.devid = MKDEV(newchr.major, 0);
        register_chrdev_region(newchr.devid, newchr_CNT, newchr_NAME);
    }
    else                      /* 没有定义设备号 */
    {
        alloc_chrdev_region(&newchr.devid, 0, newchr_CNT, newchr_NAME);    /* 申请设备号 */
        newchr.major = MAJOR(newchr.devid);    /* 获取分配号的主设备号 */
        newchr.minor = MINOR(newchr.devid);    /* 获取分配号的次设备号 */
    }
    printk("newche major=%d,minor=%d/r/n",newchr.major, newchr.minor);

    /* 2、初始化cdev */
    newchr.cdev.owner = THIS_MODULE;
    cdev_init(&newchr.cdev, &newchr_fops);

    /* 3、添加一个cdev */
    cdev_add(&newchr.cdev, newchr.devid, newchr_CNT);

    /* 4、创建类 */
    newchr.class = class_create(THIS_MODULE, newchr_NAME);
    if (IS_ERR(newchr.class))
    {
        return PTR_ERR(newchr.class);
    }

    /* 5、创建设备 */
    newchr.device = device_create(newchr.class, NULL, newchr.devid, NULL, newchr_NAME);
    if (IS_ERR(newchr.device))
    {
        return PTR_ERR(newchr.device);
    }

    return 0;
}

/*
 * @description    : 驱动出口函数
 * @param         : 无
 * @return         : 无
 */
static void __exit chr_exit(void)
{

    /* 注销字符设备驱动 */
    cdev_del(&newchr.cdev);/*  删除cdev */
    unregister_chrdev_region(newchr.devid, newchr_CNT); /* 注销设备号 */

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

module_init(chr_init);
module_exit(chr_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("xjq");

Makefil 文件 注意这里需要修改路径,请按照自身路径修改

gedit Makefil

KERNELDIR := /home/xue1995/linux/IMX6ULL/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
CURRENT_PATH := $(shell pwd)

obj-m := newchr.o

build: kernel_modules

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

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

终端输入:make -j32

编译成功以后就会生成一个名为“newchr.ko”的驱动模块文件

APP
终端输入:

gedit App.c

导入下面代码

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
/***************************************************************
使用方法     :./test /dev/chr    0 关闭
               ./test /dev/chr    1 打开    
***************************************************************/


/*
 * @description        : main主程序
 * @param - argc     : argv数组元素个数
 * @param - argv     : 具体参数
 * @return             : 0 成功;其他 失败
 */
int main(int argc, char *argv[])
{
    int fd, retvalue;
    char *filename;
    unsigned char databuf[1];
    
    if(argc != 3){
        printf("Error Usage!/r/n");
        return -1;
    }

    filename = argv[1];

    /* 打开驱动 */
    fd = open(filename, O_RDWR);
    if(fd < 0){
        printf("file %s open failed!/r/n", argv[1]);
        return -1;
    }

    databuf[0] = atoi(argv[2]);    /* 要执行的操作:打开或关闭 */

    /* 向/dev/chr文件写入数据 */
    retvalue = write(fd, databuf, sizeof(databuf));
    if(retvalue < 0){
        printf("Control Failed!/r/n");
        close(fd);
        return -1;
    }

    retvalue = close(fd); /* 关闭文件 */
    if(retvalue < 0){
        printf("file %s close failed!/r/n", argv[1]);
        return -1;
    }
    return 0;
}

终端输入,进行编译:arm-linux-gnueabihf-gcc App.c -o App

运行测试

将编译出来的 newchr.ko 和 App 这两个文件拷贝到 rootfs/lib/modules/4.1.15目录中,重启开发板,在串口工具中,进入到目录 lib/modules/4.1.15 中,输入如下命令加载 newchr.ko 驱动模块:

depmod //第一次加载驱动的时候需要运行此命令
./App /dev/newchr 1

卸载驱动输入如下命令即可:

rmmod newchrled.ko

原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/282023.html

(0)
上一篇 2022年8月24日
下一篇 2022年8月24日

相关推荐

发表回复

登录后才能评论