Page 1 of 1

Interrupt programming

Posted: Sun Nov 18, 2012 6:26 am
by blm768
I've recently been writing some code to handle interrupts, and I'm trying to find information about the Pi's interrupt system. I've found some basic information in the SoC datasheet, but it isn't quite enough to complete my implementation. Does anyone know of a resource that's a bit more comprehensive?

In a pinch, I guess I could read some kernel source code, but if there's a more "readable" resource, I'd like to start with that.

I'm specifically trying to find the answers to two questions:
  • Since the SoC doesn't seem to use an interrupt vector, to what address does it branch when an exception is received? Is it just 0?
  • The datasheet seems to imply that interrupts are not disabled when the ISR starts, which doesn't sound right for an ARM platform. Is it really that way, or am I misreading the datasheet?

Re: Interrupt programming

Posted: Sun Nov 18, 2012 12:06 pm
by gertk
This is from the bootloader I use from dwelch67:
It sets up (copies) the vectors at adress 0x00000000, you will need add your own c_irq_handler in which you need to find out what caused the interrupt. (I use it to receive a serial input character from the mini_uart and to respond to the Vsync interrupt)

This is vector.s

Code: Select all

;@-------------------------------------------------------------------------
;@-------------------------------------------------------------------------
.globl asm_start

.globl _start
asm_start:
_start:

    ldr pc,reset_handler
    ldr pc,undefined_handler
    ldr pc,swi_handler
    ldr pc,prefetch_handler
    ldr pc,data_handler
    ldr pc,unused_handler
    ldr pc,irq_handler
    ldr pc,fiq_handler
    
reset_handler: 		.word reset
undefined_handler: 	.word hang
swi_handler: 		.word hang
prefetch_handler: 	.word hang
data_handler: 		.word hang
unused_handler: 	.word hang
irq_handler: 		.word irq
fiq_handler: 		.word hang

reset:
    mov r0,#0x8000
    mov r1,#0x0000
    ldmia r0!,{r2,r3,r4,r5,r6,r7,r8,r9}
    stmia r1!,{r2,r3,r4,r5,r6,r7,r8,r9}
    ldmia r0!,{r2,r3,r4,r5,r6,r7,r8,r9}
    stmia r1!,{r2,r3,r4,r5,r6,r7,r8,r9}


    ;@ setup the different stacks 

    ;@ (PSR_IRQ_MODE|PSR_FIQ_DIS|PSR_IRQ_DIS)
    mov r0,#0xD2
    msr cpsr_c,r0
    mov sp,#0x8000

    ;@ (PSR_FIQ_MODE|PSR_FIQ_DIS|PSR_IRQ_DIS)
    mov r0,#0xD1
    msr cpsr_c,r0
    mov sp,#0x4000

    ;@ (PSR_SVC_MODE|PSR_FIQ_DIS|PSR_IRQ_DIS)
    mov r0,#0xD3
    msr cpsr_c,r0
    mov sp,#0x8000000

    ;@ SVC MODE, IRQ ENABLED, FIQ DIS
    ;@mov r0,#0x53
    ;@msr cpsr_c, r0

    bl not_main
    
;@ just hang around here
hang: b hang

.globl PUT32
PUT32:
    str r1,[r0]
    bx lr

.globl GET32
GET32:
    ldr r0,[r0]
    bx lr

.globl dummy
dummy:
    bx lr


.globl start_l1cache
start_l1cache:
mov r0, #0
mcr p15, 0, r0, c7, c7, 0 ;@ invalidate caches
mcr p15, 0, r0, c8, c7, 0 ;@ invalidate tlb
mrc p15, 0, r0, c1, c0, 0
orr r0,r0,#0x1000 ;@ instruction
orr r0,r0,#0x0004 ;@ data
mcr p15, 0, r0, c1, c0, 0
bx lr

.globl stop_l1cache
stop_l1cache:
mrc p15, 0, r0, c1, c0, 0
bic r0,r0,#0x1000 ;@ instruction
bic r0,r0,#0x0004 ;@ data
mcr p15, 0, r0, c1, c0, 0
bx lr

;@ enable interrupts
.globl enable_irq
enable_irq:
    mrs r0,cpsr
    bic r0,r0,#0x80
    msr cpsr_c,r0
    bx lr

;@ the interrupt handler, branches to c_irq_handler
irq:
    push {r0,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,lr}
    bl c_irq_handler
    pop {r0,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,lr}
    subs pc,lr,#4
    
;@ memory barrier definition
.globl MemoryBarrier
MemoryBarrier:
	mcr	p15, 0, ip, c7, c5, 0		@ invalidate I cache
	mcr	p15, 0, ip, c7, c5, 6		@ invalidate BTB
	mcr	p15, 0, ip, c7, c10, 4		@ drain write buffer
	mcr	p15, 0, ip, c7, c5, 4		@ prefetch flush
	mov	pc, lr
	


