User avatar
sharpcoder
Posts: 39
Joined: Tue Mar 12, 2013 9:07 pm
Location: Seattle, WA
Contact: Website

Interrupt Theory

Sat Mar 23, 2013 1:33 am

Hello,

Recently I've been pouring over the SoC-Peripherals.PDF document trying to make heads or tails of it (I admit I am way in over my head, but I really want to understand some of this low-level stuff, so I'm powering through!). Some things have finally clicked for me, which was awesome. But one thing I cannot wrap my head around is interrupts.

After re-reading multiple relevant sections in the peripheral document (Interrupt, Timer, etc). My understanding is this: an interrupt is like a hardware-level event. When some condition is met, the event triggers and an action is taken. In some kernel-based tutorials, I've seen Interrupts are often interfaced using a PIC (Programmable interface controller?) or some such module.

I couldn't find any approximate terminology in the SoC document. The only "programmable" type thing I could find mention of was the DMA (which I don't think is what it's used for). That being said, I do know dwelch67's github repository (blinker05) hsa IRQ Handler code, and here is a snippet from his document:
To service an interrupt we need to have our exception table at address 0x0000.
If you have been reading your ARM documents as you should by this point
you will know that for an interrupt on this processor it executes
the instruction at address 0x0001C.
Upon further investigation, I found 0x0001C ( + 0x20000000 ) = GPSET0. But the SoC document says you just use that address to set a bit on/off to mark whether it's an output pin or not (I think).

I feel like I'm missing something obvious here. So my question is this: how can I interface with the rpi interrupt system? Am I approaching this completely wrong?

Any help is appreciated. Thanks!

blm768
Posts: 24
Joined: Sun Nov 18, 2012 6:13 am

Re: Interrupt Theory

Sat Mar 23, 2013 6:52 am

sharpcoder wrote: Upon further investigation, I found 0x0001C ( + 0x20000000 ) = GPSET0. But the SoC document says you just use that address to set a bit on/off to mark whether it's an output pin or not (I think).
On an ARM processor, when an interrupt is triggered, the processor jumps to a fixed memory location. The exact location varies between different types of interrupts (IRQ, reset, undefined instruction, etc.), but IRQs always cause the processor to branch to 0x1C. Before any interrupts are triggered, the program fills that memory location with an instruction that jumps from 0x1C to the location of the actual interrupt handler.

The first step in enabling interrupts is to fill that area, which is known as the IVT (Interrupt Vector Table). The method I use is to fill the area right after the IVT with the addresses of my various interrupt handlers and to fill the IVT with instructions that load those addresses into the program counter.

Once you have the IVT and your interrupt service routines set up, you need to enable interrupts. There are two places they need to be enabled. First, the processor's interrupt bit in the status register must be set. This allows the processor to receive and process interrupts. The interrupt sources must then be turned on. Those bits are laid out in the datasheet; they start at 0x0xF200B210, but the datasheet will probably identify them by the offset 0x210 instead of by the full address.

Once you receive an interrupt, you need to save some of the registers, perform whatever task you wanted the interrupt to trigger, acknowledge the interrupt by setting yet another bit in memory, and restore the registers. If you look at some generic ARM interrupt tutorials, you should be able to find information on the exact steps.

dwelch67
Posts: 1006
Joined: Sat May 26, 2012 5:32 pm

Re: Interrupt Theory

Sun Mar 24, 2013 1:31 am

The GPIOSET register you found is likely an offset not the whole ARM address. something along the lines of GPIOBASE+0x0001C where GPIOBASE is some address.

My text was talking about the complete arm address being 0x0000001C. What you need to do is:

http://infocenter.arm.com

You might need to create a free account and login. Start perhaps with the arm architecture reference manual (known as the ARM ARM) on the left side expand arm architecture then expand reference manuals then try the ARMv5 which is the oldest/original ARM ARM and covered ARMv4 and ARMv5 but could not encompass all architectures so now they have multiple ARM ARMs. The raspberry pi uses a newer architecture, but this one will give you a good foundation, your choice either should work.

There is a programmers something chapter up front that contains basic information about arm processors, number of registers, shows the different modes (supervisor, int, user, etc) . And then covers exceptions.

The processor (not just arm but in general) has one or many interrupt inputs, when one of those asserts a know chain of events happens, which from a software perspective the code that is running at the time stops, the state of the machine such that we can return to that code is somehow preserved, and at some known address a vector or address is placed, by us (software), to indicate where our interrupt or exception handler is place, which is just code. Depending on the processor this code generally has to be more careful than normal functions, for example it may have access to general purpose registers that are being used by the code that was interrupted and when we return we dont want to mess up that code, so we have to preserve (put them back the way we found them) or dont use those registers (often not possible to do anything useful without preserving registers). ARM in particular tends to have these different modes and each mode has some shared registers and some mode specific registers, in particular the stack pointer. so the interrupt handler has its own stack, but it does share some registers with the foreground task so you need to preserve those.

No matter what processor you are running you need to have a solution for placing the address or in the case of arm the instruction in the vector/exception table such that when the interrupt occurs the handler is called per the rules of that processor.

You also need to enable and possibly route the interrupt logic in the processor. The PIC, programmable interrupt controller, in the x86 world is/was such a thing, used to be a separate chip but over time has moved around. ARM has used terms like NVIC and others to describe their interrupt controller, but it varies from one arm architecture to another. ARM does not make chips they sell/license source code to processor cores to incorporate into your chip. So in addition to the arm cores interrupt controller/scheme you will likely also have the chip vendors scheme in the logic that surrounds the arm core. It can be one to many registers that need to be configured to enable/route a single interrupt into the processor such that it can be interrupted and call the handler. The raspberry pi uses a very simple scheme, single register, very uncomplicated fortunately.

Peripherals (in general not just arm stuff) often have a local bit that indicates the state of the interrupt, sometimes you can use that bit without interrupting the processor. And there is often a specific way to clear that interrupt as well. (and a way to determine what interrupt occurred when one happens, since a useful system has many different interrupt sources). Then there is often an enable in the peripheral to allow that interrupt signal out of the peripheral. (a peripheral is for example a uart or gpio, and in this case I mean on chip not off chip, it is a peripheral to the processor but all on chip). then there is sometimes a register in the processors interrupt controller that captures that interrupt bit and that can sometimes be polled without the processor being interrupted. arm processors usually have such a thing, but it is not a hard and fast rule. then in the interrupt controller or somewhere near/in the processor there is an enable that allows the interrupt into the processor then there is sometimes in the processor an enable that globally allows or prevents interrupts. All of these things have to be turned on and enabled as well as the interrupt vector table in order for the interrupt to make it to the processor and cause the handler to be called. You have to be careful that the interrupt is not left asserted for some reason when you enable interrupts as it may cause an instant interrupt as soon as you enable it.

