/*
 * 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.
 */

/*
 * ACPI enumerator
 */

#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/sunndi.h>
#include <sys/note.h>
#include <sys/acpi/acpi.h>
#include <sys/acpica.h>
#include <util/sscanf.h>


static char keyboard_alias[] = "keyboard";
static char mouse_alias[] = "mouse";
#define	ACPI_ENUM_DEBUG		"acpi_enum_debug"
#define	PARSE_RESOURCES_DEBUG	0x0001
#define	MASTER_LOOKUP_DEBUG	0x0002
#define	DEVICES_NOT_ENUMED	0x0004
#define	PARSE_RES_IRQ		0x0008
#define	PARSE_RES_DMA		0x0010
#define	PARSE_RES_MEMORY	0x0020
#define	PARSE_RES_IO		0x0040
#define	PARSE_RES_ADDRESS	0x0080
#define	ISA_DEVICE_ENUM		0x1000
#define	PROCESS_CIDS		0x2000
static unsigned long acpi_enum_debug = 0x00;

static char USED_RESOURCES[] = "used-resources";
static dev_info_t *usedrdip = NULL;
static unsigned short used_interrupts = 0;
static unsigned short used_dmas = 0;
typedef struct used_io_mem {
	unsigned int start_addr;
	unsigned int length;
	struct used_io_mem *next;
} used_io_mem_t;
static used_io_mem_t *used_io_head = NULL;
static used_io_mem_t *used_mem_head = NULL;
static int used_io_count = 0;
static int used_mem_count = 0;

#define	MAX_PARSED_ACPI_RESOURCES	255
#define	ACPI_ISA_LIMIT	16
static int interrupt[ACPI_ISA_LIMIT], dma[ACPI_ISA_LIMIT];
#define	ACPI_ELEMENT_PACKAGE_LIMIT	32
#define	EISA_ID_SIZE	7

/*
 * insert used io/mem in increasing order
 */
static void
insert_used_resource(used_io_mem_t *used, int *used_count, used_io_mem_t **head)
{
	used_io_mem_t *curr, *prev;

	(*used_count)++;
	if (*head == NULL) {
		*head = used;
		return;
	}
	curr = prev = *head;
	/* find a place to insert */
	while ((curr != NULL) &&
	    (curr->start_addr < used->start_addr)) {
		prev = curr;
		curr = curr->next;
	}
	if (prev == curr) {
		/* head */
		*head = used;
		used->next = curr;
		return;
	} else {
		prev->next = used;
	}
	used->next = curr;
}

static void
add_used_io_mem(struct regspec *io, int io_count)
{
	int i;
	used_io_mem_t *used;

	for (i = 0; i < io_count; i++) {
		used = (used_io_mem_t *)kmem_zalloc(sizeof (used_io_mem_t),
		    KM_SLEEP);
		used->start_addr = io[i].regspec_addr;
		used->length = io[i].regspec_size;
		if (io[i].regspec_bustype == 1) {
			insert_used_resource(used, &used_io_count,
			    &used_io_head);
		} else {
			insert_used_resource(used, &used_mem_count,
			    &used_mem_head);
		}
	}
}

static void
parse_resources_irq(ACPI_RESOURCE *resource_ptr, int *interrupt_count)
{
	int i;

	for (i = 0; i < resource_ptr->Data.Irq.InterruptCount; i++) {
		interrupt[(*interrupt_count)++] =
		    resource_ptr->Data.Irq.Interrupts[i];
		used_interrupts |= 1 << resource_ptr->Data.Irq.Interrupts[i];
		if (acpi_enum_debug & PARSE_RES_IRQ) {
			cmn_err(CE_NOTE, "parse_resources() "\
			    "IRQ num %u, intr # = %u",
			    i, resource_ptr->Data.Irq.Interrupts[i]);
		}
	}
}

static void
parse_resources_dma(ACPI_RESOURCE *resource_ptr, int *dma_count)
{
	int i;

	for (i = 0; i < resource_ptr->Data.Dma.ChannelCount; i++) {
		dma[(*dma_count)++] = resource_ptr->Data.Dma.Channels[i];
		used_dmas |= 1 << resource_ptr->Data.Dma.Channels[i];
		if (acpi_enum_debug & PARSE_RES_DMA) {
			cmn_err(CE_NOTE, "parse_resources() "\
			    "DMA num %u, channel # = %u",
			    i, resource_ptr->Data.Dma.Channels[i]);
		}
	}
}

