uACPI icon indicating copy to clipboard operation
uACPI copied to clipboard

Implementing uACPI in RDOS

Open rdos314 opened this issue 8 months ago • 35 comments

I have a couple of issues/suggestions:

  1. Physical memory mappings would be improved if the OS knows what the memory will be used for. For instance, ACPI tables should be mapped as read-only, while memory that uACPI wants to write would benefit from knowing if it is MMIO or ordinary RAM since optimal page table attributes are different
  2. Provided that only one thread accesses uACPI, is there some kind of guarantee that overlapping IO port requests will not be made? This will simplify using the IO permission bitmap to only allow access to IO ports that have open requests.
  3. Is it possible to disable semaphores when these are not needed?

rdos314 avatar May 19 '25 07:05 rdos314

  1. This is impossible to implement as uACPI doesn't know what the AML bytecode is asking it to map. It could be normal RAM, or device memory, or some other memory. All it gets is an address and length. It also doesn't know the intentions of AML, whether it's going to be using this memory only for reading, or writing, or both. You should map it accordingly by looking at the memory map, see Linux code for that here.
  2. No such guarantee is possible, AML might map overlapping IO regions multiple times
  3. No, they are always needed for AML. if you only want tables see UACPI_BAREBONES_MODE, this needs 3 kernel api functions and nothing else

d-tatianin avatar May 19 '25 10:05 d-tatianin

It should be possible to determine if a map is for an ACPI table or for AML usage. Then I could map ACPI tables for read-only and caching, and everything else as no caching and read-write. Using the memory map doesn't seem like a good idea since it is different based if the boot is through EFI or BIOS. Some really old computers might even use scanning, but then ACPI probably won't work either.

I have now tested uACPI initialization on four different computers with EFI boot and five using BIOS boot. Two of the EFI computers have bugs in AML, and one of these doesn't boot with ACPICA, while the other only booted if a "new" Windows version was signalled.

I also wonder a bit why one of my computers (the one with EFI boot and bugs in AML) does not call any PCI functions, while two other that boot using EFI call PCI functions, and appear to try to verify PCI root devices, and a few others too. I think I will integrate ACPI and PCI, so I'm working on a more tight integration between them.

rdos314 avatar May 19 '25 13:05 rdos314

AML is allowed to map and modify its own ACPI tables so mapping those read-only isnt viable.

Khitiara avatar May 19 '25 16:05 Khitiara

Also, when uACPI loads ACPI tables, it copies them into an allocated buffer rather than directly access them from the mapped location.

Khitiara avatar May 19 '25 16:05 Khitiara

Then I could map ACPI tables for read-only

I believe you also have to modify tables for ACPI to function properly, like for the global lock, wakeup vectors, and probably some other stuff, so it makes no sense to map them as read only even if they are not touched by AML.

Using the memory map doesn't seem like a good idea since it is different based if the boot is through EFI or BIOS

I don't get what is the issue with that. You can just normalize it into a common format, my kernel, for example, does that to support different boot protocols. Iirc you also need to have memory map to implement sleep states properly, since ACPI requires you to save certain memory regions.

EFI call PCI functions

I think having fully working PCI functions (and all the kernel API in general) are also necessary for ACPI's initialization, even QEMU uses PCI functions during its initialization and fails if they are not properly implemented, and it gets more complex on physical hardware.

ThatMishakov avatar May 19 '25 17:05 ThatMishakov

I think another approach might be to manage map requests during table loading by mapping them read-only and then, in later stages, handle map requests as if they are for MMIO (no caching). When I debugged it, it worked to map everything read-only until I ran the later stages.

rdos314 avatar May 19 '25 18:05 rdos314

I don't get what is the issue with that. You can just normalize it into a common format, my kernel, for example, does that to support different boot protocols. Iirc you also need to have memory map to implement sleep states properly, since ACPI requires you to save certain memory regions.

I don't keep that information. I only use the memory map to decide what is available memory and what is not. I don't support sleep states, and I have no intention to support power management.