Some processors have multiple/many interrupt inputs some have a single input, so when the interrupt occurs you may know from which handler is called which interrupt it was asserted, but that may still require more investigation as a single peripheral if that is what it is may have multiple reasons for asserting an interrupt, for example a uart might have a transmitter empty and a receiver full. so one interrupt or many you need to follow the proper discovery chain to determine who caused the interrupt as part of the interrupt handler (except as in my example where I know that only one thing is enabled so I didnt have to do this, I knew who caused it). and at some point in the handler you need to clear the interrupt in the proper order, if you clear the processor end first for example without clearing the peripheral, depending on the interrupt controller, it may re-fire the interrupt. Depending on the system and situation you may want to clear the interrupt very early in the handler or very late. And also depending on the system and peripheral after you have handled the first interrupt you found you may need to go back and make sure no more happened while you were in the handler, a loop within the handler if you will. Esp with systems like the one in the raspberry pi where you have a single interrupt input (well fiq and irq are separate inputs to the core but in this case tied together).

Interrupts are a PITA, even with decades of experience, they can be not fun...

The ARM architecture at least the one we are using in the raspberry pi (the cortex-m microcontroller systems are different than the traditional arm, and the newer arm cores have many interrupt inputs) has two interrupts the FIQ and IRQ. Very often these are tied together in the chip, so that the software engineer can choose which one to use. They will have separate enables at some level between the perpherals and processor core, and have separate enables in the arm processor (cpsr) and separate handlers. the only real difference that you care about between them is if you look in the ARM ARM you see that FIQ mode has more FIQ only registers so you basically dont have to start your handler with a bunch of pushes to save registers on the stack and pops at the end to clean up, making the overall interrupt handler that much faster.

Generally, but not always, you want your interrupt handlers to be lean and mean, figure out the reason for the interrupt, and capture that info and save it in a shared place, then in your main application have something that checks the shared resource for a new interrupt. For operating systems, linux, etc where task switching happens often you can check in the task switcher (which is triggered by a timer interrupt itself) or the interrupt handler itself causes a task switch such that when it returns it does not return to the prior code just yet it saves that info and returns to some other kernel code that handles the interrupt (not in interrupt mode but supervisor mode). Basically you dont normally want to stay in the interrupt handler very long, you dont want to make system calls (printf, etc) in the handler, etc. You might find my examples violating this, but it is because I have done this on purpose, for example when debugging something I might setup all the handlers to point at some flavor of printout routine that prints info about what caused the event then basically hang in an infinite loop. But this is part of the printf() type debugging I prefer, others use other methods.

timer interrupts are a good way to learn about interrupts, there are other threads in the bare metal forum talking about different ways to get the exception table setup on a raspberry pi. you can do it the way I did in that example, I may have uploaded other examples, that use different schemes, all of which have to do with allowing the kernel.img to be loaded at address 0x8000 (but the exception table is at 0x00000000). but you can also control the gpu bootloader (config.txt) and have kernel.img loaded at 0x00000000 and solve the problem that way. Uart RX is another good one to learn with. Uart TX has a chicken and egg problem. You want to interrupt when the tx buffer is empty, well 1) what if the tx buffer is empty when you enable everything 2) what if when the tx buffer changes from busy to empty you dont have anything you want to send? And there may be others. Basically when you use uart tx interrupts, when you have something new to send you want to do two things, not necessarily in this order. Prepare a kernel/handler buffer for the data to be sent and/or add it to the end of the current buffer if there is already stuff there. If there is nothing in the tx buffer (both kernel/driver and hardware) then put the first item in the hardware tx buffer. Then when that item finishes and triggers the tx empty interrupt the interrupt handler can then go to the driver buffer and see if there are more items. My examples all use polling for tx and pretty much I dont use interrupts every in my examples, there is no need (other than to demonstrate how to do a simple interrupt handler).

didnt mean to ramble on, hope this helps. Lots of folks in this forum have experience with interrupts ARM and other systems. Keep asking questions. You sound as if you are interested and are trying hard to research the docs, the doc you needed is the arm doc as well as the broadcom doc that you have. In the arm world there are two main docs for any particular core one is the arm arm for that architecture the other is the technical reference manual, trm, for that specific core, there may be multiple cores within an architecture family. Arm splits all the goodies you need to know between the architecture family document (arm arm) and the core specific document (trm). The raspberry pi uses a ARM1176JZF-S so find the trm that is closest to that number as you can find, in that trm you should find the arm architecture revision (ARMv4, ARMv5, ARMv6, ARMv7, etc), then go get the ARM ARM for that architecture revision. Note that there is a huge difference between an ARM7 and ARMv7. ARM7, ARM9, ARM10, ARM11 are all product names like the Cortex-A, Cortex-M, Cortex-this, Cortex-that. They are not architecture versions. The ARM7 which was usually more specifically the ARM7TDMI is architecture ARMv4. The ARM9 was ARMv5. And just because some chip comes out after another doesnt mean it is from a newer architecture the Cortex-M3 hit the streets first, it is ARMv7m based, the Cortex-m4 also ARMv7m, then the Cortex-m0 came out it is ARMv6m based which is a whole lot different more than 100 less thumb2 instructions than the m3 and m4. And to add to the confusion ARMv7m and ARMv7 are not necessarily the same. the cortex-m's are in the same family but are for microcontrollers the cortex-a and its friends are for high end stuff, tablets, phones, laptops, etc.

David (a.k.a dwelch67)

User avatar
sharpcoder
Posts: 39
Joined: Tue Mar 12, 2013 9:07 pm
Location: Seattle, WA
Contact: Website

Re: Interrupt Theory

Tue Mar 26, 2013 1:51 am

@dwelch67
Thank you so much, I really appreciate this information! I am trying to learn, but without some of this assumed knowledge about low-level theory, it's very difficult. And no worries about rambling! I read it all and am very grateful to have access to so much relevant information. I'll be checking out the ARM ARM next and go from there.

Also, who thought it was a good idea to make something called ARM7 implement the ARMv4 architecture :shock:

