linux驱动框架(字符设备驱动框架,杂项设备驱动框架代码篇)

        假如你现在赖在床上,你可能会完成一个梦。假如你现在起来做事,你可能会完成一个梦想。生命太短,时光太长,活着,并高兴着,才是幸福所向。拥有一份好的心态,活出自己,高兴一天是一天!

                                                                                                                                               ----小新

一.字符设备驱动框架 

       Linux 的设备管理是和文件系统紧密结合的,各种设备都以文件的形式存放在/dev 目录下,称为设备 文件。应用程序可以打开、关闭和读写这些设备文件,完成对设备的操作,就像操作普通的数据文件一样。至此,通过已学知识,编写linux驱动以及给位前辈的经验总结出来的linux字符驱动框架:
        

1.设备号

        字符设备驱动需要申请唯一的设备号,以便系统能够正确识别和访问设备。设备号可以通过调用 alloc_chrdev_region 或 register_chrdev_region 函数来申请。  

        为了管理这些设备,系统为设备编了号,每个设备号又分为主设备号和次设备号。主设备号用来区分不同 类型的设备,而次设备号用来区分同一类型的多个设备。
     
        在 Linux 内核中,使用 cdev 结构体描述一个字符设备, cdev 结构体的定义如下:
struct cdev {
    //描述字符设备的一个结构体
    struct kobject kobj;
    struct module *owner;
    const struct file_operations *ops;
    struct list_head list;
    dev_t dev;
    unsigned int count;
};
        cdev 结构体的 dev_t 成员定义了设备号,为 32 位,其中 12 位为主设备号, 20 位为次设备号。使用下 列宏可以从
 MAJOR(dev_t dev);
 MINOR(dev_t dev);

        dev_t 获得主设备号和次设备号:而使用下列宏则可以通过主设备号和次设备号生成 dev_t。

MKDEV(int major, int minor);

   

      2.文件操作函数

        字符设备驱动需要实现一系列文件操作函数,例如 openreadwriterelease 等,以便应用程序能够访问设备。这些函数需要在驱动的 fops 结构中声明。

  1. open:打开设备文件时会被调用,可以在此函数中进行设备的初始化操作。
  2. read:读取设备文件数据时会被调用,需要实现从设备中读取数据的操作。
  3. write:向设备文件写入数据时会被调用,需要实现向设备中写入数据的操作。
  4. release:关闭设备文件时会被调用,可以在此函数中进行设备的清理操作。

例子:

#include <linux/module.h>  
#include <linux/kernel.h>  
#include <linux/fs.h>  
#include <linux/uaccess.h>  
  
#define DEVICE_NAME "my_chardev"  
#define DEVICE_MAJOR 240  
#define DEVICE_MINOR 0  
  
static int my_chardev_open(struct inode *inode, struct file *file)  
{  
    // 打开设备文件时的操作,例如进行设备的初始化  
    return 0;  
}  
  
static int my_chardev_release(struct inode *inode, struct file *file)  
{  
    // 关闭设备文件时的操作,例如进行设备的清理  
    return 0;  
}  
  
static ssize_t my_chardev_read(struct file *file, char __user *buf, size_t count, loff_t *offset)  
{  
    // 读取设备文件的操作,从设备中读取数据并返回给用户空间  
    return 0;  
}  
  
static ssize_t my_chardev_write(struct file *file, const char __user *buf, size_t count, loff_t *offset)  
{  
    // 向设备文件写入数据的操作,从用户空间读取数据并写入到设备中  
    return 0;  
}  
  
static const struct file_operations my_chardev_fops = {  
    .owner = THIS_MODULE,  
    .open = my_chardev_open,  
    .read = my_chardev_read,  
    .write = my_chardev_write,  
    .release = my_chardev_release,  
};  
  
static int __init my_chardev_init(void)  
{  
    int ret;  
    ret = register_chrdev(DEVICE_MAJOR, DEVICE_NAME, &my_chardev_fops);  
    if (ret < 0) {  
        printk(KERN_ERR "Failed to register character device\n");  
        return ret;  
    }  
    return 0;  
}  
  
static void __exit my_chardev_exit(void)  
{  
    unregister_chrdev(DEVICE_MAJOR, DEVICE_NAME);  
}

     3.中断处理

        如果字符设备驱动需要处理中断,则需要实现中断处理函数 irq_handler_callback,并在驱动注册时将中断号和回调函数传递给系统。

  1. 定义中断号:在驱动注册时,需要将中断号和中断处理函数传递给系统。可以使用 request_irq 函数来申请中断号。
  2. 中断处理函数:中断处理函数需要快速处理中断事件,并返回处理结果。在字符设备驱动中,中断处理函数通常会通过读取设备寄存器来获取中断事件,并进行相应的处理。
  3. 中断上下文:中断上下文是指中断处理函数运行时的环境,包括中断号、CPU 状态、栈等信息。在字符设备驱动中,可以通过 irq_handler_ctx 结构来访问中断上下文。
  4. 中断共享:如果多个设备共享同一个中断号,则需要实现中断共享。可以使用 share_irq 宏来声明中断共享。
static irqreturn_t my_chardev_irq_handler(int irq, void *dev_id)  
{  
    // 中断处理函数,读取设备寄存器获取中断事件,并进行相应的处理  
    return IRQ_HANDLED;  
}  

