Linux新字符设备驱动实验

news2024/11/16 7:19:43

1、新字符设备驱动原理

一、分配和释放设备号

使用 register_chrdev 函数注册字符设备的时候只需要给定一个主设备号即可,但是这样会 带来两个问题: ①、需要我们事先确定好哪些主设备号没有使用。 ②、会将一个主设备号下的所有次设备号都使用掉,比如现在设置 LED 这个主设备号为 200,那么 0~1048575(2^20-1)这个区间的次设备号就全部都被 LED 一个设备分走了。这样太浪 费次设备号了!一个 LED 设备肯定只能有一个主设备号,一个次设备号。

解决这两个问题最好的方法就是要使用设备号的时候向 Linux 内核申请,需要几个就申请 几个,由 Linux 内核分配设备可以使用的设备号:

  • 如果没有指定设备号的话就使用如下函数来申请设备号:
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 是设备名字。

注销字符设备 之 后 要 释 放 掉 设 备 号 , 不 管 是 通 过 alloc_chrdev_region 函数还是 register_chrdev_region 函数申请的设备号,统一使用如下释放函数:

void unregister_chrdev_region(dev_t from, unsigned count)

新字符设备驱动下,设备号分配示例代码如下:

第 1~3 行,定义了主/次设备号变量 major 和 minor,以及设备号变量 devid。

第 5 行,判断主设备号 major 是否有效,在 Linux 驱动中一般给出主设备号的话就表示这 个设备的设备号已经确定了,因为次设备号基本上都选择 0,这算个 Linux 驱动开发中约定俗 成的一种规定了。

第 6 行,如果 major 有效的话就使用 MKDEV 来构建设备号,次设备号选择 0。

第 7 行,使用 register_chrdev_region 函数来注册设备号。

第 9~11 行,如果 major 无效,那就表示没有给定设备号。此时就要使用 alloc_chrdev_region 函数来申请设备号。设备号申请成功以后使用 MAJOR 和 MINOR 来提取出主设备号和次设备号,当然了,第 10 和 11 行提取主设备号和次设备号的代码可以不要。

  • 如果要注销设备号的话,使用如下代码即可:
unregister_chrdev_region(devid, 1); /* 注销设备号 */

2、新的字符设备注册方法

一、字符设备结构

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

struct cdev test_cdev;

二、cdev_init 函数

定义好 cdev 变量以后就要使用 cdev_init 函数对其进行初始化,cdev_init 函数原型如下:

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

 三、cdev_add 函数

cdev_add 函数用于向 Linux 系统添加字符设备(cdev 结构体变量),首先使用 cdev_init 函数完成对 cdev 结构体变量的初始化,然后使用 cdev_add 函数向 Linux 系统添加这个字符设备。 cdev_add 函数原型如下:

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

 

四、cdev_del 函数

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

void cdev_del(struct cdev *p)
//参数 p 就是要删除的字符设备。如果要删除字符设备,
//cdev_del(&testcdev);  /* 删除 cdev */

cdev_delunregister_chrdev_region 这两个函数合起来的功能相当于 unregister_chrdev 函 数。

3、自动创建设备节点

mdev 机制

udev 是一个用户程序,在 Linux 下通过 udev 来实现设备文件的创建与删除,udev 可以检 测系统中硬件设备状态,可以根据系统中硬件设备状态来创建或者删除设备文件。比如使用 modprobe 命令成功加载驱动模块以后就自动在/dev 目录下创建对应的设备节点文件,使用 rmmod 命令卸载驱动模块以后就删除掉/dev 目录下的设备节点文件。

创建和删除类

自动创建设备节点的工作是在驱动程序的入口函数中完成的,一般在 cdev_add 函数后面添 加自动创建设备节点相关代码。首先要创建一个 class 类,class 是个结构体,class_create 是类创建函数,class_create 是个宏定义。

 

  •  将宏 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 就是要删除的类。

4、创建设备

  • 创建好类以后还不能实现自动创建设备节点,我们还需要在这个类下创建一个设 备。使用 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 是要删除的设备号。

参考示例

  • 在驱动入口函数里面创建类和设备,在驱动出口函数里面删除类和设备

 

5、设置文件私有数据

每个硬件设备都有一些属性,比如主设备号(dev_t),类(class)、设备(device)、开关状态(state) 等等,在编写驱动的时候你可以将这些属性全部写成变量的形式

dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */

 在 open 函数里面设置好私有数据以后,在 write、read、close 等函数中直接读取 private_data 即可得到设备结构体。

6、LED 灯驱动程序编写

#include <linux/module.h> //所有模块都需要的头文件
#include <linux/init.h>   // init&exit 相关宏
#include <linux/kernel.h>
#include <linux/ide.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/cdev.h>