@blm768
Thank you also! That really helps put a lot of what I've been reading into perspective. It makes so much sense and it answered my original question!

dwelch67
Posts: 1006
Joined: Sat May 26, 2012 5:32 pm

Re: Interrupt Theory

Tue Mar 26, 2013 1:32 pm

I dont know all of ARM's history, the arm2 and/or 3 I think is part of acorn which may have made chips. if you look at the amber processor project at opencores.org for example (I have some examples at github). which is arm2 or arm3 and obviously patent free or at least not a threat to arm otherwise it would have been yanked from opencores a long time ago. so armv4 makes sense as arm the companies first architecture. Where the name arm7 came from who knows, but perhaps at the time they didnt think they would be around long enough to get from armv4 to armv7 and didnt think of the confusion that would create, they had a single project and needed to get it out there.

There is a wealth of (low level) experience hanging around this forum, feel free to ask questions...

User avatar
sharpcoder
Posts: 39
Joined: Tue Mar 12, 2013 9:07 pm
Location: Seattle, WA
Contact: Website

Re: Interrupt Theory

Wed Mar 27, 2013 5:44 am

Thanks to the responses here, some sample code, and reading through the ARM ARM - I've come up with something that I think should work! I have a few simple assembly questions though...

Basically, what I've done is I created another file called irq.asm and in my linker.ld file I tell it the following:

Code: Select all

ENTRY(init)
SECTIONS
{
   vectortable: 0x0000 : {
      irq.o;
   },
   kernel 0x8000 : {
      *(.text);
      *(.data);
      *(.rodata);
   }
}
Let's just assume that all my compilation is good and the necessary files with the correct names are output. After reviewing the disassembled ELF file ( my kernel.img ), I noticed the following:

Code: Select all

