Page 1 of 1

ACPI support in assembler

Posted: Mon Jan 07, 2013 4:48 am
by online
Hello,

I am just a bit curious - is there anybody who tried to implement ACPI support in assembler for his OS project ?
Am I right that to support it the right way I need to write my own AML interpretor ? Seems to me that it will not be an easy task to do.
I know that there is a ACPICA implementation, but I want to keep my whole project in assembler only.

Thx,
online

Re: ACPI support in assembler

Posted: Mon Jan 07, 2013 5:40 am
by bubach

Re: ACPI support in assembler

Posted: Mon Jan 07, 2013 6:05 am
by online
Thanks for the links ;)
I've already seen some of the links from your post, but it seemed to me that they just parse some ACPI tables, try to find some info and that's all.

Maybe to be more specific what I am trying to do (my idea can be completely wrong) is to setup APIC. I thought that for this to be set up correctly I have to interpret the PCI routing table (_PRT)... Or not ? - and for that goal the AML interpretor is needed. If not, how this should be done ?

online

Re: ACPI support in assembler

Posted: Mon Jan 07, 2013 6:14 am
by rdos
You'll have to count the rdos-related link out. I've never provided anything close to a complete ACPI in assembly. The stub was the mere basic table parsing code. In fact, I put down a lot of work in order to be able to run ACPICA in kernel mode, and it was the first device-driver written (mostly) in C.

Re: ACPI support in assembler

Posted: Mon Jan 07, 2013 6:29 am
by Brendan
Hi,
online wrote:I know that there is a ACPICA implementation, but I want to keep my whole project in assembler only.
I don't think that's practical.

The problem isn't implementing an AML interpreter (which on its own may be conceivable), but emulating the behaviour of a specific version of Windows (which is mostly undocumented and certainly not described in the "little" ACPI specification).

It may be possible to support ACPI without pretending to be (a version of) Windows; but in this case the firmware/AML will probably disable everything except basic functionality (and then crash because the AML wasn't tested properly on OSs that don't pretend to be Windows). ;)


Cheers,

Brendan

Re: ACPI support in assembler

Posted: Mon Jan 07, 2013 6:34 am
by online
Brandan, ok - so what would you do ?
Any other feasible way howto find all the info needed for the correct APIC setup, for the correct setup of all the CPUs in the system, etc. ?

Thx,
online

Re: ACPI support in assembler

Posted: Mon Jan 07, 2013 9:40 am
by Brendan
Hi,
online wrote:Brandan, ok - so what would you do ?
Any other feasible way howto find all the info needed for the correct APIC setup, for the correct setup of all the CPUs in the system, etc. ?
You only need to parse the ACPI tables (APIC/MADT and maybe SRAT and SLIT) to correctly setup all the CPUs and don't need an AML interpreter (until/unless you do power management or support hot-plug CPUs).

To correctly setup the IO APIC you'd only disable everything (and only need a little information from ACPI's APIC/MADT table for that).

When a device driver for a device is started you'd want to setup the device's IRQ (and if/when the device driver is terminated you'd want to disable the device's IRQ again). For this, device drivers fall into one of 4 categories:
  • Device drivers that don't use any IRQs anyway (typically because they talk to a different device driver and don't talk to hardware directly themselves - e.g. USB keyboard driver relying on a USB controller driver, SATA hard disk driver relying on a SATA controller driver, etc).
  • Device drivers that are for legacy/ISA devices; where you get all the information you need from the ACPI APIC/MADT table.
  • Device drivers that are for PCI devices that support MSI, where you can use MSI and don't need any information (because the device is setup to send IRQs directly without using any IO APIC input/s).
  • Device drivers that are for PCI devices that don't support MSI. This is the only case where you'd need something (e.g. an AML interpreter) to determine IRQ routing.
For that last case, it's possible to implement a motherboard driver (one for each different motherboard that the OS supports) which uses native code to do what AML would've done. Of course I use a micro-kernel (where things like a motherboard driver are similar to normal processes) which means it may not be too hard for someone to create a wrapper around ACPICA and use that as a generic motherboard driver.

It's also theoretically possible to autodetect without ACPI (basically, send any IRQ that the device might be using to the device driver, and if/when the device driver says its device was responsible for the IRQ you can reduce the set of IRQs that the device might be using, until the set of IRQs the device could be using is reduced to 1). Note: if the driver is able to force the device to generate an IRQ this could be sped up (e.g. keep forcing the device to generate an IRQ until you know which IRQ it uses during device driver initialisation).

For my OS; I want to support all of these methods - the OS would try to find a suitable native motherboard driver, but if there isn't one it tries to find a generic motherboard driver (e.g. AML interpreter), and if it doesn't find that either then it resorts to autodetection.

