Linux驱动开发笔记(二) 基于字符设备驱动的I/O操作

文章目录

  • 前言
  • 一、设备驱动的作用与本质
    • 1. 驱动的作用
    • 2. 有无操作系统的区别
  • 二、内存管理单元MMU
  • 三、相关函数
    • 1. ioremap( )
    • 2. iounmap( )
    • 3. class_create( )
    • 4. class_destroy( )
  • 四、GPIO的基本知识
    • 1. GPIO的寄存器进行读写操作流程
    • 2. 引脚复用
    • 2. 定义GPIO寄存器物理地址
  • 五、实验代码
    • 1. 宏定义出需要的地址
    • 2. 编写LED字符设备结构体且初始化
    • 3. container_of( )函数
    • 4. file_operations结构体成员函数的实现
    • 5. 实验效果


前言

  前段时间我们学习了字符驱动,并实现了字符的回环发送,这部分我们将进行I/O的操作学习,以万能的点亮LED为例。

一、设备驱动的作用与本质

  直接操作寄存器点亮LED和通过驱动程序点亮LED最本质的区别就是有无使用操作系统。 有操作系统的存在则大大降低了应用软件与硬件平台的耦合度,它充当了我们硬件与应用软件之间的纽带, 使得应用软件只需要调用驱动程序接口API就可以让硬件去完成要求的开发,而应用软件则不需要关心硬件到底是如何工作的。

1. 驱动的作用

  设备驱动与底层硬件直接打交道,按照硬件设备的具体工作方式读写设备寄存器, 完成设备的轮询、中断处理、DMA通信,进行物理内存向虚拟内存的映射,最终使通信设备能够收发数据, 使显示设备能够显示文字和画面,使存储设备能够记录文件和数据。

2. 有无操作系统的区别

  无操作系统(即裸机)时的设备驱动也就是直接操作寄存器的方式控制硬件,在这样的系统中,虽然不存在操作系统,但是设备驱动是必须存在的。 一般情况下,对每一种设备驱动都会定义为一个软件模块,包含.h文件和.c文件,前者定义该设备驱动的数据结构并声明外部函数, 后者进行设备驱动的具体实现。其他模块需要使用这个设备的时候,只需要包含设备驱动的头文件然后调用其中的外部接口函数即可。 比如我们在51或者STM32中直接看手册查找对应的寄存器,然后往寄存器相应的位写入数据0或1便可以实现LED的亮灭。
  有操作系统时的设备驱动反观有操作系统。首先,驱动硬件工作的的部分仍然是必不可少的,其次,我们还需要将设备驱动融入内核。 为了实现这种融合,必须在所有的设备驱动中设计面向操作系统内核的接口,这样的接口由操作系统规定,对一类设备而言结构一致,独立于具体的设备,还是以led为例,我们就要将LED灯引脚对应的数据寄存器(物理地址)映射到程序的虚拟地址空间当中,然后我们就可以像操作寄存器一样去操作我们的虚拟地址啦!

二、内存管理单元MMU

  MMU是一个实际的硬件,为编程提供了方便统一的内存空间抽象,MMU内部有一个专门存放页表的页表地址寄存器,该寄存器存放着页表的具体位置,这使得只要程序在被分配的虚拟地址范围内进行读写操作,实际上就是对设备(寄存器)的访问,如下图所示。他的主要作用是将虚拟地址翻译成真实的物理地址同时管理和保护内存, 不同的进程有各自的虚拟地址空间,某个进程中的程序不能修改另外一个进程所使用的物理地址,以此使得进程之间互不干扰,相互隔离。 总体而言MMU具有如下功能:

  • 保护内存: MMU给一些指定的内存块设置了读、写以及可执行的权限,这些权限存储在页表当中,MMU会检查CPU当前所处的是特权模式还是用户模式,如果和操作系统所设置的权限匹配则可以访问,如果CPU要访问一段虚拟地址,则将虚拟地址转换成物理地址,否则将产生异常,防止内存被恶意地修改。
  • 提供方便统一的内存空间抽象,实现虚拟地址到物理地址的转换: CPU可以运行在虚拟的内存当中,虚拟内存一般要比实际的物理内存大很多,使得CPU可以运行比较大的应用程序。

在这里插入图片描述
在这里插入图片描述

三、相关函数

  上面提到了物理地址到虚拟地址的转换函数。包括ioremap()地址映射和取消地址映射iounmap()函数。

1. ioremap( )

