WRITE and READ memory mapped device registers in Linux on ARM
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
Comments
-
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 soarm-linux-gnueabi-gcc gpio.c -o gpio
But I have aSegmentation Fault
just after theprintf
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 almost 7 yearsThank 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 isregister:self._addr
? Is it0xFFFFF000
? And why+4
? -
Tagadac almost 7 years2) 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 bereturn **not** (self._reg_get(Gpio._PULL_UP_STATUS) & self._bitval) > 0
or similar no ? In this way if bit register is1
you haveFALSE
andTRUE
instead. No? (and same for pull-down) -
Tagadac almost 7 years3) Why changing
_PIO_OFFSET
from0xFFFFF000
to0xFFFFFA00
(Bank D) result inmmap: Invalid argument
? Thank you for your help ! -
Tagadac almost 7 yearsSee EDIT for 3) solution.
-
vlk almost 7 yearsHello, 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 betweenself._addr + register
toself._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 almost 7 yearsOk, I understand. Thank you !