/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (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 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * s1394_hotplug.c
 *    1394 Services Layer Hotplug Routines
 *    This file contains routines that walk the old and topology
 *    trees, at bus reset time, creating devinfo's for new nodes and offlining
 *    nodes that are removed.
 */

#include <sys/conf.h>
#include <sys/sysmacros.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/sunndi.h>
#include <sys/modctl.h>
#include <sys/sunddi.h>
#include <sys/ddi_impldefs.h>
#include <sys/types.h>

#include <sys/tnf_probe.h>

#include <sys/1394/t1394.h>
#include <sys/1394/s1394.h>
#include <sys/1394/h1394.h>
#include <sys/1394/ieee1394.h>

static void s1394_send_remove_event(s1394_hal_t *hal, dev_info_t *dip,
    t1394_localinfo_t *localinfo);
static void s1394_send_insert_event(s1394_hal_t *hal, dev_info_t *dip,
    t1394_localinfo_t *localinfo);
static dev_info_t *s1394_create_devinfo(s1394_hal_t *hal, s1394_node_t *node,
    uint32_t *unit_dir, int nunit);
static void s1394_update_unit_dir_location(s1394_hal_t *hal, dev_info_t *tdip,
    uint_t offset);

/*
 * s1394_send_remove_event()
 *    Invokes any "remove event" callback registered for dip. Passes
 *    t1394_localinfo_t as impl_data for the callback.
 */
static void
s1394_send_remove_event(s1394_hal_t *hal, dev_info_t *dip,
    t1394_localinfo_t *localinfo)
{
	char name[128];
	ddi_eventcookie_t cookie;

	(void) sprintf(name, "%s%d", ddi_driver_name(dip),
	    ddi_get_instance(dip));

	TNF_PROBE_1_DEBUG(s1394_send_remove_event_enter,
	    S1394_TNF_SL_HOTPLUG_STACK, "", tnf_string, device,
	    name);

	if (ndi_event_retrieve_cookie(hal->hal_ndi_event_hdl, dip,
	    DDI_DEVI_REMOVE_EVENT, &cookie, NDI_EVENT_NOPASS)
	    == NDI_SUCCESS) {
		(void) ndi_event_run_callbacks(hal->hal_ndi_event_hdl, dip,
		    cookie, localinfo);
	}
	TNF_PROBE_0_DEBUG(s1394_send_remove_event_exit,
	    S1394_TNF_SL_HOTPLUG_STACK, "");
}

/*
 * s1394_send_insert_event()
 *    Invokes any "insert event" callback registered for dip. Passes
 *    t1394_localinfo_t as impl_data for the callback.
 */
static void
s1394_send_insert_event(s1394_hal_t *hal, dev_info_t *dip,
    t1394_localinfo_t *localinfo)
{
	char name[128];
	ddi_eventcookie_t cookie;

	(void) sprintf(name, "%s%d", ddi_driver_name(dip),
	    ddi_get_instance(dip));

	TNF_PROBE_1_DEBUG(s1394_send_insert_event_enter,
	    S1394_TNF_SL_HOTPLUG_STACK, "", tnf_string, device,
	    name);

	if (ndi_event_retrieve_cookie(hal->hal_ndi_event_hdl, dip,
	    DDI_DEVI_INSERT_EVENT, &cookie, NDI_EVENT_NOPASS) ==
	    NDI_SUCCESS)
		(void) ndi_event_run_callbacks(hal->hal_ndi_event_hdl, dip,
		    cookie, localinfo);

	TNF_PROBE_0_DEBUG(s1394_send_insert_event_exit,
	    S1394_TNF_SL_HOTPLUG_STACK, "");
}

/*
 * s1394_create_devinfo()
 *    This routine creates a devinfo corresponding to the unit_dir passed in.
 *    It adds "hp-node", "reg", "compatible" properties to the devinfo
 *    (formats for "reg" and "compatible" properties are specified by 1275
 *    binding for IEEE1394). If unable to create the devinfo and/or add the
 *    the properties, returns NULL, otherwise, returns the devinfo created.
 *
 *    NOTE: All ndi_* routines are interrupt callable (and thus won't sleep).
 *    So, we don't drop topology_mutex across ndi calls.
 */
static dev_info_t *
s1394_create_devinfo(s1394_hal_t *hal, s1394_node_t *node, uint32_t *unit_dir,
    int nunit)
{
	dev_info_t *hal_dip;
	uint32_t *root_dir;
	dev_info_t *target_dip;

	int root_dir_len;
	int result, i, j, spec_id, sw_version;
	int mod_ven, mod_hw, mod_spec, mod_sw;
	int node_ven, node_hw, node_spec, node_sw;

	/*LINTED type is unused*/
	uint32_t type __unused, key, value;
	uint32_t unit_spec_id, unit_sw_version;
	uint32_t node_spec_id, node_sw_version;
	uint32_t node_vendor_id, node_hw_version;
	uint32_t module_spec_id, module_sw_version;
	uint32_t module_vendor_id, module_hw_version;

	char *fmt = "firewire%06x,%06x";

	char *buf[5], data[5][24];
	uint32_t reg[6];

	ASSERT(MUTEX_HELD(&hal->topology_tree_mutex));

	TNF_PROBE_2_DEBUG(s1394_create_devinfo_enter,
	    S1394_TNF_SL_HOTPLUG_STACK, "", tnf_uint, guid_hi,
	    node->node_guid_hi, tnf_uint, guid_lo, node->node_guid_lo);

	hal_dip = hal->halinfo.dip;

	/* Allocate and init a new device node instance. */
	result = ndi_devi_alloc(hal_dip, "unit", (pnode_t)DEVI_SID_NODEID,
	    &target_dip);

	if (result != NDI_SUCCESS) {
		cmn_err(CE_NOTE, "!Unable to create devinfo"
		    " (node's GUID %08x%08x)", node->node_guid_hi,
		    node->node_guid_lo);
		TNF_PROBE_2(s1394_create_devinfo_fail_alloc,
		    S1394_TNF_SL_HOTPLUG_ERROR, "", tnf_uint, guid_hi,
		    node->node_guid_hi, tnf_uint, guid_lo, node->node_guid_lo);
		TNF_PROBE_0_DEBUG(s1394_create_devinfo_exit,
		    S1394_TNF_SL_HOTPLUG_STACK, "");
		return (NULL);
	}

	/* Add "hp-node" property */
	result = ndi_prop_update_int(DDI_DEV_T_NONE, target_dip, "hp-node", 0);
	if (result != NDI_SUCCESS) {
		cmn_err(CE_NOTE, "!Unable to add \"hp-node\" property"
		    " (node's GUID %08x%08x)", node->node_guid_hi,
		    node->node_guid_lo);
#if defined(DEBUG)
		cmn_err(CE_CONT, "!Error code %d", result);
#endif
		TNF_PROBE_3(s1394_create_devinfo_hp_node,
		    S1394_TNF_SL_HOTPLUG_ERROR, "", tnf_uint, guid_hi,
		    node->node_guid_hi, tnf_uint, guid_lo, node->node_guid_lo,
		    tnf_int, error, result);
		ndi_prop_remove_all(target_dip);
		(void) ndi_devi_free(target_dip);
		TNF_PROBE_0_DEBUG(s1394_create_devinfo_exit,
		    S1394_TNF_SL_HOTPLUG_STACK, "");
		return (NULL);
	}

	spec_id = sw_version = mod_ven = mod_hw = mod_spec = mod_sw =
	    node_ven = node_hw = node_spec = node_sw = 0;
	unit_sw_version = node_sw_version = node_hw_version =
	    module_sw_version = module_hw_version = 0;


	root_dir = CFGROM_ROOT_DIR(node->cfgrom);
	root_dir_len = CFGROM_DIR_LEN(root_dir);

	for (i = 0; i < root_dir_len; i++) {

		CFGROM_TYPE_KEY_VALUE(root_dir[i + 1], type, key, value);
		switch (key) {

		case IEEE1212_MODULE_VENDOR_ID:
			module_vendor_id = value;
			mod_ven++;
			break;
		case IEEE1212_MODULE_HW_VERSION:
			module_hw_version = value;
			mod_hw++;
			break;
		case IEEE1212_MODULE_SPEC_ID:
			module_spec_id = value;
			mod_spec++;
			break;
		case IEEE1212_MODULE_SW_VERSION:
			module_sw_version = value;
			mod_sw++;
			break;
		case IEEE1212_NODE_VENDOR_ID:
			node_vendor_id = value;
			node_ven++;
			break;
		case IEEE1212_NODE_UNIQUE_ID: {
				uint32_t *node_unique_leaf =
				    &root_dir[i + 1] + value;
				node_vendor_id = (node_unique_leaf[1] >> 8);
				node_ven++;
			}
			break;
		case IEEE1212_NODE_HW_VERSION:
			node_hw_version = value;
			node_hw++;
			break;
		case IEEE1212_NODE_SPEC_ID:
			node_spec_id = value;
			node_spec++;
			break;
		case IEEE1212_NODE_SW_VERSION:
			node_sw_version = value;
			node_sw++;
			break;
		}

		if (mod_ven && mod_hw && mod_spec && mod_sw && node_ven &&
		    node_hw && node_spec && node_sw) {
			break;
		}
	}

	/*
	 * Search for unit spec and version
	 */
	for (i = 0; i < CFGROM_DIR_LEN(unit_dir); i++) {

		CFGROM_TYPE_KEY_VALUE(unit_dir[i + 1], type, key, value);
		if (key == IEEE1212_UNIT_SPEC_ID) {

			unit_spec_id = value;
			spec_id++;
		} else if (key == IEEE1212_UNIT_SW_VERSION) {

			unit_sw_version = value;
			sw_version++;
		}
		if (spec_id && sw_version)
			break;
	}

	/*
	 * Refer to IEEE1212 (pages 90-92) for information regarding various
	 * id's. Module_Vendor_Id is required. Node_Vendor_Id is optional and
	 * if not implemented, its assumed value is Module_Vendor_Id.
	 * Module_Spec_Id is optional and if not implemented, its assumed value
	 * is Module_Vendor_Id. Node_Spec_Id is optional, and if not
	 * implemented, its assumed value is Node_Vendor_Id. Unit_Spec_Id is
	 * optional, and if not implemented, its assumed value is
	 * Node_Vendor_Id.
	 */
	if (node_ven == 0) {
		node_vendor_id = module_vendor_id;
		node_ven++;
	}

	if (node_spec == 0) {
		node_spec_id = node_vendor_id;
		node_spec++;
	}

	if (mod_spec == 0) {
		module_spec_id = module_vendor_id;
		mod_spec++;
	}

	if (spec_id == 0) {
		unit_spec_id = node_vendor_id;
		spec_id++;
	}

	i = 0;
	if (sw_version != 0) {
		buf[i] = data[i];
		(void) sprintf(data[i++], fmt, unit_spec_id, unit_sw_version);
	}
	if (node_sw != 0) {
		buf[i] = data[i];
		(void) sprintf(data[i++], fmt, node_spec_id, node_sw_version);
	}
	if (node_hw != 0) {
		buf[i] = data[i];
		(void) sprintf(data[i++], fmt, node_vendor_id, node_hw_version);
	}
	if (mod_sw != 0) {
		buf[i] = data[i];
		(void) sprintf(data[i++], fmt, module_spec_id,
		    module_sw_version);
	}
	if (mod_hw != 0) {
		buf[i] = data[i];
		(void) sprintf(data[i++], fmt, module_vendor_id,
		    module_hw_version);
	}

	result = ndi_prop_update_string_array(DDI_DEV_T_NONE, target_dip,
	    "compatible", (char **)&buf, i);
	if (result != NDI_SUCCESS) {
		cmn_err(CE_NOTE, "!Unable to add \"compatible\" property"
		    " (node's GUID %08x%08x)", node->node_guid_hi,
		    node->node_guid_lo);
#if defined(DEBUG)
		cmn_err(CE_CONT, "!Error code %d; nelements %d", result, i);
		for (j = 0; j < i; j++) {
			cmn_err(CE_CONT, "!buf[%d]: %s", j, buf[j]);
		}
#endif
		ndi_prop_remove_all(target_dip);
		(void) ndi_devi_free(target_dip);
		TNF_PROBE_4(s1394_create_devinfo_fail_compat,
		    S1394_TNF_SL_HOTPLUG_ERROR, "", tnf_uint, guid_hi,
		    node->node_guid_hi, tnf_uint, guid_lo, node->node_guid_lo,
		    tnf_int, error, result, tnf_int, nelements, i);
		TNF_PROBE_0_DEBUG(s1394_create_devinfo_exit,
		    S1394_TNF_SL_HOTPLUG_STACK, "");
		return (NULL);
	}

	for (j = 0; j < i; j++) {
		TNF_PROBE_2_DEBUG(s1394_create_devinfo_props,
		    S1394_TNF_SL_HOTPLUG_STACK, "",
		    tnf_int, compat_index, j,
		    tnf_string, compat_prop, buf[j]);
	}

	/* GUID,ADDR */
	reg[0] = node->node_guid_hi;
	reg[1] = node->node_guid_lo;
	s1394_cfgrom_parse_unit_dir(unit_dir, &reg[2], &reg[3], &reg[4],
	    &reg[5]);

	reg[3] = nunit;

	result = ndi_prop_update_int_array(DDI_DEV_T_NONE, target_dip, "reg",
	    (int *)reg, 6);
	if (result != NDI_SUCCESS) {
		cmn_err(CE_NOTE, "!Unable to add \"reg\" property");
#if defined(DEBUG)
		cmn_err(CE_CONT, "!Error code %d", result);
		for (j = 0; j < 6; j++) {
			cmn_err(CE_CONT, "!reg[%d]: 0x%08x", j, reg[j]);
		}
#endif
		ndi_prop_remove_all(target_dip);
		(void) ndi_devi_free(target_dip);
		TNF_PROBE_3(s1394_create_devinfo_fail_reg,
		    S1394_TNF_SL_HOTPLUG_ERROR, "", tnf_uint, guid_hi,
		    node->node_guid_hi, tnf_uint, guid_lo, node->node_guid_lo,
		    tnf_int, error, result);
		TNF_PROBE_0_DEBUG(s1394_create_devinfo_exit,
		    S1394_TNF_SL_HOTPLUG_STACK, "");
		return (NULL);
	}

	TNF_PROBE_1_DEBUG(s1394_create_devinfo_exit,
	    S1394_TNF_SL_HOTPLUG_STACK, "",
	    tnf_opaque, target_dip, target_dip);

	return (target_dip);
}

/*
 * s1394_devi_find()
 *    Searches all children of pdip for a match of name@caddr. Builds the
 *    name and address of each child node by looking up the reg property on
 *    the node and compares the built name@addr with the name@addr passed in.
 *    Returns the child dip if a match is found, otherwise, returns NULL.
 *    NOTE:
 *    This routine is decidedly non-ddi. We had to use this one since
 *    ndi_devi_find() can find only nodes that have valid addr field
 *    set and that won't happen unless the node goes through INITCHILD
 *    (at which time nx1394.c calls ddi_set_name_addr()). If, in future,
 *    the ndi_devi_find() provides a way of looking up nodes using criteria
 *    other than addr, we can get rid of this routine.
 */
/*ARGSUSED*/
dev_info_t *
s1394_devi_find(dev_info_t *pdip, char *name, char *caddr)
{
	int i, reglen;
	char addr[32];
	uint32_t *regptr;
	dev_info_t *cdip = NULL;

	ASSERT((name != NULL) && (caddr != NULL));

	TNF_PROBE_1_DEBUG(s1394_devi_find_enter, S1394_TNF_SL_HOTPLUG_STACK,
	    "", tnf_string, addr, caddr);

	/*
	 * for each child of this parent, find name and addr and match with
	 * name and caddr passed in.
	 */
	for (cdip = (dev_info_t *)DEVI(pdip)->devi_child; cdip != NULL;
	    cdip = (dev_info_t *)DEVI(cdip)->devi_sibling) {

		i = ddi_prop_lookup_int_array(DDI_DEV_T_ANY, cdip,
		    DDI_PROP_DONTPASS, "reg", (int **)&regptr,
		    (uint_t *)&reglen);

		if (i != DDI_PROP_SUCCESS)
			continue;

		/*
		 * Construct addr from the reg property (addr is of the format
		 * GGGGGGGGGGGGGGGG[,AAAAAAAAAAAA], where GGGGGGGGGGGGGGGG is
		 * the address and AAAAAAAAAAAA is the optional unit address)
		 */
		if (regptr[2] != NULL || regptr[3] != NULL) {
			(void) sprintf(addr, "%08x%08x,%04x%08x", regptr[0],
			    regptr[1], regptr[2], regptr[3]);
		} else {
			(void) sprintf(addr, "%08x%08x", regptr[0], regptr[1]);
		}
		ddi_prop_free(regptr);

		if (strcmp(caddr, addr) == 0) {
			ASSERT(strcmp(ddi_node_name(cdip), name) == 0);
			break;
		}
	}

	if (cdip == NULL) {
		TNF_PROBE_1(s1394_devi_find_no_match,
		    S1394_TNF_SL_HOTPLUG_ERROR, "", tnf_string, addr, caddr);
	}

	TNF_PROBE_0_DEBUG(s1394_devi_find_exit, S1394_TNF_SL_HOTPLUG_STACK, "");

	return (cdip);
}

/*
 * s1394_update_devinfo_tree()
 *    Parses the config rom for the passed in node and creates/updates devinfo's
 *    for each unit directory found. If the devinfo corresponding to a unit
 *    already exists, any insert event callbacks registered for that devinfo
 *    are called (topology tree is unlocked and relocked around these
 *    callbacks). Returns DDI_SUCCESS if everything went fine and DDI_FAILURE
 *    if unable to reacquire the lock after callbacks (relock fails because of
 *    an intervening bus reset or if the services layer kills the bus reset
 *    thread). The node is marked as parsed before returning.
 */
int
s1394_update_devinfo_tree(s1394_hal_t *hal, s1394_node_t *node)
{
	dev_info_t *tdip;
	int j, units, d, lockfail = 0;
	s1394_target_t *target, *t;
	uint32_t hi, lo, size_hi, size_lo, type, key, value;
	uint32_t *ptr, *root_dir, dir_len;
	t1394_localinfo_t linfo;

	uint32_t *unit_dir_ptrs[32];
	dev_info_t *devinfo_ptrs[32];
	uint32_t new_devinfo = 0;	/* to keep track of new allocations */

	char caddr[32];

	ASSERT(MUTEX_HELD(&hal->topology_tree_mutex));

	ASSERT(CFGROM_PARSED(node) == B_FALSE);
	ASSERT(node->cfgrom != NULL);

	TNF_PROBE_2_DEBUG(s1394_update_devinfo_tree_enter,
	    S1394_TNF_SL_HOTPLUG_STACK, "", tnf_int, node_num,
	    node->node_num, tnf_opaque, cfgrom, node->cfgrom);

	/* scan through config rom looking for unit dirs */
	root_dir = CFGROM_ROOT_DIR(node->cfgrom);

	if (node->cfgrom_valid_size < CFGROM_DIR_LEN(root_dir))
		dir_len = node->cfgrom_valid_size;
	else
		dir_len = CFGROM_DIR_LEN(root_dir);

	CFGROM_TYPE_KEY_VALUE(root_dir[0], type, key, value);
	if (s1394_valid_dir(hal, node, key, root_dir) == B_FALSE) {
		cmn_err(CE_NOTE,
		    "!Bad root directory in config rom (node's GUID %08x%08x)",
		    node->node_guid_hi, node->node_guid_lo);

		TNF_PROBE_1_DEBUG(s1394_update_devinfo_tree_exit,
		    S1394_TNF_SL_HOTPLUG_STACK, "", tnf_string, msg,
		    "bad directory");

		SET_CFGROM_PARSED(node);
		CLEAR_CFGROM_GEN_CHANGED(node);	/* if set */
		CLEAR_CFGROM_NEW_ALLOC(node);

		return (DDI_SUCCESS);
	}

	for (units = 0, j = 1; j <= dir_len; j++) {
		CFGROM_TYPE_KEY_VALUE(root_dir[j], type, key, value);
		if (key == IEEE1212_UNIT_DIRECTORY && type ==
		    IEEE1212_DIRECTORY_TYPE) {
			ptr = &root_dir[j] + value;
			if (s1394_valid_dir(hal, node, key, ptr) == B_TRUE) {
				unit_dir_ptrs[units++] = ptr;
			} else {
				cmn_err(CE_NOTE, "!Bad unit directory in config"
				    " rom (node's GUID %08x%08x)",
				    node->node_guid_hi, node->node_guid_lo);
				TNF_PROBE_2(s1394_update_devinfo_tree_bad_dir,
				    S1394_TNF_SL_HOTPLUG_ERROR, "", tnf_uint,
				    guid_hi, node->node_guid_hi, tnf_uint,
				    guid_lo, node->node_guid_lo);
			}
		}
	}

	for (d = 0, j = 0; j < units; j++) {

		s1394_cfgrom_parse_unit_dir(unit_dir_ptrs[j],
		    &hi, &lo, &size_hi, &size_lo);

		lo = j;

		if (hi || lo) {
			(void) sprintf(caddr, "%08x%08x,%04x%08x",
			    node->node_guid_hi, node->node_guid_lo, hi, lo);
		} else {
			(void) sprintf(caddr, "%08x%08x",
			    node->node_guid_hi, node->node_guid_lo);
		}

		tdip = s1394_devi_find(hal->halinfo.dip, "unit", caddr);
		if (tdip != NULL) {

			rw_enter(&hal->target_list_rwlock, RW_WRITER);
			target = s1394_target_from_dip_locked(hal, tdip);
			if (target != NULL) {
				target->target_sibling = NULL;
				target->on_node = node;
				target->target_state &= ~S1394_TARG_GONE;
				target->unit_dir = unit_dir_ptrs[j] - root_dir;

				if ((t = node->target_list) != NULL) {
					ASSERT(t != target);
					while (t->target_sibling != NULL) {
						t = t->target_sibling;
						ASSERT(t != target);
					}
					t->target_sibling = target;
				} else {
					node->target_list = target;
				}

				target->target_list = node->target_list;
			}
			rw_exit(&hal->target_list_rwlock);

			s1394_update_unit_dir_location(hal, tdip,
			    unit_dir_ptrs[j] - root_dir);

		} else {
			/* create devinfo for unit@caddr */
			tdip = s1394_create_devinfo(hal, node,
			    unit_dir_ptrs[j], j);
			if (tdip != NULL) {
				new_devinfo |= (1 << d);
				s1394_update_unit_dir_location(hal, tdip,
				    unit_dir_ptrs[j] - root_dir);
			}
		}
		if (tdip != NULL)
			devinfo_ptrs[d++] = tdip;
	}

	ASSERT(MUTEX_HELD(&hal->topology_tree_mutex));
	/* Online all valid units */
	for (j = 0; j < d; j++) {
		if ((new_devinfo & (1 << j)) == 0) {
			linfo.bus_generation = hal->generation_count;
			linfo.local_nodeID = hal->node_id;
		}
		/* don't need to drop topology_tree_mutex across ndi calls */
		(void) ndi_devi_online_async(devinfo_ptrs[j], 0);
		if ((new_devinfo & (1 << j)) == 0) {
			/*
			 * send an insert event if this an existing devinfo.
			 * drop and reacquire topology_tree_mutex across
			 * the event calls
			 */
			s1394_unlock_tree(hal);
			s1394_send_insert_event(hal, devinfo_ptrs[j], &linfo);
			if (s1394_lock_tree(hal) != DDI_SUCCESS) {
				TNF_PROBE_4(s1394_update_devinfo_tree_lock_fail,
				    S1394_TNF_SL_HOTPLUG_ERROR, "",
				    tnf_int, node_num, node->node_num,
				    tnf_opaque, cfgrom, node->cfgrom,
				    tnf_int, unit, j,
				    tnf_opaque, devinfo, devinfo_ptrs[j]);
				lockfail = 1;
				break;
			}
		}
	}

	if (lockfail) {
		TNF_PROBE_0_DEBUG(s1394_update_devinfo_tree_exit,
		    S1394_TNF_SL_HOTPLUG_ERROR, "");
		return (DDI_FAILURE);
	}

	SET_CFGROM_PARSED(node);
	CLEAR_CFGROM_GEN_CHANGED(node);	/* if set */
	CLEAR_CFGROM_NEW_ALLOC(node);

	TNF_PROBE_0_DEBUG(s1394_update_devinfo_tree_exit,
	    S1394_TNF_SL_HOTPLUG_STACK, "");

	return (DDI_SUCCESS);
}

/*
 * s1394_offline_node()
 *    Offlines a node. This involves marking all targets attached to the
 *    node as gone, invoking any remove event callbacks and calling
 *    ndi_devi_offline to mark the devinfo as OFFLINE (for each unit
 *    directory on the node). The tree is unlocked and relocked around
 *    the callbacks. If unable to relock the tree, DDI_FAILURE, else
 *    returns DDI_SUCCESS.
 */
int
s1394_offline_node(s1394_hal_t *hal, s1394_node_t *node)
{
	s1394_target_t *t;
	dev_info_t *tdip;
	int j, d, units;
	uint32_t *unit_dir_ptrs[32];
	dev_info_t *devinfo_ptrs[32];
	t1394_localinfo_t linfo;
	uint_t node_num;
	uint32_t *ptr, *root_dir, dir_len;
	uint32_t hi, lo, size_hi, size_lo, type, key, value;
	char caddr[32];

	node_num = node->node_num;

	TNF_PROBE_1_DEBUG(s1394_offline_node_enter, S1394_TNF_SL_HOTPLUG_STACK,
	    "", tnf_uint, node_num, node_num);

	ASSERT(MUTEX_HELD(&hal->topology_tree_mutex));

	d = 0;
	rw_enter(&hal->target_list_rwlock, RW_WRITER);
	t = node->target_list;
	while (t != NULL) {
		TNF_PROBE_2(s1394_process_old_tree_mark,
		    S1394_TNF_SL_HOTPLUG_STACK, "", tnf_int, node_num, node_num,
		    tnf_opaque, target, t);
		t->target_state |= S1394_TARG_GONE;
		t->on_node = NULL;
		t = t->target_sibling;
	}
	rw_exit(&hal->target_list_rwlock);

	/* scan through config rom looking for unit dirs */
	root_dir = CFGROM_ROOT_DIR(node->cfgrom);

	if (node->cfgrom_valid_size < CFGROM_DIR_LEN(root_dir))
		dir_len = node->cfgrom_valid_size;
	else
		dir_len = CFGROM_DIR_LEN(root_dir);

	CFGROM_TYPE_KEY_VALUE(root_dir[0], type, key, value);

	for (units = 0, j = 1; j <= dir_len; j++) {
		CFGROM_TYPE_KEY_VALUE(root_dir[j], type, key, value);
		if (key == IEEE1212_UNIT_DIRECTORY && type ==
		    IEEE1212_DIRECTORY_TYPE) {
			ptr = &root_dir[j] + value;
			if (s1394_valid_dir(hal, node, key, ptr) == B_TRUE) {
				unit_dir_ptrs[units++] = ptr;
			}
		}
	}

	for (d = 0, j = 0; j < units; j++) {

		s1394_cfgrom_parse_unit_dir(unit_dir_ptrs[j],
		    &hi, &lo, &size_hi, &size_lo);

		lo = j;

		if (hi || lo) {
			(void) sprintf(caddr, "%08x%08x,%04x%08x",
			    node->node_guid_hi, node->node_guid_lo, hi, lo);
		} else {
			(void) sprintf(caddr, "%08x%08x",
			    node->node_guid_hi, node->node_guid_lo);
		}

		if ((tdip = s1394_devi_find(hal->halinfo.dip, "unit", caddr)) !=
		    NULL)
			devinfo_ptrs[d++] = tdip;
	}

	node->old_node = NULL;

	linfo.bus_generation = hal->generation_count;
	linfo.local_nodeID = hal->node_id;

	for (j = 0; j < d; j++) {
		s1394_unlock_tree(hal);

		TNF_PROBE_2(s1394_offline_node,
		    S1394_TNF_SL_HOTPLUG_STACK, "", tnf_int, node_num, node_num,
		    tnf_opaque, devinfo, devinfo_ptrs[j]);

		s1394_send_remove_event(hal, devinfo_ptrs[j], &linfo);
		(void) ndi_devi_offline(devinfo_ptrs[j], NDI_DEVI_REMOVE);
		if (s1394_lock_tree(hal) != DDI_SUCCESS) {
			TNF_PROBE_2(s1394_offline_node,
			    S1394_TNF_SL_HOTPLUG_ERROR, "", tnf_string, msg,
			    "unlock to relock tree", tnf_uint, node_num,
			    node_num);
			TNF_PROBE_0_DEBUG(s1394_offline_node_exit,
			    S1394_TNF_SL_HOTPLUG_STACK, "");
			return (DDI_FAILURE);
		}
	}

	ASSERT(MUTEX_HELD(&hal->topology_tree_mutex));
	TNF_PROBE_0_DEBUG(s1394_offline_node_exit, S1394_TNF_SL_HOTPLUG_STACK,
	    "");
	return (DDI_SUCCESS);
}

/*
 * s1394_process_topology_tree()
 *    Walks the topology tree, processing each node. If node that has
 *    already been parsed, updates the generation property on all devinfos
 *    for the node. Also, if the node exists in both old & new trees, ASSERTS
 *    that both point to the same config rom. If the node has valid config
 *    rom but hasn't been parsed yet, calls s1394_update_devinfo_tree()
 *    to parse and create devinfos for the node. Kicks off further config
 *    rom reading if only the bus info block for the node is read.
 *    Returns DDI_SUCCESS if everything went fine, else returns DDI_FAILURE
 *    (for eg. unable to reacquire the tree lock etc). wait_for_cbs argument
 *    tells the caller if some completions can be expected. wait_gen tells
 *    the generation the commands were issued at.
 */
int
s1394_process_topology_tree(s1394_hal_t *hal, int *wait_for_cbs,
    uint_t *wait_gen)
{
	int i;
	uint_t hal_node_num, number_of_nodes;
	s1394_node_t *node, *onode;
	s1394_status_t status;

	ASSERT(MUTEX_NOT_HELD(&hal->topology_tree_mutex));

	TNF_PROBE_0_DEBUG(s1394_process_topology_tree_enter,
	    S1394_TNF_SL_HOTPLUG_STACK, "");

	if (s1394_lock_tree(hal) != DDI_SUCCESS) {
		TNF_PROBE_0(s1394_process_topology_tree_lock_failed,
		    S1394_TNF_SL_HOTPLUG_ERROR, "");
		TNF_PROBE_0_DEBUG(s1394_process_topology_tree_exit,
		    S1394_TNF_SL_HOTPLUG_STACK, "");
		return (DDI_FAILURE);
	}

	hal_node_num = IEEE1394_NODE_NUM(hal->node_id);
	hal->cfgroms_being_read = 0;
	number_of_nodes = hal->number_of_nodes;
	s1394_unlock_tree(hal);

	for (i = 0; i < number_of_nodes; i++) {

		if (i == hal_node_num)
			continue;
		if (s1394_lock_tree(hal) != DDI_SUCCESS) {
			return (DDI_FAILURE);
		}
		node = &hal->topology_tree[i];

		TNF_PROBE_4_DEBUG(s1394_process_topology_tree,
		    S1394_TNF_SL_HOTPLUG_STACK, "",
		    tnf_int, node_num, i,
		    tnf_int, parsed, CFGROM_PARSED(node),
		    tnf_int, matched, NODE_MATCHED(node),
		    tnf_int, visited, NODE_VISITED(node));

		if (LINK_ACTIVE(node) == B_FALSE) {
			s1394_unlock_tree(hal);
			continue;
		}
		if (node->cfgrom == NULL) {
			s1394_unlock_tree(hal);
			continue;
		}

		onode = node->old_node;

		if (onode != NULL && onode->cfgrom != NULL && node->cfgrom !=
		    NULL) {
			/*
			 * onode->cfgrom != node->cfgrom should have been
			 * handled by s1394_match_GUID()!!!
			 */
			if (onode->cfgrom != node->cfgrom)
				TNF_PROBE_5(s1394_process_topology_tree_err,
				    S1394_TNF_SL_HOTPLUG_ERROR, "",
				    tnf_int, node_num, i, tnf_int, gen_changed,
				    CFGROM_GEN_CHANGED(node), tnf_int, parsed,
				    CFGROM_PARSED(node), tnf_opaque, old_cfgrom,
				    onode->cfgrom, tnf_opaque, new_cfgrom,
				    node->cfgrom);
			ASSERT(onode->cfgrom == node->cfgrom);
		}

		if (CFGROM_PARSED(node) == B_FALSE && CFGROM_ALL_READ(node) ==
		    B_TRUE) {
			ASSERT((node->cfgrom_size <
			    IEEE1394_CONFIG_ROM_QUAD_SZ) ||
			    NODE_MATCHED(node) == B_TRUE);
			rw_enter(&hal->target_list_rwlock, RW_READER);
			ASSERT(node->target_list == NULL);
			rw_exit(&hal->target_list_rwlock);
			if (s1394_update_devinfo_tree(hal, node) ==
			    DDI_FAILURE) {
				ASSERT(MUTEX_NOT_HELD(
				    &hal->topology_tree_mutex));
				TNF_PROBE_1(s1394_process_topology_tree,
				    S1394_TNF_SL_HOTPLUG_ERROR, "", tnf_string,
				    msg, "failure from update devinfo");
				TNF_PROBE_0_DEBUG(
				    s1394_process_topology_tree_exit,
				    S1394_TNF_SL_HOTPLUG_STACK, "");
				return (DDI_FAILURE);
			}
		} else if (CFGROM_PARSED(node) == B_FALSE && CFGROM_BIB_READ(
		    node) == B_TRUE) {
			if (s1394_read_rest_of_cfgrom(hal, node, &status) !=
			    DDI_SUCCESS) {
				TNF_PROBE_1(s1394_process_topology_tree,
				    S1394_TNF_SL_HOTPLUG_ERROR, "", tnf_string,
				    msg, "failure reading rest of cfgrom");
				if ((status & S1394_LOCK_FAILED) == 0) {
					ASSERT(MUTEX_HELD(&hal->
					    topology_tree_mutex));
					*wait_for_cbs = 0;
					s1394_unlock_tree(hal);
				}
				TNF_PROBE_0_DEBUG(
				    s1394_process_topology_tree_exit,
				    S1394_TNF_SL_HOTPLUG_STACK, "");
				return (DDI_FAILURE);
			} else {
				*wait_for_cbs = 1;
				*wait_gen = hal->br_cfgrom_read_gen;
			}
		}

		s1394_unlock_tree(hal);
	}

	/*
	 * flag the tree as processed; if a single bus reset happens after
	 * this, we will use tree matching.
	 */
	if (s1394_lock_tree(hal) != DDI_SUCCESS) {
		TNF_PROBE_1(s1394_process_topology_tree,
		    S1394_TNF_SL_HOTPLUG_ERROR, "", tnf_string,
		    msg, "relock failed while marking tree processed");
		TNF_PROBE_0_DEBUG(s1394_process_topology_tree_exit,
		    S1394_TNF_SL_HOTPLUG_STACK, "");
		return (DDI_FAILURE);
	}
	hal->topology_tree_processed = B_TRUE;
	s1394_unlock_tree(hal);

	TNF_PROBE_1_DEBUG(s1394_process_topology_tree_exit,
	    S1394_TNF_SL_HOTPLUG_STACK, "", tnf_int, hal_instance,
	    ddi_get_instance(hal->halinfo.dip));

	return (DDI_SUCCESS);
}

/*
 * s1394_process_old_tree()
 *    Walks through the old tree and offlines nodes that are removed. Nodes
 *    with an active link in the old tree but link powered off in the current
 *    generation are also offlined, as well as nodes with invalid config
 *    rom in current generation.
 *    The topology tree is locked/unlocked while walking through all the nodes;
 *    if the locking fails at any stage, stops further walking and returns
 *    DDI_FAILURE. Returns DDI_SUCCESS if everything went fine.
 */
int
s1394_process_old_tree(s1394_hal_t *hal)
{
	int i;
	uint_t hal_node_num_old, old_number_of_nodes;
	s1394_node_t *onode;

	TNF_PROBE_0_DEBUG(s1394_process_old_tree_enter,
	    S1394_TNF_SL_HOTPLUG_STACK, "");

	/*
	 * NODE_MATCHED(onode) == 0 indicates this node doesn't exist
	 * any more.
	 */
	ASSERT(MUTEX_NOT_HELD(&hal->topology_tree_mutex));

	if (s1394_lock_tree(hal) != DDI_SUCCESS) {
		TNF_PROBE_0(s1394_process_old_tree_lock_failed,
		    S1394_TNF_SL_HOTPLUG_ERROR, "");
		TNF_PROBE_0_DEBUG(s1394_process_old_tree_exit,
		    S1394_TNF_SL_HOTPLUG_STACK, "");
		return (DDI_FAILURE);
	}
	hal_node_num_old = IEEE1394_NODE_NUM(hal->old_node_id);
	old_number_of_nodes = hal->old_number_of_nodes;
	s1394_unlock_tree(hal);

	for (i = 0; i < old_number_of_nodes; i++) {

		if (i == hal_node_num_old)
			continue;
		if (s1394_lock_tree(hal) != DDI_SUCCESS) {
			TNF_PROBE_2(s1394_process_old_tree,
			    S1394_TNF_SL_HOTPLUG_ERROR, "", tnf_string, msg,
			    "lock failed while processing node", tnf_uint,
			    node_num, i);
			TNF_PROBE_0_DEBUG(s1394_process_old_tree_exit,
			    S1394_TNF_SL_HOTPLUG_STACK, "");
			return (DDI_FAILURE);
		}

		onode = &hal->old_tree[i];

		if (onode->cfgrom == NULL) {
			CLEAR_CFGROM_STATE(onode);
			s1394_unlock_tree(hal);
			continue;
		}

		TNF_PROBE_1_DEBUG(s1394_process_old_tree,
		    S1394_TNF_SL_HOTPLUG_STACK, "", tnf_opaque,
		    cfgrom, onode->cfgrom);

		TNF_PROBE_5_DEBUG(s1394_process_old_tree,
		    S1394_TNF_SL_HOTPLUG_STACK, "", tnf_int,
		    node_num, i, tnf_int, parsed, CFGROM_PARSED(onode), tnf_int,
		    matched, NODE_MATCHED(onode), tnf_int, visited,
		    NODE_VISITED(onode), tnf_int, generation_changed,
		    CFGROM_GEN_CHANGED(onode));

		/*
		 * onode->cur_node == NULL iff we couldn't read cfgrom in the
		 * current generation in non-tree matching case (and thus
		 * match_GUIDs couldn't set cur_node).
		 */
		if (NODE_MATCHED(onode) == B_FALSE || (onode->cur_node ==
		    NULL || ((CFGROM_VALID(onode) == B_TRUE &&
		    CFGROM_VALID(onode->cur_node) == B_FALSE) ||
		    (LINK_ACTIVE(onode) == B_TRUE && LINK_ACTIVE(onode->
		    cur_node) == B_FALSE)))) {

			if (onode->cur_node != NULL && CFGROM_VALID(onode) ==
			    B_TRUE &&
			    CFGROM_VALID(onode->cur_node) == B_FALSE) {
				TNF_PROBE_1_DEBUG(
				    s1394_process_old_tree_invalid_cfgrom,
				    S1394_TNF_SL_HOTPLUG_STACK, "",
				    tnf_int, node_num, i);
			}
			if (onode->cur_node != NULL && LINK_ACTIVE(onode) ==
			    B_TRUE && LINK_ACTIVE(onode->cur_node) == B_FALSE) {
				TNF_PROBE_1_DEBUG(
				    s1394_process_old_tree_link_off,
				    S1394_TNF_SL_HOTPLUG_STACK,
				    "", tnf_int, node_num, i);
			}
			if (s1394_offline_node(hal, onode) != DDI_SUCCESS) {
				TNF_PROBE_2(s1394_process_old_tree,
				    S1394_TNF_SL_HOTPLUG_ERROR, "", tnf_string,
				    msg, "failure from offline node", tnf_uint,
				    node_num, i);
				TNF_PROBE_0_DEBUG(s1394_process_old_tree_exit,
				    S1394_TNF_SL_HOTPLUG_STACK, "");
				return (DDI_FAILURE);
			}
			s1394_free_cfgrom(hal, onode, S1394_FREE_CFGROM_OLD);
		}

		s1394_unlock_tree(hal);
	}

	ASSERT(MUTEX_NOT_HELD(&hal->topology_tree_mutex));

	TNF_PROBE_0_DEBUG(s1394_process_old_tree_exit,
	    S1394_TNF_SL_HOTPLUG_STACK, "");

	return (DDI_SUCCESS);
}

/*
 * s1394_update_unit_dir_location()
 *    Updates the unit-dir-offset property on the devinfo.
 *    NOTE: ndi_prop_update_int() is interrupt callable (and thus won't block);
 *    so, the caller doesn't drop topology_tree_mutex when calling this routine.
 */
/*ARGSUSED*/
static void
s1394_update_unit_dir_location(s1394_hal_t *hal, dev_info_t *tdip,
    uint_t offset)
{
	ASSERT(MUTEX_HELD(&hal->topology_tree_mutex));
	ASSERT(tdip != NULL);

	TNF_PROBE_1_DEBUG(s1394_update_unit_dir_location_enter,
	    S1394_TNF_SL_HOTPLUG_STACK, "", tnf_uint, offset, offset);
	(void) ndi_prop_update_int(DDI_DEV_T_NONE, tdip, "unit-dir-offset",
	    offset);
	TNF_PROBE_0_DEBUG(s1394_update_unit_dir_location_exit,
	    S1394_TNF_SL_HOTPLUG_STACK, "");
}

/*
 * s1394_add_target_to_node()
 *    adds target to the list of targets hanging off the node. Figures out
 *    the node by searching the topology tree for the GUID corresponding
 *    to the target. Points on_node field of target structure at the node.
 */
void
s1394_add_target_to_node(s1394_target_t *target)
{
	s1394_target_t *t;
	s1394_hal_t *hal;
	uint32_t guid_hi;
	uint32_t guid_lo;
	int i;
	char name[MAXNAMELEN];
	char *ptr;

	TNF_PROBE_0_DEBUG(s1394_add_target_to_node_enter,
	    S1394_TNF_SL_HOTPLUG_STACK, "");

	hal = target->on_hal;
	ASSERT(hal != NULL);

	/* Topology tree must be locked when it gets here! */
	ASSERT(MUTEX_HELD(&hal->topology_tree_mutex));

	/* target_list_rwlock should be held in write mode */
	ASSERT(rw_read_locked(&target->on_hal->target_list_rwlock) == 0);

	if ((ptr = ddi_get_name_addr(target->target_dip)) == NULL) {
		TNF_PROBE_0_DEBUG(s1394_add_target_to_node_exit_no_name,
		    S1394_TNF_SL_HOTPLUG_STACK, "");
		return;
	}

	(void) sprintf(name, ptr);
	/* Drop the ,<ADDR> part, if present */
	if ((ptr = strchr(name, ',')) != NULL)
		*ptr = '\0';

	ptr = name;
	guid_hi = s1394_stoi(ptr, 8, 16);
	guid_lo = s1394_stoi(ptr + 8, 8, 16);

	/* Search the HAL's node list for this GUID */
	for (i = 0; i < hal->number_of_nodes; i++) {
		if (CFGROM_VALID(&hal->topology_tree[i]) == B_TRUE) {
			ASSERT(hal->topology_tree[i].cfgrom != NULL);

			if ((hal->topology_tree[i].node_guid_hi == guid_hi) &&
			    (hal->topology_tree[i].node_guid_lo == guid_lo)) {
				target->on_node = &hal->topology_tree[i];
				if ((t = hal->topology_tree[i].target_list) !=
				    NULL) {
					ASSERT(t != target);
					while (t->target_sibling != NULL) {
						t = t->target_sibling;
						ASSERT(t != target);
					}
					t->target_sibling = target;
				} else {
					hal->topology_tree[i].target_list =
					    target;
				}

				/*
				 * update target_list in all targets on the
				 * node
				 */
				t = hal->topology_tree[i].target_list;
				while (t != NULL) {
					t->target_list =
					    hal->topology_tree[i].target_list;
					t = t->target_sibling;
				}
				break;
			}
		}
	}

	TNF_PROBE_0_DEBUG(s1394_add_target_to_node_exit,
	    S1394_TNF_SL_HOTPLUG_STACK, "");
}

/*
 * s1394_remove_target_from_node()
 *    Removes target from the corresponding node's target_list.
 */
void
s1394_remove_target_from_node(s1394_target_t *target)
{
	s1394_target_t *t, *t1;
	s1394_hal_t *hal;

	TNF_PROBE_0_DEBUG(s1394_remove_target_from_node_enter,
	    S1394_TNF_SL_HOTPLUG_STACK, "");

	hal = target->on_hal;
	ASSERT(hal != NULL);

	/* Topology tree must be locked when it gets here! */
	ASSERT(MUTEX_HELD(&hal->topology_tree_mutex));

	/* target_list_rwlock should be held in write mode */
	ASSERT(rw_read_locked(&target->on_hal->target_list_rwlock) == 0);

	if (target->on_node == NULL) {
		TNF_PROBE_1_DEBUG(s1394_remove_target_from_node_NULL,
		    S1394_TNF_SL_HOTPLUG_STACK, "",
		    tnf_uint, target_state, target->target_state);
	}

	t = target->target_list;
	t1 = NULL;
	while (t != NULL) {
		if (t == target) {
			if (t1 == NULL) {
				target->target_list = t->target_sibling;
			} else {
				t1->target_sibling = t->target_sibling;
			}
			break;
		}
		t1 = t;
		t = t->target_sibling;
	}
	/* Update the target_list pointer in all the targets */
	if (target->on_node != NULL)
		target->on_node->target_list = target->target_list;

	t = t1 = target->target_list;
	while (t != NULL) {
		t->target_list = t1;
		t = t->target_sibling;
	}

	target->on_node = NULL;
	target->target_sibling = NULL;

	TNF_PROBE_0_DEBUG(s1394_remove_target_from_node_exit,
	    S1394_TNF_SL_HOTPLUG_STACK, "");
}