User:No92/ACPICA

From OSDev Wiki
Jump to navigation Jump to search

The ACPI Component Architecture ACPICA provides an operating system (OS)-independent reference implementation of the Advanced Configuration and Power Interface. It can be adapted to any host OS. The ACPICA code is meant to be directly integrated into the host OS, as a kernel-resident subsystem. Hosting the ACPICA requires no changes to the core ACPICA code. However, it does require a small OS-specific interface layer, which must be written specifically for each host OS.

The complexity of the ACPI specification leads to a lengthy and difficult implementation in OS software. The purpose of the ACPI Component Architecture is to simplify ACPI implementations for operating system vendors by providing major portions of an ACPI implementation in OS-independent ACPI modules that can be easily integrated into any OS.

Setup

Obtain ACPICA at acpica.org and extract the archive.

Formatting

There are basically two flavors of ACPICA around; they differ in code style only. Should you want your ACPICA function looking like AcpiGetIrqRoutingTable, you are lucky and don't have to do anything. Should you want them looking like acpi_get_irq_routing_table, you will have to convert them to "Linux style". Thankfully, there is a tool supplied to do exactly this. Run this:

make acpisrc

To convert the source code in a folder, you can now do this:

./generate/unix/bin/acpisrc -ldqy <source directory> <destination directory>

You might have to fix acdisasm.h:478, changing AH_TABLE to struct ah_table for a successful compilation (you do want this, right?).

Integrating the code

This boils down to copying a few files over. Start off by copying the headers from source/include. After that, copy over everything from source/components, except for the subdirectories debugger and disassembler. This means doing this:

cp source/components/{dispatcher,events,executer,hardware,parser,namespace,utilities,tables,resources}/*.c <destination>

You might have to adjust the header #includes, which can be done with sed or a simple script.

Writing your own platform header

This is a header placed in include/platform/acmyos.h. A basic one might look something like this:

#pragma once

#include <ctype.h>
#include <stdint.h>
#include <string.h>

#define ACPI_MACHINE_WIDTH          64
#define COMPILER_DEPENDENT_INT64    int64_t
#define COMPILER_DEPENDENT_UINT64   uint64_t

#define ACPI_USE_DO_WHILE_0
#define ACPI_MUTEX_TYPE             ACPI_OSL_MUTEX

#define ACPI_CACHE_T                ACPI_MEMORY_LIST
#define ACPI_USE_LOCAL_CACHE        1

#define ACPI_SINGLE_THREADED
#define ACPI_DEBUG_OUTPUT
#define ACPI_USE_STANDARD_HEADERS
#define ACPI_USE_SYSTEM_CLIBRARY

#include <acpica/platform/acgcc.h>

Be aware that this assumes that you have a few utility functions ready, mostly from string.h. This is a minimum viable example, keeping the requirements down somewhat. See the documentation about what each line means and how to modify it to suit your needs.

Writing your OSL

This is the longest task for implementation. If you have a good kernel to begin with, you probably won't have to write a lot of code though. Again, for details see the documentation.

Environmental and ACPI Tables

acpi_os_initialize

acpi_status acpi_os_initialize(void) {
	return AE_OK;
}

This is called during ACPICA initialization to do your initialization work, if needed.

acpi_os_terminate

acpi_status acpi_os_terminate(void) {
	return AE_OK;
}

This is called during ACPICA termination, should that occur.

acpi_os_get_root_pointer

acpi_physical_address acpi_os_get_root_pointer(void)

Just return the RSDP. If your OS doesn't use UEFI, this boils down to:

acpi_physical_address ret;
ret = 0;
acpi_find_root_pointer(&ret);
return ret;

Note: The ACPI specification is highly portable specification, however, it has a static part which is generally non-portable: the location of the Root System Descriptor Pointer. This pointer may be found in many different ways depending on the chipset. On PC-compatible computers (without EFI) it is located in lower memory generally somewhere between 0x80000 and 0x100000. However, even within the PC compatible platform, an EFI-enabled board will export the RSDP to the OS on when it loads it through the EFI system tables. Other boards on server machines which are not PC-compatibles, like embedded and handheld devices which implement ACPI will again, not all be expected to position the RSDP in the same place as any other board. The RSDP is therefore located in a chipset-specific manner; From the time the OS has the RSDP, the rest of ACPI is completely portable. However, the way the RSDP is found is not. This would be the reason that the ACPICA code wouldn't try to provide routines to expressly find the RSDP in a portable manner. If your system uses EFI, locate it in the system tables or use Multiboot2 compliant loader, which provides the RSDP for you.

acpi_os_predefined_override

acpi_status acpi_os_predefined_override(const struct acpi_predefined_names *PredefinedObject, acpi_string *NewValue) {
	if(!PredefinedObject || !NewValue) {
		return AE_BAD_PARAMETER;
	}

	*NewValue = NULL;
	return AE_OK;
}

This allows you to override predefined objects in the ACPI namespace. It is called when a new object is found in the ACPI namespace. However you can just put NULL in *NewValue and return.

acpi_os_table_override

acpi_status acpi_os_table_override(struct acpi_table_header *ExistingTable, struct acpi_table_header **NewTable) {
	*NewTable = 0;
	return AE_OK;
}

The same of AcpiOsPredefinedOverride but for entire ACPI tables. You can replace them. Just put NULL in *NewTable and return.

acpi_os_physical_table_override

acpi_status acpi_os_physical_table_override(struct acpi_table_header *ExistingTable, acpi_physical_address *NewAddress, uint32_t *NewTableLength) {
	*NewAddress = 0;
	return AE_OK;
}

This allows you to override a found ACPI table with a new table via a physical address and a length. Again, this gets the NULL treatment.

Memory Management

acpi_os_map_memory

void *acpi_os_map_memory(acpi_physical_address PhysicalAddress, acpi_size length)

This is not really easy. ACPICA is asking you to map a physical address in the virtual address space. If you don't use paging, just return PhysicalAddress. You need:

  1. To round Length up to the size of a page (Length can be 2, 1024 for example)
  2. Find a range of virtual addresses where map the physical frames.
  3. Map the physical frames to the virtual addresses choosen.
  4. Return the virtual address plus the page offset of the physical address. (Eg. If you where asked to map 0x40E you have to return 0xF000040E and not just 0xF0000000)

acpi_os_unmap_memory

void acpi_os_unmap_memory(void *where, acpi_size length)

Unmap pages mapped using acpi_os_map_memory. where is the virtual address returned in acpi_os_map_memory and length is equal to the length of the same function. Just remove the virtual address from the page directory and set that virtual address as reusable. Note: for the last two functions you might need a separated heap.

acpi_os_get_physical_address

acpi_status acpi_os_get_physical_address(void *LogicalAddress, acpi_physical_address *PhysicalAddress)

Get the physical address of LogicalAddress and put it in *PhysicalAddress. If you do not use paging just put LogicalAddress in *PhysicalAddress.

acpi_os_allocate

void *acpi_os_allocate(acpi_size size) {
	return malloc(size);
}

acpi_os_free

void acpi_os_free(void *Memory) {
	free(Memory);
}

acpi_os_readable

uint8_t acpi_os_readable(vy_unused void *Memory, vy_unused acpi_size Length)

This is never called, just have it in here.

acpi_os_writable

uint8_t acpi_os_writable(vy_unused void *Memory, vy_unused acpi_size Length)

This is never called, just have it in here.

Multithreading and Scheduling Services

acpi_os_get_thread_id

acpi_thread_id acpi_os_get_thread_id(void)

Return a non-zero value identifying the current thread. If you disabled threading as in the example configuration above, just return 1.

acpi_os_execute

acpi_status acpi_os_execute(vy_unused acpi_execute_type Type, vy_unused acpi_osd_exec_callback Function, vy_unused void *Context)

Create a new Thread (or process) with entry point at Function using parameter Context. Type is not really useful. When the scheduler chooses this thread it has to put Context on the stack to have something like:

Function(context);

If you disabled threading as in the example configuration above, this will not be called.

acpi_os_sleep

void acpi_os_sleep(uint64_t Milliseconds) {
	sleep(Milliseconds);
}

Put the current thread to sleep for n milliseconds. If you disabled threading as in the example configuration above, this is the same as acpi_os_stall, just with different units.

acpi_os_stall

void acpi_os_stall(vy_unused uint32_t Microseconds)

Stall the current thread for n microseconds. If you disabled threading as in the example configuration above, this is the same as acpi_os_sleep, just with different units.

acpi_os_wait_events_complete

void acpi_os_wait_events_complete(void)

This function blocks until all asynchronous events initiated by acpi_os_execute have completed. If you disabled threading as in the example configuration above, you probably don't care.

Mutual Exclusion and Synchronization

If you disabled threading as in the example configuration above, you can simply stub these functions out to do nothing and return AE_OK, if applicable.

acpi_status acpi_os_create_mutex(acpi_mutex *mutex);
void acpi_os_delete_mutex(acpi_mutex mutex);
acpi_status acpi_os_acquire_mutex(acpi_mutex mutex, uint16_t timeout);
void acpi_os_release_mutex(acpi_mutex mutex);

acpi_status acpi_os_create_semaphore(uint32_t max_units, uint32_t initial_units, acpi_semaphore *s);
acpi_status acpi_os_delete_semaphore(acpi_semaphore s);
acpi_status acpi_os_wait_semaphore(acpi_semaphore s, uint32_t units, uint16_t timeout);
acpi_status acpi_os_signal_semaphore(acpi_semaphore s, uint32_t units);

acpi_status acpi_os_create_lock(acpi_spinlock *lock);
void acpi_os_delete_lock(acpi_spinlock lock);
acpi_cpu_flags acpi_os_acquire_lock(acpi_spinlock lock);
void acpi_os_release_lock(acpi_spinlock lock, acpi_cpu_flags flags);

Interrupt Handling

Port Input/Output

PCI Configuration Space Access

Formatted output

System ACPI Table Access

Miscellaneous

undefined references fixup

I don't know where this goes, so do this here: ACPICA requires these functions to exist, even though we don't use the debugger.

acpi_status AcpiDbSingleStep(void *state, void *op, uint32_t optype) {
	panic("%s unimplemented", __func__);
}
void AcpiDbSignalBreakPoint(void *state) {
	panic("%s unimplemented", __func__);
}
void AcpiDbDumpMethodInfo(acpi_status status, void *state) {
	panic("%s unimplemented", __func__);
}
void AcpiDbDisplayArgumentObject(void *desc, void *state) {
	panic("%s unimplemented", __func__);
}
void AcpiDbDisplayResultObject(void *desc, void *walk) {
	panic("%s unimplemented", __func__);
}