static int __init my_chardev_init(void)  
{  
    int ret;  
    ret = register_chrdev(DEVICE_MAJOR, DEVICE_NAME, &my_chardev_fops);  
    if (ret < 0) {  
        printk(KERN_ERR "Failed to register character device\n");  
        return ret;  
    }  
    ret = request_irq(IRQ_NUM, my_chardev_irq_handler, IRQF_SHARED, DEVICE_NAME, NULL);  
    if (ret < 0) {  
        printk(KERN_ERR "Failed to request interrupt\n");  
        unregister_chrdev(DEVICE_MAJOR, DEVICE_NAME);  
        return ret;  
    }  
    return 0;  
}

     4.内存映射

        如果字符设备驱动需要支持内存映射,则需要实现 mmap 函数。该函数用于将设备内存映射到用户空间,以便应用程序可以直接访问设备内存。

     5.电源管理

        如果字符设备驱动需要支持电源管理,则需要实现相应的电源管理回调函数,例如 suspend 和 resume。这些函数用于在系统进入和退出电源管理状态时控制设备的电源状态。

     6.调试和测试

       编写字符设备驱动时需要注意调试和测试。可以使用 printk 函数打印调试信息,或使用 kgdb 调试器进行调试。同时,需要进行充分的测试以确保驱动的正确性和稳定性。

二.字符设备驱动框架代码

         1.驱动代码:

        编写chrdev.c文件 :

/*
* @Descripttion: 注册字符设备
* @version:
* @Author: topeet
*/
#include <linux/init.h> //初始化头文件
#include <linux/module.h> //最基本的文件,支持动态添加和卸载模块。
#include <linux/fs.h> //包含了文件操作相关 struct 的定义,例如大名鼎鼎的
struct file_operations
#include <linux/kdev_t.h>
#include <linux/cdev.h> // 对字符设备结构 cdev 以及一系列的操作函数的定义。包含了 cdev 结构及相关函数的定义。
#define DEVICE_NUMBER 1 //定义次设备号的个数
#define DEVICE_SNAME "schrdev" //定义静态注册设备的名称
#define DEVICE_ANAME "achrdev" //定义动态注册设备的名称
#define DEVICE_MINOR_NUMBER 0 //定义次设备号的起始地址
static int major_num, minor_num; //定义主设备号和次设备号
struct cdev cdev; //定义一个 cdev 结构体
module_param(major_num, int, S_IRUSR); //驱动模块传入普通参数 major_num
module_param(minor_num, int, S_IRUSR); //驱动模块传入普通参数 minor_num
int chrdev_open(struct inode *inode, struct file *file)
{
    printk("chrdev_open\n");
    return 0;
}
// file_operations chrdev_ops
struct file_operations chrdev_ops = {
    .owner = THIS_MODULE,
    .open = chrdev_open};
    static int hello_init(void)
    {
        dev_t dev_num;
        int ret; //函数返回值
        if (major_num)
        {
            /*静态注册设备号*/
            printk("major_num = %d\n", major_num); //打印传入进来的主设备号
            printk("minor_num = %d\n", minor_num); //打印传入进来的次设备号
            dev_num = MKDEV(major_num, minor_num); //MKDEV 将主设备号和次设备号合并为一个设备号
            ret = register_chrdev_region(dev_num, DEVICE_NUMBER, DEVICE_SNAME); //注册设备号
            if (ret < 0)
            {
                printk("register_chrdev_region error\n");
            }
            printk("register_chrdev_region ok\n"); //静态注册设备号成功
        }
        else
            {
                /*动态注册设备号*/
                ret = alloc_chrdev_region(&dev_num, DEVICE_MINOR_NUMBER, 1, DEVICE_ANAME);
                if (ret < 0)
                {
                    printk("alloc_chrdev_region error\n");
                }
                printk("alloc_chrdev_region ok\n"); //动态注册设备号成功
                major_num = MAJOR(dev_num); //将主设备号取出来
                minor_num = MINOR(dev_num); //将次设备号取出来
                printk("major_num = %d\n", major_num); //打印传入进来的主设备号
                printk("minor_num = %d\n", minor_num); //打印传入进来的次设备号
            }
            cdev.owner = THIS_MODULE;
            //cdev_init 函数初始化 cdev 结构体成员变量
            cdev_init(&cdev, &chrdev_ops);
            //完成字符设备注册到内核
            cdev_add(&cdev, dev_num, DEVICE_NUMBER);
            return 0;
            }
static void hello_exit(void)
{
    //注销设备号
    unregister_chrdev_region(MKDEV(major_num, minor_num), DEVICE_NUMBER); cd
    ev_del(&cdev);
    printk("gooodbye! \n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

编写Makefile文件:

obj-m += chrdevr.o #先写生成的中间文件的名字是什么,-m 的意思是把我们的驱动编译成模块
KDIR:= /home/topeet/driver/imx6ull/linux-imx-rel_imx_4.1.15_2.1.0_ga/
PWD?=$(shell pwd) #获取当前目录的变量
all:
make -C $(KDIR) M=$(PWD) modules #make 会进入内核源码的路径,然后把当前路径下的代码编译成
模块

     把chrdev.c和Makefile文件放在同一目录下,然后cd进去,执行make命令就会生成chrdev.ko文件。然后安装驱动:insmod chrdev.ko 

    (1) 结果如下

         2.app代码

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
    int fd;
    char buf[64] = {0};
    fd = open("/dev/test",O_RDWR); //打开设备节点
    if(fd < 0)
    {
        perror("open error \n");
        return fd;
    }
    //read(fd,buf,sizeof(buf)); //从文件中读取数据放入缓冲区中
    close(fd);
    return 0;
}
        我们将 app.c 文件拷贝到 Ubuntu 的/home/topeet/driver/imx6ull/13chrdev 目录下,输入以下命令 编译 app.c。生成的 app,其他开发板上面也可以运行。
arm-none-linux-gnueabi-gcc app.c -o app -static

(1)结果如下

         

 

三.下一张编写杂项设备驱动框架 

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我来挖坑啦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值