00000000 <vectorTable>:
   0:	ea001ffe 	b	8000 <init>
   4:	ea000005 	b	20 <stop>
   8:	ea000004 	b	20 <stop>
   c:	ea000003 	b	20 <stop>
  10:	ea000002 	b	20 <stop>
  14:	ea000001 	b	20 <stop>
  18:	e59ff004 	ldr	pc, [pc, #4]	; 24 <arm_interrupt_handler>
  1c:	e51ff000 	ldr	pc, [pc, #-0]	; 24 <arm_interrupt_handler>
And then of course further down it defines my kernel init function which is at the proper location, 0x8000. My question has to do with the last lines, 0x18 and 0x1C respectively. You'll notice I am using the LDR assembly command. I lovingly took this idea from blm768 (thank you! :)) But what concerns me is the value that it is trying to load into the PC register...

The function "arm_interrupt_handler" (which is an assembly function) is located at 0x0024. I don't understand why the disassembled output is trying to put the values "#4" and "#-0" into the PC register when those locations aren't correct.

Am I going insane or is my compiler screwing me over? As a side note, this is technically C++. But I've been able to compile everything from framebuffer code to GPIO output successfully, so I don't believe that should be much of a concern...

Also this compiles successfully, runs successfully (well, I haven't actually tried to invoke the interrupts yet), except I'm experiencing some weird results that have to do with drawing text to the screen. I'm not sure if that's related though. I am mostly just curious about the values being put into the PC register.

Thank you!

User avatar
rpdom
Posts: 19542
Joined: Sun May 06, 2012 5:17 am
Location: Chelmsford, Essex, UK

Re: Interrupt Theory

Wed Mar 27, 2013 6:39 am

sharpcoder wrote:But what concerns me is the value that it is trying to load into the PC register...

The function "arm_interrupt_handler" (which is an assembly function) is located at 0x0024. I don't understand why the disassembled output is trying to put the values "#4" and "#-0" into the PC register when those locations aren't correct.
What the LDR command is doing is putting the value found at memory location PC+4 and PC-0 into PC.

I haven't checked, but from what I remember on the ARM chips, the PC register actually contains the address of the next instruction to fetch, not the one currently being executed. This used to be something like "current instruction + 8" on the early ARM chips.

If that's still the case (and I suspect it's a larger value nowadays), at 001C ldr PC, [PC, #4] would load the contents of 0028 into PC transfer to that address (setting PC explicitly makes a jump to that location and the processor will "catch up").
Also, 0020 ldr PC, [PC, #0] will also load the contents of 0028 into PC and jump to the same routine.

Can you show us the contents of 0024 onwards for a few words and also the address of your interrupt routine?

I'm very interested in this subject, not having done interrupt handling on ARM before. I've just about managed to get the ARM Timer to generate an interrupt every 0.25 seconds and jump to a routine which does a tiny bit of processing, which I need for one of my simple bare metal projects :)

Edit: If I'm wrong about any of the above, please do correct me (giving section numbers in the ARM ARM would be helpful too), as I want to learn this stuff :-)

blm768
Posts: 24
Joined: Sun Nov 18, 2012 6:13 am

Re: Interrupt Theory

Wed Mar 27, 2013 7:56 am

Here's the code I'm using for my IVT:

Code: Select all

.ivt:
	ldr pc, .isrs
	ldr pc, .isrs + 4
	ldr pc, .isrs + 8
	ldr pc, .isrs + 12
	ldr pc, .isrs + 16
	ldr pc, .isrs + 20
	ldr pc, .isrs + 24
	ldr pc, .isrs + 28
.isrs:
	.word _start
	.word halt
	.word swi_handler
	.word halt
	.word halt
	.word halt
	.word irq_handler
	.word halt
I just stick this in the .data section and copy it to 0x0000 before enabling interrupts. That way, I don't have to mess with the bootloader configuration to have it load at 0x0000 rather than the default of 0x8000. The names in the .isrs table just refer to the addresses of my ISR functions.

This is actually abusing the assembler a bit; the assembler "thinks" that the LDR instructions will be loading from the .isrs table in the .data section, but when all this stuff is copied, it'll actually be loading from the area just after the IVT, which is completely outside the binary's original "footprint." However, because it's a relative load, it still works just fine as long as the whole block of data is kept together.

tufty
Posts: 1456
Joined: Sun Sep 11, 2011 2:32 pm

Re: Interrupt Theory

Wed Mar 27, 2013 8:19 am

rpdom, you're correct.

ARMv7AR-ARM, Section A2.3 (ARM Core Registers, Application level)
PC, the Program Counter
Register R15 is the program counter:
• When executing an ARM instruction, PC reads as the address of the current instruction plus 8.
• When executing a Thumb instruction, PC reads as the address of the current instruction plus 4.
• Writing an address to PC causes a branch to that address. In Thumb code, most instructions cannot access PC.
See ARM core registers on page B1-9 for the system level view of SP, LR, and PC.
The long and short of it is very much as Dave said earlier. There are 7 processor level exceptions that can happen (see ARMv7AR-ARM Section B1.6): Reset, Undefined Instruction, Supervisor Call, Prefetch Abort, Data Abort, IRQ and FIQ. There's also an unused exception in the middle, go figure. When one of these happens, the processor switches to ARM mode, switches state if necessary, and transfers execution to the code at (VECTOR_BASE + OFFSET). VECTOR_BASE defaults to 0x00000000, and the offsets are, respectively according to the list above, 0x00, 0x04, 0x08, 0x0c, 0x10, 0x18 and 0x1c. Which means you have one ARM instruction to transfer execution to the relevant hander.

A very typical approach to handling this is to have the vector table immediately followed by the addresses of the handlers, and use the "ldr pc, [pc ,#24]" approach. I wrote this up a while back, see http://stm8sdiscovery.blogspot.fr/2011/ ... -part.html

It should be noted that the indirect jump (or, indeed, any jump) incurs a cost, and another useful technique is to place the FIQ handler starting at FIQ_OFFSET (VECTOR_BASE + 0x1c) to avoid that cost - FIQ really is meant to be fast - and then follow that with the indirect offsets for the other handlers.

Simon

User avatar
sharpcoder
Posts: 39
Joined: Tue Mar 12, 2013 9:07 pm
Location: Seattle, WA
Contact: Website

Re: Interrupt Theory

Wed Mar 27, 2013 3:14 pm

rpdom wrote:Can you show us the contents of 0024 onwards for a few words and also the address of your interrupt routine?
Sure! Thought I don't know how much help it will be since it doesn't work right now :oops: Also the complete source for my project is available on my github repo. (Though it's in a weird state right now :P) https://github.com/SharpCoder/rpi-kernel

Code: Select all

arm_interrupt_handler:
	;@ Store the return link.
	sub r14, r14, #4
	stmfd sp!, {r0,r1,r2,r3,r4,r14}

	;@ Invoke our C++ irq handler.
	bl irq_handler

	;@ Restore to the original caller.
	ldmfd sp!, {r0,r1,r2,r3,r4,pc}^
The stmfd / ldmfd instructions were suggested by the ARM ARM as a way to store the original register values and then restore them after the c++ irq handler finishes it's thing. And that's the end of my irq.asm file :)

Also, to answer your other question, the address of my c++ irq handler is 0x8018. It literally has no code right now, it's just an empty function, but it's declared over in kernel land - so it has a huge offset.

tufty
Posts: 1456
Joined: Sun Sep 11, 2011 2:32 pm

Re: Interrupt Theory

Wed Mar 27, 2013 4:17 pm

That looks right. The ARM calling convention is that registers r0-4 are used for argument passing, and that r5-12 are saved by the called function as required. Thus you need to save r0-4 *before* branching to your C / C++ handler, and you need to save the link register (as a stylistic aside, code is clearer if you refer to r14 as 'lr') before you do the branch as well, or it will get blown away and you have no way back from your exception.

If it's not working, my guess would be one of the following:

a - you're not enabling the interrupts you think you are
b - you have a linkage issue that means you're not getting to your handler.

I'd probably replace the branch to handler with a bit of code to toggle the OK led state for debugging. Either that, or use the somewhat functional qemu-rpi with gdb attached to see what's really going on.

dwelch67
Posts: 1006
Joined: Sat May 26, 2012 5:32 pm

Re: Interrupt Theory

Wed Mar 27, 2013 7:39 pm

having problems with this forum perhaps, let me try this again:

In case this wasnt already covered by someone. The program counter, r15 is assumed to be 2 *instructions* ahead. In arm mode, where the instructions are 4 bytes, 32 bits, that means 8 bytes ahead, so an instruction at address 0x100 when executed the pc if used as an operand will be 0x108. For 16 bit, two byte, thumb instructions an instruction at address 0x200 the pc would be 0x204. if thumb2 (does not apply to the raspi but does in general) then you have 16 or 32 bit instructions and I seem to remember an experiment I did confirmed two instructions so 4, 6 or 8 bytes ahead depending on the instructions.

Normally in asm you shouldnt need to mess with this too much, let the assembler do it, for example do this

Code: Select all

ldr r0,some_address
and let the assembler, who knows both the address and the size of the following instructions, etc, how to build the instruction properly.

If this was already covered, then sorry for the duplication.

David

User avatar
sharpcoder
Posts: 39
Joined: Tue Mar 12, 2013 9:07 pm
Location: Seattle, WA
Contact: Website

Re: Interrupt Theory

Fri Mar 29, 2013 4:10 am

As always, I appreciate all of these responses!

Unfortunately, I'm stuck on a generally related issue that is preventing me from even attempting to implement interrupts. I've spent more hours than I care to admit figuring out what's wrong, but I've finally narrowed it down - and it is, in fact, related to interrupts!

It all started when I tried to map methods to the interrupt vector table. According to the disassembler, I was able to wire up my interrupt handler functions to the right spots, but I noticed something odd and seemingly unrelated - my rendering engine was no longer displaying any text to the frame buffer. Strange... One thing led to the next and I ended up going on a random tangent that involved completely re-factoring my frame buffer code, but when I tried to implement text rendering - again - no dice.

After a long adventure, I realized two things:

A) My entry point is no longer at 0x8000. The code that I borrowed from blm768 has a branch instruction on the very first line. I have verified that this is the new entry point, which I find odd because the bootloader is supposed to invoke 0x8000 (provided the config file is setup appropriately, which it is).
B) The second thing I noticed was that hard-coded array values were no longer accessible. What I mean by this is, quite literally, I can go: byte data[] = { 0x00, 0x10, 0x20 }; and when I try to access any value of that array, it will return zero.

I am now assuming that my linker.ld file is messing up the data positions and preventing me from being able to access memory of any kind. I think the only reason I can draw colors to the framebuffer is because I hard-code the memory location of the structure I pass to the mailbox.

Does it sound like I'm going crazy, or can the linker.ld file really do this to me? And if so... any guidance on how to fix it would be appreciated. :) Once I fix this, I will review the posts in this thread and try to properly implement all of these great suggestions about interrupts!

Thank you!

blm768
Posts: 24
Joined: Sun Nov 18, 2012 6:13 am

Re: Interrupt Theory

Fri Mar 29, 2013 6:51 am

sharpcoder wrote:I am now assuming that my linker.ld file is messing up the data positions and preventing me from being able to access memory of any kind. I think the only reason I can draw colors to the framebuffer is because I hard-code the memory location of the structure I pass to the mailbox.
If your linkscript is still the same as the version you posted above, it has a section at 0x0000. However, the bootloader is given just a flat binary, so it has no idea that the section is supposed to go there and just loads it at 0x8000. Since the next section is 0x8000 bytes from the beginning of the file, it's loaded at 0x8000 + 0x8000. All of your code thinks everything is where the linker put it, but it's actually 0x8000 bytes farther, which causes rather bad things to happen. Removing that section will probably fix the problem. If you put your IVT in the .data section, you just need to copy it from that section to 0x0000 after the program starts.

The reason that the first IVT entry sends you to the program entry point is that the first IVT entry is used when the processor is reset. You're not likely to run into that situation outside of a full hardware reboot (in which case the bootloader will take over again), but I have it there for completeness.

User avatar
sharpcoder
Posts: 39
Joined: Tue Mar 12, 2013 9:07 pm
Location: Seattle, WA
Contact: Website

Re: Interrupt Theory

Fri Mar 29, 2013 3:10 pm

blm768

Thank you! That makes so much more sense. I guess I assumed that the sections would be mapped to memory if I mapped them in the binary file properly, but in retrospect that seems silly... Now that I get what's happening, I'll give this another shot after work.

User avatar
sharpcoder
Posts: 39
Joined: Tue Mar 12, 2013 9:07 pm
Location: Seattle, WA
Contact: Website

Re: Interrupt Theory

Wed Apr 03, 2013 3:50 pm

Well, I finally got it! :D
20130402_231919.jpg
Success!
20130402_231919.jpg (51.65 KiB) Viewed 12841 times
Unfortunately, I don't 100% understand why it works. I spent days trying to get my own flavor of interrupt vector table initialization to work - but I think I was missing some key pieces. I've come away from this with some specific questions, so here goes...

Code: Select all

ldr pc, reset_handler
ldr pc, basic_handler
... x8 ...

basic_handler:
   .word   basic_handler_real

reset_handler:
   .word   reset_handler_real

reset_handler_real:
   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}
   ... [more stuff] ...
