将物理设备映射到用户空间中的指针

ysa*_*sap 6 mmap device linux-device-driver linux-kernel embedded-linux

我们有一个嵌入式系统,其中连接了内存映射设备,ARM CPU运行Linux.设备位于地址0x40400000并占用一兆字节(大部分设备不支持实际内存,但地址空间仍然映射到设备).我们目前没有此设备的设备驱动程序.

在器件中,地址处有一个特殊的只读寄存器(称为CID)0x404f0704.该寄存器包含该值CID = 0x404.我试图从ARM上运行的程序中读取该寄存器.

在网上搜索我了解了mmap()可以让我从用户空间访问物理地址的功能.因此,尝试按照我发现的几个例子,我写了以下测试:


#include <sys/mman.h>
#include <fcntl.h>
#include <err.h>
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    void          *pdev = (void *) 0x40400000;
    size_t         ldev = (1024*1024);
    int           *pu;
    int  volatile *pcid;
    int  volatile  cid;

    pu = mmap(pdev, ldev, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
    if (pu == MAP_FAILED)
        errx(1, "mmap failure");

    pcid = (int *) (((void *) pu) + 0xf0704);

    printf("pu    = %08p\n", pu);
    printf("pcid  = %08p\n", pcid);

    cid = *pcid;
    printf("CID   = %x\n", cid);

    munmap(pu, ldev);

    return (EXIT_SUCCESS);
}

使用ARM交叉编译器进行编译:

a-gcc -O0 -g3 -o mmap-test.elf mmap-test.c
Run Code Online (Sandbox Code Playgroud)

我无法得到预期的结果.我看到的是:

pu   = 0x40400000
pcid = 0x404f0704
CID  = 0
Run Code Online (Sandbox Code Playgroud)

而不是预期的

CID  = 404
Run Code Online (Sandbox Code Playgroud)

我在这里错过了什么/做错了什么?


更新:

我找到了另一个演示程序,并按照其代码我能够使我的代码工作:


int main(void)
{
    off_t          dev_base = 0x40400000;
    size_t         ldev = (1024 * 1024);
    unsigned long  mask = (1024 * 1024)-1;
    int           *pu;
    void          *mapped_base;
    void          *mapped_dev_base;
    int  volatile *pcid;
    int  volatile  cid;
    int            memfd;

    memfd = open("/dev/mem", O_RDWR | O_SYNC);
    mapped_base = mmap(0, MAP_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, memfd, dev_base & ~MAP_MASK);
    if (mapped_base == MAP_FAILED)
        errx(1, "mmap failure");
    mapped_dev_base = mapped_base + (dev_base & MAP_MASK);
    pu = mapped_dev_base;

    pcid = (int *) (((void *) pu) + 0xf0704);

    printf("pu    = %08p\n", pu);
    printf("pcid  = %08p\n", pcid);

    cid = *pcid;
    printf("CID   = %x\n", cid);

    munmap(mapped_base, ldev);
    close(memfd);

    return (EXIT_SUCCESS);
}

不过,我不太清楚为什么第一个版本不起作用.我的理解是,一旦使用,MAP_ANONYMOUS您不需要映射的文件句柄.另外,我明显错误地将addr参数(pepi在我的第一个版本中)作为物理地址.如果我现在,那么这实际上是虚拟地址.

osg*_*sgx 6

Mmap是通常与虚拟地址一起使用的功能.当你调用mmap(... MAP_ANONYMOUS)(或mmap/dev/zero文件),它会给你一个新的虚拟内存一定量,以0.返回的地址将是虚拟内存的地址.

您可以mmap一些文件(没有MAP_ANONYMOUS),然后mmap会将文件内容映射到某个虚拟内存范围.

该设备位于地址0x40400000

设备MMIO位于物理内存中; 任何进程都可以使用虚拟地址0x40400000; 但它们将被MMU(内存管理单元)映射(转换)到一些免费的物理页面.你不能只是向操作系统询问一些虚拟内存,并期望它将被mmaped到设备范围(它将是地狱的变种).

但是有一个特殊的设备/ dev/mem,它可以用作包含所有物理内存的File.当你mmap/ dev/mem你实际上要求操作系统创建一些虚拟内存的新映射到所要求的物理范围.

在你的mmap调用中:

 mapped_base = mmap(0, MAP_SIZE, PROT_READ|PROT_WRITE, 
   MAP_SHARED, memfd, dev_base & ~MAP_MASK);
Run Code Online (Sandbox Code Playgroud)

你要求将物理内存范围[0x40400000 .. 0x4050000-1](一兆字节;不包括字节0x40500000)映射到几兆字节的虚拟内存(其起始地址由mmap返回).