Hi All,
I spent some time attempting to get the network adapter working in my
test hardware machine (advent). It is an Atheros device that is
supported by netdde. It worked successfully when using PIC but not when
using IOAPIC which implied that the interrupt detection was at fault. I
found 2 separate but related issues:
1) The ACPI tables within advent have additional _PRT maps for the
subordinate PCI bridges which were not being examined by the acpi
server. The _PRT table for the subordinate bridge should be examined
first before moving onto the root PCI bridge _PRT table if necessary. It
is quite a convoluted process to discover these tables but in essence an
ACPI walk of all devices needs to be made and each device tested to see
if it is a root bridge or a PCI-PCI bridge. I decided to just make a
single walk at acpi startup and cache the relevant information for later
use within irq lookup.
2) In the event that there are no additional _PRT maps for subordinate
buses (as is the case on my Qemu virtual machines) the root bridge _PRT
map still applies. Additional translation of the INTx pins needs however
to occur first.
The general rule appears to be that interrupt pins are translated from
the down stream to the up stream sides of each bridge. So downstream dev
0 INTA maps to upstream dev 0 INTA. Downstream dev 1 INTA maps to
upstream dev1 INTB. Downstream dev 2 INTA maps to upstream dev 2 INTC
and so on. In summary for a downstream 'int-pin', the upstream
equivalent is ((int-pin + dev) %4). This translation applies across each
intermediate PCI-PCI bridge until the root bridge is reached. Note that
this means that device 0 will always match the same INTx pin wherever it
is on the bus hierarchy.
Implementing the above I was able to correctly determine the IOAPIC GSI
required for advent (using pic or IOAPIC) and also Qemu on both
interrupt controller schemes.
It seems on my advent machine that if the IOAPIC is in use then the
legacy IRQ numbers don't work at all (that was the case when I was
previously finding the fix for the AHCI controller on this machine).
Netdde doesn't actually perform a GSI translation.
libdde-linux26/...../arch/l4/pci.c has replaced the Linux function
pci_irq_enable() and calls libddekit::ddekit_pci_irq_enable() to offer
an opportunity to replace the legacy IRQ with the GSI. Doing so allowed
advent's network adapter to work with IOAPIC.
I'm aware that the Netdde is not the future for Hurd network drivers.
It's also I think the case that using IOAPIC with the Netdde drivers is
a bit hit and miss (mine didn't work with IOAPIC without a minor change
to the driver itself). I think it's true though that rumpnet still needs
to make the same GSI translation and I think that the same issues
described here will apply.
I did look at making use of the alc NetBSD driver but that I believe
requires some work for rumpkernel to produce an alc driver module to
link with rumpnet. Does anyone have a rough idea of how much work that
would be? I'm willing to have a go at this but it would obviously be
faster if someone can steer me in the right direction.
The bulk of my changes apply to libacpica/acpi_init.c. There's little
value in including the other minor changes which might not be
appropriate for merging at all anyway. I've attached the entire
acpi_init.c file rather than a patch as it is simpler to see what I'm
describing. There's an element of roughness to the my changes at the
moment, in particular use of some unnamed numeric constants for PCI
value limits and so on. It also isn't based on the absolute latest
source (with the minor revision to using irq_status.h). It's very
premature to present this as an actual patch. Anyway the main
alterations are:
1) acpi_load_pci_bridges()
This does the ACPI walk and populates a list of bridge mappings. Each
mapping has the up and down stream bus numbers and the ACPI handle that
would be used to load the _PRT table. It doesn't check if there is such
a table; that occurs during the IRQ lookup.
2) acpi_get_irq_number()
This bulk of this was factored out into a new acpi_get_irq_from_bus()
(possibly a bad function name). Now lookup of the IRQ number tries each
bridge specific _PRT (if any) in turn traversing the hierarchy to the
root bridge. Finally it tries the root bridge _PRT having performed the
necessary INTx translation(s).
3) Note that I had to remove the translation of legacy IRQs by not
adding the PCI_IRQ_START. advent uses legacy IRQ5 for the network
adapter and without removing this I ended up with the IRQ 21 when usin
PIC and not IOAPIC. I didn't understand what this translation was for so
what I'm doing here might be very wrong.
I've tried this concept on all combinations of PIC/IOAPIC, 32 bit and 64
bit, advent/Qemu and they all work with 1 exception. 64 bit on advent
with PIC (not IOAPIC) does not work. I've not studied this yet but it
perhaps relates to point 3) above as I see intnull(2) in the log.
I think that about summarises the main points and I'd be very interested
in feedback: good or bad.
Regards,
Mike.
// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
#include <acpi/acpi.h>
#include <stdio.h>
#include <sys/time.h>
#include <sys/io.h>
#include <stddef.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>
#include <mach/vm_param.h>
#include <errno.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdarg.h>
#include <semaphore.h>
#include <limits.h>
#include <device/device.h>
#include <hurd.h>
#include <pciaccess.h>
#include <hurd/irqhelp.h>
#define ACPI_MAX_TABLES 128
#define PCI_CFG1_START 0xcf8
#define PCI_CFG1_END 0xcff
#define LEGACY_ISA_IRQS 8
#define PCI_IRQ_START 16
#define PCI_INTERRUPT_PIN 0x3d
#define PCI_HDR_TYPE_REG 0x0e
#define PCI_HDR_TYPE_PPB 1
#define PCI_PPB_SECBUS_REG 0x19
extern acpi_status acpi_hw_legacy_sleep(u8 sleep_state);
// Lets keep the ACPI tables in this module
static struct acpi_table_desc initial_tables[ACPI_MAX_TABLES];
static struct pci_device **pci_devices;
static int numdevs = -1;
typedef struct acpi_pci_bridge
{
acpi_handle handle;
uint8_t up_bus;
uint8_t down_bus;
struct acpi_pci_bridge* next;
} acpi_pci_bridge_t;
static acpi_pci_bridge_t* acpi_pci_bridges;
struct slots {
uint8_t bus;
uint16_t dev;
uint16_t func;
acpi_handle handle;
struct slots *next;
};
static int
pci_init(void)
{
int i;
struct pci_device_iterator *dev_iter;
struct pci_device *pci_dev;
struct pci_device **tmp;
pci_system_init ();
numdevs = 0;
dev_iter = pci_slot_match_iterator_create (NULL);
while ((pci_dev = pci_device_next (dev_iter)) != NULL)
numdevs++;
pci_devices = malloc (sizeof(struct pci_device *));
if (pci_devices == NULL)
return errno;
tmp = pci_devices;
i = 0;
dev_iter = pci_slot_match_iterator_create (NULL);
while ((pci_dev = pci_device_next (dev_iter)) != NULL)
{
if ((tmp = realloc(pci_devices, (i + 1) * sizeof(*tmp))) == NULL)
{
free(pci_devices);
return errno;
}
pci_devices = tmp;
pci_device_probe(pci_dev);
pci_devices[i++] = pci_dev;
}
return 0;
}
struct pci_device *
lookup_pci_dev(struct acpi_pci_id *pci_id)
{
int i;
for (i = 0; i < numdevs; i++)
{
if ((pci_devices[i]->domain == pci_id->segment)
&& (pci_devices[i]->bus == pci_id->bus)
&& (pci_devices[i]->dev == pci_id->device)
&& (pci_devices[i]->func == pci_id->function))
{
return pci_devices[i];
}
}
acpi_os_printf("Can't find pci dev %04x:%d:%02x.%d\n", pci_id->segment, pci_id->bus, pci_id->device, pci_id->function);
return NULL;
}
static void
acpi_unload_pci_bridges(void)
{
while (acpi_pci_bridges != NULL)
{
acpi_pci_bridge_t* bridge = acpi_pci_bridges;
acpi_pci_bridges = bridge->next;
free(bridge);
}
}
void
acpi_ds_dump_method_stack(acpi_status status, ...)
// struct acpi_walk_state *walk_state, union acpi_parse_object *op)
{
return;
}
void
acpi_os_printf(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
acpi_os_vprintf(fmt, args);
va_end(args);
}
void
acpi_os_vprintf(const char *fmt, va_list args)
{
vprintf(fmt, args);
}
acpi_status
acpi_os_execute(acpi_execute_type type,
acpi_osd_exec_callback function, void *context)
{
function(context);
return AE_OK;
}
acpi_physical_address
acpi_os_get_root_pointer(void)
{
acpi_physical_address pa;
acpi_find_root_pointer(&pa);
return pa;
}
acpi_status
acpi_os_physical_table_override(struct acpi_table_header *existing_table,
acpi_physical_address *address,
u32 *table_length)
{
*table_length = 0;
*address = 0;
return AE_OK;
}
acpi_status
acpi_os_table_override(struct acpi_table_header *existing_table,
struct acpi_table_header **new_table)
{
if (!existing_table || !new_table)
return AE_BAD_PARAMETER;
*new_table = NULL;
return AE_OK;
}
void *
acpi_os_map_memory(acpi_physical_address phys, acpi_size size)
{
int err;
mach_port_t device_master;
device_t mem_device;
mach_port_t memobj;
vm_address_t vaddr = 0;
uint32_t pa_offset = phys % sysconf(_SC_PAGE_SIZE);
uint32_t pa_start = phys - pa_offset;
size = round_page(size + pa_offset);
err = get_privileged_ports (0, &device_master);
if (err)
{
acpi_os_printf("Cannot get device master port");
return (void *)-1;
}
err = device_open (device_master, 0, "mem", &mem_device);
mach_port_deallocate (mach_task_self (), device_master);
if (err)
{
acpi_os_printf("device_open(mem)");
return (void *)-1;
}
err =
device_map (mem_device, VM_PROT_READ | VM_PROT_WRITE, pa_start, size, &memobj, 0);
mach_port_deallocate (mach_task_self (), mem_device);
if (!err)
{
err = vm_map (mach_task_self (), &vaddr, size, 0, 1,
memobj, 0, 0,
VM_PROT_READ | VM_PROT_WRITE,
VM_PROT_READ | VM_PROT_WRITE,
VM_INHERIT_SHARE);
mach_port_deallocate (mach_task_self (), memobj);
}
if (err)
{
acpi_os_printf("Can't map memory 0x%llx\n", phys);
return (void *)-1;
}
return (void *)(vaddr + pa_offset);
}
void
acpi_os_unmap_memory(void *virt, acpi_size size)
{
void *freeme = (void *)trunc_page(virt);
if (!freeme)
{
acpi_os_printf("Nothing to unmap\n");
return;
}
munmap(freeme, size + size);
}
acpi_status
acpi_os_read_memory(acpi_physical_address phys_addr, u64 *value, u32 width)
{
void *virt_addr;
unsigned int size = width / 8;
acpi_status err = AE_BAD_ADDRESS;
if (!value)
return err;
virt_addr = acpi_os_map_memory (phys_addr, size);
if (!virt_addr)
return err;
if (!acpi_os_read_iomem (virt_addr, value, width))
err = AE_OK;
acpi_os_unmap_memory (virt_addr, size);
return err;
}
acpi_status
acpi_os_write_memory(acpi_physical_address phys_addr, u64 value, u32 width)
{
void *virt_addr;
unsigned int size = width / 8;
acpi_status err = AE_OK;
virt_addr = acpi_os_map_memory (phys_addr, size);
if (!virt_addr)
return AE_BAD_ADDRESS;
switch (width)
{
case 8:
*((volatile u8 *)virt_addr) = (u8)value;
break;
case 16:
*((volatile u16 *)virt_addr) = (u16)value;
break;
case 32:
*((volatile u32 *)virt_addr) = (u32)value;
break;
case 64:
*((volatile u64 *)virt_addr) = value;
break;
default:
acpi_os_printf("ACPI: Bad write memory width\n");
err = AE_BAD_ADDRESS;
break;
}
acpi_os_unmap_memory (virt_addr, size);
return err;
}
acpi_status
acpi_os_initialize(void)
{
/* Avoid giving ioperm to the PCI cfg registers
* since pci-arbiter controls these
*/
/* 0-0xcf7 */
if (ioperm(0, PCI_CFG1_START, 1))
{
acpi_os_printf("acpi: EPERM on ioperm 1\n");
return AE_ERROR;
}
/* 0xd00-0xffff */
if (ioperm(PCI_CFG1_END+1, 0x10000 - (PCI_CFG1_END+1), 1))
{
acpi_os_printf("acpi: EPERM on ioperm 2\n");
return AE_ERROR;
}
if (pci_init())
{
acpi_os_printf("acpi pci_init: Error realloc\n");
return AE_ERROR;
}
if (irqhelp_init())
{
acpi_os_printf("acpi irqhelp_init: failed\n");
// FIXME: When libirqhelp is fixed: return AE_ERROR;
}
return AE_OK;
}
acpi_status
acpi_os_terminate(void)
{
free(pci_devices);
acpi_unload_pci_bridges();
acpi_os_printf("Bye!\n");
return AE_OK;
}
acpi_status
acpi_os_signal(u32 function, void *info)
{
switch (function)
{
case ACPI_SIGNAL_FATAL:
acpi_os_printf("ACPI: Fatal opcode executed\n");
break;
case ACPI_SIGNAL_BREAKPOINT:
break;
default:
break;
}
return AE_OK;
}
acpi_status
acpi_os_predefined_override(const struct acpi_predefined_names *init_val,
acpi_string *new_val)
{
if (!init_val || !new_val)
return AE_BAD_PARAMETER;
*new_val = 0;
return AE_OK;
}
void
acpi_enter_sleep(int sleep_state)
{
acpi_hw_legacy_sleep (sleep_state);
}
static acpi_status
acpi_pci_link_count_all(struct acpi_resource *resource, void *context)
{
int *count = (int *)context;
(*count)++;
return AE_OK;
}
static acpi_status
acpi_pci_link_count_irq(struct acpi_resource *resource, void *context)
{
int *count = (int *)context;
if (!resource->length)
return AE_OK;
switch (resource->type)
{
case ACPI_RESOURCE_TYPE_IRQ:
{
struct acpi_resource_irq *irq_resource = &resource->data.irq;
if (!irq_resource || !irq_resource->interrupt_count || !irq_resource->interrupts[0])
return AE_OK;
(*count)++;
}
break;
case ACPI_RESOURCE_TYPE_EXTENDED_IRQ:
{
struct acpi_resource_extended_irq *irq_resource = &resource->data.extended_irq;
if (!irq_resource || !irq_resource->interrupt_count || !irq_resource->interrupts[0])
return AE_OK;
(*count)++;
}
break;
default:
break;
}
return AE_OK;
}
static acpi_status
acpi_pci_link_subset_last_irq(struct acpi_resource *resource, void *context)
{
struct acpi_resource *res = (struct acpi_resource *)context;
switch (resource->type)
{
case ACPI_RESOURCE_TYPE_IRQ:
{
struct acpi_resource_irq *irq_resource = &resource->data.irq;
if (!irq_resource || !irq_resource->interrupt_count || !irq_resource->interrupts[0])
return AE_OK;
*res = *resource;
return AE_OK;
}
break;
case ACPI_RESOURCE_TYPE_EXTENDED_IRQ:
{
struct acpi_resource_extended_irq *irq_resource = &resource->data.extended_irq;
if (!irq_resource || !irq_resource->interrupt_count || !irq_resource->interrupts[0])
return AE_OK;
*res = *resource;
return AE_OK;
}
break;
default:
break;
}
return AE_OK;
}
static acpi_status
acpi_pci_link_get_last_valid_irq(struct acpi_resource *resource, void *context)
{
int *irq = (int *)context;
if (!resource->length)
{
acpi_os_printf("Empty resource\n");
return AE_OK;
}
switch (resource->type)
{
case ACPI_RESOURCE_TYPE_IRQ:
{
struct acpi_resource_irq *irq_resource = &resource->data.irq;
if (!irq_resource || !irq_resource->interrupt_count || !irq_resource->interrupts[0])
{
// no IRQ# bits set generally w/ _STA disabled
acpi_os_printf("Empty _CRS IRQ resource\n");
return AE_OK;
}
*irq = irq_resource->interrupts[0];
}
break;
case ACPI_RESOURCE_TYPE_EXTENDED_IRQ:
{
struct acpi_resource_extended_irq *irq_resource = &resource->data.extended_irq;
if (!irq_resource || !irq_resource->interrupt_count || !irq_resource->interrupts[0])
{
// extended IRQ descriptors should return at least 1 IRQ
acpi_os_printf("Empty _CRS EXT IRQ resource\n");
return AE_OK;
}
*irq = irq_resource->interrupts[0];
}
break;
case ACPI_RESOURCE_TYPE_END_TAG:
return AE_OK;
break;
default:
acpi_os_printf("Not IRQ resource\n");
return AE_OK;
}
acpi_os_printf("Found IRQ resource %d\n", *irq);
return AE_OK;
}
static int
acpi_pci_get_device_pin(uint16_t bus, uint16_t dev, uint16_t func)
{
struct acpi_pci_id pci_id = {
.segment = 0, .bus = bus, .device = dev, .function = func
};
u64 pci_pin;
/* cast to int is safe having read only 8 bits */
return (acpi_os_read_pci_configuration(&pci_id, PCI_INTERRUPT_PIN, &pci_pin, 8)
? -1
: (int)pci_pin);
}
static int
acpi_get_irq_from_bus(acpi_handle handle,
uint16_t dev, uint16_t func, int pci_pin)
{
struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
struct acpi_buffer srs_buffer = { ACPI_ALLOCATE_BUFFER, NULL };
struct acpi_pci_routing_table *entry;
struct acpi_resource *res;
int srs_count;
acpi_handle prt = NULL;
acpi_handle lnk = NULL;
acpi_status err = AE_OK;
u16 prt_dev, prt_func;
err = acpi_get_handle(handle, METHOD_NAME__PRT, &prt);
if (ACPI_FAILURE(err))
return -EIO;
err = acpi_evaluate_object(prt, NULL, NULL, &buffer);
if (ACPI_FAILURE(err))
return -EIO;
err = acpi_get_irq_routing_table(handle, &buffer);
if (ACPI_FAILURE(err))
return -EIO;
entry = ACPI_CAST_PTR(struct acpi_pci_routing_table, ACPI_CAST_PTR(u8, buffer.pointer));
while (entry && (entry->length > 0))
{
/* Already applies to the bus of the device */
prt_dev = (entry->address >> 16) & 0xffff;
prt_func = entry->address & 0xffff;
if ((prt_dev == dev) && (prt_func == 0xffff)
/* PCI configuration enumerates pin in use as 1..4 whereas libacpica uses 0..3 */
&& (entry->pin == (pci_pin - 1)))
{
if (entry->source[0])
{
int crs_count = 0;
int prs_count_irq = 0;
int prs_count_all = 0;
int irq = -1;
/* Dynamic:
* - source field specifies PCI interrupt LNK
* - source_index specifies which resource descriptor in
* resource template of LNK to allocate this interrupt
*/
err = acpi_get_handle(ACPI_ROOT_OBJECT, entry->source, &lnk);
if (ACPI_FAILURE(err))
return -EIO;
if (!lnk)
{
acpi_os_printf("Error invalid lnk\n");
return -EIO;
}
err = acpi_walk_resources(lnk, "_CRS", acpi_pci_link_count_irq, &crs_count);
if (ACPI_FAILURE(err) || !crs_count)
acpi_os_printf("Warning: missing _CRS\n");
acpi_walk_resources(lnk, "_PRS", acpi_pci_link_count_all, &prs_count_all);
err = acpi_walk_resources(lnk, "_PRS", acpi_pci_link_count_irq, &prs_count_irq);
if (ACPI_FAILURE(err))
{
if (crs_count == 0)
{
acpi_os_printf("Error: missing _PRS when needed\n");
return -EIO;
}
}
if (crs_count > 0)
{
irq = 0;
err = acpi_walk_resources(lnk, "_CRS", acpi_pci_link_get_last_valid_irq, &irq);
if (ACPI_FAILURE(err) || !irq)
{
acpi_os_printf("Error walk_resources _CRS\n");
return -EIO;
}
err = acpi_get_current_resources(lnk, &srs_buffer);
if (ACPI_FAILURE(err))
{
acpi_os_printf("Error getting _CRS\n");
return -EIO;
}
err = acpi_set_current_resources(lnk, &srs_buffer);
if (ACPI_FAILURE(err))
{
acpi_os_printf("Error setting _SRS\n");
return -EIO;
}
/* irq += (irq > LEGACY_ISA_IRQS) ? 0 : PCI_IRQ_START; */
acpi_os_printf("Final irq %d\n", irq);
return irq;
}
else if (prs_count_irq > 0)
{
irq = 0;
err = acpi_walk_resources(lnk, "_PRS", acpi_pci_link_get_last_valid_irq, &irq);
if (ACPI_FAILURE(err) || !irq)
{
acpi_os_printf("Error walk_resources _PRS\n");
return -EIO;
}
if (irq > 16)
{
acpi_os_printf("Error IRQ > 16, not implemented\n");
return -EIO;
}
/* Found a possible irq, but need to call _SRS to set it */
/* IRQ resource + end tagged resource */
srs_count = 2;
res = (struct acpi_resource *)calloc(1, srs_count * sizeof(struct acpi_resource));
err = acpi_walk_resources(lnk, "_PRS", acpi_pci_link_subset_last_irq, res);
if (err)
{
acpi_os_printf("Error _PRS cannot be read\n");
free(res);
return -EIO;
}
/* Patch the _PRS to use for _SRS */
srs_buffer.length = sizeof(struct acpi_resource) + 1;
srs_buffer.pointer = res;
switch (res->type)
{
case ACPI_RESOURCE_TYPE_IRQ:
res->data.irq.interrupt_count = 1;
res->data.irq.interrupts[0] = irq;
break;
case ACPI_RESOURCE_TYPE_EXTENDED_IRQ:
res->data.extended_irq.interrupt_count = 1;
res->data.extended_irq.interrupts[0] = irq;
break;
}
res[1].type = ACPI_RESOURCE_TYPE_END_TAG;
res[1].length = sizeof(struct acpi_resource);
err = acpi_set_current_resources(lnk, &srs_buffer);
if (ACPI_FAILURE(err))
{
acpi_os_printf("Error setting _SRS\n");
free(res);
return -EIO;
}
free(res);
/* irq += (irq > LEGACY_ISA_IRQS) ? 0 : PCI_IRQ_START; */
acpi_os_printf("Final irq %d\n", irq);
return irq;
}
else
{
acpi_os_printf("Error: _CRS has empty IRQ resources\n");
return -EIO;
}
}
else
{
/* Static:
* - source field is zero
* - source_index specifies IRQ value hardwired to
* interrupt inputs on controller
*/
return entry->source_index;
}
}
entry = ACPI_ADD_PTR(struct acpi_pci_routing_table, entry, entry->length);
}
return -EIO;
}
static acpi_pci_bridge_t*
acpi_find_pci_bridge(int bus)
{
if (bus == 0)
return NULL;
acpi_pci_bridge_t* bridge = acpi_pci_bridges;
while (bridge != NULL)
{
if (bridge->down_bus == bus)
return bridge;
bridge = bridge->next;
}
return NULL;
}
int
acpi_get_irq_number(uint16_t bus, uint16_t dev, uint16_t func)
{
int pci_pin = acpi_pci_get_device_pin(bus, dev, func);
/* Fail if the device pin used is unknown (0) */
if (pci_pin <= 0)
return -EIO;
acpi_pci_bridge_t* bridge = acpi_find_pci_bridge(bus);
while (bridge != NULL)
{
int irq = acpi_get_irq_from_bus(bridge->handle, dev, func, pci_pin);
if (irq >= 0)
return irq;
/* Translate the downstream INTx to the upstream one */
pci_pin = ((pci_pin + dev) % 4);
bus = bridge->up_bus;
bridge = acpi_find_pci_bridge(bus);
}
acpi_handle handle;
acpi_status err = acpi_get_handle(ACPI_ROOT_OBJECT, "\\_SB.PCI0", &handle);
if (ACPI_FAILURE(err))
return -EIO;
return acpi_get_irq_from_bus(handle, dev, func, pci_pin);
}
#define IRQGETPICMODE 0
# define ACPI_PICMODE_PIC 0
# define ACPI_PICMODE_APIC 1
# define ACPI_PICMODE_SAPIC 2
/* Fetch the active interrupt mode from GNU mach */
static error_t
acpi_required_pic_mode (int* pic_mode)
{
mach_port_t devices = MACH_PORT_NULL, irq;
mach_msg_type_number_t cnt = 0;
error_t err = get_privileged_ports(NULL, &devices);
if (err)
return err;
if (devices == MACH_PORT_NULL)
return ENODEV;
err = device_open(devices, D_READ, "irq", &irq);
if (!err)
{
/* Determine if the IRQGETPICMODE feature is available in the
running kernel by specifying a NULL array for 'status'. If
the feature is available then MIG_ARRAY_TOO_LARGE is returned
as the returned status cannot fit in the result.
*/
if (device_get_status(irq, IRQGETPICMODE, NULL, &cnt)
!= MIG_ARRAY_TOO_LARGE)
err = ENOSYS;
else
{
cnt = 1;
err = device_get_status(irq, IRQGETPICMODE, pic_mode, &cnt);
}
device_close(irq);
}
mach_port_deallocate (mach_task_self (), devices);
return err;
}
static error_t
acpi_set_pic_mode (int pic_mode)
{
struct acpi_object_list arg;
union acpi_object obj;
if (pic_mode < ACPI_PICMODE_PIC || pic_mode > ACPI_PICMODE_SAPIC)
return EINVAL;
obj.type = ACPI_TYPE_INTEGER;
obj.integer.value = pic_mode;
arg.count = 1;
arg.pointer = &obj;
acpi_handle handle = ACPI_ROOT_OBJECT;
acpi_status rv = acpi_evaluate_object(handle, "\\_PIC", &arg, NULL);
/* A failure of AE_NOT_FOUND means the \_PIC method is not supported.
The default for the system in this case is ACPI_PICMODE_PIC, so
it is only a concern if an attempt is made to set a different mode.
*/
return (ACPI_FAILURE(rv)
&& (rv != AE_NOT_FOUND || (pic_mode != ACPI_PICMODE_PIC))
? EIO
: 0);
}
static error_t
acpi_configure_pic_mode (void)
{
int pic_mode = -1;
error_t err = acpi_required_pic_mode (&pic_mode);
if (err)
{
acpi_os_printf("Cannot determine PIC mode: %s\n", strerror(err));
return err;
}
err = acpi_set_pic_mode (pic_mode);
if (err)
acpi_os_printf("Failed to set ACPI PIC mode: %s\n", strerror(err));
return err;
}
static acpi_status
acpi_pci_bridge_add(acpi_handle handle, uint8_t up_bus, uint8_t down_bus)
{
acpi_pci_bridge_t* bridge =
(acpi_pci_bridge_t*)malloc(sizeof(acpi_pci_bridge_t));
if (bridge == NULL)
return AE_NO_MEMORY;
bridge->handle = handle;
bridge->up_bus = up_bus;
bridge->down_bus = down_bus;
bridge->next = acpi_pci_bridges;
acpi_pci_bridges = bridge;
return AE_OK;
}
struct acpi_find_bus_context
{
int is_bridge; /* Is this level a bridge or not */
uint8_t down_bus; /* The downstream bus num if a bridge */
struct acpi_find_bus_context* parent;
};
static acpi_status
acpi_find_busses_down(acpi_handle handle, uint32_t level,
void *context_arg, void **status)
{
struct acpi_find_bus_context** context =
(struct acpi_find_bus_context**)context_arg;
struct acpi_device_info *devinfo;
acpi_status rv = acpi_get_object_info(handle, &devinfo);
if (ACPI_FAILURE(rv))
return rv;
struct acpi_find_bus_context* device_context =
(struct acpi_find_bus_context*)malloc(sizeof(struct acpi_find_bus_context));
if (device_context == NULL)
return AE_NO_MEMORY;
device_context->is_bridge = 0;
device_context->down_bus = 0;
device_context->parent = *context;
*context = device_context;
if (devinfo->flags & ACPI_PCI_ROOT_BRIDGE)
{
device_context->is_bridge = 1;
}
else if (device_context->parent->is_bridge && (devinfo->valid & ACPI_VALID_ADR))
{
uint8_t bus = device_context->parent->down_bus;
uint32_t devfunc = ACPI_LODWORD(devinfo->address);
uint16_t dev = ACPI_HIWORD(devfunc);
uint16_t func = ACPI_LOWORD(devfunc);
if (func <= 0x7 && dev <= 0x1f)
{
struct acpi_pci_id pci_id =
{
.segment = 0, .bus = bus, .device = dev, .function = func
};
u64 val;
/* Some ACPI entries don't refer to actual devices */
if (acpi_os_read_pci_configuration(&pci_id, PCI_HDR_TYPE_REG, &val, 8))
goto exit;
if ((val & 0x7f) != PCI_HDR_TYPE_PPB)
goto exit;
if (acpi_os_read_pci_configuration(&pci_id, PCI_PPB_SECBUS_REG, &val, 8))
{
acpi_os_printf("Failed to find secondary bus for: %x:%x:%x\n", bus, dev, func);
rv = AE_IO_ERROR;
goto exit;
}
/* Only 8 bits was read from the config. space so cast to uint8_t is safe */
device_context->down_bus = (uint8_t)val;
device_context->is_bridge = 1;
rv = acpi_pci_bridge_add(handle, bus, device_context->down_bus);
}
}
exit:
ACPI_FREE(devinfo);
return rv;
}
static acpi_status
acpi_find_busses_up(acpi_handle handle, uint32_t level,
void *context_arg, void **status)
{
struct acpi_find_bus_context** context =
(struct acpi_find_bus_context**)context_arg;
struct acpi_find_bus_context* entry = *context;
*context = entry->parent;
free(entry);
return AE_OK;
}
static acpi_status
acpi_load_pci_bridges(void)
{
struct acpi_find_bus_context head_context =
{
.is_bridge = 0,
.down_bus = 0,
.parent = NULL
};
struct acpi_find_bus_context* context = &head_context;
acpi_status rv = acpi_walk_namespace
(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, UINT_MAX,
acpi_find_busses_down, acpi_find_busses_up, &context, NULL);
/* There should only be anything to free here in error cases */
while (context != &head_context)
{
struct acpi_find_bus_context* entry = context;
context = context->parent;
free(entry);
}
return rv;
}
void acpi_init(void)
{
acpi_status err;
// Hack to increase verbosity except parsing AML
//acpi_dbg_level = (ACPI_LV_ALL) & ~(ACPI_LV_FUNCTIONS);
err = acpi_initialize_tables (initial_tables, ACPI_MAX_TABLES, 0);
if (ACPI_FAILURE (err))
goto die;
err = acpi_initialize_subsystem ();
if (ACPI_FAILURE (err))
goto die;
err = acpi_reallocate_root_table ();
if (ACPI_FAILURE (err))
goto die;
err = acpi_load_tables ();
if (ACPI_FAILURE (err))
goto die;
err = acpi_load_pci_bridges();
if (ACPI_FAILURE (err))
goto die;
err = acpi_configure_pic_mode();
if (err)
goto die;
err = acpi_enable_subsystem (ACPI_FULL_INITIALIZATION);
if (ACPI_FAILURE (err))
goto die;
err = acpi_initialize_objects (ACPI_FULL_INITIALIZATION);
if (ACPI_FAILURE (err))
goto die;
acpi_os_printf("PASS!\n");
return;
die:
acpi_os_printf("OUCH!\n");
}