static void
parse_resources_io(ACPI_RESOURCE *resource_ptr, struct regspec *io,
    int *io_count)
{
	ACPI_RESOURCE_IO acpi_io = resource_ptr->Data.Io;

	if (acpi_io.AddressLength == 0)
		return;

	io[*io_count].regspec_bustype = 1; /* io */
	io[*io_count].regspec_size = acpi_io.AddressLength;
	io[*io_count].regspec_addr = acpi_io.Minimum;
	if (acpi_enum_debug & PARSE_RES_IO) {
		cmn_err(CE_NOTE, "parse_resources() "\
		    "IO min 0x%X, max 0x%X, length: 0x%X",
		    acpi_io.Minimum,
		    acpi_io.Maximum,
		    acpi_io.AddressLength);
	}
	(*io_count)++;
}

static void
parse_resources_fixed_io(ACPI_RESOURCE *resource_ptr, struct regspec *io,
    int *io_count)
{
	ACPI_RESOURCE_FIXED_IO fixed_io = resource_ptr->Data.FixedIo;

	if (fixed_io.AddressLength == 0)
		return;

	io[*io_count].regspec_bustype = 1; /* io */
	io[*io_count].regspec_addr = fixed_io.Address;
	io[*io_count].regspec_size = fixed_io.AddressLength;
	if (acpi_enum_debug & PARSE_RES_IO) {
		cmn_err(CE_NOTE, "parse_resources() "\
		    "Fixed IO 0x%X, length: 0x%X",
		    fixed_io.Address, fixed_io.AddressLength);
	}
	(*io_count)++;
}

static void
parse_resources_fixed_mem32(ACPI_RESOURCE *resource_ptr, struct regspec *io,
    int *io_count)
{
	ACPI_RESOURCE_FIXED_MEMORY32 fixed_mem32 =
	    resource_ptr->Data.FixedMemory32;

	if (fixed_mem32.AddressLength == 0)
		return;

	io[*io_count].regspec_bustype = 0; /* memory */
	io[*io_count].regspec_addr = fixed_mem32.Address;
	io[*io_count].regspec_size = fixed_mem32.AddressLength;
	if (acpi_enum_debug & PARSE_RES_MEMORY) {
		cmn_err(CE_NOTE, "parse_resources() "\
		    "Fixed Mem 32 %ul, length: %ul",
		    fixed_mem32.Address, fixed_mem32.AddressLength);
	}
	(*io_count)++;
}

static void
parse_resources_mem32(ACPI_RESOURCE *resource_ptr, struct regspec *io,
    int *io_count)
{
	ACPI_RESOURCE_MEMORY32 mem32 = resource_ptr->Data.Memory32;

	if (mem32.AddressLength == 0)
		return;

	if (resource_ptr->Data.Memory32.Minimum ==
	    resource_ptr->Data.Memory32.Maximum) {
		io[*io_count].regspec_bustype = 0; /* memory */
		io[*io_count].regspec_addr = mem32.Minimum;
		io[*io_count].regspec_size = mem32.AddressLength;
		(*io_count)++;
		if (acpi_enum_debug & PARSE_RES_MEMORY) {
			cmn_err(CE_NOTE, "parse_resources() "\
			    "Mem 32 0x%X, length: 0x%X",
			    mem32.Minimum, mem32.AddressLength);
		}
		return;
	}
	if (acpi_enum_debug & PARSE_RES_MEMORY) {
		cmn_err(CE_NOTE, "parse_resources() "\
		    "MEM32 Min Max not equal!");
		cmn_err(CE_NOTE, "parse_resources() "\
		    "Mem 32 Minimum 0x%X, Maximum: 0x%X",
		    mem32.Minimum, mem32.Maximum);
	}
}

static void
parse_resources_addr16(ACPI_RESOURCE *resource_ptr, struct regspec *io,
    int *io_count)
{
	ACPI_RESOURCE_ADDRESS16 addr16 =
	    resource_ptr->Data.Address16;

	if (addr16.AddressLength == 0)
		return;

	if (acpi_enum_debug & PARSE_RES_ADDRESS) {
		if (addr16.ResourceType == ACPI_MEMORY_RANGE) {
			cmn_err(CE_NOTE, "parse_resources() "\
			    "ADDRESS 16 MEMORY RANGE");
		} else
		if (addr16.ResourceType == ACPI_IO_RANGE) {
			cmn_err(CE_NOTE, "parse_resources() "\
			    "ADDRESS 16 IO RANGE");
		} else {
			cmn_err(CE_NOTE, "parse_resources() "\
			    "ADDRESS 16 OTHER");
		}
		cmn_err(CE_NOTE, "parse_resources() "\
		    "%s "\
		    "MinAddressFixed 0x%X, "\
		    "MaxAddressFixed 0x%X, "\
		    "Minimum 0x%X, "\
		    "Maximum 0x%X, "\
		    "length: 0x%X\n",
		    addr16.ProducerConsumer == ACPI_CONSUMER ?
		    "CONSUMER" : "PRODUCER",
		    addr16.MinAddressFixed,
		    addr16.MaxAddressFixed,
		    addr16.Minimum,
		    addr16.Maximum,
		    addr16.AddressLength);
	}
	if (addr16.ProducerConsumer == ACPI_PRODUCER ||
	    (addr16.ResourceType != ACPI_MEMORY_RANGE &&
	    addr16.ResourceType != ACPI_IO_RANGE)) {
		return;
	}
	if (addr16.AddressLength > 0) {
		if (addr16.ResourceType == ACPI_MEMORY_RANGE) {
			/* memory */
			io[*io_count].regspec_bustype = 0;
		} else {
			/* io */
			io[*io_count].regspec_bustype = 1;
		}
		io[*io_count].regspec_addr = addr16.Minimum;
		io[*io_count].regspec_size = addr16.AddressLength;
		(*io_count)++;
	}
}