//用于将物理内存地址映射到内核的虚拟地址空间
void __iomem *ioremap(phys_addr_t phys_addr, unsigned long size)//定义寄存器物理地址
#define GPIO0_BASE (0xFDD60000)
#define GPIO0_DR (GPIO0_BASE+0x0000)va_dr = ioremap(GPIO0_DR, 4);    // 将物理地址GPIO0_DR,映射给虚拟地址指针,这段地址大小为4个字节
val = ioread32(va_dr);			 //读取该地址的值,保存到临时变量,重新赋值
val |= (0x00400000);             // 设置GPIO0_A6引脚低电平
writel(val, va_dr);				 //把值重新写入到被映射后的虚拟地址当中,实际是往寄存器中写入了数据
  • 参数:
    • phys_addr:要映射的物理地址的起始地址
    • size:要映射的内存区域的大小(以字节为单位)
  • 返回值:
    • 如果成功,ioremap返回一个指向映射区域的虚拟地址的指针
    • 如果失败,返回NULL

  在使用ioremap函数将物理地址转换成虚拟地址之后,理论上我们便可以直接读写I/O内存,但是为了符合驱动的跨平台以及可移植性, 我们应该使用linux中指定的函数(如:iowrite8()、iowrite16()、iowrite32()、ioread8()、ioread16()、ioread32()等)去读写I/O内存,如下表所示:

函数名功能
unsigned int ioread8(void __iomem *addr)读取一个字节(8bit)
unsigned int ioread16(void __iomem *addr)读取一个字(16bit)
unsigned int ioread32(void __iomem *addr)读取一个双字(32bit)
void iowrite8(u8 data, void __iomem *addr)写入一个字节(8bit)
void iowrite16(u16 data, void __iomem *addr)写入一个字(16bit)
void iowrite32(u32 data, void __iomem *addr)写入一个双字(32bit)

2. iounmap( )

//取消地址映射
void iounmap(void *addr)iounmap(va_dr);     //释放掉ioremap映射之后的起始地址(虚拟地址)
  • 参数
    • addr: 需要取消ioremap映射之后的起始地址(虚拟地址)。
  • 返回值: 无

3. class_create( )

//提交目录信息
#define class_create(owner, name) \
({static struct lock_class_key _key; \_class_create(owner, name, &_key); \
})
  • 参数
    • owner:THIS_MODULE (struct module结构体的首地址这个结构体存放了驱动的出口入口)
    • name:目录名
  • 返回值
    • 成功:返回结构体首地址
    • 失败:返回错误码指针

注:IS_ERR(cls); 判断是否为错误指针
  PTR_ERR(cls); 将错误码指针转换为错误码

4. class_destroy( )

//注销目录信息
void class_destroy(struct class *cls);
  • 参数
    • cls:结构体首地址
  • 返回值:无

四、GPIO的基本知识

1. GPIO的寄存器进行读写操作流程

  • 使能GPIO时钟(默认开启,不用设置)
  • 设置引脚复用为GPIO(复位默认为GPIO,不用配置)
  • 设置引脚属性(上下拉、速率、驱动能力,默认)
  • 控制GPIO引脚为输出,并输出高低电平

2. 引脚复用

  对于rockchip系类芯片,我们需要通过参考手册以及数据手册来确定引脚的复用功能。首先可以看到泰山派的小灯连接引脚,这里我们选择GPIO1_B0_d。
在这里插入图片描述
  通过查询rk3568官方资料,可以看到该引脚的复用功能如下所示。在这里插入图片描述
  再查找其复用功能存在于SYS_GRF寄存器,和复用相关的总共8个寄存器,如下图所示:
在这里插入图片描述

  查询 Rockchip_RK3568_TRM_Part1 手册,GRF_GPIO1B_IOMUX_L寄存器(由于GPIO1_b0是在低八位,下同),如下图所示:
在这里插入图片描述
  寄存器总共32位,高16位都是使能位,控制低16位的写使能,低16位对应4个引脚,每个引脚占用3bits,不同的值引脚复用为不同功能。与此同时由[14:12]进行具体功能的设定。
  我们可以查看到SYS_GRF寄存器的复用功能基地址为0xFDC60000。
在这里插入图片描述
  此时通过命令行输入可以查询到该寄存器的设置情况,可以看到这里默认是GPIO功能。

//目标地址为Address Base(0xfdc60000)+offset(0x0008)
io -r -4 0xfdc60008

