Code: Select all
cli
newvalue = timer + 10
comparator = newvalue
sti
Code: Select all
cli
newvalue = timer + 10
--- SMM runs For more than 10 timer units here
comparator = newvalue
sti
How to go about solving this problem?
Code: Select all
cli
newvalue = timer + 10
comparator = newvalue
sti
Code: Select all
cli
newvalue = timer + 10
--- SMM runs For more than 10 timer units here
comparator = newvalue
sti
Errr... use a >= instead of ==?Craze Frog wrote:How to go about solving this problem?
To avoid this race condition, you could double check. For example:Craze Frog wrote:Looks nice until system management mode starts running in the middle of your code:in this case, timer is already bigger than newvalue when we set the comparator. So the timer is bigger than the comparator, and needs to run a whole cycle (wrap) before it triggers again. This makes the system stop receiving timer interrupts for about 5 minutes on a 32-bit system (the HPET runs rather fast).Code: Select all
cli newvalue = timer + 10 --- SMM runs For more than 10 timer units here comparator = newvalue sti
How to go about solving this problem?
Code: Select all
delay = ?;
for(;;) {
cli
newvalue = timer + delay;
--- SMM runs for more than "delay" timer units here
comparator = newvalue;
if(timer < newvalue) break; // Comparator must have been set in time
if(interrupt_status_flag == sent) break; // Comparator must have been set in time
// Comparator wasn't set in time, so retry
delay = 1;
sti
}
That's what I thought, I just couldn't find a bulletproof way.To avoid this race condition, you could double check.
Who says dealing with this stuff needs to be elegant? :) But seriously, if the second value is smaller than the original value, you know there's been a wrap around, so don't do newvalue = timer + 10; comperator = newvalue, but orgvalue = time; newvalue = orgvalue + 10; comperator = newvalue, and then compare timer with orgvalue: if timer < orgvalue than a wraparound occurred.Craze Frog wrote:If the SMM code runs so long that the timer wraps around (could happen when the timer is just about to wrap), then (timer < newvalue) and we will break; even though the comparator was not set in time. I think we may need to take the difference and see if it's within allowed limits, but that doesn't sound very elegant.
Yes, but what do I use that information for? Wrapping isn't illegal, just look at the following situation:But seriously, if the second value is smaller than the original value, you know there's been a wrap around
The timer wraps at 2^32, or 2^64 in 64-bit mode. Since you already mentioned it takes 5 minutes for a full wrap in 32 bit mode, and since SMM code will never, ever, ever take that long, I still really don't see the problem.Craze Frog wrote:The timer wraps at 100. Currently it is 80.
Which problem is it that you don't see? SMM doesn't have to run for five minutes to make the counter wrap.jal wrote:The timer wraps at 2^32, or 2^64 in 64-bit mode. Since you already mentioned it takes 5 minutes for a full wrap in 32 bit mode, and since SMM code will never, ever, ever take that long, I still really don't see the problem.Craze Frog wrote:The timer wraps at 100. Currently it is 80.
JAL
No, but it would need to run for five minutes to make it wrap twice. Therefore, even if you do not expect a wrap, if a wrap happened, SMM is to blame. Not too difficult to check then.Craze Frog wrote:Which problem is it that you don't see? SMM doesn't have to run for five minutes to make the counter wrap.
Imagine you want to setup the comparator so that an IRQ occurs in 16 ticks, and the current time is 0xFFFFFFF8. In this case simple code (like the code I posted) will fail. For example:jal wrote:No, but it would need to run for five minutes to make it wrap twice. Therefore, even if you do not expect a wrap, if a wrap happened, SMM is to blame. Not too difficult to check then.
Code: Select all
delay = 16;
for(;;) {
cli
newvalue = timer + delay; //newvalue = 0xFFFFFFF8 + 16 = 0x00000008
comparator = newvalue; //comparator = 0x00000008
if(timer < newvalue) break; // Comparator must have been set in time *WRONG*
if(interrupt_status_flag == sent) break; // Comparator must have been set in time
// Comparator wasn't set in time, so retry
delay = 1;
sti
}
Code: Select all
delay = ?;
for(;;) {
cli
starttime = timer;
comparator = starttime + delay;
elapsedtime = timer - starttime;
if(esapsedtime < delay) break; // Comparator must have been set in time
if(interrupt_status_flag == sent) break; // Comparator must have been set in time
// Comparator wasn't set in time, so retry
delay = 1;
sti
}
Code: Select all
if(esapsedtime < delay) break; // Comparator must have been set in time
If the comparator was set in time but "esapsedtime >= delay", then the HPET would've tried to send an IRQ and (for single CPU systems) the interrupt status flag will be set. The only problem is if the HPET sends an IRQ and the IRQ handler clears the interrupt status flag, which can't happen on a single-CPU system with interrupts disabled.Craze Frog wrote:Brendan, the problem with your second code is in this line:The comment is correct, but it doesn't hold the other way. That is, the comparator could very well have been set in time even if elapsedtime > delay if SMM happened to run after the comparator was correctly set, but before the timer was read again for checkup.Code: Select all
if(esapsedtime < delay) break; // Comparator must have been set in time
I'm not taking the interrupt status thing into account here.
Code: Select all
delay = ?;
for(;;) {
cli;
startIRQ = HPET_IRQ_counter;
starttime = timer;
comparator = starttime + delay;
elapsedtime = timer - starttime;
if(esapsedtime < delay) break; // Comparator must have been set in time (IRQ not sent yet)
if(interrupt_status_flag == sent) break; // Comparator must have been set in time (IRQ sent but not handled yet)
if(HPET_IRQ_counter != startIRQ) break; // Comparator must have been set in time (IRQ sent and handled)
// Comparator wasn't set in time, so retry
delay = 1;
sti;
}
Code: Select all
delay = ?;
cli;
startIRQ = HPET_IRQ_counter;
starttime = timer;
comparator = starttime + delay;
elapsedtime = timer - starttime;
if( (esapsedtime >= delay) && (interrupt_status_flag != sent) && (HPET_IRQ_counter != startIRQ) ) {
sti;
do_timer_thing();
}
sti;
Code: Select all
delay = ?;
if(delay < FOO) {
starttime = timer;
endtime = starttime + delay;
if(endtime < starttime) { // If we need to wait for timer to overflow
while(timer > 0x80000000) { /* PAUSE? */ }
}
while(timer < endtime) { /* PAUSE? */ }
do_timer_thing();
} else {
cli;
starttime = timer;
comparator = starttime + delay;
sti;
}
Code: Select all
delay = ?;
if(delay < FOO) {
starttime = timer;
endtime = starttime + delay;
if(endtime < starttime) { // If we need to wait for timer to overflow
while(timer > 0x80000000) { /* PAUSE? */ }
}
while(timer < endtime) { /* PAUSE? */ }
do_timer_thing();
} else {
cli;
startIRQ = HPET_IRQ_counter;
starttime = timer;
comparator = starttime + delay;
elapsedtime = timer - starttime;
if( (esapsedtime >= delay) && (interrupt_status_flag != sent) && (HPET_IRQ_counter != startIRQ) ) {
sti;
do_timer_thing();
}
sti;
}