static void
parse_resources_addr32(ACPI_RESOURCE *resource_ptr, struct regspec *io,
    int *io_count)
{
	ACPI_RESOURCE_ADDRESS32 addr32 =
	    resource_ptr->Data.Address32;

	if (addr32.AddressLength == 0)
		return;

	if (acpi_enum_debug & PARSE_RES_ADDRESS) {
		if (addr32.ResourceType == ACPI_MEMORY_RANGE) {
			cmn_err(CE_NOTE, "parse_resources() "\
			    "ADDRESS 32 MEMORY RANGE");
		} else
		if (addr32.ResourceType == ACPI_IO_RANGE) {
			cmn_err(CE_NOTE, "parse_resources() "\
			    "ADDRESS 32 IO RANGE");
		} else {
			cmn_err(CE_NOTE, "parse_resources() "\
			    "ADDRESS 32 OTHER");
		}
		cmn_err(CE_NOTE, "parse_resources() "\
		    "%s "\
		    "MinAddressFixed 0x%X, "\
		    "MaxAddressFixed 0x%X, "\
		    "Minimum 0x%X, "\
		    "Maximum 0x%X, "\
		    "length: 0x%X\n",
		    addr32.ProducerConsumer == ACPI_CONSUMER ?
		    "CONSUMER" : "PRODUCER",
		    addr32.MinAddressFixed,
		    addr32.MaxAddressFixed,
		    addr32.Minimum,
		    addr32.Maximum,
		    addr32.AddressLength);
	}
	if (addr32.ProducerConsumer == ACPI_PRODUCER ||
	    (addr32.ResourceType != ACPI_MEMORY_RANGE &&
	    addr32.ResourceType != ACPI_IO_RANGE)) {
		return;
	}
	if (addr32.AddressLength > 0) {
		if (addr32.ResourceType == ACPI_MEMORY_RANGE) {
			/* memory */
			io[*io_count].regspec_bustype = 0;
		} else {
			/* io */
			io[*io_count].regspec_bustype = 1;
		}
		io[*io_count].regspec_addr = addr32.Minimum;
		io[*io_count].regspec_size = addr32.AddressLength;
		(*io_count)++;
	}
}

static void
parse_resources_addr64(ACPI_RESOURCE *resource_ptr, struct regspec *io,
    int *io_count)
{
	ACPI_RESOURCE_ADDRESS64 addr64 =
	    resource_ptr->Data.Address64;

	if (addr64.AddressLength == 0)
		return;

	if (acpi_enum_debug & PARSE_RES_ADDRESS) {
		if (addr64.ResourceType == ACPI_MEMORY_RANGE) {
			cmn_err(CE_NOTE, "parse_resources() "\
			    "ADDRESS 64 MEMORY RANGE");
		} else
		if (addr64.ResourceType == ACPI_IO_RANGE) {
			cmn_err(CE_NOTE, "parse_resources() "\
			    "ADDRESS 64 IO RANGE");
		} else {
			cmn_err(CE_NOTE, "parse_resources() "\
			    "ADDRESS 64 OTHER");
		}
#ifdef _LP64
		cmn_err(CE_NOTE, "parse_resources() "\
		    "%s "\
		    "MinAddressFixed 0x%X, "\
		    "MaxAddressFixed 0x%X, "\
		    "Minimum 0x%lX, "\
		    "Maximum 0x%lX, "\
		    "length: 0x%lX\n",
		    addr64.ProducerConsumer == ACPI_CONSUMER ?
		    "CONSUMER" : "PRODUCER",
		    addr64.MinAddressFixed,
		    addr64.MaxAddressFixed,
		    addr64.Minimum,
		    addr64.Maximum,
		    addr64.AddressLength);
#else
		cmn_err(CE_NOTE, "parse_resources() "\
		    "%s "\
		    "MinAddressFixed 0x%X, "\
		    "MaxAddressFixed 0x%X, "\
		    "Minimum 0x%llX, "\
		    "Maximum 0x%llX, "\
		    "length: 0x%llX\n",
		    addr64.ProducerConsumer == ACPI_CONSUMER ?
		    "CONSUMER" : "PRODUCER",
		    addr64.MinAddressFixed,
		    addr64.MaxAddressFixed,
		    addr64.Minimum,
		    addr64.Maximum,
		    addr64.AddressLength);
#endif
	}
	if (addr64.ProducerConsumer == ACPI_PRODUCER ||
	    (addr64.ResourceType != ACPI_MEMORY_RANGE &&
	    addr64.ResourceType != ACPI_IO_RANGE)) {
		return;
	}
	if (addr64.AddressLength > 0) {
		if (addr64.ResourceType == ACPI_MEMORY_RANGE) {
			/* memory */
			io[*io_count].regspec_bustype = 0;
		} else {
			/* io */
			io[*io_count].regspec_bustype = 1;
		}
		io[*io_count].regspec_addr = addr64.Minimum;
		io[*io_count].regspec_size = addr64.AddressLength;
		(*io_count)++;
	}
}