2. 定义GPIO寄存器物理地址

  需要设置的寄存器的地址为base+offset,由下图可以知道GPIO1的基地址为:0xFE740000
在这里插入图片描述  接下来就是确定GPIO的是输入还是输出,我们这里需要的是GPIO_SWPORT_DDR_L。
在这里插入图片描述在这里插入图片描述
  可以看到GPIO_SWPORT_DDR_L的定义情况,这里我们可以重复上面提到的命令行,查看寄存器的设置情况,我们的b0应当是第1x7+1=8位。
在这里插入图片描述
  数据寄存器选择GPIO_SWPORT_DR_L,大致流程和上面一样就不再赘述了。这里便完成了对GPIO的设置。

五、实验代码

在这里插入图片描述

1. 宏定义出需要的地址

#define GPIO1_BASE (0xFE740000)//一个寄存器32位,其中高16位都是写使能位,控制低16位的写使能;低16位对应16个引脚,控制引脚的输出电平
#define GPIO1_DR_L (GPIO0_BASE + 0x0000)  // GPIO0的低十六位引脚的数据寄存器地址
#define GPIO1_DR_H (GPIO0_BASE + 0x0004)  // GPIO0的高十六位引脚的数据寄存器地址//一个寄存器32位,其中高16位都是写使能位,控制低16位的写使能;低16位对应16个引脚,控制引脚的输入输出模式
#define GPIO1_DDR_L (GPIO0_BASE + 0x0008)   // GPIO0的低十六位引脚的数据方向寄存器地址
#define GPIO1_DDR_H (GPIO0_BASE + 0x000C)   // GPIO0的低十六位引脚的数据方向寄存器地址

2. 编写LED字符设备结构体且初始化