That is what I ended up going with. I admit it is pretty much a duplication of dwelch67's code, but I decided to go with that because it seemed the most straightforward out of all the examples I found. Despite that fact, I still have a few questions. Firstly...

I really wanted this to work:

Code: Select all

ldr pc, basic_handler_real
But it didn't seem to fly. So I am wondering: what's with the .word basic_handler_real?

My theory is this: per the ARM-ARM, ldr will branch to the target location and evaluate what to do from there. I bet this is a 'trick' to make the compiler branch to the intermediary label, see the variable declaration (which contains a pointer to the handler we actually want) and then basically returns a 'branch to the actual target' command. Is that why we must do it this way?

And as a general assembly question - I notice that the entry_point doesn't seem to have any branch commands which get it to the reset_handler_real code (unless that's what the first command is doing). So does my label reset_handler_real get invoked because the code is just being processed in order? What I mean to say is: if you don't invoke a branch instruction, will the code just jump to the next label in the list once it gets to the end of the current label being evaluated?

Lastly, I found the commands ldm and stm in the ARM-ARM, but the way these are used is a bit complicated so... I just want to clarify: how do the registers (r2-r9) get filled in for these ldm/stm instructions? Do they get filled in via: the entry point when we first go "ldr sp,[handler]"?

Thank you for all the help, you guys are awesome as always! I'm so happy that I finally go this to work :)

User avatar
rpdom
Posts: 19542
Joined: Sun May 06, 2012 5:17 am
Location: Chelmsford, Essex, UK

Re: Interrupt Theory

Wed Apr 03, 2013 4:34 pm

Ok, let's have a look at this.

Code: Select all

ldr pc, basic_handler
This means Load the Program Counter with the contents of the address "basic_handler".

Code: Select all

basic_handler .word basic_handler_real
This is not an ARM instruction, it just means Store the address of "basic_handler_real" at this address in the program.

So, if your basic_handler_real is stored at 0x00009024 in memory, the address of "basic_handler" will contain 0x00009024.
When the instruction "ldr pc, basic_handler" (which will actually be assembled as "ldr pc, pc, +a_few_words") is executed it will load 0x00009024 into PC, which is the equivalent of "b 0x00009024", or "b basic_handler_real".

As for "ldr basic_handler_real", that won't work as your code is assembled to run at 0x8000 and the interrupt table needs to be copied to 0x0000, so any relative addresses will be 0x8000 out. All "b" and "bl" type instructions are relative rather than absolute on ARM. This is why you need to store the absolute address in memory for this case.

User avatar
sharpcoder
Posts: 39
Joined: Tue Mar 12, 2013 9:07 pm
Location: Seattle, WA
Contact: Website

Re: Interrupt Theory

Wed Apr 03, 2013 4:39 pm

Ah I see, thank you very much! That makes perfect sense :)

tufty
Posts: 1456
Joined: Sun Sep 11, 2011 2:32 pm

Re: Interrupt Theory

Wed Apr 03, 2013 4:42 pm

sharpcoder wrote:Well, I finally got it!
Excellent.
I really wanted this to work:

Code: Select all

ldr pc, basic_handler_real
This won't work, because "ldr rX, y" means "go to address y, and load the contents of the memory at that address (note - not the address itself) into rX". As you're loading into the program counter, this causes a direct branch to the address pointed to by the contents of memory at address y. In short, you have a (single entry) jump table.
Lastly, I found the commands ldm and stm in the ARM-ARM, but the way these are used is a bit complicated so... I just want to clarify: how do the registers (r2-r9) get filled in for these ldm/stm instructions? Do they get filled in via: the entry point when we first go "ldr sp,[handler]"?
The ldm / stm pair David is using are used to do what's effectively a very fast, fixed size block copy. Let's look at it:

Code: Select all

   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}
So, we load r0 with the source address (0x8000), and r1 with the target address (0x0). ldm loads a set of registers from an address held in another register, and optionally updates the "source address" register with the "next" address.