I should also say that I don't actually intend to write many motherboard drivers or write any code to support ACPI/AML myself. Instead, I'm "overly optimistic" and hope that sooner or later someone somewhere will volunteer to write third-party drivers. The goal is to make the OS impressive enough to encourage volunteers (it's simply not practical for one person to write a few hundred drivers each year while maintaining and improving everything from kernel to drivers to GUIs to applications; and the only other alternative is to write a boring Unix clone and port everything to it and end up with "GNU/YourKernel" instead of "GNU/Linux" or "GNU/FreeBSD" or "GNU/Hurd" or... ).


Cheers,

Brendan

Re: ACPI support in assembler

Posted: Tue Jan 08, 2013 4:23 am
by online
Hi Brendan,

thanks for the great answer.
The only thing I don't fully understand is the IRQ autodetection in the 4th case.
Could you please describe this more deeply ? How can I send the IRQ ? To force the device I must know it before to know how to force it to send an IRQ, right ?

online

Re: ACPI support in assembler

Posted: Tue Jan 08, 2013 10:01 am
by Brendan
Hi,
online wrote:The only thing I don't fully understand is the IRQ autodetection in the 4th case.
Could you please describe this more deeply ? How can I send the IRQ ? To force the device I must know it before to know how to force it to send an IRQ, right ?
Let's start from the start. For PCI, several devices can be sharing the same IRQ line. This means that (even if you know exactly which IRQ line the devices use) you want to be able to tell multiple device drivers when the shared IRQ occurs, and you want those device drivers to tell the kernel if their device generated an IRQ or not (so that the kernel can optimise the order that it tells device drivers about IRQs, and detect problems like IRQ floods).

What would happen if the kernel didn't care much and just sent every IRQ to every device driver? This would add a lot of unnecessary overhead (lots of devices drivers checking their device and telling the kernel that the device didn't generate an IRQ), but it would work fine despite the unnecessary overhead. You only really want know which devices use which IRQs to reduce this unnecessary overhead. For example, if "IRQ 44" occurs and this IRQ is only shared by 2 PCI devices, then it'd be better to only tell those 2 device drivers that the IRQ occured (and avoid telling any other device drivers that the IRQ occurred).

Now imagine that there's 20 devices (and 20 device drivers) and the kernel doesn't know which IRQs any of the devices use. It could just send every IRQ to every device driver and that'd work. However, don't forget that the device drivers would still tell the kernel if their device generated an IRQ or not. For example; if "IRQ 44" occurs and the kernel tells every device driver that the IRQ occurred, and if all of the device drivers tell the kernel "this device didn't generated an IRQ" except for one of them, then it'd be obvious that the only device that generated an IRQ is using "IRQ 44".

What if there's 8 IRQs (that aren't used for legacy/ISA devices); and 3 of those IRQs occur at about the same time and 3 different device drivers say "this device generated an IRQ"? In this case you don't know exactly which IRQ each of those 3 devices are using; but you do know that those devices aren't using any of the 5 IRQs that didn't occur.

Basically; initially the kernel might not have any idea which PCI devices are using which IRQ (each device could be using any IRQ), but as IRQs occur the kernel can "learn" (reduce the number of IRQs that each device could be using), and eventually the kernel would figure out exactly which devices use which IRQs.

This makes it sound easy. Unfortunately it's not as easy as it sounds - it's very racey, and you'd need to be very careful to avoid problems caused by "unfortunate timing". For example, the kernel might tell a driver that an IRQ occurred and the driver might tell the kernel "this device generated an IRQ", even though that device's IRQ hasn't actually been sent from the IO APIC to the CPU yet (and a different device caused the first IRQ). You'd need something "more clever".

The "more clever" approach might go something like this:
  • When an IRQ occurs check if there are devices that definitely do use that IRQ, and tell them about the IRQ first. If any of them reply with "this device caused an IRQ" then send EOI to the IO APIC and you're done.
  • Otherwise:
    • increase an "unknown IRQs in progress" counter
    • set a flag corresponding to the IRQ number
    • tell all device drivers that could be using the IRQ that the IRQ occured
    • for each device driver that could be using the IRQ that replies with "this device caused an IRQ", set a flag corresponding to the driver
    • when all device drivers that could be using the IRQ have replied:
      • decrease the "unknown IRQs in progress" counter
      • if the counter has become zero, wait a little while with interrupts enabled
      • if the small delay expires (and no IRQs are received immediately after the "unknown IRQs in progress" counter became zero):
        • create a mask from the flags corresponding to the IRQs that were received
        • for each driver that has its "device caused an IRQ" flag set; AND the set of IRQs it could be using with your mask (hopefully reducing the set of possible IRQs that the device could be using)
        • clear the flag corresponding to the IRQ numbers ready for the next group of IRQs
