在ARM架构的Linux系统中,如何读写内存映射设备寄存器

6

我正在尝试读写我的ARM9(SAM9X25)上的寄存器,按照以下步骤进行:http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.faqs/ka3750.html
我最终得到了以下代码:

#include "stdio.h"

#define PIO_WPMR_BANK_D                     0xFFFFFAE4  // PIO Write Protection Mode Register Bank D
#define PIO_PUER_BANK_D                     0xFFFFFA64  // PIO Pull-Up Enable Register Bank D
#define PIO_PUSR_BANK_D                     0xFFFFFA68  // PIO Pull-Up Status Register Bank D

#define MASK_LED7                           0xFFDFFFFF  // LED7 Mask
#define DESABLE_WRITE_PROTECTION_BANK_D     0x50494F00  // Desable write protection Bank D

int main(void) {
    printf("test");
    unsigned int volatile * const register_PIO_WPMR_BANK_D = (unsigned int *) PIO_WPMR_BANK_D;

    unsigned int volatile * const register_PIO_PUSR_BANK_D = (unsigned int *) PIO_PUSR_BANK_D;

    unsigned int volatile * const port_D = (unsigned int *) PIO_PUER_BANK_D;

    *register_PIO_WPMR_BANK_D = DESABLE_WRITE_PROTECTION_BANK_D;

    *port_D = *register_PIO_PUSR_BANK_D & MASK_LED7;

    return 0; }

我在Ubuntu 16.04中交叉编译了我的代码,方法如下:arm-linux-gnueabi-gcc gpio.c -o gpio。但是在我的板子上执行程序时,在printf之后就出现了Segmentation Fault。我知道地址是正确的...那么为什么会出现这个错误呢?这是正确的做法吗?谢谢你的帮助!
解决方案: 感谢@vlk,我终于让它工作了!这里有一个简单的例子来切换LED的状态:
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <fcntl.h>


#define handle_error(msg) \
           do { perror(msg); exit(EXIT_FAILURE); } while (0)

#define _PIOD_BANK_D                            0xA00

#define _PIO_OFFSET                             0xFFFFF000

/* When executing this on the board :
    long sz = sysconf(_SC_PAGESIZE);
    printf("%ld\n\r",sz);
   We have 4096.
*/
#define _MAP_SIZE                           0x1000  // 4096 

#define _WPMR_OFFSET                        0x0E4   // PIO Write Protection Mode Register Bank D

#define _PIO_ENABLE                         0x000
#define _PIO_DISABLE                        0x004
#define _PIO_STATUS                         0x008
#define _OUTPUT_ENABLE                      0x010
#define _OUTPUT_DISABLE                     0x014
#define _OUTPUT_STATUS                      0x018
#define _FILTER_ENABLE                      0x020
#define _FILTER_DISABLE                     0x024
#define _FILTER_STATUS                      0x028
#define _OUTPUT_DATA_SET                    0x030
#define _OUTPUT_DATA_CLEAR                  0x034
#define _OUTPUT_DATA_STATUS                 0x038
#define _PIN_DATA_STATUS                    0x03c
#define _MULTI_DRIVER_ENABLE                0x050
#define _MULTI_DRIVER_DISABLE               0x054
#define _MULTI_DRIVER_STATUS                0x058
#define _PULL_UP_DISABLE                    0x060
#define _PULL_UP_ENABLE                     0x064
#define _PULL_UP_STATUS                     0x068
#define _PULL_DOWN_DISABLE                  0x090
#define _PULL_DOWN_ENABLE                   0x094
#define _PULL_DOWN_STATUS                   0x098

#define _DISABLE_WRITE_PROTECTION           0x50494F00  // Desable write protection

#define LED_PIN                                 21

int main(void) {

    volatile void *gpio_addr;
    volatile unsigned int *gpio_enable_addr;
    volatile unsigned int *gpio_output_mode_addr;
    volatile unsigned int *gpio_output_set_addr;
    volatile unsigned int *gpio_output_clear_addr;
    volatile unsigned int *gpio_data_status_addr;
    volatile unsigned int *gpio_write_protection_addr;

    int fd = open("/dev/mem", O_RDWR|O_SYNC);
    if (fd < 0){
        fprintf(stderr, "Unable to open port\n\r");
        exit(fd);
    }


    gpio_addr = mmap(NULL, _MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, _PIO_OFFSET);


    if(gpio_addr == MAP_FAILED){
        handle_error("mmap");
    }


    gpio_write_protection_addr = gpio_addr + _PIOD_BANK_D + _WPMR_OFFSET;

    gpio_enable_addr = gpio_addr + _PIOD_BANK_D + _PIO_ENABLE;

    gpio_output_mode_addr = gpio_addr + _PIOD_BANK_D + _OUTPUT_ENABLE;

    gpio_output_set_addr = gpio_addr + _PIOD_BANK_D + _OUTPUT_DATA_SET;

    gpio_output_clear_addr = gpio_addr + _PIOD_BANK_D + _OUTPUT_DATA_CLEAR;

    gpio_data_status_addr = gpio_addr + _PIOD_BANK_D + _OUTPUT_DATA_STATUS;


    *gpio_write_protection_addr = _DISABLE_WRITE_PROTECTION;

    *gpio_enable_addr = 1 << LED_PIN;
    *gpio_output_mode_addr = 1 << LED_PIN; // Output


    // If LED
    if((*gpio_data_status_addr & (1<<LED_PIN)) > 0){
        *gpio_output_clear_addr = 1 << LED_PIN;
    }else{
        *gpio_output_set_addr = 1 << LED_PIN;
    }

    return 0;
}

