WRITE and READ memory mapped device registers in Linux on ARM

14,060

Solution 1

You can't access registers directly, because Linux use MMU and this create for your application virtual address space which is different than physical MCU address space and access outside this virtual address space cause segmentation fault.

Only Way to access these registers in Linux (if you don't want to write kernel drivers) is to open file /dev/mem as file and map it with mmap

For example I have small python library for access GPIO registers on Atmel SAM MCU gpiosam. You can inspire and port it to C.

Solution 2

busybox devmem

busybox devmem is a tiny CLI utility that mmaps /dev/mem.

You can get it in Ubuntu with: sudo apt-get install busybox

Usage: read 4 bytes from the physical address 0x12345678:

sudo busybox devmem 0x12345678

Write 0x9abcdef0 to that address:

sudo busybox devmem 0x12345678 w 0x9abcdef0

See this for a few tips on how to test it out: Accessing physical address from user space

Also mentioned at: https://unix.stackexchange.com/questions/4948/shell-command-to-read-device-registers

Share:
14,060
Tagadac
Author by

Tagadac

I like turtles !

Updated on June 12, 2022

Comments

  • Tagadac
    Tagadac almost 2 years

    I am trying to read and write registers on my ARM9 (SAM9X25) following those steps : http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.faqs/ka3750.html
    I ended with the following code :

    #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; }
    


    I cross compiled my code in Ubuntu 16.04 like so arm-linux-gnueabi-gcc gpio.c -o gpio
    But I have a Segmentation Fault just after the printf during the execution of the program on my board.
    I know the addresses are right... So why do I have this error?
    Is it the good way ?
    Thank you for your help !

    SOLUTION :
    Thank you to @vlk I could make it work ! Here is a little example for toggling a 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;
    }
    

    EDIT :
    Answer for the 3) in the comments. You have to change the mmap and the assignations like so if you want it to work with all the offsets (i.e : mmap example):

    #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
    

    And the mmap :

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

    And for the assignation :

    gpio_enable_addr = gpio_addr + _PIO_OFFSET - (_PA_OFFSET) + _PIO_ENABLE;
    
  • Tagadac
    Tagadac almost 7 years
    Thank you very much @vlk ! I could write a proper mmap ! But I have some questions about your library. 1) I don't really understand ` Gpio._mm[self._addr + register:self._addr + register + 4] = struct.pack('<L', data)` in the _reg_set definition. This is just for setting the bit of the pin to 1. But what is register:self._addr ? Is it 0xFFFFF000? And why +4 ?
  • Tagadac
    Tagadac almost 7 years
    2) In the datasheet we can read : Reading a one in PIO_PUSR means the pull-up is disabled and reading a zero means the pull-up is enabled.. So ` return (self._reg_get(Gpio._PULL_DOWN_STATUS) & self._bitval) > 0` should be return **not** (self._reg_get(Gpio._PULL_UP_STATUS) & self._bitval) > 0 or similar no ? In this way if bit register is 1 you have FALSE and TRUE instead. No? (and same for pull-down)
  • Tagadac
    Tagadac almost 7 years
    3) Why changing _PIO_OFFSET from 0xFFFFF000 to 0xFFFFFA00 (Bank D) result in mmap: Invalid argument ? Thank you for your help !
  • Tagadac
    Tagadac almost 7 years
    See EDIT for 3) solution.
  • vlk
    vlk almost 7 years
    Hello, thanks for interest, 1) because Gpio._mm is byte (8bit) array and I need to put to this array 32bit number, Gpio._mm[self._addr + register:self._addr + register + 4] return 4 bytes array at selected address between self._addr + register to self._addr + register + 4 2) Yes, you are right, It is bug in my driver. I will fix this later, thanks! 3) offset must be aligned to page size: 4KB, this is why I use self_addr.
  • Tagadac
    Tagadac almost 7 years
    Ok, I understand. Thank you !