The basic idea here is to wait until there's no IRQs and then process all the results from the "previously received" IRQs; to make sure you don't get messed up by an IRQ that you haven't received yet. This should be immune to any race conditions. Note: I am NOT saying this is the only way or that this is the best or most efficient method (it's only an example).

Also note that if a device never generates any IRQ you will never know which IRQ that device uses; but this doesn't actually matter.

The next step is to find a way to speed this up, so that the kernel can get rid of the extra "I don't know which devices actually do use the IRQ" overhead faster. This is where the idea of forcing the device to generate an IRQs comes from. For example, during boot the OS could start device drivers one at a time, and (if the driver supports it) ask the driver to force the device to generate IRQs until the kernel "learns" which IRQ the device does use (and then do the same for the next device driver, and the next, etc). In this way (for drivers that support "IRQ forcing") the kernel could figure out which devices use which IRQs during boot when other devices aren't generating unrelated IRQs and making it take longer.

How to force the device to send an interrupt depends on what sort of device it is. For some ethernet cards there's a "loopback" mode (where it receives packets that it sends; and you can configure it to generate an interrupt whenever it receives a minimal packet that you sent). For IDE/ATA/SATA (and probably SCSI) controllers you might use the "identify device" command. For a video card you might just enable an "interrupt on vertical refresh" feature. For a sound card you might setup some sort of "interrupt on buffer full" and configure it (with a tiny buffer) to record data from "microphone in".

Of course for some devices there isn't any way to force it to generate an IRQ; so you can't use this to detect all IRQs during boot and the kernel have to figure out which IRQs these devices use while the OS is running (and may never know if the device never generates an IRQ).

Finally, I should point out that regardless of how well you do this there are some assumptions and some risks. For example, if the motherboard uses "level triggered active high" for PCI (instead of "active low") then it's going to cause major problems (however this would be very unusual). You'd also have to worry about IO APIC inputs that aren't connected to anything at all (but happen to be tied low). This means that it's important for the kernel to be able to detect IRQ floods; which brings me back to the first paragraph I wrote - "you want those device drivers to tell the kernel if their device generated an IRQ or not (so that the kernel can optimise the order that it tells device drivers about IRQs, and detect problems like IRQ floods)". ;)


Cheers,

Brendan

Re: ACPI support in assembler

Posted: Tue Jan 08, 2013 10:09 am
by rdos
online wrote:Hi Brendan,

thanks for the great answer.
The only thing I don't fully understand is the IRQ autodetection in the 4th case.
Could you please describe this more deeply ? How can I send the IRQ ? To force the device I must know it before to know how to force it to send an IRQ, right ?

online
It's device-specific. Some devices have a function that lets software generate an interrupt. On those devices it is easy to generate an interrupt. On other devices there is no such function, and then you need to generate an interrupt be doing some operation that the device should issue an interrupt for.

I'd like to add a 5th possibility: Use a timer and regulary poll the device. This is useful for low-speed USB chips.

In response to Brendan's over-complicated IRQ detection scheme, I'd say that I would simplify it and let the boot process have to stages:
1. IRQ detection phase
2. Operational phase

During the IRQ-detection phase, all devices not involved in detection should be disabled. Then you enable one device at a time, and trigger an interrupt. The IRQ that happens is the one the device uses.

Pseduo-code for IRQ detection:

Code: Select all

    clear_irqs();
    delay(10ms);
    bad_irqs = read_irqs();
    do {
      clear_irqs();
      generate_device_irq();
      irq = read_irqs();
      irq = irq & (~bad_irqs);
   }
   while (num_of_irqs(irq) > 1);

Re: ACPI support in assembler

Posted: Tue Jan 08, 2013 10:36 am
by gravaera
Yo:

A key thing that should be mentioned here, mostly to ensure that the OP doesn't get in over his head is that on most other (i.e. non-IBM-PC compatible) platforms you will ever program for, the device <-> IRQ-pin mappings for each device are known. There is a datasheet for the development board which tells you what devices are present on that model of the product, and how they are connected, or there is some very simply obtainable table provided by the firmware.

The only reason the IBM-PC clones' device <-> IRQ-pin mappings require so much effort to discover is because each different board you boot on could have been manufactured by a number of vendors, even though all of these boards could claim to be IBM-PC compatibles. Additionally, even for the same model of PC-compatible purchased from the same vendor, the devices present on the board could be radically different, and many times are customized by the user. This is all without considering people who don't buy their whole PC from a single vendor, and source different parts from different vendors (building a custom system, possibly for gaming).