#define  NEWCHARLED    "newled"     //设备名
#define  NEWCHARLED_COUNT  1
/*寄存器物理地址*/
#define CCM_CCGR1_BASE              (0x020C406C)
#define SW_MUX_GPIO1_IO03_BASE      (0x02030068)
#define SW_PAD_GPIO1_IO03_BASE      (0x020E02F4)
#define GPIO1_DR_BASE               (0x0209C000)
#define GPIO1_GRIR_BASE             (0x0209C004)



/*地址映射后的虚拟地址指针*/
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;

#define LEDOFF 0  //关闭
#define LEDON  1   //打开



/*LED设备结构体*/
struct newcharled_dev
{    
    struct  cdev  cdevled;    //字符设备
    struct class  *class;     /*类*/
    struct device *device;    //设备
    dev_t  devid;           //设备号
    int    major;           //主设备号
    int    minor;            //次设备号
};

struct newcharled_dev newcharled;    //led设备

/*LED灯打开/关闭*/
static void led_switch(u8 sta)
{
      u32 val = 0;
      if(sta == LEDON)
      {
          val = readl(GPIO1_DR);
          val &= ~(1 << 3);         //bit3清零,打开led灯
          writel(val,GPIO1_DR);
      }
      else if (sta == LEDOFF)
      {
          val = readl(GPIO1_DR);
          val |= (1 << 3);
          writel(val,GPIO1_DR);
      }
}


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

static int newled_close(struct inode *inode, struct file *file)
{
    

    return 0;
}

static ssize_t newled_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
     
     int retvalue;
     unsigned char databuf[1];
     retvalue = copy_from_user(databuf,buf,count);
     if(retvalue < 0)
     {
        printk("kernel write failed!\r\n");
        return -EFAULT;
    }
     /*判断是开灯还是关灯*/
    led_switch(databuf[0]);

    return 0;
}



const struct file_operations newcharled_fops = {
    .owner = THIS_MODULE,
    .write = newled_write,
    .open  = newled_open,
    .release = newled_close,
};

/*注册驱动模块入口*/
static int __init  newled_init(void)
{  
    int ret = 0; 
    unsigned int val = 0;
    printk("newchardev  init\r\n");
    /*初始化led灯,地址映射*/
    IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE,4);
    SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE,4);
    SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE,4);
    GPIO1_DR = ioremap(GPIO1_DR_BASE,4);
    GPIO1_GDIR = ioremap(GPIO1_GRIR_BASE,4);
  
    /*初始化*/
     val = readl(IMX6U_CCM_CCGR1);
     val &= ~(3 <<26);  //先清除以前的配置bit26,27
     val |= 3 << 26;
     writel(val,IMX6U_CCM_CCGR1);

     writel(0x5,SW_MUX_GPIO1_IO03);  //设置复用
     writel(0x10b0,SW_PAD_GPIO1_IO03);  //设置电气属性
 
     val = readl(GPIO1_GDIR);
     val |= 1 << 3;               //bit3置1,设置为输出
     writel(val,GPIO1_GDIR);
 
 
    val = readl(GPIO1_DR);
    val |= ~(1 << 3);         //bit3置1,打开led灯
    writel(val,GPIO1_DR);

    newcharled.major = 0;    /*设置清零,表示由系统申请设备号*/
    /*2.注册字符设备*/
    if(newcharled.major)   /*给定主设备号*/
    {
        newcharled.devid = MKDEV(newcharled.major,0);
        ret = register_chrdev_region(newcharled.devid,NEWCHARLED_COUNT,NEWCHARLED);
    }
    else   /*没有给定主设备*/
    {
        ret = alloc_chrdev_region(&newcharled.devid,0,NEWCHARLED_COUNT,NEWCHARLED);
        newcharled.major = MAJOR(newcharled.devid);
        newcharled.minor = MINOR(newcharled.devid);
        if(ret < 0)
        {
            printk("newcharled  chardev_region err\r\n");
            return -1;
        }
        newcharled.major = MAJOR(newcharled.devid);
    }

    printk("newcharled  major = %d,minor = %d\r\n",newcharled.major,newcharled.minor);
    
    /*3.注册字符设备*/
    newcharled.cdevled.owner = THIS_MODULE;
    cdev_init(&newcharled.cdevled,&newcharled_fops);
    ret = cdev_add(&newcharled.cdevled,newcharled.devid,NEWCHARLED_COUNT);


    /*4.自动创建设备节点*/
    newcharled.class = class_create(THIS_MODULE, NEWCHARLED);
	if (IS_ERR(newcharled.class))
		return PTR_ERR(newcharled.class);

     /*创建设备*/ 
    newcharled.device = device_create(newcharled.class, NULL,newcharled.devid,NULL, NEWCHARLED);  
    if(IS_ERR(newcharled.device))
        return PTR_ERR(newcharled.device);

    return 0;

   
}