static ACPI_STATUS
parse_resources(ACPI_HANDLE handle, dev_info_t *xdip)
{
	ACPI_BUFFER	buf;
	ACPI_RESOURCE	*resource_ptr;
	ACPI_STATUS	status;
	char		*current_ptr, *last_ptr;
	struct		regspec *io;
	int		io_count = 0, interrupt_count = 0, dma_count = 0;
	int		i;

	buf.Length = ACPI_ALLOCATE_BUFFER;
	status = AcpiGetCurrentResources(handle, &buf);
	if (status != AE_OK) {
		return (status);
	}
	io = (struct regspec *)kmem_zalloc(sizeof (struct regspec) *
	    MAX_PARSED_ACPI_RESOURCES, KM_SLEEP);
	current_ptr = buf.Pointer;
	last_ptr = (char *)buf.Pointer + buf.Length;
	while (current_ptr < last_ptr) {
		if (io_count >= MAX_PARSED_ACPI_RESOURCES) {
			break;
		}
		resource_ptr = (ACPI_RESOURCE *)current_ptr;
		current_ptr += resource_ptr->Length;
		switch (resource_ptr->Type) {
		case ACPI_RESOURCE_TYPE_END_TAG:
			current_ptr = last_ptr;
			break;
		case ACPI_RESOURCE_TYPE_IO:
			parse_resources_io(resource_ptr, io, &io_count);
			break;
		case ACPI_RESOURCE_TYPE_FIXED_IO:
			parse_resources_fixed_io(resource_ptr, io, &io_count);
			break;
		case ACPI_RESOURCE_TYPE_FIXED_MEMORY32:
			parse_resources_fixed_mem32(resource_ptr, io,
			    &io_count);
			break;
		case ACPI_RESOURCE_TYPE_MEMORY32:
			parse_resources_mem32(resource_ptr, io, &io_count);
			break;
		case ACPI_RESOURCE_TYPE_ADDRESS16:
			parse_resources_addr16(resource_ptr, io, &io_count);
			break;
		case ACPI_RESOURCE_TYPE_ADDRESS32:
			parse_resources_addr32(resource_ptr, io, &io_count);
			break;
		case ACPI_RESOURCE_TYPE_ADDRESS64:
			parse_resources_addr64(resource_ptr, io, &io_count);
			break;
		case ACPI_RESOURCE_TYPE_IRQ:
			parse_resources_irq(resource_ptr, &interrupt_count);
			break;
		case ACPI_RESOURCE_TYPE_DMA:
			parse_resources_dma(resource_ptr, &dma_count);
			break;
		case ACPI_RESOURCE_TYPE_START_DEPENDENT:
			cmn_err(CE_NOTE,
			    "!ACPI source type"
			    " ACPI_RESOURCE_TYPE_START_DEPENDENT"
			    " not supported");
			break;
		case ACPI_RESOURCE_TYPE_END_DEPENDENT:
			cmn_err(CE_NOTE,
			    "!ACPI source type"
			    " ACPI_RESOURCE_TYPE_END_DEPENDENT"
			    " not supported");
			break;
		case ACPI_RESOURCE_TYPE_VENDOR:
			cmn_err(CE_NOTE,
			    "!ACPI source type"
			    " ACPI_RESOURCE_TYPE_VENDOR"
			    " not supported");
			break;
		case ACPI_RESOURCE_TYPE_MEMORY24:
			cmn_err(CE_NOTE,
			    "!ACPI source type"
			    " ACPI_RESOURCE_TYPE_MEMORY24"
			    " not supported");
			break;
		case ACPI_RESOURCE_TYPE_EXTENDED_IRQ:
			cmn_err(CE_NOTE,
			    "!ACPI source type"
			    " ACPI_RESOURCE_TYPE_EXT_IRQ"
			    " not supported");
			break;
		default:
		/* Some types are not yet implemented (See CA 6.4) */
			cmn_err(CE_NOTE,
			    "!ACPI resource type (0X%X) not yet supported",
			    resource_ptr->Type);
			break;
		}
	}

	if (io_count) {
		/*
		 * on LX50, you get interrupts of mouse and keyboard
		 * from separate PNP id...
		 */
		if (io_count == 2) {
			if ((io[0].regspec_addr == 0x60 &&
			    io[1].regspec_addr == 0x64) ||
			    (io[0].regspec_addr == 0x64 &&
			    io[1].regspec_addr == 0x60)) {
				interrupt[0] = 0x1;
				interrupt[1] = 0xc;
				interrupt_count = 2;
				used_interrupts |=
				    1 << interrupt[0];
				used_interrupts |=
				    1 << interrupt[1];
			}
		}
		add_used_io_mem(io, io_count);
		if (xdip != NULL) {
			(void) ndi_prop_update_int_array(DDI_DEV_T_NONE, xdip,
			    "reg", (int *)io, 3*io_count);
		}
	}
	if (interrupt_count && (xdip != NULL)) {
		(void) ndi_prop_update_int_array(DDI_DEV_T_NONE, xdip,
		    "interrupts", (int *)interrupt, interrupt_count);
	}
	if (dma_count && (xdip != NULL)) {
		(void) ndi_prop_update_int_array(DDI_DEV_T_NONE, xdip,
		    "dma-channels", (int *)dma, dma_count);
	}
	AcpiOsFree(buf.Pointer);
	kmem_free(io, sizeof (struct regspec) * MAX_PARSED_ACPI_RESOURCES);
	return (status);
}

