uCLinux: System Timer
Time management is crucial to Linux kernel. A massive of kernel functions are time-driven and therefore it is heavily dependent on time management. In Linux kernel, time management is handled by a piece of programmable hardware, known as system timer. The main job of system timer is to generate an interrupt at a fixed frequency. A dedicated code, known as timer interrupt routine is executed for each issued interrupt to update system time and perform periodic work.
In hardwave abstraction, timer is implemented using programmable counter. To use the counter for time-keeping, an initial value is first loaded into it, and the counter is started by enabling it to either increment or decrement mode. An interrupt is generated each time when the counting reaches zero. Depending on architecture, there would be different hardware module used for system timer. In the case of Microblaze, On-chip Peripheral Bus (OPB) Timer is central of time management in Linux kernel.
Now, we will turn our attention to timer module used in Microblaze architecure. OPB Timer is a 32-bit timer module that attaches to the Microblaze peripheral bus. The timer has a load register that is used to store an the initial value, and a control/status register that is used to store the operating mode and status of the timer. There are two operating modes available in OPB timer: capture, generate and PWM mode. In Linux kernel, only generate mode is used, and the characteristics of this mode is summarized as follow:
- The initial value loaded into the load register is called the generate value.
- On startup, the generate value in the load register must be loaded into the counter by setting the Load bit in the Timer Control Status Register (TCSR).
- When the ARHT bit (Auto Reload/Hold) is set to ’1’ and the counter rolls over from all ’1’s to all ’0’s when counting up, or conversely from all ’0’s to all ’1’s when counting down, the generate value in the load register will be automatically reloaded into the counter. and the counter will continue to count.
- When the ARHT bit (Auto Reload/Hold) is set to ’0’ and the counter rolls over from all ’1’s to all ’0’s, when counting up, or conversely, from all ’0’s to all ’1’s, when counting down, the counter will hold at the current value and will not reload the generate value.
- The counter can be set up to count either up or down as determined by the selection of the UDT bit in the TCSR .
The timer need to be initialized before it can be used for time management. The kernel code that perform the initialization is opb_timer.c:
#define TCSR0 (0x00)
#define TLR0 (0x04)
#define TCR0 (0x08)
#define TCSR_MDT (1<<0)
#define TCSR_UDT (1<<1)
#define TCSR_GENT (1<<2)
#define TCSR_CAPT (1<<3)
#define TCSR_ARHT (1<<4)
#define TCSR_LOAD (1<<5)
#define TCSR_ENIT (1<<6)
#define TCSR_ENT (1<<7)
#define TCSR_TINT (1<<8)
#define TCSR_PWMA (1<<9)
#define TCSR_ENALL (1<<10)
#define BASE_ADDR CONFIG_XILINX_TIMER_0_BASEADDR
#define opb_timer_base BASE_ADDR
printk(KERN_INFO "TIMER at 0x%08lXn",
(unsigned long) BASE_ADDR);
/* set the initial value to the load register */
iowrite32(CONFIG_XILINX_CPU_CLOCK_FREQ/HZ, opb_timer_base + TLR0);
/* load the initial value */
iowrite32(TCSR_LOAD, opb_timer_base + TCSR0);
iowrite32(TCSR_TINT|TCSR_ENT|TCSR_ENIT|TCSR_ARHT|TCSR_UDT, opb_timer_base + TCSR0);
With the introduction of opb_timer, the codes are self-descriptive. The first three lines are used to define the address offset for Control Status, Load, and Timer register. Next, the bit location of Control Status register is defined. The base address of OPB timer is defined in BASE_ADDR using the config option of CONFIG_XILINX_TIMER_0_BASEADDR.
In system_timer_init(), a message notifying the timer address is first displayed, followed by writing an initial value of CONFIG_XILINX_CPU_CLOCK_FREQ/HZ into opb_timer_base + TLR0, which is address of load register. The initial value is loaded by writing TCSR_LOAD into address of Control Status register, given by opb_timer_base + TCSR0. Lastly, the timer is started by setting TCSR_TINT, TCSR_ENT, TCSR_ENIT, TCSR_ARHT and TCSR_UDT in Control/Status register. The logical operation in TCSR_TINT|TCSR_ENT|TCSR_ENIT|TCSR_ARHT|TCSR_UDT will enable the interrupt and timer, setting the timer to reload generate value and act as down counter.