/*驱动模块出口*/
static void __exit newled_exit(void)
{
    printk("newchardev exit\r\n");
    /*取消地址映射*/
     iounmap(IMX6U_CCM_CCGR1);
     iounmap(SW_MUX_GPIO1_IO03);
     iounmap(SW_PAD_GPIO1_IO03);
     iounmap(GPIO1_DR);
     iounmap(GPIO1_GDIR);

    /*1.注销字符设备*/
    cdev_del(&newcharled.cdevled);

    /*2.注销设备号*/
    unregister_chrdev_region(newcharled.devid, NEWCHARLED_COUNT);

   /*3.摧毁设备*/
   device_destroy(newcharled.class,newcharled.devid);
   
    /*4.卸载设备节点类*/
    class_destroy(newcharled.class);

}



/*注册和卸载驱动模块*/
module_init(newled_init);
module_exit(newled_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("hsj");   

编写测试 APP

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>

#define LEDOFF 0 
#define LEDON  1

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("open failed\r\n");
      return -1;
   }
   
   databuf[0] = atoi(argv[2]);   //将字符转换为数字
   retvalue = write(fd,databuf,1);
   if (retvalue < 0)
    {
      printf("LED Control failed!\r\n");
      close(fd);
      return -1;   
    }


    close(fd);
    return 0;
}

7、运行测试

depmod        //第一次加载驱动的时候需要运行此命令

modprobe newchrled.ko       //加载驱动

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/503853.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

多线程(线程同步和互斥+线程安全+条件变量)

线程互斥 线程互斥&#xff1a; 任何时刻&#xff0c;保证只有一个执行流进入临界区访问临界资源&#xff0c;通常对临界资源起到保护作用 相关概念 临界资源&#xff1a; 一次仅允许一个进程使用的共享资源临界区&#xff1a; 每个线程内部&#xff0c;访问临界资源的代码&am…

信息抽取与命名实体识别:从原理到实现

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️ &#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

STM32-江科大

新建工程 引入启动文件 Start中是启动文件&#xff0c;是STM32中最基本的文件&#xff0c;不需要修改&#xff0c;添加即可。 启动文件包含很多类型&#xff0c;要根据芯片型号来进行选择&#xff1a; 如果是选择超值系列&#xff0c;那就使用带 VL 的启动文件&#xff0c;…

多元统计分析-主成分分析的原理与实现

目录 一、什么是主成分分析&#xff1f; 二、主成分分析的原理 三、主成分分析的应用 四、使用sklearn实现主成分分析 五、总结 一、什么是主成分分析&#xff1f; 主成分分析&#xff08;Principal Component Analysis&#xff0c;PCA&#xff09;是一种常用的多元统计分…

Docker部署FAST OS DOCKER容器管理工具

Docker部署FAST OS DOCKER容器管理工具 一、FAST OS DOCKER介绍1. FAST OS DOCKER简介2. FAST OS DOCKER特点 二、本次实践介绍1. 本次实践简介2. 本次实践环境 三、本地环境检查1.检查Docker服务状态2. 检查Docker版本 四、下载FAST OS DOCKER镜像五、部署FAST OS DOCKER1. 创…

理解控制变量、内生变量、外生变量、工具变量

文章目录 前言一、控制变量二、内生变量、外生变量三、工具变量&#xff08;IV&#xff09; 前言 1.解释变量&#xff08;或自变量&#xff09;&#xff1a;解释变量是指作为研究对象&#xff0c;用于解释某个现象或行为模式的变量。其中有些解释变量是直接影响被解释变量的&a…

自学黑客(网络安全),一般人我劝你还是算了吧

一、自学网络安全学习的误区和陷阱 1.不要试图先成为一名程序员&#xff08;以编程为基础的学习&#xff09;再开始学习 我在之前的回答中&#xff0c;我都一再强调不要以编程为基础再开始学习网络安全&#xff0c;一般来说&#xff0c;学习编程不但学习周期长&#xff0c;而…

重塑未来:AI对教育行业的深远影响与挑战

自从AI人工智能的发展进入“iPhone时刻”以来&#xff0c;我们已身处一个日新月异的时代。在众多领域&#xff0c;AI已经大放异彩&#xff0c;而教育作为培养下一代的关键领域&#xff0c;自然也受到了这场科技革命的影响。 AI对教育行业重大影响 最近可汗学院&#xff08;Kh…

图论网络模型及求最小路径和造价实战

学习知识要实时简单回顾&#xff0c;我把学习的图论简单梳理一下&#xff0c;方便入门与复习。 图论网络 图论网络简介 图论起源于 18 世纪。第一篇图论论文是瑞士数学家欧拉于 1736 年发表的“哥尼斯堡的七座桥”。1847 年&#xff0c;克希霍夫为了给出电网络方程而引进了“…

