This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

Assign asm code to ISR vector in C

I have a small co-operative kernel written in asm for MSP430. (I did look at SYS/BIOS but it is too big and complicated by a factor of 10, and I am also not fond of wizard-generated code.) There is a scheduler tick ISR that is used for handling sleep and semaphore/barrier-take timeouts, also written in asm. I could just allocate a fixed timer resource to the OS, and bind the ISR to that interrupt vector in the standard way. If possible though, I would rather let the project BSP define a timer to use, and invoke the scheduler tick ISR for the interrupt generated by that specific timer resource. This would be more flexible and also makes it easier to set up the resource.

I was looking for something like this (pseudo-code)

    extern void os_tick_isr(void)

    vector(INT_VEC_T0) os_tick_isr;

Is there a way to cause the address of an asm block to be loaded into the appropriate interrupt vector segment, directly from C? I would strongly prefer not to have to go to the linker files.

  • Did you try to use msp430 forum search? using keywords "assembler" and "ISR".

    I got this thread which possibly can answer your question.

  • Hi Sean,

    Sean Kinghan said:

    I have a small co-operative kernel written in asm for MSP430. (I did look at SYS/BIOS but it is too big and complicated by a factor of 10, and I am also not fond of wizard-generated code.) 

    I wrote a small kernel/scheduler system for MSP430 value line, and it is available open source:

    https://code.google.com/p/mcu-simple-scheduler/

    All is written in C, so you should be able to compile it with either CCS, IAR, or even MSP-GCC. Would this be also interesting for you?

  • I did indeed search the forum, not quite with those keywords. The thread you mention I had already seen, but that is unfortunately exactly the opposite of what I want to do - he is talking about calling C from asm, I want to call asm from C. But specifically, and this is quite crucial, without the compiler overhead generated by the __interrupt attribute, and without the need for a call and return sequence. The OS is heavily optimised to avoid unnecessary overheads like this, so pushing and popping unused registers, and making a call sequence, all cost time I don't want to waste.

    To hopefully clarify this a little, something like "#pragma vector = INT_VEC_X" would work fine if it accepted a following external reference to the actual ISR code, but it demands a local function definition to follow.

  • Hi Leo,

    It is very interesting to me, but not useful unfortunately, as I am not too keen on the idea of porting my existing code to it.

    The biggest problem is the nature of the co-operative scheduling model being what you call co-routines (which is not really how I understand co-routines) and what I know as proto-threads, specifically that they don't save state in a TLS fashion. This would break all my existing code, which is very reliant on retention of context. Secondly, I don't trust the compiler's efficiency - I get dramatic improvements in size and speed by using hand-coded asm. At a cost in maintenance and portability, certainly, but that's the trade-off I choose to make, as it applies only to the OS module, not the applications code which is what changes most.

    However, based on my very brief examination, there are some very good ideas in that project, some of which I am sure to make use of as the need arises. Although it is no use to me because of the overheads, I particularly like the use of the compiler to implicitly generate the context save and restore code.

    Curiously, I also have a hybrid co-operative/preemptive model in which same-priority tasks co-operate, and higher-priority tasks preempt those lower down. It seems to give a useful trade-off between through-put and latency.

  • Sean Kinghan said:
    Is there a way to cause the address of an asm block to be loaded into the appropriate interrupt vector segment, directly from C?

    I don't think so. You can make the compiler generate the proper entry for a C function, or you add the reference to the vector location in assembly. But you cannot make the C compiler put an unresolved external address value into the vector table.

    On MSPGCC, I solved this by adding the 'naked' attribute to the C ISR (this prevents the compiler to generate any entry/exit code, not even an RTI) followed by a jmp to the ASM function. Still the overhead of this jump, but well...

  • OK, thanks Jens. That looks to be the best I am going to get from the compiler. I had seen the "naked" attribute some time ago while browsing the compiler docs, but forgotten about it. I will decide which approach suits me better - I can maybe live with a 2(?)-cycle jump cost.  ;-)

  • If it turns out any other OCD type needs something like this, I figured out a way. Basically it relies on the linker resolving references.

    The C code at some point calls an asm routine, selecting one of multiple possibilities predefined in the asm module, eg.

         os_tick__init__ta2( );

    where the ...__ta2 could be ...__ta0 etc. Inside this stub asm routine, reference is made to a vector address word (simply by dummy assigning it to R12) defined in the appropriate segment for the timer-2A0 interrupt vector (say .int44). In the definition of this vector it is initialised to hold the starting address of the appropriate ISR code, which in this case is a tick incrementer common to all the timer ISR possibilities.

    Each possible vectoring option needs to be defined in the asm module, although only the referenced portion is linked in. The actual body of the ISR code is, as mentioned, shared.

  • I use a similar method (in C with inline assembly, not with full assembly functions) for my UART handling. Up to 4 RX and TX vectors share the same code. I just load an index at the entry point of the ISR and then forward to a common code.

    However, if you use a register to pass a parameter (or to receive the dummy reference), you have to save the original register content before loading the value into the register. And you need to manually restore the register content at the end of your common ISR. Els the eecution of your ISR will ater the working registers of your interrupted main code in the middle of operation, with likely devastating effects.
    Maybe you should assign the reference to a global variable instead. The additional cycles are compensated by not needing a register push/pull at two distant locations.

  • Hi Jens,

    It looks to me like you are talking about parameterizing the handler code with an index passed in a register, so that the register value in effect encodes what the original entry point was? I can't see how that is too functionally different than making a call to a procedure with an index argument, other than by being able to replace the call/ret sequence with a jump. I say this from the perspective of analogizing the behaviour so as to better understand. It is admittedly a much faster process to jump than call, and slightly less stack intensive, and therefore certainly worth pursuing should a call not be required. However, I wasn't needing to do that, as there is only ever one (run-time) source for this interrupt, which is a timer tick. The BSP source code chooses a timer resource, which may vary depending on what else the board needs to do, and I wanted that choice of resource to be embodied in a single line of code in the C source, rather than scattered through C, asm and link files. So what I was trying to work out was how to bind the handler address at link time, to just one of potentially many possible compile-time selected sources, and thereby avoid dispatching overheads at run-time. Kind of the inverse of what you are doing, in the sense of being deliberately highly static rather than deliberately highly dynamic.

    As for save and restore of register values, yeah, I've been there. Too many times. I avoid the need in this specific piece of code by directly accessing the memory without going through the registers. It's just an increment of a 32-bit ticks counter, so I just add.w #1, x then addc.w #0, x+2, and we are done. Well, not really - I do have to check to see if the system has now reached a state in which I should exit low power mode and allow the scheduler to find the next active task (one which has timed out), but that is just a dec.w/jnz sequence of instructions. Something like a one-spoke timing wheel ... Anyway, also no registers there, and the status bits have already been preserved for me.

    I must say, I am very enamoured of the MSP430 design. The overall regularity of the ISA opcodes and addressing modes is great. (Particularly in this case) the means to avoid touching the registers by going direct to memory. The power mode bits in the status register, with the consequent automatic restore of low power mode on reti. Etc, etc - they did a great job with this chip. The only thing I am not too keen on is the cycle count complexity, and dearth of formal information on the values. Speaking of which, do you know of a decent cycle reference, and also any really really in depth information about the chip? I mean info about microcoding, bus pipelining, branch slot filling, that sort of thing?

  • Oh yeah, turns out that the "naked" attribute I was reading about was in the GCC docs (I think for AVR32 perhaps), and not in CCS ... Bummer.

  • Sean Kinghan said:
    It is admittedly a much faster process to jump than call

    That's the point. Frequently used ISRs should be as fast as possible (I also use some tricks to optimize the indexing into the different FIFOs, by putting the index directly into the highbyte of an index into a 'superbuffer', where the read/write counters are the lowbyte and increment as byte with wrap-around at 0xff). And some IFDEFs depending on the number of UARTs used on the specific project.

    Sean Kinghan said:
    as there is only ever one (run-time) source for this interrupt

    So the main problem is not how to select it (this could be done by simple #ifdef in the C code) but how to make the C compiler put the correct reference into the vector table based on C defines.

    Maybe you can fool the compiler by following the #pragma with an ISR function declaration (with the name of the required ASM ISR) rather than a definition. Maybe this still places the reference into the vector table. Just an idea.

  • It's all sorted out - I managed to get the linker to do what I want by having the C source call an initialization function that in asm references a definition located in the appropriately defined segment. There's a description somewhere earlier. A #define would work, yes, to select the correct "touching" function.

    Jens said:
     Maybe you can fool the compiler by following the #pragma with an ISR function declaration (with the name of the required ASM ISR) rather than a definition. Maybe this still places the reference into the vector table.

    I will try that, and let you know.

  • No luck asking the compiler to #pragma vector the declaration of an externally defined function. It gives an error message explicitly stating that only a function definition is applicable following this pragma.

  • Too bad. Feature intentionally (or unknowingly) disabled. :(
    Thanks for trying.

**Attention** This is a public forum