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.
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.