/* keyboard mouse is under i8042, everything else under isa */
static dev_info_t *
get_bus_dip(char *nodename, dev_info_t *isa_dip)
{
	static dev_info_t *i8042_dip = NULL;
	struct regspec i8042_regs[] = {
		{1, 0x60, 0x1},
		{1, 0x64, 0x1}
	};
	int i8042_intrs[] = {0x1, 0xc};

	if (strcmp(nodename, keyboard_alias) != 0 &&
	    strcmp(nodename, mouse_alias) != 0)
		return (isa_dip);

	if (i8042_dip)
		return (i8042_dip);

	ndi_devi_alloc_sleep(isa_dip, "i8042", (pnode_t)DEVI_SID_NODEID,
	    &i8042_dip);
	(void) ndi_prop_update_int_array(DDI_DEV_T_NONE, i8042_dip,
	    "reg", (int *)i8042_regs, 6);
	(void) ndi_prop_update_int_array(DDI_DEV_T_NONE, i8042_dip,
	    "interrupts", (int *)i8042_intrs, 2);
	(void) ndi_prop_update_string(DDI_DEV_T_NONE, i8042_dip,
	    "unit-address", "1,60");
	(void) ndi_devi_bind_driver(i8042_dip, 0);
	return (i8042_dip);
}

/*
 * put content of properties (if any) to dev info tree at branch xdip
 * return non-zero if a "compatible" property was processed, zero otherwise
 *
 */
static int
process_properties(dev_info_t *xdip, property_t *properties)
{
	int	rv = 0;

	while (properties != NULL) {
		(void) ndi_prop_update_string(DDI_DEV_T_NONE, xdip,
		    properties->name, properties->value);
		if (strcmp(properties->name, "compatible") == 0)
			rv = 1;
		properties = properties->next;
	}

	return (rv);
}

void
eisa_to_str(ACPI_INTEGER id, char *np)
{
	static const char hextab[] = "0123456789ABCDEF";

	/*
	 *  Expand an EISA device name:
	 *
	 * This routine converts a 32-bit EISA device "id" to a
	 * 7-byte ASCII device name, which is stored at "np".
	 */

	*np++ = '@' + ((id >> 2)  & 0x1F);
	*np++ = '@' + ((id << 3)  & 0x18) + ((id >> 13) & 0x07);
	*np++ = '@' + ((id >> 8)  & 0x1F);
	*np++ = hextab[(id >> 20) & 0x0F];
	*np++ = hextab[(id >> 16) & 0x0F];
	*np++ = hextab[(id >> 28) & 0x0F];
	*np++ = hextab[(id >> 24) & 0x0F];
	*np = 0;
}

/*
 * process_cids() -- process multiple CIDs in a package
 */