//led字符设备结构体
struct led_chrdev {struct cdev dev;unsigned int __iomem *va_dr;    // 数据寄存器虚拟地址保存变量unsigned int __iomem *va_ddr;   // 数据方向寄存器虚拟地址保存变量unsigned int led_pin; 			// 引脚
};static struct led_chrdev led_cdev[DEV_CNT] = {{.led_pin = 8				//CPIO1_B0的偏移为8+0=8},
};

3. container_of( )函数

  在Linux驱动编程当中我们会经常和container_of()这个函数打交道,其宏定义实现如下所示:

#define container_of(ptr, type, member) ({                      \const typeof( ((type *)0)->member ) *__mptr = (ptr);    \(type *)( (char *)__mptr - offsetof(type,member) );})
  • 参数:
    • ptr: 结构体变量中某个成员的地址
    • type: 结构体类型
    • member: 该结构体变量的具体名字
  • 返回值: 结构体type的首地址

  原理其实很简单,就是通过已知类型type的成员member的地址ptr,计算出结构体type的首地址。 type的首地址 = ptr - size ,需要注意的是它们的大小都是以字节为单位计算的,container_of( )函数的主要作用如下:

  • 判断ptr 与 member 是否为同一类型
  • 计算size大小,结构体的起始地址 = (type *)((char *)ptr - size) (注:强转为该结构体指针)

注:文件私有数据
  一般很多的linux驱动都会将文件的私有数据private_data指向设备结构体,其保存了用户自定义设备结构体的地址。 自定义结构体的地址被保存在private_data后,可以通过读、写等操作通过该私有数据去访问设备结构体中的成员, 这样做体现了linux中面向对象的程序设计思想。

4. file_operations结构体成员函数的实现

static int led_chrdev_open(struct inode *inode, struct file *filp)
{unsigned int val = 0;struct led_chrdev *led_cdev = (struct led_chrdev *)container_of(inode->i_cdev, struct led_chrdev, dev);filp->private_data = container_of(inode->i_cdev, struct led_chrdev, dev);printk("open\n");//读取数据方向寄存器val = ioread32(led_cdev->va_ddr);//设置数据方向寄存器为pin位可写val |= ((unsigned int)0x1 << (led_cdev->led_pin+16));	//设置数据方向寄存器为pin位输出val |= ((unsigned int)0X1 << (led_cdev->led_pin));//写入数据方向寄存器iowrite32(val,led_cdev->va_ddr);//读取数据寄存器val = ioread32(led_cdev->va_dr);//设置数据寄存器为pin位可写val |= ((unsigned int)0x1 << (led_cdev->led_pin+16));//设置数据寄存器为pin位高电平val |= ((unsigned int)0x1 << (led_cdev->led_pin));//写入数据寄存器iowrite32(val, led_cdev->va_dr);return 0;
}

  这部分代码位open_operations结构体的设置,其中container_of()函数和寄存器设置部分需要联系前节4.2的介绍反复理解(笔者这里看了很久才顿悟)。

5. 实验效果

最近临时变化地点,后续补上。

免责声明:本程序参考了野火和北京讯为科技的部分视频资料,不作商用仅供学习,若有侵权和错误请联系笔者删除

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

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

相关文章

【2024最新华为OD-C卷试题汇总】传递悄悄话的最长时间(100分) - 三语言AC题解(Python/Java/Cpp)

&#x1f36d; 大家好这里是清隆学长 &#xff0c;一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C卷的三语言AC题解 &#x1f4bb; ACM银牌&#x1f948;| 多次AK大厂笔试 &#xff5c; 编程一对一辅导 &#x1f44f; 感谢大家的订阅➕ 和 喜欢&#x1f497; 文章目录 前…

东哥一句兄弟,你还当真了?

关注卢松松&#xff0c;会经常给你分享一些我的经验和观点。 你还真把自己当刘强东兄弟了?谁跟你是兄弟了?你在国外的房子又不给我住&#xff0c;你出去旅游也不带上我!都成人年了&#xff0c;东哥一句客套话&#xff0c;别当真! 今天&#xff0c;东哥在高管会上直言&…

[数据结构] -- 单链表

&#x1f308; 个人主页&#xff1a;白子寰 &#x1f525; 分类专栏&#xff1a;C打怪之路&#xff0c;python从入门到精通&#xff0c;数据结构&#xff0c;C语言&#xff0c;C语言题集&#x1f448; 希望得到您的订阅和支持~ &#x1f4a1; 坚持创作博文(平均质量分82)&#…

c++编程14——STL(3)list

欢迎来到博主的专栏&#xff1a;c编程 博主ID&#xff1a;代码小豪 文章目录 list成员类型构造、析构、与赋值iterator元素访问修改元素list的操作 list list的数据结构是一个链表&#xff0c;准确的说应该是一个双向链表。这是一个双向链表的节点结构&#xff1a; list的使用…

Vue学习笔记3——事件处理

事件处理 1、事件处理器&#xff08;1&#xff09;内联事件处理器&#xff08;2&#xff09;方法事件处理器 2、事件参数3、事件修饰符 1、事件处理器 我们可以使用v-on 指令(简写为)来监听DOM事件&#xff0c;并在事件触发时执行对应的JavaScript。 用法: v-on:click"me…

JVM学习-执行引擎

执行引擎 执行引擎是Java虚拟机核心组成部分之一虚拟机是一个相对于物理机的概念&#xff0c;这两种机器都有代码执行能力&#xff0c;其区别是物理机的执行引擎是直接建立在处理器、缓存、指令集和操作系统层面上的&#xff0c;而虚拟机的执行引擎是由软件自行实现的&#xf…

【算法】递归、搜索与回溯——简介

简介&#xff1a;递归、搜索与回溯&#xff0c;本节博客主要是简单记录一下关于“递归、搜索与回溯”的相关简单概念&#xff0c;为后续算法做铺垫。 目录 1.递归1.1递归概念2.2递归意义2.3学习递归2.4写递归代码步骤 2.搜索3.回溯与剪枝 递归、搜索、回溯的关系&#xff1a; …

ICML2024 定义新隐私保护升级:DP-BITFIT新型微调技术让AI模型学习更安全

DeepVisionary 每日深度学习前沿科技推送&顶会论文分享&#xff0c;与你一起了解前沿深度学习信息&#xff01; 引言&#xff1a;差分隐私在大模型微调中的重要性和挑战 在当今的深度学习领域&#xff0c;大型预训练模型的微调已成为提高各种任务性能的关键技术。然而&am…

推特热帖:大语言模型自荐能够替代的20种人类工作!快来看你是否需要转行!

最近推特上有一个例子引起了广泛的讨论&#xff0c;事情的起因是这样的&#xff1a;网友让 GPT-4o 预测一下自己未来将会替代人类哪些工作&#xff1f; 这听起来很有趣&#xff01;GPT-4o会给出什么样的预测呢&#xff1f; 3.5研究测试&#xff1a;hujiaoai.cn 4研究测试&…

02-Linux【基础篇】

一、Linux的目录结构 1.基本介绍 Linux的文件系统采用层级式的树状目录结构&#xff0c;在此结构中的最上层是根目录"/"&#xff0c;然后在此目录下再创建其他的目录 深刻理解Linux树状文件目录是非常重要的 记住一句经典的话&#xff1a;在Linux世界里&#xff…

如何在 DigitalOcean Droplet 云主机上创建 Ubuntu 服务器

在本文中&#xff0c;你将通过 DigitalOcean 的管理面板创建一个 Ubuntu 服务器&#xff0c;并将其配置为使用你的 SSH 密钥。设置好服务器后&#xff0c;你可以在其上部署应用程序和网站。 本教程是DigitalOcean云课程简介的一部分&#xff0c;它指导用户完成将应用程序安全地…

win10右键没有默认打开方式的选项的处理方法

问题描述 搞了几个PDF书籍学习一下&#xff0c;不过我不想用默认的WPS打开&#xff0c;因为WPS太恶心人了&#xff0c;占用资源又高。我下载了个Sumatra PDF&#xff0c;这时候我像更改pdf文件默认的打开程序&#xff0c;发现右击没有这个选项。 问题解决 右击文件–属性–…

汽车以太网发展现状及挑战

一、汽车以太网技术联盟 目前推动汽车以太网技术应用与发展的组织包括&#xff1a;OPEN Alliance&#xff08;One-Pair Ether-Net Alliance SIG&#xff09;联盟&#xff0c;主要致力于汽车以太网推广与使用&#xff0c;该联盟通过推进 BroadR- Reach 单对非屏蔽双绞线以太网传…

设计新境界:大数据赋能UI的创新美学

设计新境界&#xff1a;大数据赋能UI的创新美学 引言 随着大数据技术的蓬勃发展&#xff0c;它已成为推动UI设计创新的重要力量。大数据不仅为界面设计提供了丰富的数据资源&#xff0c;还赋予了设计师以全新的视角和工具来探索美学的新境界。本文将探讨大数据如何赋能UI设计…

面试八股之JVM篇3.5——垃圾回收——G1垃圾回收器

&#x1f308;hello&#xff0c;你好鸭&#xff0c;我是Ethan&#xff0c;一名不断学习的码农&#xff0c;很高兴你能来阅读。 ✔️目前博客主要更新Java系列、项目案例、计算机必学四件套等。 &#x1f3c3;人生之义&#xff0c;在于追求&#xff0c;不在成败&#xff0c;勤通…

常见 JVM 面试题补充

原文地址 : 26 福利&#xff1a;常见 JVM 面试题补充 (lianglianglee.com) CMS 是老年代垃圾回收器&#xff1f; 初步印象是&#xff0c;但实际上不是。根据 CMS 的各个收集过程&#xff0c;它其实是一个涉及年轻代和老年代的综合性垃圾回收器。在很多文章和书籍的划分中&…

SpringCloud Alibaba的相关组件的简介及其使用

Spring Cloud Alibaba是阿里巴巴为开发者提供的一套微服务解决方案&#xff0c;它基于Spring Cloud项目&#xff0c;提供了一系列功能强大的组件&#xff0c;包括服务注册与发现、配置中心、熔断与限流、消息队列等。 本文将对Spring Cloud Alibaba的相关组件进行简介&#xff…

ROCm上运行预训练BERT

14.10. 预训练BERT — 动手学深度学习 2.0.0 documentation (d2l.ai) 下载数据集 在d2l-zh/pytorch/data目录解压&#xff1a; ~/d2l-zh/pytorch/data$ unzip wikitext-2-v1.zip Archive: wikitext-2-v1.zipcreating: wikitext-2/inflating: wikitext-2/wiki.test.tokens …

数据库--数据库基础(一)

目录 第一章 绪论 一.数据库的基本概念 1. 数据库的4个基本概念 2、数据库系统的特点 二.数据库和文件 三.数据模型 1.概念模型 2.逻辑模型(物理模型) 2.1关系模型 四.数据库系统的三级模式结构&#xff1a; 五数据库的二级映像功能与数据独立性 第二章 关系数据库…

2024电工杯数学建模B题高质量成品论文,包括代码数据

2024电工杯数学建模B题高质量成品论文&#xff0c;包括代码数据 完整内容见文末名片 摘要 大学时期是学生们知识学习和身体成长的重要阶段&#xff0c;良好的饮食习惯对于促进生长发育和保证身体健康具有重要意义。针对当前大学生中存在的饮食结构不合理及不良饮食习惯的问题…