I think having fully working PCI functions (and all the kernel API in general) are also necessary for ACPI's initialization, even QEMU uses PCI functions during its initialization and fails if they are not properly implemented, and it gets more complex on physical hardware.

I have working PCI functions, but I think the PCI device tree can be cached in the ACPI server, primarily since these relate to the ACPI names and ACPI is required for interrupt routing.

rdos314 avatar May 19 '25 18:05 rdos314

I think another approach might be to manage map requests during table loading by mapping them read-only and then, in later stages, handle map requests as if they are for MMIO (no caching). When I debugged it, it worked to map everything read-only until I ran the later stages.

To me, this just sounds weird, and I don't see how that is an improvement over mapping tables R/W, and how it would stop tables from being mapped as so after you start treating everything as MMIO

I don't keep that information. I only use the memory map to decide what is available memory and what is not. I don't support sleep states, and I have no intention to support power management.

I am not a uACPI developer, but I've seen several discussions about it before, and the conclusion was that it is just not feasible for that (deducing memory types) to be done by uACPI and has to be done by the kernel. Also, if you don't want to deal with that and you only target x86, I think that the caching types should generally be handled correctly by MTTRs, or you can maybe just map everything as uncached

ThatMishakov avatar May 19 '25 20:05 ThatMishakov

I have working PCI functions, but I think the PCI device tree can be cached in the ACPI server, primarily since these relate to the ACPI names and ACPI is required for interrupt routing.