So, in this case, we load multiple (ldm) registers r2-r9 from the address held in r0, increment the value of r0 after loading (ia, increment after), and write it back into r0 (!). After we have loaded, r0 will contain 0x8020 (0x8000 + 8 * 4), r2 will contain the contents of memory at 0x8000-0x8003, r3 the contents of 0x8004-0x8007 and so on.

We then use the "stm" to store those same values at 0x0 (which is where the vector table really needs to be).

dwelch67
Posts: 1006
Joined: Sat May 26, 2012 5:32 pm

Re: Interrupt Theory

Thu Apr 04, 2013 2:02 am

sharpcoder wrote: Lastly, I found the commands ldm and stm in the ARM-ARM, but the way these are used is a bit complicated so... I just want to clarify: how do the registers (r2-r9) get filled in for these ldm/stm instructions? Do they get filled in via: the entry point when we first go "ldr sp,[handler]"?
The ARM documentation can/does overly complicate the ldm/stm descriptions. As tufty described simply think of the ldmia as starting with the first register, the one before the brackets, as the base address, and starting at that address load/read a value from memory and put it in the first, register, then read the next word sized value in memory and put that in the next register and so on until the register list is depleated. Same goes for stmia start with the base address in the first register, then store one word at a time in memory sequentially for each register in the list.

Taking a smaller example

Code: Select all

mov r0,#0x1000
ldmia r0!,{r1,r2,r3}
when ldmia starts r0 is 0x1000, so

Code: Select all

first
r1 = memory[0x1000]
r2 = memory[0x1004]
r3 = memory[0x1008]
then
r0 = 0x100C
the pseudo code memory[0x1000] means in this case the 32 bit (4 bytes) word in memory at address 0x1000.

Now lets get rid of the exclamation point after the r0, the exclamation point in this instruction means save the base address back to that register.

Code: Select all

mov r0,#0x1000
ldmia r0,{r1,r2,r3}

Code: Select all

r1 = memory[0x1000]
r2 = memory[0x1004]
r3 = memory[0x1008]
Actually to be closer to the description lets say this:

Code: Select all

base = r0 = 0x1000
r1 = memory[base+0x000]
r2 = memory[base+0x004]
r3 = memory[base_0x008]
Where base is implied to be an internal value.

So without the exclamation point r0 is not modified at the end it remains 0x1000 after the instruction is over. lets change the instruction from ldmia to ldmdb

Code: Select all

mov r0,#0x1000
ldmdb r0,{r1,r2,r3}
the ia meant "increment after" the db means "decrement before". So before we start to load from memory the base address is taken from the base address but decremented by the number of registers in the list times 4 (4 bytes = 32 bits, the size of each register).

Code: Select all

base = r0 - (3*4) = r0 - 12 = 0x1000 - 0xC = 0xFF4 
r1 = memory[base+0x000] = memory[0xFF4] 
r2 = memory[base+0x004] = memory[0xFF8]
r3 = memory[base+0x008] = memory[0xFFC] 
So the base address used for addressing memory was "decremented before" the memory operations started.

Put the exclamation point back on

Code: Select all

mov r0,#0x1000
ldmdb r0!,{r1,r2,r3}

Code: Select all

base = r0 - (3*4) = r0 - 12 = 0x1000 - 0xC = 0xFF4 
r1 = memory[base+0x000] = memory[0xFF4] 
r2 = memory[base+0x004] = memory[0xFF8]
r3 = memory[base+0x008] = memory[0xFFC] 
r0 = r0 - (3*4) = 0xFF4
stm is store multiple, same as load multiple but a write instead of read

Code: Select all

mov r0,#0x1000
stmia r0!,{r1,r2,r3}

Code: Select all

base = r0 
memory[base+0x000] = r1 
memory[base+0x004] = r2
memory[base+0x008] = r3 
r0 = base+0x00C
r0 is modified at the end just like the ldm because the exclamation point says to do so.

Where the ia vs db becomes most apparent is with the push and pop, which with ARM instructions and some flavors of thumb instructions is just pseudo code for ldm or stm.

Code: Select all

push {r1} = stmdb r13!,{r1}
base = r13 - (1*4) = r13 - 4;
memory[base+0x00] = r1
r13 = r13 - 4
When pushing a value on the stack, the stack pointer points at the "top of the stack" so if the stack pointer happens to have the value 0x2010 before the push, and that means 0x2010 is the top of the stack, there is a value there being saved on the stack, so to add something to the stack we decrement to 0x200C basically pointing the stack pointer at a new location on top of the stack then we write something there, decrement the stack pointer before, decrement before, then write. And we want the stack pointer to remember the new location of the stack so we modify the stack pointer to point to the new top of stack.

Popping from the stack

Code: Select all

pop {r1} = ldmia r13!,{r1}
base = r13
r1 = memory[base+0x00]
r13 = r13 + 4
to pop something off the stack we want to read from the thing on the top of the stack so we need to start at the address pointed to by r13, unmodified. then after reading that value to "remove it from the stack" we increment the stack pointer after using it, increment after.

Pushing and popping more than one register is done the same way. there is another syntax instead of ia and db, I forget them one is fd, the other I dont remember, they didnt make sense to me like ia and db. over time the ia and db syntax has dominated in disassemblers and arm syntax, etc...

So if you wanted to make a memcpy routine you could have a loop that inside the loop contains:

Code: Select all

r0 = address of source (0x1000)
r1 = address of destination (0x2000)
;while(--length*4)
ldmia r0!,{r2,r3,r4,r5}
stmia r1!,{r2,r3,r4,r5}
;endwhile

Code: Select all

;ldmia
base = r0 = 0x1000
r2 = memory[base+0x0] (0x1000)
r3 = memory[base+0x4] (0x1004)
r4 = memory[base+0x8] (0x1008)
r5 = memory[base+0xC] (0x100C)
r0 = r0 + (4*4) = r0 + 16 = 0x1010
;stmia
base = r1 = 0x2000
memory[base+0x0]= memory[0x2000] = r2
memory[base+0x4]= memory[0x2004] = r3
memory[base+0x8]= memory[0x2008] = r4
memory[base+0xC]= memory[0x200C] = r5
r1 = r1 + (4*4) = r1 + 16 = 0x2010
;second time through loop
;ldmia
base = r0 = 0x1010
r2 = memory[base+0x0] (0x1010)
r3 = memory[base+0x4] (0x1014)
r4 = memory[base+0x8] (0x1018)
r5 = memory[base+0xC] (0x101C)
r0 = r0 + (4*4) = r0 + 16 = 0x1020
;stmia
base = r1 = 0x2010
memory[base+0x0]= memory[0x2010] = r2
memory[base+0x4]= memory[0x2014] = r3
memory[base+0x8]= memory[0x2018] = r4
memory[base+0xC]= memory[0x201C] = r5
r1 = r1 + (4*4) = r1 + 16 = 0x2020
so basically the end result is:

Code: Select all

memory[0x2000] = memory[0x1000]
memory[0x2004] = memory[0x1004]
memory[0x2008] = memory[0x1008]
memory[0x200C] = memory[0x100C]
memory[0x2010] = memory[0x1010]
memory[0x2014] = memory[0x1014]
memory[0x2018] = memory[0x1018]
memory[0x201C] = memory[0x101C]
memory[0x2020] = memory[0x1020]
memory[0x2024] = memory[0x1024]
memory[0x2028] = memory[0x1028]
...
the more registers in the register list in the ldm and stm instructions means more memory read and written PER INSTRUCTION, so you have the savings of number of instructions.

so this

Code: Select all

   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}
copies 8 words of memory for every ldmia/stmia pair. So the two pairs above copy 16 words from 0x8000 to 0x0000 using 6 instructions.

Code: Select all

   mov r0, #0x8000
   mov r1, #0x0000
   ldmia r0!,{r2,r3,r4,r5}
   stmia r1!,{r2,r3,r4,r5}
   ldmia r0!,{r2,r3,r4,r5}
   stmia r1!,{r2,r3,r4,r5}
   ldmia r0!,{r2,r3,r4,r5}
   stmia r1!,{r2,r3,r4,r5}
   ldmia r0!,{r2,r3,r4,r5}
   stmia r1!,{r2,r3,r4,r5}
The above copies the same amount of data but it takes more instructions because there are fewer words loaded and stored for each ldmia/stmia pair.

These are really only for aligned addresses, if you dig into memcpy routines for arm you will see that they do some stuff to figure out of the source and dest are aligned or not, then in an ideal world they can have the middle/main loop in the copy use ldmia/stmia pairs with four registers each (a nice balance between performance and register consumption and interrupt latency). Before and after the middle part of the memcpy they copy the odd bytes to get to aligned addresses. this doesnt always work though, think about a memcpy from 0x1001 to 0x2002...

The arm documentation (ldm/stm) pseudo code is trying to show the loading or storing using a loop that goes through the register list and how the base address of that loop is calculated, and if the base register is modified at the end how that is calculated. You have the combinations of increment before or decrement after, then the option of saving or not. What wasnt shown above was a shortcut to listing the registers, I like to list then explicitly but you can for example say {r2-r5} instead of {r2,r3,r4,r5}. Also note that it loads or stores them in numerical order, if you do not list them in numerical order (and the assembler tolerates that) it doesnt matter the hardware does it in numerical order, so if this is accepted {r5,r1,r9,r11} the order loaded or stored is in numerical order r1, r5, r9, r11. So somehow the arm documentation will try to explain that as well possibly in a complicated way...

hope this helps
David

User avatar
rpdom
Posts: 19542
Joined: Sun May 06, 2012 5:17 am
Location: Chelmsford, Essex, UK

Re: Interrupt Theory

Thu Apr 04, 2013 5:30 am

Thank you, dwelch, for a clear and comprehensive explanation :)
dwelch67 wrote:Also note that it loads or stores them in numerical order, if you do not list them in numerical order (and the assembler tolerates that) it doesnt matter the hardware does it in numerical order, so if this is accepted {r5,r1,r9,r11} the order loaded or stored is in numerical order r1, r5, r9, r11. So somehow the arm documentation will try to explain that as well possibly in a complicated way...
As I understand, in the ldm/stm instruction code, bits 0 to 15 correspond to the registers that will be used for the operation. If bit 7 is set (1), then r7 will be included. So all the ARM has to do is test each bit in turn and process the relevant register if the bit is set. The assembler will just build the instruction by setting bits while it parses the parameters given.

So if you say {r0, r10, r5, r6}, bits 0, 10, 5 and 6 are set and the instruction will process r0, r5, r6 and r10 as it scans the opcode.

Not too complicated :)

dwelch67
Posts: 1006
Joined: Sat May 26, 2012 5:32 pm

Re: Interrupt Theory

Thu Apr 04, 2013 6:31 am

rpdom wrote:Thank you, dwelch, for a clear and comprehensive explanation :)
dwelch67 wrote:Also note that it loads or stores them in numerical order, if you do not list them in numerical order (and the assembler tolerates that) it doesnt matter the hardware does it in numerical order, so if this is accepted {r5,r1,r9,r11} the order loaded or stored is in numerical order r1, r5, r9, r11. So somehow the arm documentation will try to explain that as well possibly in a complicated way...
As I understand, in the ldm/stm instruction code, bits 0 to 15 correspond to the registers that will be used for the operation. If bit 7 is set (1), then r7 will be included. So all the ARM has to do is test each bit in turn and process the relevant register if the bit is set. The assembler will just build the instruction by setting bits while it parses the parameters given.

So if you say {r0, r10, r5, r6}, bits 0, 10, 5 and 6 are set and the instruction will process r0, r5, r6 and r10 as it scans the opcode.

Not too complicated :)
Yep, that is how that works...I know the encoding of the instruction but have not looked at how they explain the ordering in a while. Register ordering if you understand the instruction encoding is simple, if instruction encoding is not obvious to you (you as in the general you anyone reading this rather than you the person I am replying to) then register ordering might not be either.

thumb is done the same way but 8 bits r0-r7 then a ninth bit which is either lr or pc depending on push or pop. thumb2 has one or more flavors which I dont remember the encoding off hand...I assume it uses a similar scheme (and allows at least some registers above r7, but I dont think all of them). I did recently look up to confirm that for at least two of them the push and pop in assembly language are pseudo instructions (in this case, machine code is identical) for stm or ldm. One of them (thumb or thumb2) is truly a push or pop instruction and does not match that flavors stm/ldm machine code. Not that any of that matters to the topic at hand...

From an older ARM ARM

The registers are stored in sequence, the lowest-numbered register to the lowest
memory address (start_address), through to the highest-numbered register to the
highest memory address (end_address).

For each of i=0 to 15, bit in the register_list field of the instruction is 1 if Ri is in
the list and 0 otherwise. If bits[15:0] are all zero, the result is UNPREDICTABLE.


