Section 19.1 GPIO I/O Memory
The peripheral address space, \(\hex{0x3e000000}\)–\(\hex{0x3effffff}\) on the Raspberry Pi 3, cannot be accessed directly from an application program. The Linux kernel includes a driver to a special character file, /dev/mem
, which is a mirror of main memory. Similar to any file, we can open it, read bytes from or write bytes to it, and close it. The “position” of each byte in /dev/mem
is the byte address in physical memory. In principle, we could program the GPIO device by opening /dev/mem
, moving a pointer to the desired location of a GPIO port, and writing the appropriate value in that byte location.
As you can probably guess, this would create a serious security flaw for ordinary user programs because the program could access any byte in memory. To provide some security protection, only programs with root
access can open the /dev/mem
file. In Raspbian this is done by preceding the program's name with sudo
when executing it.
Raspbian provides even more security by including a driver to the /dev/gpiomem
file. This file only mirrors the memory associated with the GPIO device. User programs that access the /dev/gpiomem
do not need to be executed with the sudo
modifier.
Linux (and most other modern operating systems) use a memory management scheme that is based on virtual memory addresses. When you create a program, the loader assigns virtual addresses to all the memory locations. All memory references in the program are based on the virtual memory address space. Program code is divided into Pages of equal size. As an application executes, the pages containing the currently needed code and data are loaded into physical memory. The Linux kernel automatically translates the virtual memory addresses into the corresponding physical memory addresses, which depend on where the page was loaded.
We need to do the inverse when referencing I/O addresses. I/O devices are assigned physical memory addresses, which the Linux kernel prevents user programs from accessing. We need to map this physical memory into the program's virtual addressing scheme so that the program can access it. Listing 19.1.1 shows how this is done.
@ IOmemory.s @ Opens the /dev/gpiomem device and maps GPIO memory @ into program virtual address space. @ 2017-09-29: Bob Plantz @ Define my Raspberry Pi .cpu cortex-a53 .fpu neon-fp-armv8 .syntax unified @ modern syntax @ Constants for assembler @ The following are defined in /usr/include/asm-generic/fcntl.h: @ Note that the values are specified in octal. .equ O_RDWR,00000002 @ open for read/write .equ O_DSYNC,00010000 @ synchronize virtual memory .equ __O_SYNC,04000000 @ programming changes with .equ O_SYNC,__O_SYNC|O_DSYNC @ I/O memory @ The following are defined in /usr/include/asm-generic/mman-common.h: .equ PROT_READ,0x1 @ page can be read .equ PROT_WRITE,0x2 @ page can be written .equ MAP_SHARED,0x01 @ share changes @ The following are defined by me: .equ PERIPH,0x3f000000 @ RPi 2 & 3 peripherals @ .equ PERIPH,0x20000000 @ RPi zero & 1 peripherals .equ GPIO_OFFSET,0x200000 @ start of GPIO device .equ O_FLAGS,O_RDWR|O_SYNC @ open file flags .equ PROT_RDWR,PROT_READ|PROT_WRITE .equ NO_PREF,0 .equ PAGE_SIZE,4096 @ Raspbian memory page .equ FILE_DESCRP_ARG,0 @ file descriptor .equ DEVICE_ARG,4 @ device address .equ STACK_ARGS,8 @ sp already 8-byte aligned @ Constant program data .section .rodata .align 2 device: .asciz "/dev/gpiomem" fdMsg: .asciz "File descriptor = %i\n" memMsg: .asciz "Using memory at %p\n" @ The program .text .align 2 .global main .type main, %function main: sub sp, sp, 16 @ space for saving regs str r4, [sp, 0] @ save r4 str r5, [sp, 4] @ r5 str fp, [sp, 8] @ fp str lr, [sp, 12] @ lr add fp, sp, 12 @ set our frame pointer sub sp, sp, STACK_ARGS @ sp on 8-byte boundary @ Open /dev/gpiomem for read/write and syncing ldr r0, deviceAddr @ address of /dev/gpiomem ldr r1, openMode @ flags for accessing device bl open mov r4, r0 @ use r4 for file descriptor @ Display file descriptor ldr r0, fdMsgAddr @ format for printf mov r1, r4 @ file descriptor bl printf @ Map the GPIO registers to a virtual memory location so we can access them str r4, [sp, FILE_DESCRP_ARG] @ /dev/gpiomem file descriptor ldr r0, gpio @ address of GPIO str r0, [sp, DEVICE_ARG] @ location of GPIO mov r0, NO_PREF @ let kernel pick memory mov r1, PAGE_SIZE @ get 1 page of memory mov r2, PROT_RDWR @ read/write this memory mov r3, MAP_SHARED @ share with other processes bl mmap mov r5, r0 @ save virtual memory address @ Display virtual address mov r1, r5 ldr r0, memMsgAddr bl printf mov r0, r5 @ memory to unmap mov r1, PAGE_SIZE @ amount we mapped bl munmap @ unmap it mov r0, r4 @ /dev/gpiomem file descriptor bl close @ close the file mov r0, 0 @ return 0; add sp, sp, STACK_ARGS @ fix sp ldr r4, [sp, 0] @ restore r4 ldr r5, [sp, 4] @ r5 ldr fp, [sp, 8] @ fp ldr lr, [sp, 12] @ lr add sp, sp, 16 @ restore sp bx lr @ return .align 2 @ addresses of messages fdMsgAddr: .word fdMsg deviceAddr: .word device openMode: .word O_FLAGS memMsgAddr: .word memMsg gpio: .word PERIPH+GPIO_OFFSET
When I ran this program I got (your numerical output may differ):
pi@rpi3:~/bookProgs/chap19 $sudo ./IOmemory
File descriptor = 3
Using memory at 0x76f77000
The open
function requires two arguments:
A pointer to the name of the device/file to be opened.
A mode specifying how the opened device/file can be used by the program.
The name of the special file we are opening is “/dev/gpiomem”. The flags specifying the open mode, O_FLAGS
, were chosen such that our program can read values from this I/O memory and write to it. The synchronization flags ensure that any values written to this memory will be completed before the program continues execution. It returns a file descriptor number.
The mmap
function takes six arguments:
The address where the device should be mapped into, which needs to be in our programming virtual memory space. Specifying \(0\) to
mmap
allows the operating system to pick an address for us.The amount of memory for the mapping. One page is sufficient.
A mode specifying the protection given to the mapped memory. This cannot conflict with the protection specified in the
open
operation. We want to read from and write to this memory.A mode specifying whether this mapped memory should or should not be shared with other processes. We will allow sharing. You may wish to have more than one program accessing the GPIO at the same time.
The file descriptor to the device being mapped. This came from our call to
open
.The physical memory location of the I/O device.
There are two steps for finding the physical memory location of the GPIO device:
Determine the address where peripherals begin. It is \(\hex{0x3e000000}\) on my Raspberry Pi 3. The program in Exercise 18.6.1 will do this for your Raspberry Pi.
Obtain the relative address of the GPIO device from the beginning of the peripheral address.
The relative address of the GPIO device is found in the BCM2835 datasheet [6]. You will be working with three addresses when reading this datasheet:
VideoCore bus addresses are the ones given in the BCM2835 datasheet.
Physical device addresses are where the GPIO registers are actually hardwired.
Virtual memory addresses are the ones that your program will use to access the GPIO registers.
The VideoCore bus addresses all begin at \(\hex{0x7e000000}\text{,}\) so you need to subtract this number from the addresses of devices listed in the datasheet in order to obtain the relative address of the specific device you wish to program. This relative address will be combined with your program's virtual memory address to access the device.
The GPIO has 41 registers. Table 6-1 in the BCM2835 datasheet shows that they start at \(\hex{0x7e200000}\text{,}\) so the GPIO is located at \(\hex{0x200000}\) relative to the beginning of I/O memory. This is shown in Listing 19.1.1 with the statement:
.equ GPIO_OFFSET,0x200000 @ start of GPIO device
And we then go on to specify the physical address of the GPIO device with:
gpio: .word PERIPH+GPIO_OFFSET
where PERIPH
was previously determined as explained above and defined:
.equ PERIPH,0x3f000000 @ RPi 2 & 3 peripherals
The mmap
function returns the virtual address that the GPIO device has been mapped to. We can now program the device.
Note that before ending this function I unmap the GPIO memory and close the /dev/gpiomem
device:
mov r0, r5 @ memory to unmap mov r1, PAGE_SIZE @ amount we mapped bl munmap @ unmap it mov r0, r4 @ /dev/gpiomem file descriptor bl close @ close the file
Both these actions would occur automatically when ending the program, so they may seem unnecessary since the /dev/gpiomem
device is opened and the GPIO memory is mapped in the main
function in this program. But a common programming error is to open a device or allocate memory in a subfunction and not close the device or deallocate the memory. If the subfunction is called multiple times in the program, multiple instances of the open device or allocated memory are the result. Good rules of thumb are:
Close devices in the same function where you opened them.
Deallocate memory in the same function where you allocated it.
Now that we know how to access GPIO memory, we will see how to use specific pins in Section 19.2.