I guess it is kinda up to you, but my OS also handles ACPI, PCI and some other ACPI-related drivers (EC, GPIO, GED, and I'll probably add more) in the same server. I also have a mechanism for publishing devices discovered from ACPI, to be used by other servers, "inspired by" Managarm

ThatMishakov avatar May 19 '25 20:05 ThatMishakov

I have a few more questions before I do things on my own:

  1. Is it possible to get the PCI device tree in a simple way?
  2. What does the initialization code do with PCI devices? Does it set them in power on (through PCI registers)? Does it enable upstream PCI hubs? Are there specific functions that are not done on initialization that need to be done before using them?

rdos314 avatar May 21 '25 20:05 rdos314

It seems like only the root PCI root bridges are part of ACPI, and that they must be identified via _HID.

There seems to be a "chicked-and-egg" issue because the OS must identify the root bridges before it can support PCI configuration requests. This suggests that discovery of root bridges must be done when the namespace is initialized but before any PCI requests are done.

I have issues with IRQs too. There is a function to setup which type of interrupt controller is used (PIC, IOAPIC), but I'm unsure when this should be done. The IRQ resources does not seem to contain the correct values for PCI LNK objects (when comparing to ACPICA).

I also wonder why current resource retrieval does not return current setting when this is dynamic. For instance, the resources for the PCI root bridge contains an address16 bus resource, but nowhere in the structure is the current value, so my code need to use the evaluate method on _BBN and _SEG to get the values for bus and segment. The resource doesn't contain the original ACPI name either so code could use evaluate on it.

rdos314 avatar May 23 '25 13:05 rdos314

There seems to be a "chicked-and-egg" issue because the OS must identify the root bridges before it can support PCI configuration requests. This suggests that discovery of root bridges must be done when the namespace is initialized but before any PCI requests are done.

I believe that before you enumerate the namespace, you should get the handlers from the MCFG table if it's available (which should become avaialble after you call uacpi_initialize), or otherwise, on x86, directly from 0xCF8. I guess that after you do enumerate it, you can find additional MMIO windows in _CBA (?)

Also, I think that sometimes the firmware needs to reference the devices that are not present, so you shouldn't be checking if the device exists anyway.

I have issues with IRQs too. There is a function to setup which type of interrupt controller is used (PIC, IOAPIC), but I'm unsure when this should be done. The IRQ resources does not seem to contain the correct values for PCI LNK objects (when comparing to ACPICA).

I think that it needs to be done after calling uacpi_namespace_load and before initializing PCI. At least my kernel does that, and it seems to work without issues

I also wonder why current resource retrieval does not return current setting when this is dynamic. For instance, the resources for the PCI root bridge contains an address16 bus resource, but nowhere in the structure is the current value, so my code need to use the evaluate method on _BBN and _SEG to get the values for bus and segment. The resource doesn't contain the original ACPI name either so code could use evaluate on it.

I think that _BBN and _SEG are optional and are 0 if not present. To discover root bridges, I scan all PNP0A03 and PNP0A08 devices, and get the resources (calling _BBN and _SEG) from their handles, and also scan for devices and interrupts descending from those.

ThatMishakov avatar May 23 '25 15:05 ThatMishakov

I'd recommend that you join the osdev discord server if you want general help on uACPI APIs or other related questions, me and other people who are familiar with uACPI generally answer there and help people out more quickly

d-tatianin avatar May 24 '25 11:05 d-tatianin

To discover root bridges, I scan all PNP0A03 and PNP0A08 devices, and get the resources (calling _BBN and _SEG) from their handles, and also scan for devices and interrupts descending from those.

Do you have a link to this code?

For now, I have decided that the new computer, which doesn't boot with ACPICA, doesn't need to support PCI IRQs; instead, it will work with MSI/MSI-X for all relevant devices. It is sufficient to run the standard initialization of uACPI before installing various PCI devices, and there is no need to read anything from uACPI. Still, for other platforms, this will not work, so I need a method to retrieve the PCI IRQs.

rdos314 avatar May 27 '25 18:05 rdos314

To discover root bridges, I scan all PNP0A03 and PNP0A08 devices, and get the resources (calling _BBN and _SEG) from their handles, and also scan for devices and interrupts descending from those.

Do you have a link to this code?

For now, I have decided that the new computer, which doesn't boot with ACPICA, doesn't need to support PCI IRQs; instead, it will work with MSI/MSI-X for all relevant devices. It is sufficient to run the standard initialization of uACPI before installing various PCI devices, and there is no need to read anything from uACPI. Still, for other platforms, this will not work, so I need a method to retrieve the PCI IRQs.

I'm doing this in my OS (I'm not sure if it's absolutely correct though, and I need to do something about the commented Raspberry Pi thing). I think you can also look at Managarm and other operating systems, though I think everyone else does it in kernel.

ThatMishakov avatar May 27 '25 21:05 ThatMishakov

Your link helped a lot, and it seems like the correct routings per device are now returned and registered in my PCI device objects.

rdos314 avatar May 28 '25 10:05 rdos314

You still don't handle the "no source" case correctly, please read https://uefi.org/specs/ACPI/6.5/06_Device_Configuration.html#prt-pci-routing-table

UPD: Nvm, you save the index in Irq by default, that should work I think

d-tatianin avatar May 28 '25 10:05 d-tatianin

I fixed the no source case (by checking the field). I missed that aspect.

I suspose that to get the IRQ for a PCI function, I read the pin from PCI configuration space and then translate it to an IRQ number through the IRQ table in the PCI device?

The new computer apparently does not have any processor objects at all, but they are still in the APIC table. It's the first time I've seen this.

rdos314 avatar May 28 '25 13:05 rdos314

Processor objects are deprecated and are replaced by normal Device objects with PNP id of ACPI0007

d-tatianin avatar May 29 '25 08:05 d-tatianin

Are they useful? Long ago processor objects could be used for controlling speed & voltage, but on newer systems they are quite useless.

I fixed it, so I'm now using memory-mapped PCI configuration when available, but I still haven't linked to the OS API since I don't have a computer at home that sends PCI configuration requests during initialization.

rdos314 avatar May 29 '25 20:05 rdos314

Very much so, yes. Each processor will have _PDC and _OSC methods that allow various advanced power management and other features to be configured by the operating system. See all files starting with processor_* here: https://elixir.bootlin.com/linux/v6.15/source/drivers/acpi

d-tatianin avatar May 30 '25 09:05 d-tatianin

Tested the IRQ processing logic on four different machines now, and the results appear to be identical to ACPICA, so the logic appears to work now.

rdos314 avatar Jun 05 '25 09:06 rdos314

I read up on _PDC and _OSC, and they only announce capabilities, not how to change the frequency or C states. You need additional methods that describe C states and how to change them, objects that are no longer present in ACPI. The reason is that both Intel and AMD have their own processor drivers that work independently of ACPI. The control methods change from processor to processor.

rdos314 avatar Jun 11 '25 20:06 rdos314

I've changed all files in my fork to use a MIT licence, which should be more compatible with the uACPI code. Will eventually change all files in RDOS to use MIT as well and move it to GIT hub.

rdos314 avatar Jul 14 '25 09:07 rdos314

I have some issues with Reset. In the ACPICA implementation, ACPI run in kernel mode, and so the AcpiReset function could be called from anywhere, including from IRQs without crashing. Now there is a communication protocol between ACPI clients and the ACPI server (due to the "micro-kernel" approach), which breaks the reset function. When Reset is called in kernel space, it must be implemented without the use of synchronization primitives, and cannot rely on the scheduler working.

My question is if the AcpiReset function is implemented without relying on delays or semaphores? Do I need to consider these calls (and ignore them), or is it safe to run AcpiReset in any context without adding checks on semaphores or delays?

rdos314 avatar Jul 31 '25 08:07 rdos314

Resetting from an IRQ handler is not a good idea.

  1. Firmware requires that you run _PTS(5) (uacpi_prepare_for_sleep_state) when resetting, which requires executing AML, so it may block for an unpredictable amount of time, use mutexes etc.
  2. Resetting from an IRQ handler means you don't do any sort of FS sync, or userspace notification, which is also not something a non-toy kernel can afford to do
  3. While uacpi_reboot() doesn't use any semaphores, it does use stalls to make sure it doesn't return control to the caller right away (since reset takes time to complete), but also to prevent hanging Indefinitely if reset doesn't happen for whatever reason

d-tatianin avatar Jul 31 '25 08:07 d-tatianin

I would want it to setup the RESET, and then just return. I suppose I can check in stall for a pending RESET and then assume the RESET is triggered. I have a "default" method of RESET that triggers a tripple fault, and which works on most PCs. So, I would call the default method when RESET returns or when stall is called. I suppose that I could also add a check for blocking in the scheduler, and invoke the default method from there.

Calling into the user space ACPI server is not a big deal. Just disable interrupts, setup the correct CR3 and run on a preallocated stack.

Triggering a RESET from an IRQ, or when a kernel panic is triggered is pretty important for me. I cannot afford hanging systems under these conditions, so this must be stable, otherwise I would need to turn-off ACPI reset and only do default RESET. I already do this when ACPICA misbehaves.

rdos314 avatar Jul 31 '25 11:07 rdos314

There's no difference here in terms of what uacpi requires for resetting vs acpica, I would just look at how other operating systems handle resetting in panic etc.

d-tatianin avatar Jul 31 '25 11:07 d-tatianin

The uacpi_reboot seems to be pretty straigt-forward. It might allocate an IO port or a PCI device. The support layer will need to keep these static to avoid allocation when reset is invoked. It checks the initialization level, but I won't call reboot unless uACPI is half-initialized, so no problem. When a successful reboot is initiated, it will call stall, which I can use to trigger the default reset method.

rdos314 avatar Jul 31 '25 12:07 rdos314

Actually, the uacpi_reboot code only references FADT, and nothing else. This means it should be possible to extract the reset register and reset value in kernel space, and use these to do ACPI RESET in kernel space instead of needing to do this through user space.

Apparently, it's possible to extract the FADT pointer during kernel initialization, and then to support the three address space cases (memory, IO or PCI) easily.

rdos314 avatar Jul 31 '25 13:07 rdos314