Further Foxconn fun
Ryan kindly sent me a copy of the ACPI tables for his motherboard, so I've had the opportunity to look at them in a little more detail. There's nothing especially surprising. The first method of interest is OSFL, which I've annotated below:
Method (OSFL, 0, NotSerialized)
{
If (LNotEqual (OSVR, Ones))
{
Return (OSVR)
}
This block simply skips the checks if they've already been evaluated and returns the cached value
If (LEqual (PICM, Zero))
{
Store (0xAC, DBG8)
}
If the programmable interrupt controller has been set up in PIC mode rather than APIC mode, 0xAC is written to i/o port 0x80. This would then show up on a plug-in card if one were attached. Simply debug code
Store (One, OSVR)
Set OSVR to 1, which in this case clearly means "Unknown OS"
If (CondRefOf (_OSI, Local1))
This checks whether the OS supports the _OSI method. If it does, the following block is executed. If not, the older _OS method is used to detect the OS
{
If (_OSI ("Windows 2000"))
{
Store (0x04, OSVR)
}
Newer versions of Windows will also claim to support the interfaces defined in older versions, so this set of checks is done in release order
If (_OSI ("Windows 2001"))
{
Store (Zero, OSVR)
}
If (_OSI ("Windows 2001 SP1"))
{
Store (Zero, OSVR)
}
If (_OSI ("Windows 2001 SP2"))
{
Store (Zero, OSVR)
}
If (_OSI ("Windows 2001.1"))
{
Store (Zero, OSVR)
}
If (_OSI ("Windows 2001.1 SP1"))
{
Store (Zero, OSVR)
}
If (_OSI ("Windows 2006"))
{
Store (Zero, OSVR)
}
If we've got this far, OSVR is now set to 0. Linux will claim to support all of these interfaces, and so OSVR should be 0 on Linux systems. Note that there is no _OSI check for Linux - the 2.6.24 change to remove Linux from the set of claimed interfaces is therefore irrelevant
}
Else
{
Linux supports _OSI, so we should never be here. But if we somehow are...
If (MCTH (_OS, "Microsoft Windows NT"))
{
Store (0x04, OSVR)
}
Linux has responded to _OS with "Microsoft Windows NT" since 2.6.9. MCTH is simply a string matching routine defined elsewhere in the DSDT. So, worst case here is that OSVR is 4
Else
{
If (MCTH (_OS, "Microsoft WindowsME: Millennium Edition"))
{
Store (0x02, OSVR)
}
If (MCTH (_OS, "Linux"))
{
Store (0x03, OSVR)
}
..because this could never be true unless you're running 2.6.8.1 or earlier. But even so, getting here would still indicate failure - we've supported _OSI since before then, and so should never come anywhere near this code block.
}
}
Return (OSVR)
}
In summary, we end up with the following values:
Value |
OS |
0 |
Windows XP, 2003 or Vista. Linux (assuming absence of bugs) |
1 |
Unknown OS |
2 |
Windows ME |
3 |
A version of Linux that doesn't implement _OSI and is from before 2.6.9 |
4 |
Windows NT 4 and 2000. A version of Linux that doesn't implement _OSI and is 2.6.9 or later (I don't believe any such version exists |
Now, where is this used? The majority of the OSFL checks only check whether the return value is 1 or 2, which will only be true for an OS that (a) doesn't claim to be Windows or (b) is Windows ME. Linux doesn't fall into either of these categories, so we can ignore them. The first interesting hit we have is in the HPET code, where _STA will return 0xf (device present and working) if OSFL is 0 and 0xb (device present and working, but should not be shown in the UI) otherwise. This is just to keep the HPET from showing up in versions of Windows that don't know what it is. The only other interesting hit is the following code from the PCI bus initialisation pathway:
If (LEqual (OSFL (), Zero))
{
Store (0x59, SMIC)
}
Else
{
If (LEqual (OSFL (), 0x04))
{
Store (0x5A, SMIC)
}
Else
{
Store (0x58, SMIC)
}
}
This writes different values to SMIC (which turns out to be i/o port 0xb2) depending on the OS. 0xb2 is the standard(ish) way to trigger a system management interrupt, which causes the CPU to execute some code from a memory region that can't be accessed by the OS. This isn't
that unusual, but it's a little weird. In any case, note that there's no check for whether OSFL is 3 here (which would be true if the _OS call returned Linux), and so Linux is being treated identically to Windows ME and any unknown OS. In reality, Linux will be treated identically to either Vista or 2000. This block provides no evidence of conspiracy. Finally, the OS version flag is written to a region of memory before suspend and read back afterwards. Nothing appears to be done with this information - it's conceivable that the low-level resume code in the BIOS has conditionals based on this, but I suspect that it's just boilerplate code that's ignored.
To summarise:
- There is no code in this DSDT that could determine that the system is running any Linux kernel of 2.6.9 or later. This may even be true of earlier versions - I'm not sure when _OSI support was added
- Even if the code did manage to determine that the system was running Linux, there are no codepaths that are Linux specific. Every piece of code is run on at least one version of Windows
What's the problem, then? I've no idea. The only "significant" issue is that the OEMB table provided by the BIOS has an incorrect checksum. Given that the OEMB table is
never used by Linux (it's a vendor extension of some kind, with the best hint I've been able to find being that it can be used to pass information from the BIOS to the OS - kind of like the rest of ACPI, then...), this is pretty unimportant. And given that the OEMB table isn't part of the ACPI spec, it's certainly entirely irrelevant when it comes to determining whether the system is ACPI compliant or not.
Are there ACPI issues with Ryan's system? It sounds like it. The "Error attaching device data" complaints indicate some kind of failure on the part of the kernel to work out how the devices correspond to the ACPI namespace, but I strongly suspect that this is a Linux bug. Failure to reboot after suspend? Could be anything (I'd need direct access to the hardware to figure it out properly), but again it's almost certainly a Linux bug. The standard way Linux reboots systems is to bang the keyboard controller, and it's conceivable that something we're doing on resume is leaving the keyboard controller in a slightly confused state. We're clearly doing
something wrong there, given that my Dell comes up without a keyboard about one resume in twenty - I just haven't had time to look into it yet.
The only remaining thing is the mutex handwaving. I've got no clue what's going on there. Ryan's suggested change (from Acquire (MUTE, 0x03E8) to Acquire (MUTE, 0xFFFF)) simply means that the OS will wait forever until it acquires the mutex - in the past it would only wait a second. The reason the compiler generates a warning here is that the firmware never checks whether it acquired the mutex or not! Bumping the timeout to infinity obviously fixes this warning (there's no need to check the return code if you're happy to wait forever rather than failing), but the original code is merely stupid as opposed to a spec violation.
Take home messages? There's no evidence whatsoever that the BIOS is deliberately targeting Linux. There's also no obvious spec violations, but some further investigation would be required to determine for sure whether the runtime errors are due to a Linux bug or a firmware bug. Ryan's modifications should result in precisely no reasonable functional change to the firmware (if it's ever hitting the mutex timeout, something has already gone horribly wrong), and if they do then it's because Linux isn't working as it's intended to. I can't find any way in which the code Foxconn are shipping is worse than any other typical vendor. This entire controversy is entirely unjustified.
Syndicated 2008-07-27 02:47:39 from Matthew Garrett