static void
process_cids(ACPI_OBJECT *rv, device_id_t **dd)
{
	device_id_t *d;
	char tmp_cidstr[8];	/* 7-character EISA ID */
	int i;

	if ((rv->Package.Count == 0) || rv->Package.Elements == NULL)
		return; /* empty package */

	/*
	 * Work the package 'backwards' so the resulting list is
	 * in original order of preference.
	 */
	for (i = rv->Package.Count - 1; i >= 0; i--) {
		/* get the actual acpi_object */
		ACPI_OBJECT obj = rv->Package.Elements[i];
		switch (obj.Type) {
		case ACPI_TYPE_INTEGER:
			eisa_to_str(obj.Integer.Value, tmp_cidstr);
			d = mf_alloc_device_id();
			d->id = strdup(tmp_cidstr);
			d->next = *dd;
			*dd = d;
			break;
		case ACPI_TYPE_STRING:
			d = mf_alloc_device_id();
			d->id = strdup(obj.String.Pointer);
			d->next = *dd;
			*dd = d;
			break;
		default:
			if (acpi_enum_debug & PROCESS_CIDS) {
				cmn_err(CE_NOTE, "unexpected CID type: %d",
				    obj.Type);
			}
			break;
		}
	}
}

/*
 * Convert "raw" PNP and ACPI IDs to IEEE 1275-compliant form.
 * Some liberty is taken here, treating "ACPI" as a special form
 * of PNP vendor ID.  strsize specifies size of buffer.
 */
static void
convert_to_pnp1275(char *pnpid, char *str, int strsize)
{
	char	vendor[5];
	uint_t	id;

	if (strncmp(pnpid, "ACPI", 4) == 0) {
		/* Assume ACPI ID: ACPIxxxx */
		sscanf(pnpid, "%4s%x", vendor, &id);
	} else {
		/* Assume PNP ID: aaaxxxx */
		sscanf(pnpid, "%3s%x", vendor, &id);
	}

	snprintf(str, strsize, "pnp%s,%x", vendor, id);
}

/*
 * Given a list of device ID elements in most-to-least-specific
 * order, create a "compatible" property.
 */
static void
create_compatible_property(dev_info_t *dip, device_id_t *ids)
{
	char		**strs;
	int		list_len, i;
	device_id_t	*d;

	/* count list length */
	list_len = 0;
	d = ids;
	while (d != NULL) {
		list_len++;
		d = d->next;
	}

	/* create string array */
	strs = (char **)kmem_zalloc(list_len * sizeof (char *), KM_SLEEP);
	i = 0;
	d = ids;
	while (d != NULL) {
		/* strlen("pnpXXXX,xxxx") + 1 = 13 */
		strs[i] = kmem_zalloc(13, KM_SLEEP);
		convert_to_pnp1275(d->id, strs[i++], 13);
		d = d->next;
	}

	/* update property */
	(void) ndi_prop_update_string_array(DDI_DEV_T_NONE, dip,
	    "compatible", strs, list_len);


	/* free memory */
	for (i = 0; i < list_len; i++)
		kmem_free(strs[i], 13);

	kmem_free(strs, list_len * sizeof (char *));
}

/*
 * isa_acpi_callback()
 */
