Power management timer and ACPI interrupt setup
Posted: Mon Aug 10, 2015 3:09 am
Hello, this is my first post on the OSDev forum, so I'll do my best to follow the guidelines!
So i've been developing my latest kernel project for a while now, and this is the furthest i've ever gotten with writing a kernel. I'm at the stage where i'm trying to implement a concrete timing source to help in managing my SMP multitasking environment. As most OSDever's seem to do, i started out using the PIT and the TSC as the basis for my kernel timing. This worked wonderfully for me to implement a task Sleep() call, but now that i've moved from UP to SMP, as well as finding out that the TSC is usually variant, I want to implement a better clock source. My choice for this was the ACPI management timer.
My problem now is that I cannot seem to get the PM Timer's overflow event to trigger correctly. According to the ACPI specification, when I have the TMR_EN bit set in the PM1a/b_EVT_BLK ACPI register, and the TMR_STS bit is set in the corresponding status register, it should trigger a System Control Interrupt, which I do receive, but unfortunately, this only happens once. The specification doesn't seem to specify as to whether or not the TMR_STS bit needs to be cleared to receive the interrupt again, but that's what i've assumed tentatively, trying both clearing it and leaving it, and still nothing. I should note that I have verified that the timer is counting on all of my test platforms, and about as accurately as i can test using the PIT. Interestingly, when I test in VirtualBox, about twenty minutes later, I begin to receive a storm of these events for no reason i can fathom. This only seems to happen in VirtualBox, and i suspect it might be some quirk with whatever i'm doing wrong in the ACPI interrupt handler? I really am stuck on this.
I've searched through the forums and googled relentlessly, to no avail. It seems there aren't a lot of people besides Microsoft and Ubuntu developers who are super interested in the PM Timer's protocol. I've even resorted to looking at the linux code for the PM Timer, but it doesn't seem to implement the interrupt, relying instead on the fact that it's update code should be called at least once between the (minimum) 24 bit overflow of the counter, or at least once every four seconds. I've been toying with the idea of just leaving my kernel's timing service free-running so it can keep semi-accurate track of system uptime, but that seems wasteful and unnecessary to me when the timer interrupt has been specifically provided to simulate a larger timer.
Here is the code I use to parse the FADT. I'm pretty sure that this is working correctly, as i'm able to set up the ACPI interrupt properly, and it's header verifies correctly.
FADT.h
FADT.cpp
This is where I set up the ACPI interrupt, as well as it's hooking mechanism. This is also the base namespace for all ACPI stuff, and includes a definition of the Generic Address Structure used in the FADT
ACPI.h
ACPI.cpp
Finally, here is where i initialize the PM Timer, install it's interrupt hook, read it's actual value, and update the emulated 64-bit timer.
PMTimer.h
PMTimer.cpp
Additionally, points of interest might be how the timer is used in timing code:
TaskSleep.h
TaskSleep.cpp
Or the main kernel initialization, where i'm testing it all:
kinit.cpp
So i've been developing my latest kernel project for a while now, and this is the furthest i've ever gotten with writing a kernel. I'm at the stage where i'm trying to implement a concrete timing source to help in managing my SMP multitasking environment. As most OSDever's seem to do, i started out using the PIT and the TSC as the basis for my kernel timing. This worked wonderfully for me to implement a task Sleep() call, but now that i've moved from UP to SMP, as well as finding out that the TSC is usually variant, I want to implement a better clock source. My choice for this was the ACPI management timer.
My problem now is that I cannot seem to get the PM Timer's overflow event to trigger correctly. According to the ACPI specification, when I have the TMR_EN bit set in the PM1a/b_EVT_BLK ACPI register, and the TMR_STS bit is set in the corresponding status register, it should trigger a System Control Interrupt, which I do receive, but unfortunately, this only happens once. The specification doesn't seem to specify as to whether or not the TMR_STS bit needs to be cleared to receive the interrupt again, but that's what i've assumed tentatively, trying both clearing it and leaving it, and still nothing. I should note that I have verified that the timer is counting on all of my test platforms, and about as accurately as i can test using the PIT. Interestingly, when I test in VirtualBox, about twenty minutes later, I begin to receive a storm of these events for no reason i can fathom. This only seems to happen in VirtualBox, and i suspect it might be some quirk with whatever i'm doing wrong in the ACPI interrupt handler? I really am stuck on this.
I've searched through the forums and googled relentlessly, to no avail. It seems there aren't a lot of people besides Microsoft and Ubuntu developers who are super interested in the PM Timer's protocol. I've even resorted to looking at the linux code for the PM Timer, but it doesn't seem to implement the interrupt, relying instead on the fact that it's update code should be called at least once between the (minimum) 24 bit overflow of the counter, or at least once every four seconds. I've been toying with the idea of just leaving my kernel's timing service free-running so it can keep semi-accurate track of system uptime, but that seems wasteful and unnecessary to me when the timer interrupt has been specifically provided to simulate a larger timer.
Here is the code I use to parse the FADT. I'm pretty sure that this is working correctly, as i'm able to set up the ACPI interrupt properly, and it's header verifies correctly.
FADT.h
FADT.cpp
This is where I set up the ACPI interrupt, as well as it's hooking mechanism. This is also the base namespace for all ACPI stuff, and includes a definition of the Generic Address Structure used in the FADT
ACPI.h
ACPI.cpp
Finally, here is where i initialize the PM Timer, install it's interrupt hook, read it's actual value, and update the emulated 64-bit timer.
PMTimer.h
PMTimer.cpp
Additionally, points of interest might be how the timer is used in timing code:
TaskSleep.h
TaskSleep.cpp
Or the main kernel initialization, where i'm testing it all:
kinit.cpp