PiOS: ARM Timer and Interrupts
For quite some time I've been working on a prototypic operating system, PiOS (previously PilotOS) [1]. I've been writing articles earlier about processes [2][3] and I want to discuss the current state here. Additional to a rewrite of most of the code, so removing the assembler-code and rewriting its functionality in C, I've also created some drivers for i2c and a LCD character display. The latter two I cannot test at the moment, so these serve as untested prove of concept-drivers. Today I have implemented the ARM Timer [4, pg. 196] and have received my first interrupts.
At first I normally add a map for the registers of the peripheral, which are bound to an address in the memory, so I have a data structure with names (normally a struct) to address the registers (see Memory Mapped I/O). The ARM-timer has nine 32-Bit registers:
struct _pios_arm_timer_t
{
uint32_t load; ///< load a specific value into the counter, after counted-down an IRQ occurs
uint32_t value; ///< a value, counted down. On 0 IRQ is set and the value is reloaded from reload-register
uint32_t control; ///< control register, which allows for setting some settings
uint32_t irqack; ///< write only for clearing the IRQ-line
uint32_t irqraw; ///< is an interrupt pending? (i.e. LSB is set to 1)
uint32_t irqmasked; ///< is the IRQ pending bit set and the interrupt enable bit?
uint32_t reload; ///< copy of load, but writing will not trigger an overwrite of value, just when value reaches 0
uint32_t prescale; ///< setting a prescaler for the timer - LSB 10 Bits (timer_clock = apb_clock/(pre_divider+1))
uint32_t freerunning; ///< a free running value (has its own prescaler in control-register)
} typedef pios_arm_timer_t;
This timer is quite simple. It has a free running register, i.e. it will be increased with every timer-tick (note the prescaler!). Also the timer can be used as an alarm-clock. One can save a value into the register load, which will be decremented on every timer-tick, until it reaches zero. When zero an interrupt will be triggered and the value, last written to load (or reload) will be copied to value once again.
The settings of the timer can be controlled via the control-Register. The semantics of the bits are demonstrated in the BCM2835 peripheral manuel. The bits can be set as C-constants:
#define PIOS_ARM_TIMER_32BIT 2
#define PIOS_ARM_TIMER_PRESCALE_1 (0 << 2)
#define PIOS_ARM_TIMER_PRESCALE_16 (1 << 2)
#define PIOS_ARM_TIMER_PRESCALE_256 (2 << 2)
#define PIOS_ARM_TIMER_PRESCALE_UNDEF (3 << 2)
#define PIOS_ARM_TIMER_IRQ_ENABLE (1 << 5)
#define PIOS_ARM_TIMER_ENABLE (1 << 7)
#define PIOS_ARM_TIMER_HALT_IN_DEBUG (1 << 8)
#define PIOS_ARM_TIMER_FREERUNNING_ENABLE (1 << 9)
#define PIOS_ARM_TIMER_FREERUNNING_PRESCALER(a) ((a&0xff) << 16)
The freerunning-Register has a different prescale-value than the normal alarm-clock mode of the Timer, so it can be viewed more or less independently. Note that the according Macro takes an 8-Bit value and shifts it by 16 places to the left - a better solution would be nice and will maybe come some when in the future(tm).
Understanding how does a Timer work
In principle a timer uses an oscillator, for example an Quarz-oscillator. This crystal is motivated to vibrate slightly by a voltage (see piezo-electrical effect). This vibration leads to a charge generating between the two ends of the crystal. It vibrates more or less at a constant and exact frequency and generates therefore a periodic signal, which is ideally already a square-wave. But to be sure one should send it through something like a Schmidt-trigger, wo generate a good square-wave. The Schmidt-trigger can also generate voltage-levels so that the digital hardware will recognize them correctly as HIGH or LOW-levels.
The timer or counter itself is a series of Flip-Flops (i.e. 1 Bit-memory). One can use T-Flip-Flops (Trigger-Flip-Flops), which will change there value when the input-signal has the logical level of 1. These memory units are connected so that, when the least significant bit (LSB) changes from 1 to 0, the next more significant unit changes its value. This can be done by using the output of the LSB-Flip-Flop as input for the clock-signal of the next higher FF. Flip-Flops normally react to an edge in the clock-signal, not to HIGH/LOW-levels.
The Prescaler
A prescaler or predivider divides a faster voltage-signal, for example as it is output by the Schmidt-trigger, so it reaches a slower frequency. For division by a factor of two, four, eight, etc. one can use Flip-Flops, which are put before the counter. The output of this prescale-circuit is then used as the input of the counter. The prescaler-Flip-Flops in a way "stretch" the HIGH and LOW-level-phases of the input-signal for the counter.
Interrupts
The ARM-processor of the Raspberry Pi can handle exactly eight interrupt sources. These Interrupts do not have to be generated by external hardware, but can also be generated by the CPU itself. The processor needs to know what has to be done, when an interrupt occurs. Therefore a so called Interrupt Table is put into the memory, which denotes what has to be done, or what address has to be put into the Programm Counter (pc) when a specific interrupt occurs. This table is put into the physical address 0x0000 0000, i.e. exactly at the Null-point of the memory and is 8 by 8 Bytes big (2*4 Byte per interrupt-source). As far as I know the interrupt table has to be set in software and cannot be generated at Compile-time, like with the AVR-processors. The following code (from [5]) works for that:
start:
ldr pc, _reset_h
ldr pc, _undefined_instruction_vector_h
ldr pc, _software_interrupt_vector_h
ldr pc, _prefetch_abort_vector_h
ldr pc, _data_abort_vector_h
ldr pc, _unused_handler_h
ldr pc, _interrupt_vector_h
ldr pc, _fast_interrupt_vector_h
_reset_h: .word _reset_
_undefined_instruction_vector_h: .word undef_vector
_software_interrupt_vector_h: .word swi_vector
_prefetch_abort_vector_h: .word abort_vector
_data_abort_vector_h: .word abort_vector
_unused_handler_h: .word _reset_
_interrupt_vector_h: .word irq_vector
_fast_interrupt_vector_h: .word fiq_vector
_reset_:
mov r0, #0x8000
mov r1, #0x0000
ldmia r0!,{r2, r3, r4, r5, r6, r7, r8, r9} /** load 32 Byte worth of data **/
stmia r1!,{r2, r3, r4, r5, r6, r7, r8, r9} /** store 32 Byte **/
ldmia r0!,{r2, r3, r4, r5, r6, r7, r8, r9}
stmia r1!,{r2, r3, r4, r5, r6, r7, r8, r9}
In a first attempt I created the following empty interrupt service routines, which uses the UART for a simple output and hung in an endless loop:
void __attribute__((interrupt("UNDEF"))) undef_vector(void)
{
pios_uart_puts ("UNDEF :( \n");
while( 1 )
{
/* Do Nothing! */
}
}
void __attribute__((interrupt("IRQ"))) irq_vector(void)
{
pios_uart_puts (" -> ! IRQ :) \n");
while( 1 )
{
/* Do Nothing! */
}
}
void __attribute__((interrupt("FIQ"))) fiq_vector(void)
{
pios_uart_puts ("FIQ :( \n");
while( 1 )
{
/* Do Nothing! */
}
}
void __attribute__((interrupt("SWI"))) swi_vector(void)
{
pios_uart_puts ("SWI :( \n");
while( 1 )
{
/* Do Nothing! */
}
}
void __attribute__((interrupt("ABORT"))) abort_vector(void)
{
pios_uart_puts ("ABORT :(\n");
while( 1 )
{
/* Do Nothing! */
}
}
Enable Interrupts
So our processor knows what to do, when an interrupt arrives. But it will not do anything, because we need to enable interrupts in our processor. So we can disable interrupts, i.e. advice our processor to ignore interrupts (as well as fast interrupts). This is default, when the Raspberry Pi is switched on. [7] states, that 0x80 is the bit for disabling interrupts in our processor. So we can enable the interrupt handling by clearing the value of that bit.
pios_irq_enable:
mrs r0, cpsr
bic r0, r0, #0x80
msr cpsr_c, r0
mov pc, lr
Note, that the Programm Status Register (cpsr) can only be modified by specific instructions (mrs, msr). These instructions are only allowed in specific privilege settings of the processor and will lead to an interrupt in the user-mode. User-programs should not be allowed to edit the state-register of our processor and are therefore not allowed to change the mode.
Interrupt-Controller
The ARM1176jzf-s, which is built into the Raspberry Pi, uses an additional trick. The ARM-core itself can only handle 8 interrupt sources. But interrupts can come from several external devices and may have different reasons. To be able to enable or disable interrupts more precisely the Pi has an interrupt controller, which works together with the ARM-code.
According to the manual [4, pg. 109] the controller can be found at 0x2000 B2000 (note: the address in the manual is the one which is used on the ARM-bus, not the one used by the ARM-code, i.e. our software). The correct registers for enabling or disabling interrupt sources are located here. We want to enable the timer as interrupt source, so we write the correct value into the Base Interrupt Enable register.
//*(0x2000B218) = (1<<0)
RPI_GetIrqController()->Enable_Basic_IRQs = RPI_BASIC_ARM_TIMER_IRQ;
Set-up the Alarm Clock
Before the timer-interrupt is enabled we should set-up the alarm clock. We want to set the correct start value into the load-Register and activate the Timer with the correct value for us (32-Bit mode, with enabled interrupts and enabled timer):
// pios_arm_timer->load = load;
pios_arm_timer_setLoad ( 0x2000 );
/**
* if ( !(prescaler == PIOS_ARM_TIMER_PRESCALE_1 || prescaler == PIOS_ARM_TIMER_PRESCALE_16
* || prescaler == PIOS_ARM_TIMER_PRESCALE_256 || prescaler == PIOS_ARM_TIMER_PRESCALE_UNDEF) )
* {
* prescaler = PIOS_ARM_TIMER_PRESCALE_256;
* }
* uint32_t val = PIOS_ARM_TIMER_32BIT | prescaler | (irq ? PIOS_ARM_TIMER_IRQ_ENABLE : 0) | PIOS_ARM_TIMER_FREERUNNING_ENABLE | (enable ? PIOS_ARM_TIMER_ENABLE:0);
* pios_arm_timer->control = val;
**/
pios_arm_timer_init ( true, PIOS_ARM_TIMER_PRESCALE_256, true );
Nun we receive interrupts from our timer.
[1] https://github.com/naums/PiOS
[2] https://www.stefannaumann.de/en/2016/05/pilotos-processes/
[3] https://www.stefannaumann.de/en/2016/05/pilotos-processes-2-process-states-and-mutual-exclusion/
[4] https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2835/BCM2835-ARM-Peripherals.pdf
[5] http://www.valvers.com/open-software/raspberry-pi/step04-bare-metal-programming-in-c-pt4/
[6] http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0333h/ch02s09s01.html
[7] http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0333h/I2837.html