Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I prove __udelay() is working correctly on my ARM embedded system?

We have an ARM9 using the 3.2 kernel -- everything seems to work fine. Recently I was asked to add some code to add a 50ms pulse on some GPIO lines at startup. The pulse code is fine; I can see the lines go down and up, as expected. What does not work the way I expected is the udelay() function. Reading the docs makes me think the units are in microseconds, but as measured in the logic analyzer it was way too short. So I finally added this code to get 50ms.

// wait 50ms to be sure PCIE reset takes
for (i=0;i<6100;i++) // measured on logic analyzer - seems wrong to me!!
{
   __udelay(2000); // 2000 is max
}

I don't like it, but it works fine. There are some odd constants and instructions in the udelay code. Can someone enlighten me as to how this is supposed to work? This code is called after all the clocks are initialized, so everything else seems ok.

like image 800
Jeff Avatar asked Nov 15 '12 23:11

Jeff


1 Answers

According to Linus in this thread:

If it's about 1% off, it's all fine. If somebody picked a delay value that is so sensitive to small errors in the delay that they notice that - or even notice something like 5% - then they have picked too short of a delay.

udelay() was never really meant to be some kind of precision instrument. Especially with CPU's running at different frequencies, we've historically had some rather wild fluctuation. The traditional busy loop ends up being affected not just by interrupts, but also by things like cache alignment (we used to inline it), and then later the TSC-based one obviously depended on TSC's being stable (which they weren't for a while).

So historically, we've seen udelay() being really off (ie 50% off etc), I wouldn't worry about things in the 1% range.

Linus

So it's not going to be perfect. It's going to be off. By how much is dependent on a lot of factors. Instead of using a for loop, consider using mdelay instead. It might be a bit more accurate. From the O'Reilly Linux Device Drivers book:

The udelay call should be called only for short time lapses because the precision of loops_per_second is only eight bits, and noticeable errors accumulate when calculating long delays. Even though the maximum allowable delay is nearly one second (since calculations overflow for longer delays), the suggested maximum value for udelay is 1000 microseconds (one millisecond). The function mdelay helps in cases where the delay must be longer than one millisecond.

It's also important to remember that udelay is a busy-waiting function (and thus mdelay is too); other tasks can't be run during the time lapse. You must therefore be very careful, especially with mdelay, and avoid using it unless there's no other way to meet your goal.

Currently, support for delays longer than a few microseconds and shorter than a timer tick is very inefficient. This is not usually an issue, because delays need to be just long enough to be noticed by humans or by the hardware. One hundredth of a second is a suitable precision for human-related time intervals, while one millisecond is a long enough delay for hardware activities.

Specifically the line "the suggested maximum value for udelay is 1000 microseconds (one millisecond)" sticks out at me since you state that 2000 is the max. From this document on inserting delays:

mdelay is macro wrapper around udelay, to account for possible overflow when passing large arguments to udelay

So it's possible you're running into an overflow error. Though I wouldn't normally consider 2000 to be a "large argument".

But if you need real accuracy in your timing, you'll need to deal with the offset like you have, roll your own or use a different kernel. For information on how to roll your own delay function using assembler or using hard real time kernels, see this article on High-resolution timing.

See also: Linux Kernel: udelay() returns too early?

like image 52
embedded.kyle Avatar answered Oct 15 '22 07:10

embedded.kyle