EmbeddedRelated.com
Forums
Memfault Beyond the Launch

UART TX FIFO and INTs problem

Started by forum_microbit February 17, 2004
At 02:22 PM 2/22/04 -0500, you wrote:
>At 10:13 AM 2/22/04 -0600, you wrote:
> >As a side note. The disabling and restoring of the global interrupt
> >flag in the cpsr can has a similar problem.
>
><snip>
>
> >For more info on this, Atmel has
> >an APP note discribing it on their web site in the AT91 ARM7 section.
>
>Thanks for the pointer.

I just had a chance to read the app note (I presume this is the one
http://www.atmel.com/dyn/resources/prod_documents/DOC1156.PDF) and it
appears to be warning about faults that can happen when using a style of
code I would consider very error prone to begin with. Unless I'm missing
something (I've only had a little while to consider it) the only time a
problem occurs if an interrupt routine actually modifies the cpu status of
the interrupted code. What would ever prompt anyone to do that in the
first place?

Robrt

" 'Freedom' has no meaning of itself. There are always restrictions,
be they legal, genetic, or physical. If you don't believe me, try to
chew a radio signal. "

Kelvin Throop, III



An Engineer's Guide to the LPC2100 Series

David Willmore wrote:

> > > I'd second this. I see nothing in here to stop a compiler from
> > > moving stuff around to it's hearts content. You might want
> > > to look at adding some barrier() statements to the above code
> > > to prevent that kind of code rearrangement.
> >
> > I'm not too familiar with GNU (I presume barrier() is GNU).
> > Whta's a barrier() statement.
> > Forgive me if I convey dumb or lazy.
> > I _do_ want to learn, and I don't expect others to do my homework
> for me.
> > I have noticed an "inline" statement in Bill's code, I don't even
> know what
> > that's
> > all about.
>
> barrier() is just a family of constructs which, like volatile, tell the
> compiler what it's allowed to move around and what it's not. Consider the
> following pseudo code:
>
> write_to_reg(location_A, value_1);
> delay()
> write_to_reg(location_A, value_2);
>
> There is no guarantee that those statements will be performed in the order
> you might guess. The compiler may even optimize some of them away
> entirely. If write_to_reg is a macro, the compiler may chose to remove
> the first invocation as the second will just overwrite it. It may even
> remove the delay if it can figure out that it's not effecting any
> variables.
>
> [snip]

If write_to_reg is a function, GCC will in NO WAY change the
ordering. If they are macros, then unless the person who wrote the
headers is a complete hack, they will contain volatile (and anyone smart
enough to write header files knows this), and GCC will not reorder them.

I've used GCC on several architectures, and I have never seen it
arbitrarily reorder code execution. While it depends on the
optimization level selected, I've only ever seen GCC reorder items in
registers, and only then when there was no visible effect, i.e.

int a;

a++;
do_some_func ();
a++;

I use GCC on x86, MSP430, AVR frequently, and occasionally on 68K
and Sparc, written interrupt routines purely in C on all of those except
68K, and never run into re-ordering issues. If anything, I tend to get
bit by forgetting to declare a global as volatile, and run into the
issue of the value being cached in a register, so things like queue
incrementers don't behave as expected. And I've come to be able to
recognize those pretty quickly :(

I usually use -Os optimization on AVRs, and -O2 or -O3 on x86 and
MSP430. On Sparcs I usually use -O1, but that's because there's a
particular bug in the revision of compiler I'm running on it.

--jc



Robert Adsett wrote:

> At 02:22 PM 2/22/04 -0500, you wrote:
> >At 10:13 AM 2/22/04 -0600, you wrote:
> > >As a side note. The disabling and restoring of the global interrupt
> > >flag in the cpsr can has a similar problem.
> >
> ><snip>
> >
> > >For more info on this, Atmel has
> > >an APP note discribing it on their web site in the AT91 ARM7 section.
> >
> >Thanks for the pointer.
>
> I just had a chance to read the app note (I presume this is the one
> http://www.atmel.com/dyn/resources/prod_documents/DOC1156.PDF)
> <http://www.atmel.com/dyn/resources/prod_documents/DOC1156.PDF%29> and it
> appears to be warning about faults that can happen when using a style of
> code I would consider very error prone to begin with. Unless I'm missing
> something (I've only had a little while to consider it) the only time a
> problem occurs if an interrupt routine actually modifies the cpu
> status of
> the interrupted code. What would ever prompt anyone to do that in the
> first place?
>
> [snip]

It's a common method of creating interrupt prioritization. If you
have a "slow" interrupt routine, or something more critical, it's common
to re-enable interrupts inside the interrupt routines.

As an example, the AVRs (and I'm still laying out my board, after a
brief haitus, so I haven't gone back to look at the interrupt internals
on the LPC), if you have a timer routine that takes 500 or 1000us to
complete, and you're getting high speed serial interrupts, you may
choose to re-enable interrupts in the timer routine so that the serial
routine can be serviced.

However, some interrupts this can't be done for, such as... serial
interrupts. Because until you read the data register, the interrupt
will remain pending, and if you re-enable interrupts, you'll interrupt
right back into the serial interrupt handler. And you can't effectively
pre-calculate the buffer pointer, so you really have to just about
perform the complete interrupt routine.

I kinda miss the days of the 8051 and such when an arbitrary
peripherial that generates interrupts could be specified to be high or
low priority.

--jc



Hi David,

I was certainly used to fairly aggressive and neat opts with IAR, but I've
never seen it change the sequence of things, that seems a bit rich.
Mind you, that was on MSP430 and on AVR mainly.

Now that I know these things can happen (moving code around), I know what to
look for and can stop bugging you guys :-)
Without knowing this things look daunting.

Is there a specific include file to use barrier() ???

Best regards,
Kris



At 02:50 PM 2/22/04 -0500, you wrote:
>Robert Adsett wrote:
>
> > At 02:22 PM 2/22/04 -0500, you wrote:
> > >At 10:13 AM 2/22/04 -0600, you wrote:
> > > >As a side note. The disabling and restoring of the global interrupt
> > > >flag in the cpsr can has a similar problem.
> > >
> > ><snip>
> > >
> > > >For more info on this, Atmel has
> > > >an APP note discribing it on their web site in the AT91 ARM7 section.
> > >
> > >Thanks for the pointer.
> >
> > I just had a chance to read the app note (I presume this is the one
> > http://www.atmel.com/dyn/resources/prod_documents/DOC1156.PDF)
> > <http://www.atmel.com/dyn/resources/prod_documents/DOC1156.PDF%29> and it
> > appears to be warning about faults that can happen when using a style of
> > code I would consider very error prone to begin with. Unless I'm missing
> > something (I've only had a little while to consider it) the only time a
> > problem occurs if an interrupt routine actually modifies the cpu
> > status of
> > the interrupted code. What would ever prompt anyone to do that in the
> > first place?
> >
> > [snip]
>
> It's a common method of creating interrupt prioritization. If you
>have a "slow" interrupt routine, or something more critical, it's common
>to re-enable interrupts inside the interrupt routines.

That I can understand (I've done it myself), but even in that case you
don't modify the status register of the process you are interrupting but
only your own copy. The usual interrupt entry in either SW or HW goes
something like

save processor status
save interrupt return address

and then on exit

return to saved return address
restore processor status

The issue in the appnote involves modifying the saved processor
status. (ie that of the interrupted code) I don't understand why you
would want to do that. In the best case it will do nothing and in the
worst.... > I kinda miss the days of the 8051 and such when an arbitrary
>peripherial that generates interrupts could be specified to be high or
>low priority.

Or the ST10/C166 where they are almost infinitely variable :)

The VIC would seem to provide at least some of that but I haven't played
with it yet.

Robert

" 'Freedom' has no meaning of itself. There are always restrictions,
be they legal, genetic, or physical. If you don't believe me, try to
chew a radio signal. "

Kelvin Throop, III



Robert Adsett wrote:

> [snip]
>
> That I can understand (I've done it myself), but even in that case you
> don't modify the status register of the process you are interrupting but
> only your own copy. The usual interrupt entry in either SW or HW goes
> something like
>
> save processor status
> save interrupt return address
>
> and then on exit
>
> return to saved return address
> restore processor status
>
> The issue in the appnote involves modifying the saved processor
> status. (ie that of the interrupted code) I don't understand why you
> would want to do that. In the best case it will do nothing and in the
> worst....
>
[snip]

You're right about the context of the processor status save. The only
reason I can think of for modifying the PSW like that would be in a task
switcher of some kind.

--jc



Hi all,

Well, here'e some feedback to this bizarre issue.
Everything was already volatile, except for the local "temp" variable.
Refer the earlier example below :

> > > UINT temp;
> > >
> > > /* Handle TX buffer wrap */
> >temp = (tx_head+1)%RS232_SIZE;
> > >
> > > /* Disable TX interrupts */
> > > U0IER = 0x01;
> > >
> > > /* Circ buffer not empty ? */
> > > if (comm_tx_running)
> > > {
> > > comm_tx[tx_head] = (UCHAR)ch;
> > > tx_head = temp;
> > > }
> > > else
> > > {
> > > comm_tx_running = 1;
> > > U0THR = ch;
> > > }
> > >
> > > /* Reenable TX interrupts */
> > > U0IER = 0x03;
> > > return (ch);

Declaring "temp" volatile didn't help a bit, on the contrary, some tests
started
exhibiting default_IRQs, whereas before they didn't.

It MUST be that the compiler is changing the sequence of things (when I
single step
at C level, it's certainly all over the place) - despite the volatiles.
In any case, even so, I don't get why that should have a bearing on reading
the head_pointer
and increment/wrapping it, and then inside the protected part using the
current pointer, and then
updating it
- VERSUS -
Post incrementing on tx_head while doing the write to comm_tx[] TX buffer.

I must assume that Bill Knight's original speculation that direct writes to
eg. UxIER still
*can* cause "spurious interrupts" is correct.
There must be a mechanism where the write instruction is still be in
execution unit while a
vectored INT request decodes in the VIC.

In any case, I presume that the best prottection is to have it in a separate
function....?
I found that calling a function that explicitly Disables IRQ Global,
sets/clears THRE,
then re-Enables IRQ Global, then returns makes all the permuations of
previous code now
behave as I expected it to, regardless of the method of doing the TX buffer
write and update
of the pointer, and regardless of how I set the optimisation.

This to me implies that the instruction sequences indeed are being changed.
I've been stepping around in ASM, but it is _very_ time consuming, it
started to annoy me :-)
I do intend to fully get to the bottom of this, as I want to know what on
earth was causing these
bizarre behaviours, it seems that it's compiler reordering, I will revisit
this later.
I'm writing 2 functions set_int() and clr_int() that take a register and a
bit number, and then
Enable/Disable individual INTs.

Does that seem like a plausible scenario ????

Best regards,
Kris




Kris,

Instead of stepping around, why not use objdump on the elf file to
produce an assembly listing, and take a look at the ordering?

objdump -dSt foo >foo.lst

Be sure to compile your sources with the -g option.

--jc

microbit wrote:

> Hi all,
>
> Well, here'e some feedback to this bizarre issue.
> Everything was already volatile, except for the local "temp" variable.
> Refer the earlier example below :
>
> > > > UINT temp;
> > > >
> > > > /* Handle TX buffer wrap */
> > >temp = (tx_head+1)%RS232_SIZE;
> > > >
> > > > /* Disable TX interrupts */
> > > > U0IER = 0x01;
> > > >
> > > > /* Circ buffer not empty ? */
> > > > if (comm_tx_running)
> > > > {
> > > > comm_tx[tx_head] = (UCHAR)ch;
> > > > tx_head = temp;
> > > > }
> > > > else
> > > > {
> > > > comm_tx_running = 1;
> > > > U0THR = ch;
> > > > }
> > > >
> > > > /* Reenable TX interrupts */
> > > > U0IER = 0x03;
> > > > return (ch);
>
> Declaring "temp" volatile didn't help a bit, on the contrary, some tests
> started
> exhibiting default_IRQs, whereas before they didn't.
>
> It MUST be that the compiler is changing the sequence of things (when I
> single step
> at C level, it's certainly all over the place) - despite the volatiles.
> In any case, even so, I don't get why that should have a bearing on
> reading
> the head_pointer
> and increment/wrapping it, and then inside the protected part using the
> current pointer, and then
> updating it
> - VERSUS -
> Post incrementing on tx_head while doing the write to comm_tx[] TX buffer.
>
> I must assume that Bill Knight's original speculation that direct
> writes to
> eg. UxIER still
> *can* cause "spurious interrupts" is correct.
> There must be a mechanism where the write instruction is still be in
> execution unit while a
> vectored INT request decodes in the VIC.
>
> In any case, I presume that the best prottection is to have it in a
> separate
> function....?
> I found that calling a function that explicitly Disables IRQ Global,
> sets/clears THRE,
> then re-Enables IRQ Global, then returns makes all the permuations of
> previous code now
> behave as I expected it to, regardless of the method of doing the TX
> buffer
> write and update
> of the pointer, and regardless of how I set the optimisation.
>
> This to me implies that the instruction sequences indeed are being
> changed.
> I've been stepping around in ASM, but it is _very_ time consuming, it
> started to annoy me :-)
> I do intend to fully get to the bottom of this, as I want to know what on
> earth was causing these
> bizarre behaviours, it seems that it's compiler reordering, I will revisit
> this later.
> I'm writing 2 functions set_int() and clr_int() that take a register and a
> bit number, and then
> Enable/Disable individual INTs.
>
> Does that seem like a plausible scenario ????
>
> Best regards,
> Kris


Hi John,
 
> Kris,
>
>     Instead of stepping around, why not use objdump on the elf file to
> produce an assembly listing, and take a look at the ordering?
>
>     objdump -dSt foo >foo.lst
>   
>     Be sure to compile your sources with the -g option.
Because of the confusion reigning, I'm trying it alternating between HI-TECH C
and also on CrossWorks for ARM.
I'm not proficient with the actual GCC toolchain itself, but I can easily check the tool to
produce an ASM listing (absolute I hope).
I will investigate this at ASM level when I return to it, and certainly post my findings here.
 
On one hand it's like I've already spent way too much time on it ("Don't muck with it" :-)
but on the other hand it's given me further insight into the instruction set, the VIC and
other obvious intrinsic issues.
 
I need to now start re-thinking my tasking, as this project set out to easily port around between
different MCUs. (before ARM came into ti)
I need to rework my HW abstraction. so other changes can still efficiently run just as well on MSP430
as on ARM or AVR etc. , it's quite a bit of a challenge.....
 
 
-- Kris
 


At 08:07 AM 2/23/04 +1100, you wrote:
>I must assume that Bill Knight's original speculation that direct writes to
>eg. UxIER still
>*can* cause "spurious interrupts" is correct.
>There must be a mechanism where the write instruction is still be in
>execution unit while a
>vectored INT request decodes in the VIC.

That triggers a thought. There is a writeback cache on the LPC. It's only
a single entry and I thought it was only for the SRAM but maybe it's more
universal than I remember. There was a note in the User manual saying that
if a write to ram (?) absolutely had to be there a second write was
required. It also appeared to be the case that a write followed by a read
to the same location would work though since it would read out of the
cache. In fact I thought reading through it that the only place this could
be an issue is if you wanted the written value to get through a
reset. It's a bit of a stretch but it might be worth checking the user manual. >In any case, I presume that the best prottection is to have it in a separate
>function....?
>I found that calling a function that explicitly Disables IRQ Global,
>sets/clears THRE,
>then re-Enables IRQ Global, then returns makes all the permuations of
>previous code now
>behave as I expected it to, regardless of the method of doing the TX buffer
>write and update
>of the pointer, and regardless of how I set the optimisation.
>
>This to me implies that the instruction sequences indeed are being changed.
>I've been stepping around in ASM, but it is _very_ time consuming, it
>started to annoy me :-)

gcc -S -Os is very enlightening. Definitely not a simple mapping.

>I do intend to fully get to the bottom of this, as I want to know what on
>earth was causing these
>bizarre behaviours, it seems that it's compiler reordering, I will revisit
>this later.
>I'm writing 2 functions set_int() and clr_int() that take a register and a
>bit number, and then
>Enable/Disable individual INTs.
>
>Does that seem like a plausible scenario ????

Sounds good. I'd expand those functions a bit and have them return the
previous interrupt state so you could do something like;

pre_int = DisableINT( INT_BLAH);

blah blah;

RestoreINT( pre_int); That allows things like nested disable/enables to work. >Best regards,
>Kris >
>
>Yahoo! Groups Links >
>

" 'Freedom' has no meaning of itself. There are always restrictions,
be they legal, genetic, or physical. If you don't believe me, try to
chew a radio signal. "

Kelvin Throop, III



Memfault Beyond the Launch