static ACPI_STATUS
isa_acpi_callback(ACPI_HANDLE ObjHandle, uint32_t NestingLevel, void *a,
    void **b)
{
	_NOTE(ARGUNUSED(NestingLevel, b))

	ACPI_BUFFER	rb;
	ACPI_DEVICE_INFO *info = NULL;
	char		*path = NULL;
	char		*hidstr = NULL;
	char		tmp_cidstr[8];	/* EISAID size */
	dev_info_t	*dip = (dev_info_t *)a;
	dev_info_t	*xdip = NULL;
	device_id_t	*d, *device_ids = NULL;
	const master_rec_t	*m;
	int		compatible_present = 0;

	/*
	 * get full ACPI pathname for object
	 */
	rb.Length = ACPI_ALLOCATE_BUFFER;
	rb.Pointer = NULL;
	if (AcpiGetName(ObjHandle, ACPI_FULL_PATHNAME, &rb) != AE_OK) {
		cmn_err(CE_WARN, "!acpi_enum: could not get pathname");
		goto done;
	}
	path = (char *)rb.Pointer;

	/*
	 * Get device info object
	 */
	if (AcpiGetObjectInfo(ObjHandle, &info) != AE_OK) {
		cmn_err(CE_WARN, "!acpi_enum: could not get device"
		    " info for %s", path);
		goto done;
	}

	/*
	 * If device isn't present, we don't enumerate
	 * NEEDSWORK: what about docking bays and the like?
	 */
	if (info->Valid & ACPI_VALID_STA) {
		/*
		 * CA 6.3.6 _STA method
		 * Bit 0 -- device is present
		 * Bit 1 -- device is enabled
		 * Bit 2 -- device is shown in UI
		 */
		if (!((info->CurrentStatus & 0x7) == 7)) {
			goto done;
		}
	} else {
		cmn_err(CE_WARN, "!acpi_enum: no _STA for %s", path);
		goto done;
	}

	/*
	 * Keep track of _HID value
	 */
	if (!(info->Valid & ACPI_VALID_HID)) {
		/* No _HID, we skip this node */
		goto done;
	}
	hidstr = info->HardwareId.String;

	/*
	 * Attempt to get _CID value
	 */
	rb.Length = ACPI_ALLOCATE_BUFFER;
	rb.Pointer = NULL;
	if (AcpiEvaluateObject(ObjHandle, "_CID", NULL, &rb) == AE_OK &&
	    rb.Length != 0) {
		ACPI_OBJECT *rv = rb.Pointer;

		switch (rv->Type) {
		case ACPI_TYPE_INTEGER:
			eisa_to_str(rv->Integer.Value, tmp_cidstr);
			d = mf_alloc_device_id();
			d->id = strdup(tmp_cidstr);
			d->next = device_ids;
			device_ids = d;
			break;
		case ACPI_TYPE_STRING:
			d = mf_alloc_device_id();
			d->id = strdup(rv->String.Pointer);
			d->next = device_ids;
			device_ids = d;
			break;
		case ACPI_TYPE_PACKAGE:
			process_cids(rv, &device_ids);
			break;
		default:
			break;
		}
		AcpiOsFree(rb.Pointer);
	}

	/*
	 * Add _HID last so it's at the head of the list
	 */
	d = mf_alloc_device_id();
	d->id = strdup(hidstr);
	d->next = device_ids;
	device_ids = d;

	/*
	 * master_file_lookup() expects _HID first in device_ids
	 */
	if ((m = master_file_lookup(device_ids)) !=  NULL) {
		/* PNP description found in master table */
		if (!(strncmp(hidstr, "ACPI", 4))) {
			dip = ddi_root_node();
		} else {
			dip = get_bus_dip(m->name, dip);
		}
		ndi_devi_alloc_sleep(dip, m->name,
		    (pnode_t)DEVI_SID_NODEID, &xdip);
		(void) ndi_prop_update_string(DDI_DEV_T_NONE, xdip,
		    "model", m->description);
		compatible_present = process_properties(xdip, m->properties);
	} else {
		/* for ISA devices not known to the master file */
		if (!(strncmp(hidstr, "PNP03", 5))) {
			/* a keyboard device includes PNP03xx */
			dip = get_bus_dip(keyboard_alias, dip);
			ndi_devi_alloc_sleep(dip, keyboard_alias,
			    (pnode_t)DEVI_SID_NODEID, &xdip);
			(void) ndi_prop_update_string(DDI_DEV_T_NONE, xdip,
			    "compatible", "pnpPNP,303");
			(void) ndi_prop_update_string(DDI_DEV_T_NONE, xdip,
			    "model", "PNP03xx keyboard");
		} else {
			if (!(strncmp(hidstr, "PNP0F", 5))) {
				/* a mouse device include PNP0Fxx */
				dip = get_bus_dip(mouse_alias, dip);
				ndi_devi_alloc_sleep(dip, mouse_alias,
				    (pnode_t)DEVI_SID_NODEID, &xdip);
				(void) ndi_prop_update_string(DDI_DEV_T_NONE,
				    xdip, "compatible", "pnpPNP,f03");
				(void) ndi_prop_update_string(DDI_DEV_T_NONE,
				    xdip, "model", "PNP0Fxx mouse");
			} else {
				(void) parse_resources(ObjHandle, xdip);
				goto done;
			}
		}
	}

	(void) ndi_prop_update_string(DDI_DEV_T_NONE, xdip, "acpi-namespace",
	    path);

	(void) parse_resources(ObjHandle, xdip);

	/* Special processing for mouse and keyboard devices per IEEE 1275 */
	/* if master entry doesn't contain "compatible" then we add default */
	if (strcmp(m->name, keyboard_alias) == 0) {
		(void) ndi_prop_update_int(DDI_DEV_T_NONE, xdip, "reg", 0);
		(void) ndi_prop_update_string(DDI_DEV_T_NONE, xdip,
		    "device-type", keyboard_alias);
		if (!compatible_present)
			(void) ndi_prop_update_string(DDI_DEV_T_NONE, xdip,
			    "compatible", "pnpPNP,303");
	} else if (strcmp(m->name, mouse_alias) == 0) {
		(void) ndi_prop_update_int(DDI_DEV_T_NONE, xdip, "reg", 1);
		(void) ndi_prop_update_string(DDI_DEV_T_NONE, xdip,
		    "device-type", mouse_alias);
		if (!compatible_present)
			(void) ndi_prop_update_string(DDI_DEV_T_NONE, xdip,
			    "compatible", "pnpPNP,f03");
	}

	/*
	 * Create default "compatible" property if required
	 */
	if (!ddi_prop_exists(DDI_DEV_T_ANY, xdip,
	    DDI_PROP_DONTPASS, "compatible"))
		create_compatible_property(xdip, device_ids);

	(void) ndi_devi_bind_driver(xdip, 0);

done:
	/* discard _HID/_CID list */
	d = device_ids;
	while (d != NULL) {
		device_id_t *next;

		next = d->next;
		mf_free_device_id(d);
		d = next;
	}

	if (path != NULL)
		AcpiOsFree(path);
	if (info != NULL)
		AcpiOsFree(info);

	return (AE_OK);
}