《Netty》从零开始学netty源码(五十五)之PooledByteBufAllocator

PooledByteBufAllocator 通过前面的学习我们大体了解了PooledByteBufAllocator管辖下的数据结构&#xff0c;整体情况如下&#xff1a; PooledByteBufAllocator主要管理了三类内存&#xff0c;堆内存heapArenas、直接内存directArenas、线程缓存PoolThreadCache&#xff0c;前…

Java笔记_18(IO流)

Java笔记_18 一、IO流1.1、IO流的概述1.2、IO流的体系1.3、字节输出流基本用法1.4、字节输入流基本用法1.5、文件拷贝1.6、IO流中不同JDK版本捕获异常的方式 二、字符集2.1、GBK、ASCII字符集2.2、Unicode字符集2.3、为什么会有乱码2.4、Java中编码和解码的代码实现2.5、字符输…

直方图均衡化与规定化原理解释以及matlab实现

直方图均衡化(HE) Histogram Equalization (HE) 设灰度水平在 r k , k ∈ [ 0 &#xff0c; L − 1 ] r_k,k\in[0&#xff0c;L-1] rk​,k∈[0&#xff0c;L−1] 内 一幅图像 f f f 的非归一化直方图定义为 h ( r k ) n k h(r_k)n_k h(rk​)nk​ s T ( r ) sT(r) sT(r)为…

【统计模型】心脏病患病影响因素探究

目录 心脏病患病影响因素探究 一、研究目的 二、数据来源和相关说明 三、描述性统计分析 四、数据建模 4.1 全模型 &#xff08;1&#xff09;模型构建 &#xff08;2&#xff09;模型预测 4.2 基于AIC准则的选模型A 4.3 基于BIC准则的选模型B 4.4 模型评估 五、结论…

Vector - CAPL - CANoe硬件配置函数 - 02

Hardware Configuration 硬件配置中包含CAN或者CANFD的参数配置&#xff0c;其中包含波特率、时间片1、时间片2、时间量子中的同步跳跃宽度、采样点数等信息&#xff1b;随着研发系统中各类型的平台化&#xff0c;测试想要跟上研发的进度&#xff0c;也必须进行平台化&#xff…

linux【网络编程】之网络套接字预备

linux【网络编程】之网络套接字 一、必备知识1.1 端口号1.2 端口号方面疑问及解决方案 二、TCP/UDP协议三、网络字节流四、socket编程4.1 认识接口4.2 浅析sockaddr结构 一、必备知识 在【网络基础】中我们提到了IP地址&#xff0c;接下来了解一下网络通信中其他方面的知识 1…

浏览器的渲染

浏览器的渲染 浏览器的渲染过程分为两大阶段&#xff0c;八大步骤&#xff0c;由两个线程完成&#xff0c; 下面是总的过程 第一个 渲染主线程 它包括5个步骤&#xff0c; 1、html解析 parse 解析我们的HTML&#xff0c;生成DOM树结构 2、样式计算 computed style 比如我们…

系统运维(Git篇)

Git基础 Git Git是一种分布式版本控制系统&#xff0c;可以帮助我们管理代码的版本和变更。通过学习Git&#xff0c;我们可以更好地理解版本控制的原理和应用&#xff0c;同时也可以掌握Git的使用和管理技巧。 Docker Docker是一种容器化平台&#xff0c;可以将应用程序及其依赖…

华为OD机试真题2023(JAVA)

目录 华为OD机试是什么&#xff1f;华为OD面试流程&#xff1f;华为OD机试通过率高吗&#xff1f;华为OD薪资待遇&#xff1f;华为OD晋升空间&#xff1f; 大家好&#xff0c;我是哪吒。 本专栏包含了最新最全的华为OD机试真题&#xff0c;有详细的分析和Java代码解答。已帮助…

web前端的同源策略是什么?

一、同源策略 1995年&#xff0c;同源政策由 Netscape 公司(网景公司)引入浏览器。目前&#xff0c;所有浏览器都实行这个政策。同源政策的目的&#xff0c;是为了保证用户信息的安全&#xff0c;防止恶意的网站窃取数据。随着互联网的发展&#xff0c;“同源政策”越来越严格…

深入理解java虚拟机精华总结:运行时栈帧结构、方法调用、字节码解释执行引擎

深入理解java虚拟机精华总结&#xff1a;运行时栈帧结构、方法调用、字节码解释执行引擎 运行时栈帧结构局部变量表操作数栈动态连接方法返回地址 方法调用解析分派静态分派动态分派 基于栈的字节码解释执行引擎 运行时栈帧结构 Java虚拟机以方法作为最基本的执行单元&#xf…