Linux驱动开发——物理地址映射(①)

简介: Linux驱动开发——物理地址映射(①)

文章目录

Linux驱动开发之物理地址映射

ioremap函数

使用方式

方式一

方式二

不使用GPIO库函数方式来修改之前操作LED灯代码示例

总结


Linux驱动开发之物理地址映射

如果不采用GPIO库函数,那么我们如何能在底层驱动中访问外设对应的硬件寄存器呢?是像类似单片机编程一样直接对硬件寄存器访问么?


  • 在Linux系统中,不管是在用户空间还是内核空间一律不允许直接访问硬件外设的基地址(包括寄存器的基地址)。如果要想访问,必须将外设的基地址映射到用户空间的虚拟地址或者内核控件的虚拟地址,一旦映射完成,将来应用程序或者驱动程序访问映射的用户虚拟地址或者内核虚拟地址就是在访问对应的物理地址。

  • 注意:虚拟地址包括用户虚拟地址和内核虚拟地址。

  • 用户虚拟地址范围:0x00000000~0xBFFFFFFF
  • 内核虚拟地址范围:0xC0000000~0xFFFFFFFF

那么如何能够将外设的物理地址映射到内核空间的虚拟地址上呢?


  • 使用函数——ioremap

ioremap函数

void *ioremap(unsigned long phys_addr, int size)


  • 函数功能:完成物理地址和内核虚拟地址的映射关系。
  • 参数:
  • phys_addr:要访问、映射的外设的物理地址起始地址。
  • size:要访问、映射的外设的物理地址范围(大小)
  • 返回值:返回映射的内存起始虚拟地址。

注意: 将来如果对物理地址不在访问,需要解除映射,使用如下函数


void iounmap(void *vir_addr)


  • 函数功能:解除物理地址和内核虚拟地址的映射关系。
  • 参数(vir_addr):映射的内核虚拟地址起始地址。

使用方式

方式一

例如LED1相关的寄存器物理地址(gpio_c_12):


GPIOCOUT:0xC001C000
 GPIOCOUTENB: 0xC001C004
 GPIOCALTFN0:0xC001C020


每一个寄存器的大小为4字节,那么Linux内核访问上述的寄存器的方式就是:


unsigned long *gpiocout, *gpiocoutenb, *gpiocaltfn0;
gpiocout = ioremap(0xC001C000, 4);
gpiocoutenb = ioremap(0xC001C004, 4);
gpiocaltfn0 = ioremap(0xC001C020, 4);
//配置GPIO功能,配置输出时:
*gpiocaltfn0 &= ~(3 << 24);
*gpiocaltfn0 |= (1 << 24);
*gpiocoutenb |= (1 << 12);


方式二

由于GPIO相关寄存器在物理上都是连续的,只需要指定一个起始物理地址,然后指定一个大小,大小包括了需要访问的所有寄存器即可。


void *gpiobase;
unsigned long *gpiocout, *gpiocoutenb, *gpiocaltfn0;
gpiobase = ioremap(0xC001C000, 0x24);   
gpiocout = (unsigned long *)(gpiobase + 0x00);
gpiocoutenb = (unsigned long *)(gpiobase+0x04);
gpiocaltfn0 = (unsigned long *)(gpiobase+0x20); 
//配置GPIO功能,配置为输出:
gpiocaltfn0 &= ~(3 << 24);
gpiocaltfn0 |= (1 << 24);
gpiocoutenb |= (1 << 12);


不使用GPIO库函数方式来修改之前操作LED灯代码示例

将之前的GPIO库函数操作LED灯代码进行修改


  • led_drv.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/io.h> //ioremap
