ARM Integrator-CP IRQTimerAndPIC
ARM Integrator-CP IRQ, Timer, And PIC
This demonstrates initializing the exceptions vector table, timer 0, and the programmable interrupt controller.
Tool |
---|
GCC |
LD |
OBJCOPY |
I used GCC 4.4.5 and GCC 4.8.2 targeting the ARM EABI. Also, Binutils 2.23.91.20131118 and 2.20.1.
Author
I Pancakes wrote this to help jump start you into developing for the ARM using QEMU or even a real piece of hardware. I have wrote software for both emulators and real hardware, but this has only been tested on QEMU so far. Please make any needed changes if you find problems. Also let me know at [email protected] if you find this useful, have comments, or suggestions.
You might want to double check the exceptions other than FIQ, IRQ, and SWINT because I may not be manipulating the LR register for a correct return if needed.
Also keep in mind that on a real board you may be flashing this code directly to FLASH (or ROM) (not RAM) and therefore you will not be able to write to global variables in your code (only read). You can avoid this by (1) not using global variables except only as read only. (2) Set the .bss and .data in the linker script to RAM, but then you will lose any initializers on that data. (3) Write a boot loader that loads this image in ELF (or similar) format. If it was placed in RAM there would be no problem.
QEMU will load an ELF, but I designed this to be used not only with an emulator but also with real hardware. With QEMU you can actually write to your global variables but on real hardware you will not if they reside in FLASH.
Another trick to maintain a global state. Is to designate an area of memory to serve as a header for instance:
#define KERNEL_HEAP 0x8000
#define KERNEL_HEAPSZ 0x1000
typedef struct _KERNEL_STATE {
HEAP heap;
uint32 variable1;
uint32 variable2;
} KERNEL_STATE;
KERNEL_STATE *ks;
ks = (KERNEL_STATE*)KERNEL_HEAP;
heapInit(&ks->heap);
heapAddBlock(&ks->heap, KERNEL_HEAP + sizeof(KERNEL_STATE), KERNEL_HEAPSZ - sizeof(KERNEL_STATE));
/* load other areas of memory if needed */
heapAddBlock(&ks->heap, OTHERAREA, OTHERAREASZ);
This allows you to designate an area of memory as part of your heap, while designating a structure to exist at the beginning. Basically, KERNEL_STATE becomes your structure of global variables.
Source
#ifdef B64
typedef unsigned long long uintptr;
#else
typedef unsigned int uintptr;
#endif
typedef unsigned long long uint64;
typedef unsigned int uint32;
typedef unsigned char uint8;
typedef unsigned short uint16;
#define ARM4_XRQ_RESET 0x00
#define ARM4_XRQ_UNDEF 0x01
#define ARM4_XRQ_SWINT 0x02
#define ARM4_XRQ_ABRTP 0x03
#define ARM4_XRQ_ABRTD 0x04
#define ARM4_XRQ_RESV1 0x05
#define ARM4_XRQ_IRQ 0x06
#define ARM4_XRQ_FIQ 0x07
#define CTRL_ENABLE 0x80
#define CTRL_MODE_FREE 0x00
#define CTRL_MODE_PERIODIC 0x40
#define CTRL_INT_ENABLE (1<<5)
#define CTRL_DIV_NONE 0x00
#define CTRL_DIV_16 0x04
#define CTRL_DIV_256 0x08
#define CTRL_SIZE_32 0x02
#define CTRL_ONESHOT 0x01
#define REG_LOAD 0x00
#define REG_VALUE 0x01
#define REG_CTRL 0x02
#define REG_INTCLR 0x03
#define REG_INTSTAT 0x04
#define REG_INTMASK 0x05
#define REG_BGLOAD 0x06
#define PIC_IRQ_STATUS 0x0
#define PIC_IRQ_RAWSTAT 0x1
#define PIC_IRQ_ENABLESET 0x2
#define PIC_IRQ_ENABLECLR 0x3
#define PIC_INT_SOFTSET 0x4
#define PIC_INT_SOFTCLR 0x5
#define PIC_FIQ_STATUS 8
#define PIC_FIQ_RAWSTAT 9
#define PIC_FIQ_ENABLESET 10
#define PIC_FIQ_ENABLECLR 11
#define KSTACKSTART 0x2000
#define KSTACKEXC 0x4000
void start(void);
/*
This could be non-standard behavior, but as long as this resides at the top of this source and it is the
first file used in the linking process (according to alphanumerical ordering) this code will start at the
beginning of the .text section.
*/
void __attribute__((naked)) entry()
{
asm("mov sp, %[ps]" : : [ps]"i" (KSTACKSTART));
/* send to serial output */
asm("mov r1, #0x16000000");
asm("mov r2, #65");
asm("str r2, [r1]");
/* call main kernel function */
asm("bl start");
}
#define KEXP_TOPSWI \
uint32 lr; \
asm("mov sp, %[ps]" : : [ps]"i" (KSTACKEXC)); \
asm("push {lr}"); \
asm("push {r0,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12}"); \
asm("mov %[ps], lr" : [ps]"=r" (lr));
#define KEXP_TOP3 \
uint32 lr; \
asm("mov sp, %[ps]" : : [ps]"i" (KSTACKEXC)); \
asm("sub lr, lr, #4"); \
asm("push {lr}"); \
asm("push {r0,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12}"); \
asm("mov %[ps], lr" : [ps]"=r" (lr));
#define KEXP_BOT3 \
asm("pop {r0,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12}"); \
asm("LDM sp!, {pc}^")
#define SERIAL_BASE 0x16000000
#define SERIAL_FLAG_REGISTER 0x18
#define SERIAL_BUFFER_FULL (1 << 5)
void k_serdbg_putc(char c)
{
while (*(volatile unsigned long*)(SERIAL_BASE + SERIAL_FLAG_REGISTER) & (SERIAL_BUFFER_FULL));
*(volatile unsigned long*)SERIAL_BASE = c;
}
uint32 arm4_cpsrget()
{
uint32 r;
asm("mrs %[ps], cpsr" : [ps]"=r" (r));
return r;
}
void arm4_cpsrset(uint32 r)
{
asm("msr cpsr, %[ps]" : : [ps]"r" (r));
}
void arm4_xrqenable_fiq()
{
arm4_cpsrset(arm4_cpsrget() & ~(1 << 6));
}
void arm4_xrqenable_irq()
{
arm4_cpsrset(arm4_cpsrget() & ~(1 << 7));
}
void k_exphandler(uint32 lr, uint32 type) {
uint32 *t0mmio;
uint32 swi;
k_serdbg_putc('H');
/* clear interrupt in timer HW so it will lower its line to the PIC
and therefore allow the PIC to lower its line to the CPU
if you do not clear it, an interrupt will
be immediantly raised apon return from this
interrupt
*/
t0mmio = (uint32*)0x13000000;
t0mmio[REG_INTCLR] = 1;
/*
Get SWI argument (index).
*/
if (type == ARM4_XRQ_SWINT) {
swi = ((uint32*)((uintptr)lr - 4))[0] & 0xffff;
if (swi == 4) {
k_serdbg_putc('@');
}
}
if (type != ARM4_XRQ_IRQ && type != ARM4_XRQ_FIQ && type != ARM4_XRQ_SWINT) {
/*
Ensure, the exception return code is correctly handling LR with the
correct offset. I am using the same return for everything except SWI,
which requires that LR not be offset before return.
*/
k_serdbg_putc('!');
for(;;);
}
return;
}
void __attribute__((naked)) k_exphandler_irq_entry() { KEXP_TOP3; k_exphandler(lr, ARM4_XRQ_IRQ); KEXP_BOT3; }
void __attribute__((naked)) k_exphandler_fiq_entry() { KEXP_TOP3; k_exphandler(lr, ARM4_XRQ_FIQ); KEXP_BOT3; }
void __attribute__((naked)) k_exphandler_reset_entry() { KEXP_TOP3; k_exphandler(lr, ARM4_XRQ_RESET); KEXP_BOT3; }
void __attribute__((naked)) k_exphandler_undef_entry() { KEXP_TOP3; k_exphandler(lr, ARM4_XRQ_UNDEF); KEXP_BOT3; }
void __attribute__((naked)) k_exphandler_abrtp_entry() { KEXP_TOP3; k_exphandler(lr, ARM4_XRQ_ABRTP); KEXP_BOT3; }
void __attribute__((naked)) k_exphandler_abrtd_entry() { KEXP_TOP3; k_exphandler(lr, ARM4_XRQ_ABRTD); KEXP_BOT3; }
void __attribute__((naked)) k_exphandler_swi_entry() { KEXP_TOPSWI; k_exphandler(lr, ARM4_XRQ_SWINT); KEXP_BOT3; }
void arm4_xrqinstall(uint32 ndx, void *addr) {
char buf[32];
uint32 *v;
v = (uint32*)0x0;
v[ndx] = 0xEA000000 | (((uintptr)addr - (8 + (4 * ndx))) >> 2);
}
void start() {
uint32 *t0mmio;
uint32 *picmmio;
arm4_xrqinstall(ARM4_XRQ_RESET, &k_exphandler_reset_entry);
arm4_xrqinstall(ARM4_XRQ_UNDEF, &k_exphandler_undef_entry);
arm4_xrqinstall(ARM4_XRQ_SWINT, &k_exphandler_swi_entry);
arm4_xrqinstall(ARM4_XRQ_ABRTP, &k_exphandler_abrtp_entry);
arm4_xrqinstall(ARM4_XRQ_ABRTD, &k_exphandler_abrtd_entry);
arm4_xrqinstall(ARM4_XRQ_IRQ, &k_exphandler_irq_entry);
arm4_xrqinstall(ARM4_XRQ_FIQ, &k_exphandler_fiq_entry);
k_serdbg_putc('Z');
asm("swi #4");
/* enable IRQ */
arm4_cpsrset(arm4_cpsrget() & ~(1 << 7));
/* initialize timer and PIC
The timer interrupt line connects to the PIC. You can make
the timer interrupt an IRQ or FIQ just by enabling the bit
needed in either the IRQ or FIQ registers. Here I use the
IRQ register. If you enable both IRQ and FIQ then FIQ will
take priority and be used.
*/
picmmio = (uint32*)0x14000000;
picmmio[PIC_IRQ_ENABLESET] = (1<<5) | (1<<6) | (1<<7);
/*
See datasheet for timer initialization details.
*/
t0mmio = (uint32*)0x13000000;
t0mmio[REG_LOAD] = 0xffffff;
t0mmio[REG_BGLOAD] = 0xffffff;
t0mmio[REG_CTRL] = CTRL_ENABLE | CTRL_MODE_PERIODIC | CTRL_SIZE_32 | CTRL_DIV_NONE | CTRL_INT_ENABLE;
t0mmio[REG_INTCLR] = ~0; /* make sure interrupt is clear (might not be mandatory) */
k_serdbg_putc('K');
k_serdbg_putc('\n');
/* infinite loop */
for(;;);
}
Linker Script (file: link.ld)
This is saved in a file named link.ld which is referenced by the linker. QEMU loads the image to 0x10000, but on a real piece of hardware it might be different. This load position by QEMU may have something to do with the integrator-cp board, but I am not sure. Most ARM code is actually position independent code because the branch instructions do not address 32-bits and this is because of the 32-bit size of all instructions in ARM mode. But, when your code references symbols in the other sections it will use absolute addressing. It is possible in theory for GCC to emit position independent data references but I have found no standard option. I have heard of patches that add this ability to GCC, but just keep this in mind. I also yank out the text section in the build scripts below to make a flat binary and this is why I place everything into the text section.
ENTRY (entry)
SECTIONS
{
. = 0x10000;
_BOI = .;
.text : { *(.text*) *(.rodata*) *(.data*) *(.bss*)}
_EOI = .;
}
Compile And Link
VERY IMPORTANT - I do not include linking with LIBGCC in this example because I know some of you may not be using a proper cross-compiler setup. BUT, you SHOULD link against LIBGCC because it is part of the GCC compiler. See ARM Overview (Missing Division Functions / LIBGCC) and also Libgcc.
I only left it out because I would rather wet your appetite instead of run you away with this being difficult to compile or link.
arm-eabi-gcc -s -I./inc -nostdlib -nostartfiles -ffreestanding -std=gnu99 -c *.c
arm-eabi-ld -T link.ld -o __arm.bin *.o
arm-eabi-objcopy -j .text -O binary __arm.bin arm.bin
Test
qemu-system-arm -m 8 -kernel arm.bin -serial stdio
Going Further
I would take a good look at all the parts of this demonstration and ensure you understand what each part does. I would recommend you pay careful attention to the exception entry and exit routines and make sure that you understand each instruction and how it works. This will save you a headache later on when things are not acting quite right. But, here are some links to continue with. These are not the only links, but are just a few that I thought would be quite helpful.
Page | Description |
---|---|
Heap | Information about implementing heap |
Memory Management | Information about higher level memory management |
ARM Overview | See more tutorials and starting points for other things. |
Integrator Datasheet | Datasheet for the Integrator |
IRQ, Timer, PIC, And Task Switch | This page is a continuation of the above source code. It adds in a very simple task switch. |