And also note there is ia, increment after, ib increment before, da decrement after and db decrement before. If you think about it an increment after without the writeback is just a "load this stuff at the address I specified" which you can sometimes use ldm without the ia suffix depending on the assembler.

Also there is a flavor with a ^ symbol after the register list which indicates the user registers, writeback is not valid with this option.

The ARM ARM I am looking at at the moment jumps you around sections/pages to figure out how it works...

Code: Select all

address = start_address
for i = 0 to 15
  if register_list[i] == 1 then
    Memory[address,4] = Ri
    address = address + 4
...
end_address = address - 4
Is actually pretty obvious, I would hope, but start address is not defined on that page you have to know to hop to another page and then down another page or more to find how start_address is defined. And the write back is not shown in the pseudo code, at least not the one I am looking at...So you have to gather that information from the description which points you at some other section.

David

gauravbansal
Posts: 1
Joined: Wed Aug 13, 2014 1:31 pm

Re: Interrupt Theory

Wed Aug 13, 2014 1:54 pm

Hi David

I have been following your posts very keenly on stackoverflow.com. I think you have a tremendous hold on the ARM basics. Kudos to that. I am also one of those who is trying to understand how the low level IRQ/FIQ handling works in ARMv7 architecture.
I am trying to understand how nested interrupts work. I have gone through the ARM ARM sections on IRQ handling. While browsing other resources, I came across this handler code which has kind of got me by the neck. I am sure it works but I don't think I get it as to why is the author doing it this way as ARM ARM has a simpler implementation.


Here is the code for IRQ top level handler
===============================
ARM_irq:
/* IRQ entry {{{ */
MOV r13,r0 /* save r0 in r13_IRQ */
SUB r0,lr,#4 /* put return address in r0_SYS */
MOV lr,r1 /* save r1 in r14_IRQ (lr) */
MRS r1,spsr /* put the SPSR in r1_SYS */

MSR cpsr_c,#(SYS_MODE | NO_IRQ) /* SYSTEM, no IRQ, but FIQ enabled! */
STMFD sp!,{r0,r1} /* save SPSR and PC on SYS stack */
STMFD sp!,{r2-r3,r12,lr} /* save APCS-clobbered regs on SYS stack */
MOV r0,sp /* make the sp_SYS visible to IRQ mode */
SUB sp,sp,#(2*4) /* make room for stacking (r0_SYS, r1_SYS) */

MSR cpsr_c,#(IRQ_MODE | NO_IRQ) /* IRQ mode, IRQ/FIQ disabled */
STMFD r0!,{r13,r14} /* finish saving the context (r0_SYS,r1_SYS)*/

MSR cpsr_c,#(SYS_MODE | NO_IRQ) /* SYSTEM mode, IRQ disabled */
/* IRQ entry }}} */

LDR r12,=BSP_irq
MOV lr,pc /* copy the return address to link register */
BX r12 /* call the C IRQ-handler (ARM/THUMB) */

/* IRQ exit {{{ */
MSR cpsr_c,#(SYS_MODE | NO_INT) /* SYSTEM mode, IRQ/FIQ disabled */
MOV r0,sp /* make sp_SYS visible to IRQ mode */
ADD sp,sp,#(8*4) /* fake unstacking 8 registers from sp_SYS */

MSR cpsr_c,#(IRQ_MODE | NO_INT) /* IRQ mode, both IRQ/FIQ disabled */
MOV sp,r0 /* copy sp_SYS to sp_IRQ */
LDR r0,[sp,#(7*4)] /* load the saved SPSR from the stack */
MSR spsr_cxsf,r0 /* copy it into spsr_IRQ */

LDMFD sp,{r0-r3,r12,lr}^ /* unstack all saved USER/SYSTEM registers */
NOP /* can't access banked reg immediately */
LDR lr,[sp,#(6*4)] /* load return address from the SYS stack */
MOVS pc,lr /* return restoring CPSR from SPSR */
/* IRQ exit }}} */

Any help will be appreciated
Gaurav

dwelch67
Posts: 1006
Joined: Sat May 26, 2012 5:32 pm

Re: Interrupt Theory

Wed Aug 13, 2014 11:46 pm

That looks like a context switch. Basically a task manager or operating system switching from one task to another so it has to switch modes to the system or user mode or both, save all the registers, then whomever is next gets swapped in.

That is way overcomplicated for a simple interrupt handler because it is more than a simple interrupt handler. Yes an interrupt is just some sort of event it is a way for hardware to interrupt you like the doorbell does, from what you are doing, you handle the doorbell then you go back to what you were doing. The alternative is your friend stands outside by the door and waits for you to wander by and look out which could be minutes or hours (polling).

How you handle the interrupt relies heavily on the hardware not just ARM but the interrupt manager and the peripheral. The basic thing is make sure you dont mess up any registers the foreground task was using, some processors do this for you, most you have to do some pushing of registers to preserve them, gives you the flexibility to choose how many to save a few clock cycles if your handler is not using all of them (in a hand coded assembly type solution). Then you need to figure out where the interrupt came from and sometimes the interrupt controller can tell you sometimes it hints if an interrupt is shared and then you have to wander through the peripherals to find out who on that interrupt line did it. then based on that peripheral and the interrupt and why it interrupted you handle it, you pull the byte out of the uart receiver or usb this or video that or whatever. You also have to clear the interrupt at the source and sometimes in the interrupt controller and sometimes other places and often the order you clear things matters (usually farthest away first). THEN, you should go back and check for more interrupts that came in and who caused them and keep doing this until all clear, then restore the state (registers,etc) and return based on the proper return instruction.

what happens when an interrupt happens during another interrupt, well that depends on the interrupt controller and the processor, etc. so there is no fixed answer. With these arm cores or this arm processor there is one regular and one fast interrupt line and that is all that is on the core, the chip vendor (broadcom in this case) has to come up with an interrupt controller that allows you to mux the multiple interrupt events into one or the other processor input, for enabling and disabling, etc. Some cores and some chips have more elaborate stuff with priority encoding and such to allow a higher priority interrupt to interrupt a lower priority interrupt.

sorry started to ramble there...I think that code you posted is a task switcher, much more complicated unless you are wanting to make a task switcher. a task switcher is probably driven by a timer so along with the saving/changing states of the foreground tasks you also have to clear and maybe reset the timer to interrupt you again after a while (to switch to the next task).

David

Return to “Bare metal, Assembly language”