Because the presence or absence of any device cannot be assumed (you cannot assume for example that every PC motherboard you're booted on will have any particular device), vendors cannot assign fixed IRQ-pin mappings to each device. Therefore every manufacturer has to simply state the layout of Device <-> IRQ-pin mappings for each board model it manufactures (using ACPI, for example...), or else it is impossible for a kernel to know (rationally) the mapping of devices to IRQ-pins.

It is completely reasonable to refuse to attach a particular bus to the kernel's device tree if you don't even know how device IRQs on that bus are routed to the IRQ controllers on the board. In other words, on any well-manufactured board, the information needed on Device <-> IRQ-pin mapping will be present and available, and such extensive IRQ probing will not be needed, because PC-clone motherboard manufacturers have an active interest in making usable products -- they gain nothing from producing a board that cannot be used by kernels du jour (because they will make losses and not profits). Whether or not you choose to do probing and use heuristics is really a choice, and is probably not a choice you should make early on in design due to the complexity involved in getting something like that right.

However, your problem seems to be different -- you just don't want to have any non-ASM code in your kernel. In that case, have you considered building ACPICA separately as a library or external driver and linking to it from your kernel? And if you would prefer not to do this (why not?) the only other thing you can do is write an ACPICA yourself. However, you may probably want to step back and realize that High Level Languages are present and have come into common use for (generally useful) reasons, and that your goal itself might actually be (significantly) less sensible than you first gauged it to be.

--Peace out,
gravaera

Re: ACPI support in assembler

Posted: Thu Jan 10, 2013 2:49 am
by online
I really appreciate your feedback - thanks a lot for the answers, guys.
At least I have enough info for now :-)
I'll try to implement the autodetection and I'll also think about linking the ACPICA with my kernel.

online

Re: ACPI support in assembler

Posted: Fri Jan 11, 2013 5:33 pm
by tom9876543
Brendan wrote: The problem isn't implementing an AML interpreter (which on its own may be conceivable), but emulating the behaviour of a specific version of Windows (which is mostly undocumented and certainly not described in the "little" ACPI specification).

It may be possible to support ACPI without pretending to be (a version of) Windows; but in this case the firmware/AML will probably disable everything except basic functionality (and then crash because the AML wasn't tested properly on OSs that don't pretend to be Windows). ;)


Cheers,

Brendan
ACPI is totally outrageously unspeakably bad design, Intel should be ashamed they developed it.
I don't see why every single ACPI feature can't be implemented as a simple PCI device, or extensions to existing PCI/ISA/CPU etc devices.
Just look at the Power Button - for f#$#ks sake - how hard would it have been to simply implement a STANDARD PCI device that manages the power button and power supply (battery information etc).
Also there was the Linux power bug related to the BIOS (http://www.phoronix.com/scan.php?page=a ... tion&num=1). A simple question - why the f#$%k is power management information in the BIOS/ACPI when it should really be in the PCI configuration space.

Re: ACPI support in assembler

Posted: Sat Jan 12, 2013 4:00 am
by rdos
The biggest problem with the whole spec it that OSversion exists. BIOS developpers should not care about which OS version is running. As soon as they do, things will start getting out of hand.

Re: ACPI support in assembler

Posted: Sat Jan 12, 2013 10:33 am
by Owen
tom9876543 wrote:I don't see why every single ACPI feature can't be implemented as a simple PCI device, or extensions to existing PCI/ISA/CPU etc devices.
It could, but no doubt it would never exist as a physical device, but as SMM code implemented by the BIOS vendor. I see no way in which this would be better
tom9876543 wrote:Just look at the Power Button - for f#$#ks sake - how hard would it have been to simply implement a STANDARD PCI device that manages the power button and power supply (battery information etc).
Quite difficult, considering most batteries don't talk PCI (I.E. again it would end up SMM code or on a new microcontroller)
tom9876543 wrote:Also there was the Linux power bug related to the BIOS (http://www.phoronix.com/scan.php?page=a ... tion&num=1). A simple question - why the f#$%k is power management information in the BIOS/ACPI when it should really be in the PCI configuration space.
Because the only people who know how power management should work for a given motherboard are the motherboard vendors?

The fact that AML is a bytecode format is actually a virtue. Why? Becasue when it goes wrong, you can decompile it to see why it's going wrong, and perhaps deduce the behavior that it expects, plus the interfaces to the various mini ACPI devices that motherboard/system vendors implement.

Changing away from ACPI wouldn't fix the biggest issue with it: that the only testing any motherboard manufacturer performs is "Does it boot Windows (XP, Vista, 7, 8 )" before shipping.