This is what my c_irq_handler looks like:

Code: Select all

//-------------------------------------------------------------------------
// uart receive interrupt
void c_irq_handler ( void )
{
    	unsigned int rb,rc;
    //an interrupt has occurred, find out why
    while(1) //resolve all interrupts to uart
    {
        rb=GET32(AUX_MU_IIR_REG);
        // test if interrupt is pending
        if((rb & 1)==1) break; 

        // test if a character is available
        if((rb & 4)==4)
        {
            //receiver holds a valid byte
            rc=GET32(AUX_MU_IO_REG); //read byte from rx fifo
            rxbuffer[rxhead]=rc & 0xFF;
            rxhead=(rxhead+1)&RXBUFMASK;
        }
    }
    	// SMI interrupt ? (for Vsync)
	if (GET32(IRQ_PEND2) & 0x00010000){
		// set vsync flag for external use
		vsync_flag=1;	
		// reset interrupt flag
		PUT32(0x20600000,0x00000000);
	}
}

Re: Interrupt programming

Posted: Mon Nov 19, 2012 6:20 am
by dwelch67
Get the ARM Architectural Reference Manual and the ARM Technical Reference Manual for this platform at infocenter.arm.com. the ARM ARM discusses how the exceptions work on arm platforms.

this platform uses the traditional arm exception solution.

starting at address 0x00000000 with reset there are a dozen or so exceptions handlers. The processor executes the instruction at the handlers address. You have room for one instruction so make it a branch.

The raspberry pi boot solution, currently, is to load your program at address 0x8000. So in some way or form you will need to put the right instruction at the right address. (either FIQ or IRQ) then as you suspected you will need to enable interrupts, they are disabled on reset, running bare metal that means it is up to you.

I have an interrupt example that walks you through the steps, starting with polling then headed toward an actual interrupt handler. It uses the uart but other interrupts work the same way, simply enable a different interrupt, the rest is the same. http://github.com/dwelch67

My solution for putting the instruction in the right place lets the assembler and linker the work of computing the handler addresses. Many ways to do this, this is only one.

Some folks here like to load their bare metal programs at address 0x0000 using a config.txt file. that makes the job even easier.

Re: Interrupt programming

Posted: Mon Nov 19, 2012 6:38 pm
by blm768
OK; I think I get it now. The datasheet was a little confusing, especially for someone who's just getting started with ARM internals. The code definitely looks useful; I'll have to borrow a few snippets.

Too bad QEMU doesn't support the Pi's hardware; I'll have to wait for my board before I can actually test my code. It shouldn't be too much longer, though...

Thanks for your help!

Re: Interrupt programming

Posted: Mon Nov 19, 2012 8:05 pm
by hendrixjl
dwelch67... concerning your uart02 example.
The broadcom sheet says:
"GPIO pins should be set up first the before enabling the UART. ... So when it is enabled any data at the inputs will immediately be received."

I notice that the first thing you do in uart02.c is set the enable bit to true! After setting up the UART, you then set gpio14 and 15 to TX/RX.

I take it broadcom's warning was unnecessary for the the mini uart?

Re: Interrupt programming

Posted: Mon Nov 19, 2012 9:54 pm
by hendrixjl
gertk wrote:

Code: Select all

reset:
    mov r0,#0x8000
    mov r1,#0x0000
    ldmia r0!,{r2,r3,r4,r5,r6,r7,r8,r9}
    stmia r1!,{r2,r3,r4,r5,r6,r7,r8,r9}
    ldmia r0!,{r2,r3,r4,r5,r6,r7,r8,r9}
    stmia r1!,{r2,r3,r4,r5,r6,r7,r8,r9}
why the load (ldmia) and store (stmia) twice?

Re: Interrupt programming

Posted: Mon Nov 19, 2012 10:15 pm
by dwelch67
Off hand I am guessing the enable is required to be able to talk to the uart at all, then the enables for tx and rx are after they are configured, which makes sense. I probably didnt but flushing the receiver is not a bad idea to cover the bogus data that may come in right after enabling the receiver if the line is transitioning.

The gpio pins are multiplexed, so at some point the external pin needs to be connected internally to the uart logic, floating and state changes can/will happen, and that can lead to bogus characters if the rx is enabled (or tx to the other side). I did not spend any/much time worrying about this. Ideally you want to get the I/O connected, deal with pull ups or downs or let it float whatever the choice, configure the uart. Perhaps let everything settle and turn it on. Some hardware doesnt do the final connection until you enable things so even if you do what you can it may still get bad stuff. Not saying that is how this hardware works.

Experiment for yourself, move things around and see what happens. (docs are never perfect, the broadcom docs are far from perfect).

If my init routine is broken enough, then show me the problem and the fix and I will take it from there, do my own experiments, etc.

