This blog post will introduce my GSOC project for this year, and detail some current ideas for its implementation. If everything goes better than expected there is some bonus work planned, but for now the project is as follows.
For this project I will merge the last GSoC work and include/conclude some of the improvements made post-GSoC (can be seen at https://github.com/asuol/rtems/commit/eca62b41521da2e606f38320d31c83ec82e30ab6), namely:
Ability to have multiple arguments passed to an user-defined interrupt handler
All ISR handlers now receive a void pointer, which is to be used by the user to pass an user-defined struct with any data to be used by their user-defined ISR routine.
Have each interrupt being handled on a task
There are three handling levels through which an interrupt must pass until it is addressed:
- A generic and global (unique) ISR (generic_isr), installed with rtems_interrupt_handler_install the first time any interrupt is enabled in the API (through gpio_enable_interrupt), which is responsible for clearing the interrupt on the Pi and calling the pin’s ISR task (see following point);
- A generic task (generic_handler_task), which is created for every pin when the first interrupt is enabled on that pin (through gpio_enable_interrupt) and is responsible for calling the linked-list of user-defined ISRs (see next section);
- An used-defined ISR, which will finally address the interrupt (more details in the next section).
In this setup the interrupt is cleared in step 1, meaning that the time spent in ISR context is minimal. The tasks referred in step 2 and 3 are started right after they are created and put on wait (with rtems_event_receive), until they receive an event from the level below. After each processed interrupt they return to their waiting state, meaning that these tasks are always ready to start.
Each user-defined ISR is associated with a pin through a call to gpio_interrupt_handler_install during gpio_enable_interrupt, and the interrupt is only enabled on hardware in the end of gpio_enable_interrupt.
Ability to have multiple devices connected to a single GPIO pin
This allows for instance two devices to share a pin for interrupt handling. I started to work on this after the last GSOC, and the objective is to have multiple ISR routing (one per device) associated with a single GPIO pin, stored on a linked list associated with each individual pin. This allows a single GPIO pin to be shared among several devices for interrupt handling, with the requirement that the type of interrupt has to be the same for all connected devices (since the GPIO pin can only expect a single type of interrupt). This means that if more than one device is connected to a pin and an interrupt occurs it is impossible to know which device caused it.
For that reason, when an interrupt is detected the pin’s linked list of ISR routines is iterated and all user-defined ISRs there are called, and expected to return a gpio_irq_state informing if the device associated with that handler acknowledged or not the interrupt. In summary, an user-defined handler must:
- Exist for every connected device on a shared pin (and only one per device);
- Start by probing their device to check if it was the source of the interrupt. If the device acknowledges that it has indeed generated an interrupt, proceed with the interrupt handling and return an IRQ_HANDLED status at the end. If not return IRQ_NONE.
The current setup defines that each ISR on the linked list is called sequentially, but it can be done in parallel since each ISR is a separate task. When an ISR task returns the state information a counter is used to count how many ISR’s acknowledged the interrupt. If none acknowledged it flag it as a spurious interrupt.
Up to now the GPIO API has no locking mechanism in place. The main idea is to have a mutex lock for each GPIO pin, so that each API function starts by acquiring the locks to any pins it will be using at the start, and releasing them before returning.
This mutex would be included in the rpi_gpio_pin structure defined in include/gpio.h. It should also be made sure that the API is started only once, so the currently used static bool var “is_initialized” should be atomic (maybe using C11 atomic operations).
This will allow the GPIO functions to work on a multi-thread (and with PI 2, on a SMP) setup.
For I2C there is a need to port the last year GSOC I2C code to new the new I2C API which was introduced in November (https://lists.rtems.org/pipermail/devel/2014-November/009065.html), deprecating the previously used libi2c API for I2C buses. This new API, which is compatible with the Linux user-space API, divides in three modules:
- I2C Bus Driver – Implements the generic bus code, which can be used by an I2C device to access/operate the bus (initiate/dentroy the bus, message tranfers, obtain/release the bus, …). Any I2C bus must be setup by using or adapting this driver, so the last year’s I2C code for the Raspberry Pi BSP will be used on this setup;
- I2C Device Driver – The device API, which every I2C device will need to implement (device/device type dependent). Last year’s test device drivers may be ported to this new model to test the bus or new device drivers may be developed to test the bus;
- Linux I2C User-Space API – This allows an user-space program to access an I2C bus or operate an I2C device.
The last year SPI code uses the libi2c API, which is still the API used for SPI buses although it was deprecated for I2C (as explained before). Although desired, there is no plan at this point for a new SPI API in RTEMS, so at this point the plan is to merge the last year SPI code (after some more testing, namely of the bi-directional mode which was implemented last year but not tested because the device I had to test it was somehow connected to the wrong power supply (ooops)) after the GPIO work is done.
This section is more or less a transcript from the proposal, which explains the rationale behind my plan of working with the EMMC module for the SD host and card support.
The onboard SD card reader is connected internally to the following GPIOs (http://www.raspberrypi.org/documentation/hardware/raspberrypi/schematics/README.md):
GPIO47 – SD_CARD_DET – SD card detection line;
GPIO48 – SD_CLK_R – SD card clock line;
GPIO49 – SD_CMD_R – SD card command line;
GPIO50 – SD_DATA0_R – SD card data line 0;
GPIO51 – SD_DATA1_R – SD card data line 1;
GPIO52 – SD_DATA2_R – SD card data line 2;
GPIO53 – SD_DATA3_R – SD card data line 3.
These GPIOs can be operated (transparently) through the EMMC module, serving as an interface to the SD host and card and should be used to operate the SD cards in SD mode. To operate the card in SPI mode these GPIOs must be bit-banged to provide SPI communication, since these GPIOs are not connected to any SPI hardware (lowering even further the performance, as SPI mode is slower than SD).
In SPI mode the actual GPIO setup would be:
- GPIO47 – GPIO input to detect the presence of the SD card (because in the PI the card must be always present, this can be done as a reassurance that the host controller recognizes the card);
- GPIO48 – SD card clock line though a bit-banged spi clock line;
- GPIO49 – SD card command line through a bit-banged spi MOSI (master-out-slave-in) data line;
- GPIO53 – Chip select through a bit-banged spi chip select line.
SPI access to the PI SD card onboard reader is unusual, but there are some reports of success. As for the SD protocol in SPI mode, in the case of RTEMS there is a SD libchip driver using SPI mode (libchip/i2c/spi-sd-card.c) which could be used as a base, and improved if needed. The first step would be, however, to provide a big-banged SPI interface on the mentioned GPIOs using the libi2c API (which is still the SPI API used in RTEMS, and which is also at the base of the spi-sd-card driver).
An alternative would be to use the SD mode with the raspberry EMMC module, which I have already used in the past with success using the SD Host controller simplified specifications (v3.00)and the SD Physical layer simplified specifications (v3.01). This time a better approach would be to use the FreeBSD SD/MMC stack (such as the one Sebastian Huber ported to RTEMS (https://git.rtems.org/sebh/rtems-libusb.git/tree/rtems/freebsd/dev/mmc?id=3c82a1500da3192de2504a1360e065fd84a1f3a0)) which is also based on the simplified specifications, since it provides already a proven and tested implementation of the protocol instead of re-inventing the wheel as I did at the time (https://bitbucket.org/asuol/rtems-graduation-project/src). One thing to note is that the FreeBSD SD/MMC stack is currently on Sebastian’s private repository and not in libbsd yet, so that would have to be addressed.
Independently of the SD operating mode chosen the card’s partitions must be read and the filesystems loaded, so RTEMS can read and write from/to the card (in the past I have tested with a single FAT partition by reading an existing file’s content and writing a new file with some other content and checking the end result on a computer).
Information about the model, revision, memory, clocks, power state, temperature and other board specific details and managed in the Raspberry Pi by the Video Core (the Pi GPU) which is a black box within the Pi. The only way to communicate with the Video Core is through the Mailbox interface (https://asuolgsoc2014.wordpress.com/2014/05/26/mailbox-interface/), which works by sending request tags and retrieving the answers. In this interface there are several channels, such as the “Framebuffer” channel and the “Property tags (ARM -> VC)”, the latter being the channel of interest for model identification (https://github.com/raspberrypi/firmware/wiki/Mailbox-property-interface).
Knowing the board model and revision can help adjusting configurations that are revision specific, such as the I2C bus on the GPIO header (which uses the GPIOs 0/1 on revision 1 and GPIOs 2/3 on revision 2). It can also detect the actual memory size of the board, so it can be adjusted accordingly, and may also be used to get DMA channels.
A small set of functions such as rpi_get_board_revision() could return either the integer value of the revision (e.g.: 0009) or the human-readable value (in this case model A revision 2.0 (http://elinux.org/RPi_HardwareHistory#Board_Revision_History)), or could just print one of the values, or do all three depending on a parameter. Model identification and other relevant data would be probed at startup, and at any time if needed through a small set of functions. Functions can also be created to get SoC temperature readings or to dinamically adjust memory.
This identification would be made during the BSP startup at the bsp_predriver_hook function (RPI_BSP/startup/bspstart.c), along with the bus initializations according with the user’s preferences defined with BSP_OPTS.