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相关宏的定义准确无误,如果不能确定这点的话,那么还是使用物理地址映射的方式来操作外设是最可靠的,因为芯片手册中都会详细描述相关的物理寄存器的配置和使用。所以,能够轻松使用物理地址映射的方式来自如操作硬件配置是一项必备的能力。


相关文章
|
6天前
|
Linux 编译器 Android开发
FFmpeg开发笔记(九)Linux交叉编译Android的x265库
在Linux环境下,本文指导如何交叉编译x265的so库以适应Android。首先,需安装cmake和下载android-ndk-r21e。接着,下载x265源码,修改crosscompile.cmake的编译器设置。配置x265源码,使用指定的NDK路径,并在配置界面修改相关选项。随后,修改编译规则,编译并安装x265,调整pc描述文件并更新PKG_CONFIG_PATH。最后,修改FFmpeg配置脚本启用x265支持,编译安装FFmpeg,将生成的so文件导入Android工程,调整gradle配置以确保顺利运行。
24 1
FFmpeg开发笔记(九)Linux交叉编译Android的x265库
|
1月前
|
存储 缓存 Linux
如何在Linux环境下对pip的缓存地址进行修改
如何在Linux环境下对pip的缓存地址进行修改
|
存储 编译器
Linux--程序地址空间
Linux--程序地址空间
|
1月前
|
Linux Shell C语言
【Shell 命令集合 设备管理 】Linux 设置键盘映射表 loadkeys命令 使用指南
【Shell 命令集合 设备管理 】Linux 设置键盘映射表 loadkeys命令 使用指南
37 0
|
1月前
|
存储 算法 Shell
【Shell 命令集合 设备管理 】Linux 显示当前系统中定义的键盘映射表 dumpkeys命令 使用指南
【Shell 命令集合 设备管理 】Linux 显示当前系统中定义的键盘映射表 dumpkeys命令 使用指南
31 0
|
20天前
|
Linux 编译器 Windows
【Linux】10. 进程地址空间
【Linux】10. 进程地址空间
19 4
|
22天前
|
Linux API C语言
FFmpeg开发笔记(一)搭建Linux系统的开发环境
本文指导初学者如何在Linux上搭建FFmpeg开发环境。首先,由于FFmpeg依赖第三方库,可以免去编译源码的复杂过程,直接安装预编译的FFmpeg动态库。推荐网站<https://github.com/BtbN/FFmpeg-Builds/releases>提供适用于不同系统的FFmpeg包。但在安装前,需确保系统有不低于2.22版本的glibc库。详细步骤包括下载glibc-2.23源码,配置、编译和安装。接着,下载Linux版FFmpeg安装包,解压至/usr/local/ffmpeg,并设置环境变量。最后编写和编译简单的C或C++测试程序验证FFmpeg环境是否正确配置。
39 8
FFmpeg开发笔记(一)搭建Linux系统的开发环境
|
1月前
|
网络协议 Linux C++
Linux C/C++ 网络编程中地址格式转换(inet_pton和inet_ntop函数)
Linux C/C++ 网络编程中地址格式转换(inet_pton和inet_ntop函数)
27 0
|
1月前
|
存储 缓存 Linux
探秘Linux块设备驱动程序:成为内核开发大师的第一步
探秘Linux块设备驱动程序:成为内核开发大师的第一步
93 0
|
1月前
|
存储 Linux 程序员
【Linux】—— 进程地址空间
【Linux】—— 进程地址空间