编辑:
在评论中回答3)的问题。如果您想让它适用于所有偏移量(例如:mmap示例),则必须更改mmap和赋值,如下所示:

#define _PIO_OFFSET                         0xFFFFFA00 // Instead of 0xFFFFF000
#define _MAP_SIZE                           0x1000  // 4096 
#define _MAP_MASK                           (_MAP_SIZE - 1)
#define _PA_OFFSET                          _PIO_OFFSET & ~_MAP_MASK

并且 mmap:

gpio_addr = mmap(NULL, _MAP_SIZE + _PIO_OFFSET - _PA_OFFSET, PROT_READ | PROT_WRITE, MAP_SHARED, fd, _PA_OFFSET);

而对于分配:
gpio_enable_addr = gpio_addr + _PIO_OFFSET - (_PA_OFFSET) + _PIO_ENABLE;

阅读此内容:从用户空间访问物理地址 - Justin J.
1
你不能直接从应用程序空间访问它们,但是你可以使用mmap来穿过操作系统,或者编写内核驱动程序或运行baremetal,但按照现有的写法,应该会出现段错误。 - old_timer
2个回答

7

您无法直接访问寄存器,因为Linux使用MMU,这会为您的应用程序创建虚拟地址空间,该空间不同于物理MCU地址空间,访问虚拟地址空间之外的内容会导致分段错误。

在Linux中访问这些寄存器的唯一方法(如果您不想编写内核驱动程序)是将文件/dev/mem作为文件打开并使用mmap进行映射。

例如,我有一个小型的Python库,用于访问Atmel SAM MCU上的GPIO寄存器gpiosam。您可以借鉴并将其移植到C语言。


非常感谢 @vlk!我现在可以编写一个合适的mmap了!但是我对你的库有一些疑问。1) 我真的不理解 _reg_set 中的 Gpio._mm[self._addr + register:self._addr + register + 4] = struct.pack('<L', data),这只是将引脚位设置为1。但是 register:self._addr 是什么?它是 0xFFFFF000 吗?为什么要 +4 - Tagadac
  1. 在数据手册中,我们可以读到:在PIO_PUSR中读取1表示禁用上拉,读取0表示启用上拉。。所以 return (self._reg_get(Gpio._PULL_DOWN_STATUS) & self._bitval) > 0 应该是 return **not** (self._reg_get(Gpio._PULL_UP_STATUS) & self._bitval) > 0 或类似的吧?这样如果位寄存器为1,你就有了FALSETRUE。对吗?(下拉也一样)
- Tagadac
为什么将 _PIO_OFFSET0xFFFFF000 更改为 0xFFFFFA00(Bank D)会导致 mmap: Invalid argument 错误?感谢您的帮助! - Tagadac
请查看EDIT以获取**3)**的解决方案。 - Tagadac
1
你好,感谢您的关注, 1)因为Gpio._mm是一个字节(8位)数组,我需要将32位数字放入该数组中,“Gpio._mm[self._addr + register:self._addr + register + 4]”在所选地址之间返回4个字节数组,范围为“self._addr + register”到“self._addr + register + 4” 2)是的,您说得对,这是我的驱动程序中的错误。我稍后会修复它,谢谢! 3)偏移量必须对齐到页面大小:4KB,这就是为什么我使用self_addr的原因。 - vlk
好的,我明白了。谢谢! - Tagadac

7

busybox devmem

busybox devmem是一个小型的CLI实用程序,它映射/dev/mem

您可以在Ubuntu中使用以下命令获取它:sudo apt-get install busybox

用法:从物理地址0x12345678读取4个字节:

sudo busybox devmem 0x12345678

0x9abcdef0写入该地址:

sudo busybox devmem 0x12345678 w 0x9abcdef0

以下是一些有关如何测试它的提示:从用户空间访问物理地址

还在这里提到:https://unix.stackexchange.com/questions/4948/shell-command-to-read-device-registers


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接