each ldmia/stmia pair copy a certain number of words, I needed to copy X number of words and that is how I chose to do it, N sets of ldmia/stmia with Y number of words each. A loop of single ldr/str or ldrd/strd or ldm/stm pairs would work, etc. There are many solutions. If you look at the amount of data in the vector table plus the addresses following that all needs to be copied for that solution to work.

David

Re: Interrupt programming

Posted: Mon Nov 19, 2012 11:11 pm
by hendrixjl
But it looks to me like you are loading and storing the same words twice. I'm sure I just don't understand the syntax.

Re: Interrupt programming

Posted: Tue Nov 20, 2012 2:14 am
by hendrixjl
I got It! I missed the meaning of the "ia" at the end of the load multiple and store multiple instructions: increment after.

Re: Interrupt programming

Posted: Tue Dec 04, 2012 7:01 pm
by smccain
I had a question regarding the addresses you are using for the ldmia and sp instructions. You mentioned that you chose 0x8000 as the starting address for the ldmia instruction, but what if you are using a bootloader loaded into upper memory?

For example, I have my ld script setup to reserver 0x8000 - 0x001ffffc for any uploaded user program. The bootloader sits at 0x001ffffc. What values would I use for r0 and sp in the example given above?

Here is some more information:
My ld script:

Code: Select all

MEMORY
{
	ram : ORIGIN = 0x8000, LENGTH = 0x1000000
}

SECTIONS {
	.init 0x0000 : {
		*(.init)
	}
	
	.text 0x8000 : {
		*(.text)
	} > ram
	
	.data : {
		*(.data)
	} > ram
	
	.bss : { *(.bss)
	} > ram

	/DISCARD/ : {
		*(*)
	}
}
And here is an excerpt from my listing file:

Code: Select all

c:\dev\timer-test\output.elf:     file format elf32-littlearm


Disassembly of section .init:

00000000 <_start>:
   0:	ea07fffd 	b	1ffffc <reset>

Disassembly of section .text:

00008000 <reset-0x1f7ffc>:
	...

001ffffc <reset>:
  1ffffc:	e3a0d302 	mov	sp, #134217728	; 0x8000000
  200000:	eb000000 	bl	200008 <_main>

00200004 <hang$>:
  200004:	eafffffe 	b	200004 <hang$>

00200008 <_main>:
  200008:	e92d4008 	push	{r3, lr}
  20000c:	e3a00010 	mov	r0, #16
  200010:	e3a01001 	mov	r1, #1

Re: Interrupt programming

Posted: Tue Dec 04, 2012 8:00 pm
by dwelch67
I recently updated my examples with blinker07 that uses the system timer interrupt.

If you are building for and able to load your application at address 0x00000000 then you dont need to the copy trick or any other scheme to setup the exception table. Well you do need a scheme but it is in how you build your application so that the exception table is loaded at address zero. (either use old raspi gpu bootloader code or look around the bare metal forum for info on what config.txt entry to use to load your binary at zero, my examples are all designed around loading at address 0x8000).

Yes, absolutely, with bare metal you control the memory space, you decide where the stack or stacks in this case are where your application lives, etc. if you start with one of my examples and then change the memory layout you need to be careful to change all of it, having the stack collide with code can make for a nasty bug to try to find.

last note, remember that the stack instructions decrement before so if you set your stack pointer to 0x8000 before you start your C code the top of the stack is at 0x7FFC and 0x8000 wont be touched (unless there is a bug) so it is perfectly safe to use 0x8000 on up and let the stack grow down. Pushing, as generally created by the C compilers (barring any non-default settings) for ARM means it decrements the stack pointer before putting something on the stack and pops from the address in the stack pointer. So a push of one register when the sp is 0x1000 means that word goes in memory at 0xFFC and the sp is left with 0xFFC so the sp points at the first USED memory location. A pop of one word when the sp is at 0x1000 means the word at 0x1000 is read and stored in the register and the sp is changed to 0x1004

Hope that helps,
David

Re: Interrupt programming

Posted: Wed Dec 05, 2012 12:39 am
by smccain
Yes, that makes much more sense now. I also read your blinker07 example which also shed some light on things that I didn't understand. So I have a couple of questions now and I think I am off to the races. If I load my bootloader image at 0x2000000 and it takes up say 3k bytes and I set sp to 0x8000000 that should leave me over 100 megabytes for the stack right? Or are my calculations off? I'm assuming stack grows down.

Also, I'm loading user code at 0x8000 (where I reserve 0x2000000 - 0x8000 in my linker script) so the original sp assignments for irq and fiq in your example will also work right?

Here's my thinking:
If sp is 0x8000 and I push something it will decrement sp first and then put the value on the stack at 0x7fcc and sp then points at the last value pushed onto the stack. When I pop it does the opposite, it first retrieves the value at 0x7fcc (in this example) and then increments sp to point to the top of the stack at 0x8000. If my assumptions about that are correct then using 0x8000 for sp in the irq handler should work fine.