/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*
* Copyright 2016, Joyent, Inc.
*/
/*
* Copyright (c) 2009-2010, Intel Corporation.
* All rights reserved.
*/
#include <sys/types.h>
#include <sys/cmn_err.h>
#include <sys/sysmacros.h>
#include <sys/sunddi.h>
#include <sys/sunndi.h>
#include <sys/acpi/acpi.h>
#include <sys/acpica.h>
#include <sys/acpidev.h>
#include <sys/acpidev_rsc.h>
#include <sys/acpidev_impl.h>
#define ACPIDEV_RES_INIT_ITEMS 8
#define ACPIDEV_RES_INCR_ITEMS 8
/* Data structure to hold parsed resources during walking. */
struct acpidev_resource_handle {
boolean_t acpidev_consumer;
int acpidev_reg_count;
int acpidev_reg_max;
acpidev_phys_spec_t *acpidev_regp;
acpidev_phys_spec_t acpidev_regs[ACPIDEV_RES_INIT_ITEMS];
int acpidev_range_count;
int acpidev_range_max;
acpidev_ranges_t *acpidev_rangep;
acpidev_ranges_t acpidev_ranges[ACPIDEV_RES_INIT_ITEMS];
int acpidev_bus_count;
int acpidev_bus_max;
acpidev_bus_range_t *acpidev_busp;
acpidev_bus_range_t acpidev_buses[ACPIDEV_RES_INIT_ITEMS];
int acpidev_irq_count;
int acpidev_irqp[ACPIDEV_RES_IRQ_MAX];
int acpidev_dma_count;
int acpidev_dmap[ACPIDEV_RES_DMA_MAX];
};
acpidev_resource_handle_t
acpidev_resource_handle_alloc(boolean_t consumer)
{
acpidev_resource_handle_t rhdl;
rhdl = kmem_zalloc(sizeof (*rhdl), KM_SLEEP);
rhdl->acpidev_consumer = consumer;
rhdl->acpidev_reg_max = ACPIDEV_RES_INIT_ITEMS;
rhdl->acpidev_regp = rhdl->acpidev_regs;
rhdl->acpidev_range_max = ACPIDEV_RES_INIT_ITEMS;
rhdl->acpidev_rangep = rhdl->acpidev_ranges;
rhdl->acpidev_bus_max = ACPIDEV_RES_INIT_ITEMS;
rhdl->acpidev_busp = rhdl->acpidev_buses;
return (rhdl);
}
void
acpidev_resource_handle_free(acpidev_resource_handle_t rhdl)
{
size_t sz;
ASSERT(rhdl != NULL);
if (rhdl != NULL) {
if (rhdl->acpidev_regp != rhdl->acpidev_regs) {
sz = sizeof (acpidev_phys_spec_t) *
rhdl->acpidev_reg_max;
kmem_free(rhdl->acpidev_regp, sz);
}
if (rhdl->acpidev_rangep != rhdl->acpidev_ranges) {
sz = sizeof (acpidev_ranges_t) *
rhdl->acpidev_range_max;
kmem_free(rhdl->acpidev_rangep, sz);
}
if (rhdl->acpidev_busp != rhdl->acpidev_buses) {
sz = sizeof (acpidev_bus_range_t) *
rhdl->acpidev_bus_max;
kmem_free(rhdl->acpidev_busp, sz);
}
kmem_free(rhdl, sizeof (struct acpidev_resource_handle));
}
}
static void
acpidev_resource_handle_grow(acpidev_resource_handle_t rhdl)
{
size_t sz;
if (rhdl->acpidev_reg_count == rhdl->acpidev_reg_max) {
acpidev_phys_spec_t *regp;
/* Prefer linear incremental here. */
rhdl->acpidev_reg_max += ACPIDEV_RES_INCR_ITEMS;
sz = sizeof (*regp) * rhdl->acpidev_reg_max;
regp = kmem_zalloc(sz, KM_SLEEP);
sz = sizeof (*regp) * rhdl->acpidev_reg_count;
bcopy(rhdl->acpidev_regp, regp, sz);
if (rhdl->acpidev_regp != rhdl->acpidev_regs) {
kmem_free(rhdl->acpidev_regp, sz);
}
rhdl->acpidev_regp = regp;
}
if (rhdl->acpidev_range_count == rhdl->acpidev_range_max) {
acpidev_ranges_t *rngp;
/* Prefer linear incremental here. */
rhdl->acpidev_range_max += ACPIDEV_RES_INCR_ITEMS;
sz = sizeof (*rngp) * rhdl->acpidev_range_max;
rngp = kmem_zalloc(sz, KM_SLEEP);
sz = sizeof (*rngp) * rhdl->acpidev_range_count;
bcopy(rhdl->acpidev_rangep, rngp, sz);
if (rhdl->acpidev_rangep != rhdl->acpidev_ranges) {
kmem_free(rhdl->acpidev_rangep, sz);
}
rhdl->acpidev_rangep = rngp;
}
if (rhdl->acpidev_bus_count == rhdl->acpidev_bus_max) {
acpidev_bus_range_t *busp;
/* Prefer linear incremental here. */
rhdl->acpidev_bus_max += ACPIDEV_RES_INCR_ITEMS;
sz = sizeof (*busp) * rhdl->acpidev_bus_max;
busp = kmem_zalloc(sz, KM_SLEEP);
sz = sizeof (*busp) * rhdl->acpidev_bus_count;
bcopy(rhdl->acpidev_busp, busp, sz);
if (rhdl->acpidev_busp != rhdl->acpidev_buses) {
kmem_free(rhdl->acpidev_busp, sz);
}
rhdl->acpidev_busp = busp;
}
}
ACPI_STATUS
acpidev_resource_insert_reg(acpidev_resource_handle_t rhdl,
acpidev_regspec_t *regp)
{
ASSERT(rhdl != NULL);
ASSERT(regp != NULL);
if (rhdl->acpidev_reg_count >= rhdl->acpidev_reg_max) {
acpidev_resource_handle_grow(rhdl);
}
ASSERT(rhdl->acpidev_reg_count < rhdl->acpidev_reg_max);
rhdl->acpidev_regp[rhdl->acpidev_reg_count] = *regp;
rhdl->acpidev_reg_count++;
return (AE_OK);
}
ACPI_STATUS
acpidev_resource_get_regs(acpidev_resource_handle_t rhdl,
uint_t mask, uint_t value, acpidev_regspec_t *regp, uint_t *cntp)
{
uint_t i, j;
ASSERT(rhdl != NULL);
ASSERT(cntp != NULL);
if (rhdl == NULL || cntp == NULL || (regp == NULL && *cntp != 0)) {
return (AE_BAD_PARAMETER);
}
for (i = 0, j = 0; i < rhdl->acpidev_reg_count; i++) {
if ((rhdl->acpidev_regp[i].phys_hi & mask) == value) {
if (j < *cntp) {
regp[j] = rhdl->acpidev_regp[i];
}
j++;
}
}
if (j >= *cntp) {
*cntp = j;
return (AE_LIMIT);
} else {
*cntp = j;
return (AE_OK);
}
}
uint_t
acpidev_resource_get_reg_count(acpidev_resource_handle_t rhdl,
uint_t mask, uint_t value)
{
uint_t i, j;
ASSERT(rhdl != NULL);
for (i = 0, j = 0; i < rhdl->acpidev_reg_count; i++) {
if ((rhdl->acpidev_regp[i].phys_hi & mask) == value) {
j++;
}
}
return (j);
}
ACPI_STATUS
acpidev_resource_insert_range(acpidev_resource_handle_t rhdl,
acpidev_ranges_t *rangep)
{
ASSERT(rhdl != NULL);
ASSERT(rangep != NULL);
if (rhdl->acpidev_range_count >= rhdl->acpidev_range_max) {
acpidev_resource_handle_grow(rhdl);
}
ASSERT(rhdl->acpidev_range_count < rhdl->acpidev_range_max);
rhdl->acpidev_rangep[rhdl->acpidev_range_count] = *rangep;
rhdl->acpidev_range_count++;
return (AE_OK);
}
ACPI_STATUS
acpidev_resource_get_ranges(acpidev_resource_handle_t rhdl,
uint_t mask, uint_t value, acpidev_ranges_t *rangep, uint_t *cntp)
{
uint_t i, j;
ASSERT(rhdl != NULL);
ASSERT(cntp != NULL);
if (rhdl == NULL || cntp == NULL || (rangep == NULL && *cntp != 0)) {
return (AE_BAD_PARAMETER);
}
for (i = 0, j = 0; i < rhdl->acpidev_range_count; i++) {
if ((rhdl->acpidev_rangep[i].child_hi & mask) == value) {
if (j < *cntp) {
rangep[j] = rhdl->acpidev_rangep[i];
}
j++;
}
}
if (j >= *cntp) {
*cntp = j;
return (AE_LIMIT);
} else {
*cntp = j;
return (AE_OK);
}
}
uint_t
acpidev_resource_get_range_count(acpidev_resource_handle_t rhdl,
uint_t mask, uint_t value)
{
uint_t i, j;
ASSERT(rhdl != NULL);
for (i = 0, j = 0; i < rhdl->acpidev_range_count; i++) {
if ((rhdl->acpidev_rangep[i].child_hi & mask) == value) {
j++;
}
}
return (j);
}
ACPI_STATUS
acpidev_resource_insert_bus(acpidev_resource_handle_t rhdl,
acpidev_bus_range_t *busp)
{
ASSERT(rhdl != NULL);
ASSERT(busp != NULL);
if (rhdl->acpidev_bus_count >= rhdl->acpidev_bus_max) {
acpidev_resource_handle_grow(rhdl);
}
ASSERT(rhdl->acpidev_bus_count < rhdl->acpidev_bus_max);
rhdl->acpidev_busp[rhdl->acpidev_bus_count] = *busp;
rhdl->acpidev_bus_count++;
return (AE_OK);
}
ACPI_STATUS
acpidev_resource_get_buses(acpidev_resource_handle_t rhdl,
acpidev_bus_range_t *busp, uint_t *cntp)
{
uint_t i, j;
ASSERT(rhdl != NULL);
ASSERT(cntp != NULL);
if (rhdl == NULL || cntp == NULL || (busp == NULL && *cntp != 0)) {
return (AE_BAD_PARAMETER);
}
for (i = 0, j = 0; i < rhdl->acpidev_bus_count; i++) {
if (j < *cntp) {
busp[j] = rhdl->acpidev_busp[i];
}
j++;
}
if (j >= *cntp) {
*cntp = j;
return (AE_LIMIT);
} else {
*cntp = j;
return (AE_OK);
}
}
uint_t
acpidev_resource_get_bus_count(acpidev_resource_handle_t rhdl)
{
ASSERT(rhdl != NULL);
return (rhdl->acpidev_bus_count);
}
ACPI_STATUS
acpidev_resource_insert_dma(acpidev_resource_handle_t rhdl, int dma)
{
ASSERT(rhdl != NULL);
if (rhdl->acpidev_dma_count >= ACPIDEV_RES_DMA_MAX) {
ACPIDEV_DEBUG(CE_WARN,
"!acpidev: too many DMA resources, max %u.",
ACPIDEV_RES_DMA_MAX);
return (AE_LIMIT);
}
rhdl->acpidev_dmap[rhdl->acpidev_dma_count] = dma;
rhdl->acpidev_dma_count++;
return (AE_OK);
}
ACPI_STATUS
acpidev_resource_get_dmas(acpidev_resource_handle_t rhdl,
uint_t *dmap, uint_t *cntp)
{
uint_t i, j;
ASSERT(rhdl != NULL);
ASSERT(cntp != NULL);
if (rhdl == NULL || cntp == NULL || (dmap == NULL && *cntp != 0)) {
return (AE_BAD_PARAMETER);
}
for (i = 0, j = 0; i < rhdl->acpidev_dma_count; i++) {
if (j < *cntp) {
dmap[j] = rhdl->acpidev_dmap[i];
}
j++;
}
if (j >= *cntp) {
*cntp = j;
return (AE_LIMIT);
} else {
*cntp = j;
return (AE_OK);
}
}
uint_t
acpidev_resource_get_dma_count(acpidev_resource_handle_t rhdl)
{
ASSERT(rhdl != NULL);
return (rhdl->acpidev_dma_count);
}
ACPI_STATUS
acpidev_resource_insert_irq(acpidev_resource_handle_t rhdl, int irq)
{
ASSERT(rhdl != NULL);
if (rhdl->acpidev_irq_count >= ACPIDEV_RES_IRQ_MAX) {
ACPIDEV_DEBUG(CE_WARN,
"!acpidev: too many IRQ resources, max %u.",
ACPIDEV_RES_IRQ_MAX);
return (AE_LIMIT);
}
rhdl->acpidev_irqp[rhdl->acpidev_irq_count] = irq;
rhdl->acpidev_irq_count++;
return (AE_OK);
}
ACPI_STATUS
acpidev_resource_get_irqs(acpidev_resource_handle_t rhdl,
uint_t *irqp, uint_t *cntp)
{
uint_t i, j;
ASSERT(rhdl != NULL);
ASSERT(cntp != NULL);
if (rhdl == NULL || cntp == NULL || (irqp == NULL && *cntp != 0)) {
return (AE_BAD_PARAMETER);
}
for (i = 0, j = 0; i < rhdl->acpidev_irq_count; i++) {
if (j < *cntp) {
irqp[j] = rhdl->acpidev_irqp[i];
}
j++;
}
if (j >= *cntp) {
*cntp = j;
return (AE_LIMIT);
} else {
*cntp = j;
return (AE_OK);
}
}
uint_t
acpidev_resource_get_irq_count(acpidev_resource_handle_t rhdl)
{
ASSERT(rhdl != NULL);
return (rhdl->acpidev_irq_count);
}
static ACPI_STATUS
acpidev_resource_address64(acpidev_resource_handle_t rhdl,
ACPI_RESOURCE_ADDRESS64 *addrp)
{
ACPI_STATUS rc = AE_OK;
uint_t high;
ASSERT(addrp != NULL && rhdl != NULL);
if (addrp->Address.AddressLength == 0) {
return (AE_OK);
}
switch (addrp->ResourceType) {
case ACPI_MEMORY_RANGE:
high = ACPIDEV_REG_TYPE_MEMORY;
if (addrp->Decode == ACPI_SUB_DECODE) {
high |= ACPIDEV_REG_SUB_DEC;
}
if (addrp->Info.Mem.Translation) {
high |= ACPIDEV_REG_TRANSLATED;
}
if (addrp->Info.Mem.Caching == ACPI_NON_CACHEABLE_MEMORY) {
high |= ACPIDEV_REG_MEM_COHERENT_NC;
} else if (addrp->Info.Mem.Caching == ACPI_CACHABLE_MEMORY) {
high |= ACPIDEV_REG_MEM_COHERENT_CA;
} else if (addrp->Info.Mem.Caching ==
ACPI_WRITE_COMBINING_MEMORY) {
high |= ACPIDEV_REG_MEM_COHERENT_WC;
} else if (addrp->Info.Mem.Caching ==
ACPI_PREFETCHABLE_MEMORY) {
high |= ACPIDEV_REG_MEM_COHERENT_PF;
} else {
ACPIDEV_DEBUG(CE_WARN,
"!acpidev: unknown memory caching type %u.",
addrp->Info.Mem.Caching);
rc = AE_ERROR;
break;
}
if (addrp->Info.Mem.WriteProtect == ACPI_READ_WRITE_MEMORY) {
high |= ACPIDEV_REG_MEM_WRITABLE;
}
/* Generate 'reg' for producer. */
if (addrp->ProducerConsumer == ACPI_CONSUMER &&
rhdl->acpidev_consumer == B_TRUE) {
acpidev_regspec_t reg;
reg.phys_hi = high;
reg.phys_mid = addrp->Address.Minimum >> 32;
reg.phys_low = addrp->Address.Minimum & 0xFFFFFFFF;
reg.size_hi = addrp->Address.AddressLength >> 32;
reg.size_low = addrp->Address.AddressLength &
0xFFFFFFFF;
rc = acpidev_resource_insert_reg(rhdl, &reg);
if (ACPI_FAILURE(rc)) {
ACPIDEV_DEBUG(CE_WARN, "!acpidev: failed to "
"insert regspec into resource handle.");
}
/* Generate 'ranges' for producer. */
} else if (addrp->ProducerConsumer == ACPI_PRODUCER &&
rhdl->acpidev_consumer == B_FALSE) {
uint64_t paddr;
acpidev_ranges_t range;
range.child_hi = high;
range.child_mid = addrp->Address.Minimum >> 32;
range.child_low = addrp->Address.Minimum & 0xFFFFFFFF;
/* It's IO on parent side if Translation is true. */
if (addrp->Info.Mem.Translation) {
range.parent_hi = ACPIDEV_REG_TYPE_IO;
} else {
range.parent_hi = high;
}
paddr = addrp->Address.Minimum +
addrp->Address.TranslationOffset;
range.parent_mid = paddr >> 32;
range.parent_low = paddr & 0xFFFFFFFF;
range.size_hi = addrp->Address.AddressLength >> 32;
range.size_low = addrp->Address.AddressLength &
0xFFFFFFFF;
rc = acpidev_resource_insert_range(rhdl, &range);
if (ACPI_FAILURE(rc)) {
ACPIDEV_DEBUG(CE_WARN, "!acpidev: failed to "
"insert range into resource handle.");
}
}
break;
case ACPI_IO_RANGE:
high = ACPIDEV_REG_TYPE_IO;
if (addrp->Decode == ACPI_SUB_DECODE) {
high |= ACPIDEV_REG_SUB_DEC;
}
if (addrp->Info.Io.Translation) {
high |= ACPIDEV_REG_TRANSLATED;
}
if (addrp->Info.Io.RangeType == ACPI_NON_ISA_ONLY_RANGES) {
high |= ACPIDEV_REG_IO_RANGE_NONISA;
} else if (addrp->Info.Io.RangeType == ACPI_ISA_ONLY_RANGES) {
high |= ACPIDEV_REG_IO_RANGE_ISA;
} else if (addrp->Info.Io.RangeType == ACPI_ENTIRE_RANGE) {
high |= ACPIDEV_REG_IO_RANGE_FULL;
} else {
ACPIDEV_DEBUG(CE_WARN,
"!acpidev: unknown IO range type %u.",
addrp->Info.Io.RangeType);
rc = AE_ERROR;
break;
}
if (addrp->Info.Io.TranslationType == ACPI_SPARSE_TRANSLATION) {
high |= ACPIDEV_REG_IO_SPARSE;
}
/* Generate 'reg' for producer. */
if (addrp->ProducerConsumer == ACPI_CONSUMER &&
rhdl->acpidev_consumer == B_TRUE) {
acpidev_regspec_t reg;
reg.phys_hi = high;
reg.phys_mid = addrp->Address.Minimum >> 32;
reg.phys_low = addrp->Address.Minimum & 0xFFFFFFFF;
reg.size_hi = addrp->Address.AddressLength >> 32;
reg.size_low = addrp->Address.AddressLength &
0xFFFFFFFF;
rc = acpidev_resource_insert_reg(rhdl, &reg);
if (ACPI_FAILURE(rc)) {
ACPIDEV_DEBUG(CE_WARN, "!acpidev: failed to "
"insert regspec into resource handle.");
}
/* Generate 'ranges' for producer. */
} else if (addrp->ProducerConsumer == ACPI_PRODUCER &&
rhdl->acpidev_consumer == B_FALSE) {
uint64_t paddr;
acpidev_ranges_t range;
range.child_hi = high;
range.child_mid = addrp->Address.Minimum >> 32;
range.child_low = addrp->Address.Minimum & 0xFFFFFFFF;
/* It's Memory on parent side if Translation is true. */
if (addrp->Info.Io.Translation) {
range.parent_hi = ACPIDEV_REG_TYPE_MEMORY;
} else {
range.parent_hi = high;
}
paddr = addrp->Address.Minimum +
addrp->Address.TranslationOffset;
range.parent_mid = paddr >> 32;
range.parent_low = paddr & 0xFFFFFFFF;
range.size_hi = addrp->Address.AddressLength >> 32;
range.size_low = addrp->Address.AddressLength &
0xFFFFFFFF;
rc = acpidev_resource_insert_range(rhdl, &range);
if (ACPI_FAILURE(rc)) {
ACPIDEV_DEBUG(CE_WARN, "!acpidev: failed to "
"insert range into resource handle.");
}
}
break;
case ACPI_BUS_NUMBER_RANGE:
/* Only support producer of BUS. */
if (addrp->ProducerConsumer == ACPI_PRODUCER &&
rhdl->acpidev_consumer == B_FALSE) {
uint64_t end;
acpidev_bus_range_t bus;
end = addrp->Address.Minimum +
addrp->Address.AddressLength;
if (end < addrp->Address.Minimum || end > UINT_MAX) {
ACPIDEV_DEBUG(CE_WARN, "!acpidev: bus range "
"in ADDRESS64 is invalid.");
rc = AE_ERROR;
break;
}
bus.bus_start = addrp->Address.Minimum & 0xFFFFFFFF;
bus.bus_end = end & 0xFFFFFFFF;
ASSERT(bus.bus_start <= bus.bus_end);
rc = acpidev_resource_insert_bus(rhdl, &bus);
if (ACPI_FAILURE(rc)) {
ACPIDEV_DEBUG(CE_WARN, "!acpidev: failed to "
"insert bus range into resource handle.");
}
}
break;
default:
ACPIDEV_DEBUG(CE_WARN,
"!acpidev: unknown resource type %u in ADDRESS64.",
addrp->ResourceType);
rc = AE_BAD_PARAMETER;
}
return (rc);
}
static ACPI_STATUS
acpidev_resource_walk_producer(ACPI_RESOURCE *rscp, void *ctxp)
{
ACPI_STATUS rc = AE_OK;
acpidev_resource_handle_t rhdl;
ASSERT(ctxp != NULL);
rhdl = (acpidev_resource_handle_t)ctxp;
ASSERT(rhdl->acpidev_consumer == B_FALSE);
switch (rscp->Type) {
case ACPI_RESOURCE_TYPE_DMA:
case ACPI_RESOURCE_TYPE_IRQ:
case ACPI_RESOURCE_TYPE_EXTENDED_IRQ:
case ACPI_RESOURCE_TYPE_FIXED_IO:
case ACPI_RESOURCE_TYPE_MEMORY24:
case ACPI_RESOURCE_TYPE_MEMORY32:
case ACPI_RESOURCE_TYPE_FIXED_MEMORY32:
case ACPI_RESOURCE_TYPE_GENERIC_REGISTER:
case ACPI_RESOURCE_TYPE_VENDOR:
ACPIDEV_DEBUG(CE_NOTE,
"!acpidev: unsupported producer resource type %u, ignored.",
rscp->Type);
break;
case ACPI_RESOURCE_TYPE_IO:
{
acpidev_ranges_t range;
range.child_hi = ACPIDEV_REG_TYPE_IO;
range.child_hi |= ACPIDEV_REG_IO_RANGE_FULL;
if (rscp->Data.Io.IoDecode == ACPI_DECODE_16) {
range.child_hi |= ACPIDEV_REG_IO_DECODE16;
}
range.parent_hi = range.child_hi;
range.parent_mid = range.child_mid = 0;
range.parent_low = range.child_low = rscp->Data.Io.Minimum;
range.size_hi = 0;
range.size_low = rscp->Data.Io.AddressLength;
if ((uint64_t)range.child_low + range.size_low > UINT16_MAX) {
ACPIDEV_DEBUG(CE_WARN, "!acpidev: invalid IO record, "
"IO max is out of range.");
rc = AE_ERROR;
} else if (range.size_low != 0) {
rc = acpidev_resource_insert_range(rhdl, &range);
if (ACPI_FAILURE(rc)) {
ACPIDEV_DEBUG(CE_WARN, "!acpidev: failed to "
"insert range into resource handle.");
}
}
break;
}
case ACPI_RESOURCE_TYPE_ADDRESS16:
case ACPI_RESOURCE_TYPE_ADDRESS32:
case ACPI_RESOURCE_TYPE_ADDRESS64:
{
ACPI_RESOURCE_ADDRESS64 addr64;
if (rscp->Data.Address.ProducerConsumer != ACPI_PRODUCER) {
ACPIDEV_DEBUG(CE_NOTE, "!acpidev: producer encountered "
"a CONSUMER resource, ignored.");
} else if (ACPI_FAILURE(AcpiResourceToAddress64(rscp,
&addr64))) {
ACPIDEV_DEBUG(CE_WARN,
"!acpidev: failed to convert resource to ADDR64.");
} else if (ACPI_FAILURE(rc = acpidev_resource_address64(rhdl,
&addr64))) {
ACPIDEV_DEBUG(CE_WARN,
"!acpidev: failed to handle ADDRESS resource.");
}
break;
}
case ACPI_RESOURCE_TYPE_EXTENDED_ADDRESS64:
{
ACPI_RESOURCE_ADDRESS64 addr64;
if (rscp->Data.ExtAddress64.ProducerConsumer != ACPI_PRODUCER) {
ACPIDEV_DEBUG(CE_NOTE, "!acpidev: producer encountered "
"a CONSUMER resource, ignored.");
break;
}
*(ACPI_RESOURCE_ADDRESS *)&addr64 = rscp->Data.Address;
addr64.Address.Granularity =
rscp->Data.ExtAddress64.Address.Granularity;
addr64.Address.Minimum =
rscp->Data.ExtAddress64.Address.Minimum;
addr64.Address.Maximum =
rscp->Data.ExtAddress64.Address.Maximum;
addr64.Address.TranslationOffset =
rscp->Data.ExtAddress64.Address.TranslationOffset;
addr64.Address.AddressLength =
rscp->Data.ExtAddress64.Address.AddressLength;
if (ACPI_FAILURE(rc = acpidev_resource_address64(rhdl,
&addr64))) {
ACPIDEV_DEBUG(CE_WARN,
"!acpidev: failed to handle EXTADDRESS resource.");
}
break;
}
case ACPI_RESOURCE_TYPE_START_DEPENDENT:
case ACPI_RESOURCE_TYPE_END_DEPENDENT:
ACPIDEV_DEBUG(CE_NOTE, "!acpidev: producer encountered "
"START_DEPENDENT or END_DEPENDENT tag, ignored.");
break;
case ACPI_RESOURCE_TYPE_END_TAG:
/* Finish walking when we encounter END_TAG. */
rc = AE_CTRL_TERMINATE;
break;
default:
ACPIDEV_DEBUG(CE_NOTE,
"!acpidev: unknown ACPI resource type %u, ignored.",
rscp->Type);
break;
}
return (rc);
}
static ACPI_STATUS
acpidev_resource_walk_consumer(ACPI_RESOURCE *rscp, void *ctxp)
{
ACPI_STATUS rc = AE_OK;
acpidev_resource_handle_t rhdl;
ASSERT(ctxp != NULL);
rhdl = (acpidev_resource_handle_t)ctxp;
ASSERT(rhdl->acpidev_consumer == B_TRUE);
switch (rscp->Type) {
case ACPI_RESOURCE_TYPE_MEMORY24:
case ACPI_RESOURCE_TYPE_GENERIC_REGISTER:
case ACPI_RESOURCE_TYPE_VENDOR:
ACPIDEV_DEBUG(CE_NOTE,
"!acpidev: unsupported consumer resource type %u, ignored.",
rscp->Type);
break;
case ACPI_RESOURCE_TYPE_EXTENDED_IRQ:
{
int i;
if (rscp->Data.ExtendedIrq.ProducerConsumer != ACPI_CONSUMER) {
ACPIDEV_DEBUG(CE_NOTE, "!acpidev: consumer encountered "
"a PRODUCER resource, ignored.");
break;
}
for (i = 0; i < rscp->Data.ExtendedIrq.InterruptCount; i++) {
if (ACPI_SUCCESS(acpidev_resource_insert_irq(rhdl,
rscp->Data.ExtendedIrq.Interrupts[i]))) {
continue;
}
ACPIDEV_DEBUG(CE_WARN, "!acpidev: failed to insert"
"Extended IRQ into resource handle.");
rc = AE_ERROR;
break;
}
break;
}
case ACPI_RESOURCE_TYPE_IRQ:
{
int i;
for (i = 0; i < rscp->Data.Irq.InterruptCount; i++) {
if (ACPI_SUCCESS(acpidev_resource_insert_irq(rhdl,
rscp->Data.Irq.Interrupts[i]))) {
continue;
}
ACPIDEV_DEBUG(CE_WARN, "!acpidev: failed to insert"
"IRQ into resource handle.");
rc = AE_ERROR;
break;
}
break;
}
case ACPI_RESOURCE_TYPE_DMA:
{
int i;
for (i = 0; i < rscp->Data.Dma.ChannelCount; i++) {
if (ACPI_SUCCESS(acpidev_resource_insert_dma(rhdl,
rscp->Data.Dma.Channels[i]))) {
continue;
}
ACPIDEV_DEBUG(CE_WARN, "!acpidev: failed to insert"
"dma into resource handle.");
rc = AE_ERROR;
break;
}
break;
}
case ACPI_RESOURCE_TYPE_IO:
case ACPI_RESOURCE_TYPE_FIXED_IO:
{
acpidev_regspec_t reg;
reg.phys_hi = ACPIDEV_REG_TYPE_IO;
reg.phys_hi |= ACPIDEV_REG_IO_RANGE_FULL;
if (rscp->Type == ACPI_RESOURCE_TYPE_IO) {
if (rscp->Data.Io.IoDecode == ACPI_DECODE_16) {
reg.phys_hi |= ACPIDEV_REG_IO_DECODE16;
}
reg.phys_low = rscp->Data.Io.Minimum;
reg.size_low = rscp->Data.Io.AddressLength;
} else {
reg.phys_hi |= ACPIDEV_REG_IO_DECODE16;
reg.phys_low = rscp->Data.FixedIo.Address;
reg.size_low = rscp->Data.FixedIo.AddressLength;
}
reg.phys_mid = 0;
reg.size_hi = 0;
if ((uint64_t)reg.phys_low + reg.size_low > UINT16_MAX) {
ACPIDEV_DEBUG(CE_WARN, "!acpidev: invalid IO/FIXEDIO "
"record, IO max is out of range.");
rc = AE_ERROR;
} else if (reg.size_low != 0) {
rc = acpidev_resource_insert_reg(rhdl, &reg);
if (ACPI_FAILURE(rc)) {
ACPIDEV_DEBUG(CE_WARN, "!acpidev: failed to "
"insert reg into resource handle.");
}
}
break;
}
case ACPI_RESOURCE_TYPE_MEMORY32:
case ACPI_RESOURCE_TYPE_FIXED_MEMORY32:
{
acpidev_regspec_t reg;
reg.phys_hi = ACPIDEV_REG_TYPE_MEMORY;
reg.phys_hi |= ACPIDEV_REG_MEM_COHERENT_CA;
if (rscp->Type == ACPI_RESOURCE_TYPE_MEMORY32) {
if (rscp->Data.Memory32.WriteProtect ==
ACPI_READ_WRITE_MEMORY) {
reg.phys_hi |= ACPIDEV_REG_MEM_WRITABLE;
}
reg.phys_low = rscp->Data.Memory32.Minimum;
reg.size_low = rscp->Data.Memory32.AddressLength;
} else {
if (rscp->Data.FixedMemory32.WriteProtect ==
ACPI_READ_WRITE_MEMORY) {
reg.phys_hi |= ACPIDEV_REG_MEM_WRITABLE;
}
reg.phys_low = rscp->Data.FixedMemory32.Address;
reg.size_low = rscp->Data.FixedMemory32.AddressLength;
}
reg.phys_mid = 0;
reg.size_hi = 0;
if ((uint64_t)reg.phys_low + reg.size_low > UINT32_MAX) {
ACPIDEV_DEBUG(CE_WARN,
"!acpidev: invalid MEMORY32/FIXEDMEMORY32 record, "
"memory max is out of range.");
rc = AE_ERROR;
} else if (reg.size_low != 0) {
rc = acpidev_resource_insert_reg(rhdl, &reg);
if (ACPI_FAILURE(rc)) {
ACPIDEV_DEBUG(CE_WARN, "!acpidev: failed to "
"insert reg into resource handle.");
}
}
break;
}
case ACPI_RESOURCE_TYPE_ADDRESS16:
case ACPI_RESOURCE_TYPE_ADDRESS32:
case ACPI_RESOURCE_TYPE_ADDRESS64:
{
ACPI_RESOURCE_ADDRESS64 addr64;
if (rscp->Data.Address.ProducerConsumer != ACPI_CONSUMER) {
ACPIDEV_DEBUG(CE_NOTE, "!acpidev: consumer encountered "
"a PRODUCER resource, ignored.");
} else if (ACPI_FAILURE(AcpiResourceToAddress64(rscp,
&addr64))) {
ACPIDEV_DEBUG(CE_WARN,
"!acpidev: failed to convert resource to ADDR64.");
} else if (ACPI_FAILURE(rc = acpidev_resource_address64(rhdl,
&addr64))) {
ACPIDEV_DEBUG(CE_WARN,
"!acpidev: failed to handle ADDRESS resource.");
}
break;
}
case ACPI_RESOURCE_TYPE_EXTENDED_ADDRESS64:
{
ACPI_RESOURCE_ADDRESS64 addr64;
if (rscp->Data.ExtAddress64.ProducerConsumer != ACPI_CONSUMER) {
ACPIDEV_DEBUG(CE_NOTE, "!acpidev: consumer encountered "
"a PRODUCER resource, ignored.");
break;
}
*(ACPI_RESOURCE_ADDRESS *)&addr64 = rscp->Data.Address;
addr64.Address.Granularity =
rscp->Data.ExtAddress64.Address.Granularity;
addr64.Address.Minimum =
rscp->Data.ExtAddress64.Address.Minimum;
addr64.Address.Maximum =
rscp->Data.ExtAddress64.Address.Maximum;
addr64.Address.TranslationOffset =
rscp->Data.ExtAddress64.Address.TranslationOffset;
addr64.Address.AddressLength =
rscp->Data.ExtAddress64.Address.AddressLength;
if (ACPI_FAILURE(rc = acpidev_resource_address64(rhdl,
&addr64))) {
ACPIDEV_DEBUG(CE_WARN,
"!acpidev: failed to handle EXTADDRESS resource.");
}
break;
}
case ACPI_RESOURCE_TYPE_START_DEPENDENT:
case ACPI_RESOURCE_TYPE_END_DEPENDENT:
ACPIDEV_DEBUG(CE_NOTE, "!acpidev: consumer encountered "
"START_DEPENDENT or END_DEPENDENT tag, ignored.");
break;
case ACPI_RESOURCE_TYPE_END_TAG:
/* Finish walking when we encounter END_TAG. */
rc = AE_CTRL_TERMINATE;
break;
default:
ACPIDEV_DEBUG(CE_NOTE,
"!acpidev: unknown ACPI resource type %u, ignored.",
rscp->Type);
break;
}
return (rc);
}
ACPI_STATUS
acpidev_resource_walk(ACPI_HANDLE hdl, char *method,
boolean_t consumer, acpidev_resource_handle_t *rhdlp)
{
ACPI_STATUS rc = AE_OK;
ACPI_HANDLE mhdl = NULL;
acpidev_resource_handle_t rhdl = NULL;
ASSERT(hdl != NULL);
ASSERT(method != NULL);
ASSERT(rhdlp != NULL);
if (hdl == NULL) {
ACPIDEV_DEBUG(CE_WARN,
"!acpidev: hdl is NULL in acpidev_resource_walk().");
return (AE_BAD_PARAMETER);
} else if (method == NULL) {
ACPIDEV_DEBUG(CE_WARN,
"!acpidev: method is NULL in acpidev_resource_walk().");
return (AE_BAD_PARAMETER);
} else if (rhdlp == NULL) {
ACPIDEV_DEBUG(CE_WARN, "!acpidev: resource handle ptr is NULL "
"in acpidev_resource_walk().");
return (AE_BAD_PARAMETER);
}
/* Check whether method exists under object. */
if (ACPI_FAILURE(AcpiGetHandle(hdl, method, &mhdl))) {
char *objname = acpidev_get_object_name(hdl);
ACPIDEV_DEBUG(CE_NOTE,
"!acpidev: method %s doesn't exist under %s",
method, objname);
acpidev_free_object_name(objname);
return (AE_NOT_FOUND);
}
/* Walk all resources. */
rhdl = acpidev_resource_handle_alloc(consumer);
if (consumer) {
rc = AcpiWalkResources(hdl, method,
acpidev_resource_walk_consumer, rhdl);
} else {
rc = AcpiWalkResources(hdl, method,
acpidev_resource_walk_producer, rhdl);
}
if (ACPI_SUCCESS(rc)) {
*rhdlp = rhdl;
} else {
acpidev_resource_handle_free(rhdl);
}
if (ACPI_FAILURE(rc)) {
char *objname = acpidev_get_object_name(hdl);
ACPIDEV_DEBUG(CE_WARN, "!acpidev: failed to walk resource from "
"method %s under %s.", method, objname);
acpidev_free_object_name(objname);
}
return (rc);
}
ACPI_STATUS
acpidev_resource_process(acpidev_walk_info_t *infop, boolean_t consumer)
{
ACPI_STATUS rc;
char path[MAXPATHLEN];
acpidev_resource_handle_t rhdl = NULL;
ASSERT(infop != NULL);
if (infop == NULL) {
ACPIDEV_DEBUG(CE_WARN, "!acpidev: invalid parameter "
"in acpidev_resource_process().");
return (AE_BAD_PARAMETER);
}
/* Walk all resources. */
(void) ddi_pathname(infop->awi_dip, path);
rc = acpidev_resource_walk(infop->awi_hdl, METHOD_NAME__CRS,
consumer, &rhdl);
if (ACPI_FAILURE(rc)) {
ACPIDEV_DEBUG(CE_WARN,
"!acpidev: failed to walk ACPI resources of %s(%s).",
path, infop->awi_name);
return (rc);
}
if (consumer) {
/* Create device properties for consumer. */
/* Create 'reg' and 'assigned-addresses' properties. */
if (rhdl->acpidev_reg_count > 0 &&
ndi_prop_update_int_array(DDI_DEV_T_NONE, infop->awi_dip,
"reg", (int *)rhdl->acpidev_regp,
rhdl->acpidev_reg_count * sizeof (acpidev_regspec_t) /
sizeof (int)) != NDI_SUCCESS) {
ACPIDEV_DEBUG(CE_WARN, "!acpidev: failed to set "
"'reg' property for %s.", path);
rc = AE_ERROR;
goto out;
}
if (rhdl->acpidev_reg_count > 0 &&
ndi_prop_update_int_array(DDI_DEV_T_NONE, infop->awi_dip,
"assigned-addresses", (int *)rhdl->acpidev_regp,
rhdl->acpidev_reg_count * sizeof (acpidev_regspec_t) /
sizeof (int)) != NDI_SUCCESS) {
ACPIDEV_DEBUG(CE_WARN, "!acpidev: failed to set "
"'assigned-addresses' property for %s.", path);
rc = AE_ERROR;
goto out;
}
/* Create 'interrupts' property. */
if (rhdl->acpidev_irq_count > 0 &&
ndi_prop_update_int_array(DDI_DEV_T_NONE, infop->awi_dip,
"interrupts", (int *)rhdl->acpidev_irqp,
rhdl->acpidev_irq_count) != NDI_SUCCESS) {
ACPIDEV_DEBUG(CE_WARN, "!acpidev: failed to set "
"'interrupts' property for %s.", path);
rc = AE_ERROR;
goto out;
}
/* Create 'dma-channels' property. */
if (rhdl->acpidev_dma_count > 0 &&
ndi_prop_update_int_array(DDI_DEV_T_NONE, infop->awi_dip,
"dma-channels", (int *)rhdl->acpidev_dmap,
rhdl->acpidev_dma_count) != NDI_SUCCESS) {
ACPIDEV_DEBUG(CE_WARN, "!acpidev: failed to set "
"'dma-channels' property for %s.", path);
rc = AE_ERROR;
goto out;
}
} else {
/* Create device properties for producer. */
/* Create 'ranges' property. */
if (rhdl->acpidev_range_count > 0 &&
ndi_prop_update_int_array(DDI_DEV_T_NONE, infop->awi_dip,
"ranges", (int *)rhdl->acpidev_rangep,
rhdl->acpidev_range_count * sizeof (acpidev_ranges_t) /
sizeof (int)) != NDI_SUCCESS) {
ACPIDEV_DEBUG(CE_WARN, "!acpidev: failed to set "
"'ranges' property for %s.", path);
rc = AE_ERROR;
goto out;
}
/* Create 'bus-range' property. */
if (rhdl->acpidev_bus_count > 0 &&
ndi_prop_update_int_array(DDI_DEV_T_NONE, infop->awi_dip,
"bus-range", (int *)rhdl->acpidev_busp,
rhdl->acpidev_bus_count * sizeof (acpidev_bus_range_t) /
sizeof (int)) != NDI_SUCCESS) {
ACPIDEV_DEBUG(CE_WARN, "!acpidev: failed to set "
"'bus-range' property for %s.", path);
rc = AE_ERROR;
goto out;
}
}
out:
/* Free resources allocated by acpidev_resource_walk. */
acpidev_resource_handle_free(rhdl);
return (rc);
}