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


相关文章
|
JavaScript Linux 网络安全
Termux安卓终端美化与开发实战:从下载到插件优化,小白也能玩转Linux
Termux是一款安卓平台上的开源终端模拟器,支持apt包管理、SSH连接及Python/Node.js/C++开发环境搭建,被誉为“手机上的Linux系统”。其特点包括零ROOT权限、跨平台开发和强大扩展性。本文详细介绍其安装准备、基础与高级环境配置、必备插件推荐、常见问题解决方法以及延伸学习资源,帮助用户充分利用Termux进行开发与学习。适用于Android 7+设备,原创内容转载请注明来源。
3514 77
|
10月前
|
Ubuntu 搜索推荐 Linux
详解Ubuntu的strings与grep命令:Linux开发的实用工具。
这就是Ubuntu中的strings和grep命令,透明且强大。我希望你喜欢这个神奇的世界,并能在你的Linux开发旅程上,通过它们找到你的方向。记住,你的电脑是你的舞台,在上面你可以做任何你想做的事,只要你敢于尝试。
472 32
|
8月前
|
监控 Linux 开发者
理解Linux操作系统内核中物理设备驱动(phy driver)的功能。
综合来看,物理设备驱动在Linux系统中的作用是至关重要的,它通过与硬件设备的紧密配合,为上层应用提供稳定可靠的通信基础设施。开发一款优秀的物理设备驱动需要开发者具备深厚的硬件知识、熟练的编程技能以及对Linux内核架构的深入理解,以确保驱动程序能在不同的硬件平台和网络条件下都能提供最优的性能。
463 0
|
11月前
|
JavaScript Linux Python
在Linux服务器中遇到的立即重启后的绑定错误:地址已被使用问题解决
总的来说,解决"地址已被使用"的问题需要理解Linux的网络资源管理机制,选择合适的套接字选项,以及合适的时间点进行服务重启。以上就是对“立即重启后的绑定错误:地址已被使用问题”的全面解答。希望可以帮你解决问题。
568 20
|
缓存 监控 Linux
在Linux中,如何看当前系统有几颗物理CPU和每颗CPU的核数?
在Linux中,如何看当前系统有几颗物理CPU和每颗CPU的核数?
|
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开发知识可参考相关书籍。
714 0
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
|
网络协议 Linux 开发工具
linux系统配置固定地址
linux系统配置固定地址
|
存储 监控 安全
Linux存储安全:物理安全基石
【8月更文挑战第17天】在数字化时代,数据安全至关重要。Linux存储安全的物理防护作为基石,通过选择安全的数据中心、实施严格的访问控制、环境监控、物理隔离及设备锁定等措施,有效防范未授权访问和环境威胁。结合具体实施方法与案例代码,能大幅提升系统的物理安全性,确保数据安全无虞。
211 10
|
Java Linux API
Linux设备驱动开发详解2
Linux设备驱动开发详解
298 6
|
消息中间件 算法 Unix
Linux设备驱动开发详解1
Linux设备驱动开发详解
387 5