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

在学了在学了! 2020-08-18 20:05:11 2309

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

总结

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

转自:https://blog.csdn.net/qq_37596943/article/details/103811120#comments_12812080

声明:本文内容由易百纳平台入驻作者撰写,文章观点仅代表作者本人,不代表易百纳立场。如有内容侵权或者其他问题,请联系本站进行删除。
红包 点赞 收藏 评论 打赏
评论
0个
内容存在敏感词
手气红包
    易百纳技术社区暂无数据
相关专栏
置顶时间设置
结束时间
删除原因
  • 广告/SPAM
  • 恶意灌水
  • 违规内容
  • 文不对题
  • 重复发帖
打赏作者
易百纳技术社区
在学了在学了!
您的支持将鼓励我继续创作!
打赏金额:
¥1易百纳技术社区
¥5易百纳技术社区
¥10易百纳技术社区
¥50易百纳技术社区
¥100易百纳技术社区
支付方式:
微信支付
支付宝支付
易百纳技术社区微信支付
易百纳技术社区
打赏成功!

感谢您的打赏,如若您也想被打赏,可前往 发表专栏 哦~

举报反馈

举报类型

  • 内容涉黄/赌/毒
  • 内容侵权/抄袭
  • 政治相关
  • 涉嫌广告
  • 侮辱谩骂
  • 其他

详细说明

审核成功

发布时间设置
发布时间:
是否关联周任务-专栏模块

审核失败

失败原因
备注
拼手气红包 红包规则
祝福语
恭喜发财,大吉大利!
红包金额
红包最小金额不能低于5元
红包数量
红包数量范围10~50个
余额支付
当前余额:
可前往问答、专栏板块获取收益 去获取
取 消 确 定

小包子的红包

恭喜发财,大吉大利

已领取20/40,共1.6元 红包规则

    易百纳技术社区