static void
used_res_interrupts(void)
{
	int intr[ACPI_ISA_LIMIT];
	int count = 0;
	int i;

	for (i = 0; i < ACPI_ISA_LIMIT; i++) {
		if ((used_interrupts >> i) & 1) {
			intr[count++] = i;
		}
	}
	(void) ndi_prop_update_int_array(DDI_DEV_T_NONE, usedrdip,
	    "interrupts", (int *)intr, count);
}

static void
used_res_dmas(void)
{
	int dma[ACPI_ISA_LIMIT];
	int count = 0;
	int i;

	for (i = 0; i < ACPI_ISA_LIMIT; i++) {
		if ((used_dmas >> i) & 1) {
			dma[count++] = i;
		}
	}
	(void) ndi_prop_update_int_array(DDI_DEV_T_NONE, usedrdip,
	    "dma-channels", (int *)dma, count);
}

static void
used_res_io_mem(char *nodename, int *count, used_io_mem_t **head)
{
	int *io;
	used_io_mem_t *used = *head;
	int i;

	*count *= 2;
	io = (int *)kmem_zalloc(sizeof (int)*(*count), KM_SLEEP);
	for (i = 0; i < *count; i += 2) {
		used_io_mem_t *prev;
		if (used != NULL) {
			io[i] = used->start_addr;
			io[i+1] = used->length;
			prev = used;
			used = used->next;
			kmem_free(prev, sizeof (used_io_mem_t));
		}
	}
	(void) ndi_prop_update_int_array(DDI_DEV_T_NONE, usedrdip,
	    nodename, (int *)io, *count);
	kmem_free(io, sizeof (int)*(*count));
	*head = NULL;
}

/*
 * acpi_isa_device_enum() -- call from isa nexus driver
 * returns 1 if deviced enumeration is successful
 *         0 if deviced enumeration fails
 */
int
acpi_isa_device_enum(dev_info_t *isa_dip)
{
	char *acpi_prop;

	if (ddi_prop_lookup_string(DDI_DEV_T_ANY, ddi_root_node(),
	    DDI_PROP_DONTPASS, ACPI_ENUM_DEBUG, &acpi_prop) ==
	    DDI_PROP_SUCCESS) {
		long data;
		if (ddi_strtol(acpi_prop, NULL, 0, &data) == 0) {
			acpi_enum_debug = (unsigned long)data;
			e_ddi_prop_remove(DDI_DEV_T_NONE, ddi_root_node(),
			    ACPI_ENUM_DEBUG);
			e_ddi_prop_update_int(DDI_DEV_T_NONE,
			    ddi_root_node(), ACPI_ENUM_DEBUG, data);
		}
		ddi_prop_free(acpi_prop);
	}

	if (acpi_enum_debug & ISA_DEVICE_ENUM) {
		cmn_err(CE_NOTE, "acpi_isa_device_enum() called");
	}

	if (acpica_init() != AE_OK) {
		cmn_err(CE_WARN, "!isa_enum: init failed");
		/* Note, pickup by i8042 nexus */
		(void) e_ddi_prop_update_string(DDI_DEV_T_NONE,
		    ddi_root_node(), "acpi-enum", "off");
		return (0);
	}

	usedrdip = ddi_find_devinfo(USED_RESOURCES, -1, 0);
	if (usedrdip == NULL) {
		ndi_devi_alloc_sleep(ddi_root_node(), USED_RESOURCES,
		    (pnode_t)DEVI_SID_NODEID, &usedrdip);

	}

	process_master_file();

	/*
	 * Do the actual enumeration
	 */
	(void) AcpiGetDevices(NULL, isa_acpi_callback, isa_dip, 0);

	free_master_data();
	used_res_interrupts();
	used_res_dmas();
	used_res_io_mem("device-memory", &used_mem_count, &used_mem_head);
	used_res_io_mem("io-space", &used_io_count, &used_io_head);
	(void) ndi_devi_bind_driver(usedrdip, 0);

	return (1);
}