Hello everyone!
I'm working with MCF51QE128 and CW v6.1. I was used to workig with HCS12 where interrupts can be either enable or disable. On this other MCU, there's a prioriry level for them. My question is how can I get the actual value, then set another one (for example 7 to disable all) and then, when I'm done, restore the previous value.
I was told, that there's an asm function: asm_set_ipl (you can look for it here http://powerlinkstm32.googlecode.com/svn/trunk/MN/openPOWERLINK_v1.6/Target/PLCcore-CF54/no_os/gnu/c...) but I don't know how to add it to my project, or if it is another way to do that
Thanks in advance!
Thanks for your replies!
I found very helpful your post to understand better some coding techniques. I did search the forum for "asm_set_ipl" but all I've found is about using it, no how to implement it. In the posts I saw, they all already have the 'asm_set_ipl' function available.
My questions would be:
1) How to write this function in C so I can call it there or how to do add and ASM file to my project? One thing I notice from the link in my first post is that the files extension is .S and not .ASM why is that?
2) Can I do this:
uint32 asm_set_ipl (uint32 new_ipl){
asm{
/*code of the first post for asm_set_ipl*/
}
}
If so, how would I pass the new_value to the ASM routine (wich register) and where does it return the old one? (I'm not familiar with the coldfire instruction set)
Jim, Tom, I'm not working with threads, as Tom said I'm "writing simple code based on 8-bitter designs". For what I'm doing enabling/disabling all interrupts will work because I don't have any priority level management. But I'd like to know how would I do it "in the right way".
SebaS
There should be a macro defined in the project, but you can use the code. SetIntLevel(0) to enable, and 7 to disable.
or in hidef.h that is in a default project.
#define EnableInterrupts asm { move.w SR,D0; andi.l #0xF8FF,D0; move.w D0,SR; }
/*!< Macro to enable all interrupts. */
#define DisableInterrupts asm { move.w SR,D0; ori.l #0x0700,D0; move.w D0,SR; }
/*!< Macro to disable all interrupts. */
To answer you original question, I stumbled upon this put it in a file something.S:
.global asm_set_ipl .global _asm_set_ipl .text /********************************************************************/ /* * This routines changes the IPL to the value passed into the routine. * It also returns the old IPL value back. * Calling convention from C: * old_ipl = asm_set_ipl(new_ipl); * it passes return value thru D0. * Note that only the least significant three bits of the passed * value are used. */ asm_set_ipl: _asm_set_ipl: link A6,#-8 movem.l D6-D7,(SP) move.w SR,D7 /* current sr */ move.l D7,D6 /* prepare return value */ andi.l #0x0700,D6 /* mask out IPL */ lsr.l #8,D6 /* IPL */ andi.l #0x07,D0 /* least significant three bits */ lsl.l #8,D0 /* move over to make mask */ andi.l #0x0000F8FF,D7 /* zero out current IPL */ or.l D0,D7 /* place new IPL in sr */ move.w D7,SR move.l D6, D0 /* Return value in D0. */ movem.l (SP),D6-D7 lea 8(SP),SP unlk A6 rts
Sebasira originally asked:
> I was told, that there's an asm function: asm_set_ipl (you can look for it here
> << see link in original>> but I don't know how to add it to my project,
> or if it is another way to do that
Jim just responded:
> To answer you original question, I stumbled upon this put it in a file something.S:
The code you included in your post is EXACTLY the same as the code in the original post. He's got that code. He doesn't know what to do with it. I don't think "put it in a file something.S" is going to work without telling CW about this file.
I don't have CodeWarrior. Could someone who does please tell the original poster how to include assembly code in an assembly file into a CW project? Or say how to "wrap" this function in inline assembly to do the same thing. Or that this code is already present in CodeWarrior and what you have to do (#include "thing.h" and project settings) to use it? Or where these instructions are in the CW Documentation?
Tom
This code is tested on a V2, but I think it will work on V1 as well:
void SetIntLevel(level) { asm { /* Enable the given interrupt level in the SR register */ move.w SR, D0 andi.l #0x0000F8FF, D0 // Clear bits. or.l #((level & 7) << 8), D0 // Set desired level. move.w D0, SR } }
> This code is tested on a V2,
Three things.
1 - That code sets an IPL, but unlike asm_set_ipl() it doesn't return the previous value. It can't be used to change-then-RESTORE the IPL. It is always safer to do that on these chips.
2 - The OP has the code. He doesn't know how to incorporate assembly code into a CodeWarrior project. That's the main problem he needs help with
3 - I didn't know the ColdFire had an assembly addressing mode that could perform "#((level & 7) << 8)" :-) I assume the compiler is translating that bit - but declared as an immediate? That's some weird syntax. Can you post the disassembly?
Tom
I use the previous code in interrupt handler to allow higher priority interrupts to run. The return from interrupt restores the previous value.
I never found any reason to change the mask in foreground code other than to disable. I suppose you could cobble something together from these posts.
If you want safe routines for mutexing, I use this, which allows other called functions to use CLI with out harm:
void CLI(void); void STI(void); volatile byte SR_level = 0x00; volatile word saved_SR = 0x00; void CLI(void) { // If this is the first call, the save SR and disable int. if (++SR_level == 1) { asm { move.w SR,D0; move.w D0,saved_SR; ori.l #0x700,D0; move.w D0,SR; } } } void STI(void) { // If this is the last call, then restore SR. if (--SR_level == 0) { asm { move.w saved_SR,D0; move.w D0,SR; } } }
> I never found any reason to change the mask in foreground code other than to disable.
You must be writing simple code based on 8-bitter designs. These are 32-bitters. You might as well code for the features rather than try to emulate an older technology.
The point of always performing a "save/raise/restore" sequence is threefold:
1 - It lets you write code using the multiple execution levels of these chip. You can write pretty good "poor man's threading" using this. Simply having the background code taking as long as it likes with an interrupt-triggered "high priority thread" separate from time-critical interrupts lets you write simple, yet powerful and predictable (timing) programs.
2 - It lets you write "call safe from anywhere" functions that access data structures that have to have locking associated with them. You don't want to have to have one set of functions that you can only call from the background (because it disables interrupts) and another one you can only call from a single interrupt level (because it doesn't disable interrupts).
3 - It is "simple code that simply works and obviously works".
The product I'm working on now uses all levels:
IPL0: background (slow) code
IPL1: Time-critical logging code
IPL2: USB stack interrupts
IPL3: Ethernet
IPL4: Serial port and ADC software interrupt ADC completion
IPL5: CAN, Flash erase complete and QSPI (CAN & ADC)
IPL6: High-speed CAN
IPL7: Software Watchdog, Power monitor, Code Profiler.
Your "CLI/STI with call-depth memory" solves (2) but can't be used with code taking advantage of (1). It can't work if you're using threads either.
I think I can see some problems with it. The fact that it is using two globals is a big clue that it might be unsafe. What is protecting access to those globals? Where are the locks? You may need to disable interrupts around their accesses, except the purpose of this function...
> if (++SR_level == 1)
SR_level is a shared global variable. If the CPU doesn't execute the "++" atomically (but does a load/increment/store) then it can get incremented twice and stored once. If it does atomically increment it, then it has to reload it after the increment to test it, and it may have been accessed between those by an interrupt. The optimiser may spot the test, and so decide to "load, increment, store, test the incremented data" rather than "atomic increment, then load and test" which is slower. The "volatile" might change the order, but it may not do what you want. You really need this part in assembler so you know which hazards it really has.
The above code might work, but it doesn't OBVIOUSLY work.
What happens if there's an interrupt between that instruction and the one that finally disables the interrupt by executing the "move.w D0,SR;"?
The interrupt routine will see that "SR_LEVEL" has already been incremented, so it WON'T DISABLE THE INTERRUPTS! That interrupt routine can then be interrupted by a higher one that also calls this function and it won't disable the interrupts either! The mainline won't have a problem, but any variables shared between the interrupt routines are unsafe.
You may not code this way, but some future programmer who is modifying your code might fall for this trap.
The above hazards and faults won't matter if ALL of the interrupt service routines are running at the same IPL, or all of them separately force the IPL to "7", but that's not likely.
The whole POINT of "int oldLevel = asm_set_ipl(NEW_LEVEL) is that the saved state is on the STACK and not in a Global. That simple approach makes it safe by design. It means it can be used anywhere and just simply works simply.
Tom
I do set the interrupt levels when I initialize, and do make use of them. Of course on V1 you can't change them.
CLI/STI are ONLY called from the foreground and of course are 100% safe.
> CLI/STI are ONLY called from the foreground and of course are 100% safe.
But they can only work in that simple environment. They're 100% unsafe everywhere else. You don't want to recommend them (on a forum like this) to people who might innocently use them where they won't work safely.
> ONLY called from the foreground
That too much of a restriction. How do you write common functions to handle queues between the interrupts and the mainline when it has to work differently when called from interrupts and from mainline?
The advantage of asm_set_ipl() is that it doesn't have those restrictions, and works safely everywhere.
Tom
You know what I just remembered, I got CLI/STI from a processor expert project.
In that (and any other, except your imagined one) context, that limitation seems fine. I use them in any foreground function that shares resources with an interrupt handler and they work perfectly. In the handler, I don't need to do anything, because the handler will never run in the critical section formed by the CLI/STI.
Search the Freescale Forums (that "Search" box at the upper right of these pages) for "asm_set_ipl()". This one:
Re: MCF5235 spurious interrupt on UART 0
contains:
"For example in a CodeWarrior project there is often a routine called asm_set_ipl, which you can call from C as follows:"
So doesn't CodeWarrior already supply this function somewhere? Have you read through/searched all the CodeWarrior documentation and sample projects?
Read through the other articles that search finds.
You could also google for "CodeWarrior asm_set_ipl" and find if any of the 122 hits help. This one doesn't answer this question, but it shows a CodeWarrior project with this function, and also gives a lot of very useful information about the ColdFire architecture and how to best use it from CodeWarrior:
http://www.freescale.com.cn/cstory/ftf/2009/download/ent_f0253.pdf
If you find a good explanation or pointers to documents that help you, please summarise back to this post for the next person with the same problem.