#include <linux/uaccess.h>
//记录各个寄存器的内核虚拟地址
static void *gpiobase;
static unsigned long *gpiocout; //数据寄存器内核虚拟地址
static unsigned long *gpiocoutenb;//输出使能寄存器内核虚拟地址
static unsigned long *gpiocaltfn0;//复用功能选择寄存器的内核虚拟地址
#define LED_ON  0x100001 //开灯命令
#define LED_OFF 0x100002 //关灯命令
static long led_ioctl(struct file *file,
                        unsigned long cmd,
                        unsigned long arg)
{
    //1.分配内核缓冲区
    int kindex;
    //2.拷贝用户缓冲区数据到内核缓冲区
    copy_from_user(&kindex, (int *)arg, sizeof(kindex));
    //3.解析用户发送来的命令
    switch(cmd) {
        case LED_ON:
            if(kindex == 1)
                *gpiocout &= ~(1 << 12);
            /*
            else if(kindex == 2)
                ....
            else if(kindex == 3)
                ....
            else if(kindex == 4)
                ....
            */
            break;
        case LED_OFF:
            if(kindex == 1)
                *gpiocout |= (1 << 12);
            /*
            else if(kindex == 2)
                ....
            else if(kindex == 3)
                ....
            else if(kindex == 4)
                ....
            */
            break;
        default:
            return -1;
    }
    //4.添加调试打印信息,将操作的寄存器的值打印出来
    //如果将来发现寄存器的值是对的,但是灯没有反应
    //请问:是谁的问题?此问题势必是硬件问题
    printk("GPIOCALTFN0=%#x, GPIOCOUTENB=%#x, GPIOCOUT=%#x\n", *gpiocaltfn0, *gpiocoutenb, *gpiocout);
    return 0; 
}
//定义初始化硬件操作接口对象
static struct file_operations led_fops = {
    .owner = THIS_MODULE,
    .unlocked_ioctl = led_ioctl
};
//定义初始化混杂设备对象
static struct miscdevice led_misc = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = "myled",
    .fops = &led_fops
};
static int led_init(void)
{
    //1.注册混杂设备到内核
    misc_register(&led_misc);
    //2.将各个寄存器的物理地址映射到内核虚拟地址
    //一旦完成映射,驱动访问映射的内核虚拟地址就是
    //在访问对应的物理地址
    gpiobase = ioremap(0xC001C000, 0x24);
    gpiocout = (unsigned long *)(gpiobase + 0x00);
    gpiocoutenb = (unsigned long *)(gpiobase + 0x04);
    gpiocaltfn0 = (unsigned long *)(gpiobase + 0x20);
    //3.配置引脚为GPIO功能,配置为输出,输出1(省电)
    *gpiocaltfn0 &= ~(0x3 << 24);
    *gpiocaltfn0 |= (1 << 24);
    *gpiocoutenb |= (1 << 12);
    *gpiocout |= (1 << 12);
    return 0;
}
static void led_exit(void)
{
    //1.输出1,解除地址映射
    *gpiocout |= (1 << 12);
    iounmap(gpiobase);
    //2.卸载混杂设备
    misc_deregister(&led_misc);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");


  • led_test.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#define LED_ON  0x100001 //开灯命令
#define LED_OFF 0x100002 //关灯命令
int main(int argc, char *argv[])
{
    int fd;
    int index; //分配用户缓冲区,保存操作灯的编号
    if(argc != 3) {
        printf("用法:%s <on|off> <1|2|3|4>\n", argv[0]);
        return -1;
    }
    //打开设备
    fd = open("/dev/myled", O_RDWR);
    if (fd < 0) {
        printf("打开设备失败!\n");
        return -1;
    }
    //"1"->1
    index = strtoul(argv[2], NULL, 0);
    //应用ioctl->软中断->内核sys_ioctl->驱动led_ioctl
    if(!strcmp(argv[1], "on"))
        ioctl(fd, LED_ON, &index);
    else if(!strcmp(argv[1], "off"))
        ioctl(fd, LED_OFF, &index);
    //关闭设备
    close(fd);
    return 0;
}


  • Makefile
obj-m += led_drv.o
all:
  make -C /opt/kernel SUBDIRS=$(PWD) modules
clean:
  make -C /opt/kernel SUBDIRS=$(PWD) clean


  • 执行结果:

20200102215013821.png

总结

通常都能够使用gpio库函数去操作gpio,但是gpio库函数的使用基于platform下对匹配的芯片型号的gpio相关宏的定义准确无误,如果不能确定这点的话,那么还是使用物理地址映射的方式来操作外设是最可靠的,因为芯片手册中都会详细描述相关的物理寄存器的配置和使用。所以,能够轻松使用物理地址映射的方式来自如操作硬件配置是一项必备的能力。


相关文章
|
3月前
|
缓存 监控 Linux
在Linux中,如何看当前系统有几颗物理CPU和每颗CPU的核数?
在Linux中,如何看当前系统有几颗物理CPU和每颗CPU的核数?
|
27天前
|
Linux API 开发工具
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
ijkplayer是由B站研发的移动端播放器,基于FFmpeg 3.4,支持Android和iOS。其源码托管于GitHub,截至2024年9月15日,获得了3.24万星标和0.81万分支,尽管已停止更新6年。本文档介绍了如何在Linux环境下编译ijkplayer的so库,以便在较新的开发环境中使用。首先需安装编译工具并调整/tmp分区大小,接着下载并安装Android SDK和NDK,最后下载ijkplayer源码并编译。详细步骤包括环境准备、工具安装及库编译等。更多FFmpeg开发知识可参考相关书籍。
75 0
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
|
1月前
|
网络协议 Linux 开发工具
linux系统配置固定地址
linux系统配置固定地址
|
1月前
|
Linux C++
Linux c/c++文件虚拟内存映射
这篇文章介绍了在Linux环境下,如何使用虚拟内存映射技术来提高文件读写的速度,并通过C/C++代码示例展示了文件映射的整个流程。
45 0
|
2月前
|
存储 Linux 开发工具
如何进行Linux内核开发【ChatGPT】
如何进行Linux内核开发【ChatGPT】
|
3月前
|
Java Linux API
Linux设备驱动开发详解2
Linux设备驱动开发详解
43 6
|
3月前
|
消息中间件 算法 Unix
Linux设备驱动开发详解1
Linux设备驱动开发详解
48 5
|
3月前
|
存储 监控 安全
Linux存储安全:物理安全基石
【8月更文挑战第17天】在数字化时代,数据安全至关重要。Linux存储安全的物理防护作为基石,通过选择安全的数据中心、实施严格的访问控制、环境监控、物理隔离及设备锁定等措施,有效防范未授权访问和环境威胁。结合具体实施方法与案例代码,能大幅提升系统的物理安全性,确保数据安全无虞。
55 10
|
3月前
|
编解码 安全 Linux
基于arm64架构国产操作系统|Linux下的RTMP|RTSP低延时直播播放器开发探究
这段内容讲述了国产操作系统背景下,大牛直播SDK针对国产操作系统与Linux平台发布的RTMP/RTSP直播播放SDK。此SDK支持arm64架构,基于X协议输出视频,采用PulseAudio和Alsa Lib处理音频,具备实时静音、快照、缓冲时间设定等功能,并支持H.265编码格式。此外,提供了示例代码展示如何实现多实例播放器的创建与管理,包括窗口布局调整、事件监听、视频分辨率变化和实时快照回调等关键功能。这一技术实现有助于提高直播服务的稳定性和响应速度,适应国产操作系统在各行业中的应用需求。
108 3
|
4月前
|
Web App开发 缓存 Linux
FFmpeg开发笔记(三十六)Linux环境安装SRS实现视频直播推流
《FFmpeg开发实战》书中第10章提及轻量级流媒体服务器MediaMTX,适合测试RTSP/RTMP协议,但不适合生产环境。推荐使用SRS或ZLMediaKit,其中SRS是国产开源实时视频服务器,支持多种流媒体协议。本文简述在华为欧拉系统上编译安装SRS和FFmpeg的步骤,包括安装依赖、下载源码、配置、编译以及启动SRS服务。此外,还展示了如何通过FFmpeg进行RTMP推流,并使用VLC播放器测试拉流。更多FFmpeg开发内容可参考相关书籍。
103 2
FFmpeg开发笔记(三十六)Linux环境安装SRS实现视频直播推流