/*
 * 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
 * http://www.opensource.org/licenses/cddl1.txt.
 * 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 (c) 2004-2012 Emulex. All rights reserved.
 * Use is subject to license terms.
 * Copyright 2020 RackTop Systems, Inc.
 */

#define	DEF_EVENT_STRUCT  /* Needed for emlxs_events.h in emlxs_event.h */
#include <emlxs.h>


/* Required for EMLXS_CONTEXT in EMLXS_MSGF calls */
EMLXS_MSG_DEF(EMLXS_EVENT_C);


static uint32_t emlxs_event_check(emlxs_port_t *port, emlxs_event_t *evt);
static void emlxs_event_destroy(emlxs_hba_t *hba, emlxs_event_entry_t *entry);

extern void
emlxs_null_func() {}


static uint32_t
emlxs_event_check(emlxs_port_t *port, emlxs_event_t *evt)
{
	emlxs_hba_t *hba = HBA;

	/* Check if the event is being requested */
	if ((hba->event_mask & evt->mask)) {
		return (1);
	}

#ifdef SAN_DIAG_SUPPORT
	if ((port->sd_event_mask & evt->mask)) {
		return (1);
	}
#endif /* SAN_DIAG_SUPPORT */

	return (0);

} /* emlxs_event_check() */


extern uint32_t
emlxs_event_queue_create(emlxs_hba_t *hba)
{
	emlxs_event_queue_t *eventq = &EVENTQ;
	ddi_iblock_cookie_t iblock;

	/* Clear the queue */
	bzero(eventq, sizeof (emlxs_event_queue_t));

	cv_init(&eventq->lock_cv, NULL, CV_DRIVER, NULL);

	if (!(hba->intr_flags & EMLXS_MSI_ENABLED)) {
		/* Get the current interrupt block cookie */
		(void) ddi_get_iblock_cookie(hba->dip, (uint_t)EMLXS_INUMBER,
		    &iblock);

		/* Create the mutex lock */
		mutex_init(&eventq->lock, NULL, MUTEX_DRIVER, (void *)iblock);
	}
#ifdef  MSI_SUPPORT
	else {
		/* Create event mutex lock */
		mutex_init(&eventq->lock, NULL, MUTEX_DRIVER,
		    DDI_INTR_PRI(hba->intr_arg));
	}
#endif

	return (1);

} /* emlxs_event_queue_create() */


extern void
emlxs_event_queue_destroy(emlxs_hba_t *hba)
{
	emlxs_port_t *vport;
	emlxs_event_queue_t *eventq = &EVENTQ;
	uint32_t i;
	uint32_t wakeup = 0;

	mutex_enter(&eventq->lock);

	/* Clear all event masks and broadcast a wakeup */
	/* to clear any sleeping threads */
	if (hba->event_mask) {
		hba->event_mask = 0;
		hba->event_timer = 0;
		wakeup = 1;
	}

	for (i = 0; i < MAX_VPORTS; i++) {
		vport = &VPORT(i);

		if (vport->sd_event_mask) {
			vport->sd_event_mask = 0;
			wakeup = 1;
		}
	}

	if (wakeup) {
		cv_broadcast(&eventq->lock_cv);

		mutex_exit(&eventq->lock);
		BUSYWAIT_MS(10);
		mutex_enter(&eventq->lock);
	}

	/* Destroy the remaining events */
	while (eventq->first) {
		emlxs_event_destroy(hba, eventq->first);
	}

	mutex_exit(&eventq->lock);

	/* Destroy the queue lock */
	mutex_destroy(&eventq->lock);
	cv_destroy(&eventq->lock_cv);

	/* Clear the queue */
	bzero(eventq, sizeof (emlxs_event_queue_t));

	return;

} /* emlxs_event_queue_destroy() */


/* Event queue lock must be held */
static void
emlxs_event_destroy(emlxs_hba_t *hba, emlxs_event_entry_t *entry)
{
	emlxs_event_queue_t *eventq = &EVENTQ;
	emlxs_port_t *port;
	uint32_t missed = 0;

	port = (emlxs_port_t *)entry->port;

	eventq->count--;
	if (eventq->count == 0) {
		eventq->first = NULL;
		eventq->last = NULL;
	} else {
		if (entry->prev) {
			entry->prev->next = entry->next;
		}
		if (entry->next) {
			entry->next->prev = entry->prev;
		}
		if (eventq->first == entry) {
			eventq->first = entry->next;
		}
		if (eventq->last == entry) {
			eventq->last = entry->prev;
		}
	}

	entry->prev = NULL;
	entry->next = NULL;

	if ((entry->evt->mask == EVT_LINK) ||
	    (entry->evt->mask == EVT_RSCN)) {
		if (!(entry->flag & EMLXS_DFC_EVENT_DONE)) {
			hba->hba_event.missed++;
			missed = 1;
		}
	}

	EMLXS_MSGF(EMLXS_CONTEXT, &emlxs_event_dequeued_msg,
	    "%s[%d]: flag=%x missed=%d cnt=%d",
	    entry->evt->label, entry->id, entry->flag, missed, eventq->count);

	/* Call notification handler */
	if (entry->evt->destroy != emlxs_null_func) {
		entry->evt->destroy(entry);
	}

	/* Free context buffer */
	if (entry->bp && entry->size) {
		kmem_free(entry->bp, entry->size);
	}

	/* Free entry buffer */
	kmem_free(entry, sizeof (emlxs_event_entry_t));

	return;

} /* emlxs_event_destroy() */


extern void
emlxs_event(emlxs_port_t *port, emlxs_event_t *evt, void *bp, uint32_t size)
{
	emlxs_hba_t *hba = HBA;
	emlxs_event_queue_t *eventq = &EVENTQ;
	emlxs_event_entry_t *entry;
	uint32_t i;
	uint32_t mask;

	if (emlxs_event_check(port, evt) == 0) {
		goto failed;
	}

	/* Create event entry */
	if (!(entry = (emlxs_event_entry_t *)kmem_alloc(
	    sizeof (emlxs_event_entry_t), KM_NOSLEEP))) {
		EMLXS_MSGF(EMLXS_CONTEXT, &emlxs_event_debug_msg,
		    "%s: Unable to allocate event entry.", evt->label);

		goto failed;
	}

	/* Initialize */
	bzero(entry, sizeof (emlxs_event_entry_t));

	entry->evt = evt;
	entry->port = (void *)port;
	entry->bp = bp;
	entry->size = size;

	mutex_enter(&eventq->lock);

	/* Set the event timer */
	entry->timestamp = hba->timer_tics;
	if (evt->timeout) {
		entry->timer = entry->timestamp + evt->timeout;
	}

	/* Eventq id starts with 1 */
	if (eventq->next_id == 0) {
		eventq->next_id = 1;
	}

	/* Set the event id */
	entry->id = eventq->next_id++;

	/* Set last event table */
	mask = evt->mask;
	for (i = 0; i < 32; i++) {
		if (mask & 0x01) {
			eventq->last_id[i] = entry->id;
		}
		mask >>= 1;
	}

	/* Put event on bottom of queue */
	entry->next = NULL;
	if (eventq->count == 0) {
		entry->prev = NULL;
		eventq->first = entry;
		eventq->last = entry;
	} else {
		entry->prev = eventq->last;
		entry->prev->next = entry;
		eventq->last = entry;
	}
	eventq->count++;

	if ((entry->evt->mask == EVT_LINK) ||
	    (entry->evt->mask == EVT_RSCN)) {
		hba->hba_event.new++;
	}

	EMLXS_MSGF(EMLXS_CONTEXT, &emlxs_event_queued_msg,
	    "%s[%d]: bp=%p size=%d cnt=%d", entry->evt->label,
	    entry->id, bp, size, eventq->count);

	/* Broadcast the event */
	cv_broadcast(&eventq->lock_cv);

	mutex_exit(&eventq->lock);

	return;

failed:

	if (bp && size) {
		kmem_free(bp, size);
	}

	return;

} /* emlxs_event() */


extern void
emlxs_timer_check_events(emlxs_hba_t *hba)
{
	emlxs_config_t *cfg = &CFG;
	emlxs_event_queue_t *eventq = &EVENTQ;
	emlxs_event_entry_t *entry;
	emlxs_event_entry_t *next;

	if (!cfg[CFG_TIMEOUT_ENABLE].current) {
		return;
	}

	if ((hba->event_timer > hba->timer_tics)) {
		return;
	}

	if (eventq->count) {
		mutex_enter(&eventq->lock);

		entry = eventq->first;
		while (entry) {
			if ((!entry->timer) ||
			    (entry->timer > hba->timer_tics)) {
				entry = entry->next;
				continue;
			}

			/* Event timed out, destroy it */
			next = entry->next;
			emlxs_event_destroy(hba, entry);
			entry = next;
		}

		mutex_exit(&eventq->lock);
	}

	/* Set next event timer check */
	hba->event_timer = hba->timer_tics + EMLXS_EVENT_PERIOD;

	return;

} /* emlxs_timer_check_events() */


extern void
emlxs_log_rscn_event(emlxs_port_t *port, uint8_t *payload, uint32_t size)
{
	uint8_t *bp;
	uint32_t *ptr;

	/* Check if the event is being requested */
	if (emlxs_event_check(port, &emlxs_rscn_event) == 0) {
		return;
	}

	if (size > MAX_RSCN_PAYLOAD) {
		size = MAX_RSCN_PAYLOAD;
	}

	size += sizeof (uint32_t);

	/* Save a copy of the payload for the event log */
	if (!(bp = (uint8_t *)kmem_alloc(size, KM_NOSLEEP))) {
		EMLXS_MSGF(EMLXS_CONTEXT, &emlxs_event_debug_msg,
		    "%s: Unable to allocate buffer.", emlxs_rscn_event.label);

		return;
	}

	/*
	 * Buffer Format:
	 *	word[0] = DID of the RSCN
	 *	word[1] = RSCN Payload
	 */
	ptr = (uint32_t *)bp;
	*ptr++ = port->did;
	bcopy(payload, (char *)ptr, (size - sizeof (uint32_t)));

	emlxs_event(port, &emlxs_rscn_event, bp, size);

	return;

} /* emlxs_log_rscn_event() */


extern void
emlxs_log_vportrscn_event(emlxs_port_t *port, uint8_t *payload, uint32_t size)
{
	uint8_t *bp;
	uint8_t *ptr;

	/* Check if the event is being requested */
	if (emlxs_event_check(port, &emlxs_vportrscn_event) == 0) {
		return;
	}

	if (size > MAX_RSCN_PAYLOAD) {
		size = MAX_RSCN_PAYLOAD;
	}

	size += sizeof (NAME_TYPE);

	/* Save a copy of the payload for the event log */
	if (!(bp = (uint8_t *)kmem_alloc(size, KM_NOSLEEP))) {
		EMLXS_MSGF(EMLXS_CONTEXT, &emlxs_event_debug_msg,
		    "%s: Unable to allocate buffer.",
		    emlxs_vportrscn_event.label);

		return;
	}

	/*
	 * Buffer Format:
	 *	word[0 - 4] = WWPN of the RSCN
	 *	word[5] = RSCN Payload
	 */
	ptr = bp;
	bcopy(&port->wwpn, ptr, sizeof (NAME_TYPE));
	ptr += sizeof (NAME_TYPE);
	bcopy(payload, ptr, (size - sizeof (NAME_TYPE)));

	emlxs_event(port, &emlxs_vportrscn_event, bp, size);

	return;

} /* emlxs_log_vportrscn_event() */


extern uint32_t
emlxs_flush_ct_event(emlxs_port_t *port, uint32_t rxid)
{
	emlxs_hba_t *hba = HBA;
	emlxs_event_queue_t *eventq = &EVENTQ;
	emlxs_event_entry_t *entry;
	uint32_t *ptr;
	uint32_t found = 0;

	mutex_enter(&eventq->lock);

	for (entry = eventq->first; entry != NULL; entry = entry->next) {
		if ((entry->port != port) ||
		    (entry->evt != &emlxs_ct_event)) {
			continue;
		}

		ptr = (uint32_t *)entry->bp;
		if (rxid == *ptr) {
			/* This will prevent a CT exchange abort */
			/* in emlxs_ct_event_destroy() */
			entry->flag |= EMLXS_DFC_EVENT_DONE;

			emlxs_event_destroy(hba, entry);
			found = 1;
			break;
		}
	}

	mutex_exit(&eventq->lock);

	return (found);

} /* emlxs_flush_ct_event() */


extern uint32_t
emlxs_log_ct_event(emlxs_port_t *port, uint8_t *payload, uint32_t size,
    uint32_t rxid)
{
	uint8_t *bp;
	uint32_t *ptr;

	/* Check if the event is being requested */
	if (emlxs_event_check(port, &emlxs_ct_event) == 0) {
		return (1);
	}

	if (size > MAX_CT_PAYLOAD) {
		size = MAX_CT_PAYLOAD;
	}

	size += sizeof (uint32_t);

	/* Save a copy of the payload for the event log */
	if (!(bp = (uint8_t *)kmem_alloc(size, KM_NOSLEEP))) {
		EMLXS_MSGF(EMLXS_CONTEXT, &emlxs_event_debug_msg,
		    "%s: Unable to allocate buffer.", emlxs_ct_event.label);

		return (1);
	}

	/*
	 * Buffer Format:
	 *	word[0] = RXID tag for outgoing reply to this CT request
	 *	word[1] = CT Payload
	 */
	ptr = (uint32_t *)bp;
	*ptr++ = rxid;
	bcopy(payload, (char *)ptr, (size - sizeof (uint32_t)));

	emlxs_event(port, &emlxs_ct_event, bp, size);

	return (0);

} /* emlxs_log_ct_event() */


extern void
emlxs_ct_event_destroy(emlxs_event_entry_t *entry)
{
	emlxs_port_t *port = (emlxs_port_t *)entry->port;
	emlxs_hba_t *hba = HBA;
	uint32_t rxid;

	if (!(entry->flag & EMLXS_DFC_EVENT_DONE)) {

		rxid = *(uint32_t *)entry->bp;

		/* Abort exchange */
		emlxs_thread_spawn(hba, emlxs_abort_ct_exchange,
		    entry->port, (void *)(unsigned long)rxid);
	}

	return;

} /* emlxs_ct_event_destroy() */


extern void
emlxs_log_link_event(emlxs_port_t *port)
{
	emlxs_hba_t *hba = HBA;
	uint8_t *bp;
	dfc_linkinfo_t *linkinfo;
	uint8_t *byte;
	uint8_t *linkspeed;
	uint8_t *liptype;
	uint8_t *resv1;
	uint8_t *resv2;
	uint32_t size;

	/* Check if the event is being requested */
	if (emlxs_event_check(port, &emlxs_link_event) == 0) {
		return;
	}

	size = sizeof (dfc_linkinfo_t) + sizeof (uint32_t);

	/* Save a copy of the buffer for the event log */
	if (!(bp = (uint8_t *)kmem_alloc(size, KM_NOSLEEP))) {
		EMLXS_MSGF(EMLXS_CONTEXT, &emlxs_event_debug_msg,
		    "%s: Unable to allocate buffer.", emlxs_link_event.label);

		return;
	}

	/*
	 * Buffer Format:
	 *	word[0] = Linkspeed:8
	 *	word[0] = LIP_type:8
	 *	word[0] = resv1:8
	 *	word[0] = resv2:8
	 *	word[1] = dfc_linkinfo_t data
	 */
	byte = (uint8_t *)bp;
	linkspeed = &byte[0];
	liptype = &byte[1];
	resv1 = &byte[2];
	resv2 = &byte[3];
	linkinfo = (dfc_linkinfo_t *)&byte[4];

	*resv1 = 0;
	*resv2 = 0;

	if (hba->state <= FC_LINK_DOWN) {
		*linkspeed = 0;
		*liptype = 0;
	} else {
		/* Set linkspeed */
		if (hba->linkspeed == LA_2GHZ_LINK) {
			*linkspeed = HBA_PORTSPEED_2GBIT;
		} else if (hba->linkspeed == LA_4GHZ_LINK) {
			*linkspeed = HBA_PORTSPEED_4GBIT;
		} else if (hba->linkspeed == LA_8GHZ_LINK) {
			*linkspeed = HBA_PORTSPEED_8GBIT;
		} else if (hba->linkspeed == LA_10GHZ_LINK) {
			*linkspeed = HBA_PORTSPEED_10GBIT;
		} else if (hba->linkspeed == LA_16GHZ_LINK) {
			*linkspeed = HBA_PORTSPEED_16GBIT;
		} else if (hba->linkspeed == LA_32GHZ_LINK) {
			*linkspeed = HBA_PORTSPEED_32GBIT;
		} else {
			*linkspeed = HBA_PORTSPEED_1GBIT;
		}

		/* Set LIP type */
		*liptype = port->lip_type;
	}

	bzero(linkinfo, sizeof (dfc_linkinfo_t));

	linkinfo->a_linkEventTag = hba->link_event_tag;
	linkinfo->a_linkUp = HBASTATS.LinkUp;
	linkinfo->a_linkDown = HBASTATS.LinkDown;
	linkinfo->a_linkMulti = HBASTATS.LinkMultiEvent;

	if (hba->state <= FC_LINK_DOWN) {
		linkinfo->a_linkState = LNK_DOWN;
		linkinfo->a_DID = port->prev_did;
	} else if (hba->state < FC_READY) {
		linkinfo->a_linkState = LNK_DISCOVERY;
	} else {
		linkinfo->a_linkState = LNK_READY;
	}

	if (linkinfo->a_linkState != LNK_DOWN) {
		if (hba->topology == TOPOLOGY_LOOP) {
			if (hba->flag & FC_FABRIC_ATTACHED) {
				linkinfo->a_topology = LNK_PUBLIC_LOOP;
			} else {
				linkinfo->a_topology = LNK_LOOP;
			}

			linkinfo->a_alpa = port->did & 0xff;
			linkinfo->a_DID = linkinfo->a_alpa;
			linkinfo->a_alpaCnt = port->alpa_map[0];

			if (linkinfo->a_alpaCnt > 127) {
				linkinfo->a_alpaCnt = 127;
			}

			bcopy((void *)&port->alpa_map[1], linkinfo->a_alpaMap,
			    linkinfo->a_alpaCnt);
		} else {
			if (port->node_count == 1) {
				linkinfo->a_topology = LNK_PT2PT;
			} else {
				linkinfo->a_topology = LNK_FABRIC;
			}

			linkinfo->a_DID = port->did;
		}
	}

	bcopy(&hba->wwpn, linkinfo->a_wwpName, 8);
	bcopy(&hba->wwnn, linkinfo->a_wwnName, 8);

	emlxs_event(port, &emlxs_link_event, bp, size);

	return;

} /* emlxs_log_link_event() */


extern void
emlxs_log_dump_event(emlxs_port_t *port, uint8_t *buffer, uint32_t size)
{
	emlxs_hba_t *hba = HBA;
	uint8_t *bp;

	/* Check if the event is being requested */
	if (emlxs_event_check(port, &emlxs_dump_event) == 0) {
#ifdef DUMP_SUPPORT
		/* Schedule a dump thread */
		emlxs_dump(hba, EMLXS_DRV_DUMP, 0, 0);
#endif /* DUMP_SUPPORT */
		return;
	}

	if (buffer && size) {
		/* Save a copy of the buffer for the event log */
		if (!(bp = (uint8_t *)kmem_alloc(size, KM_NOSLEEP))) {
			EMLXS_MSGF(EMLXS_CONTEXT, &emlxs_event_debug_msg,
			    "%s: Unable to allocate buffer.",
			    emlxs_dump_event.label);

			return;
		}

		bcopy(buffer, bp, size);
	} else {
		bp = NULL;
		size = 0;
	}

	emlxs_event(port, &emlxs_dump_event, bp, size);

	return;

} /* emlxs_log_dump_event() */


extern void
emlxs_log_temp_event(emlxs_port_t *port, uint32_t type, uint32_t temp)
{
	emlxs_hba_t *hba = HBA;
	uint32_t *bp;
	uint32_t size;

	/* Check if the event is being requested */
	if (emlxs_event_check(port, &emlxs_temp_event) == 0) {
#ifdef DUMP_SUPPORT
		/* Schedule a dump thread */
		emlxs_dump(hba, EMLXS_TEMP_DUMP, type, temp);
#endif /* DUMP_SUPPORT */
		return;
	}

	size = 2 * sizeof (uint32_t);

	if (!(bp = (uint32_t *)kmem_alloc(size, KM_NOSLEEP))) {
		EMLXS_MSGF(EMLXS_CONTEXT, &emlxs_event_debug_msg,
		    "%s: Unable to allocate buffer.", emlxs_temp_event.label);

		return;
	}

	bp[0] = type;
	bp[1] = temp;

	emlxs_event(port, &emlxs_temp_event, bp, size);

	return;

} /* emlxs_log_temp_event() */



extern void
emlxs_log_fcoe_event(emlxs_port_t *port, menlo_init_rsp_t *init_rsp)
{
	emlxs_hba_t *hba = HBA;
	uint8_t *bp;
	uint32_t size;

	/* Check if the event is being requested */
	if (emlxs_event_check(port, &emlxs_fcoe_event) == 0) {
		return;
	}

	/* Check if this is a FCOE adapter */
	if (hba->model_info.device_id != PCI_DEVICE_ID_HORNET) {
		return;
	}

	size = sizeof (menlo_init_rsp_t);

	if (!(bp = (uint8_t *)kmem_alloc(size, KM_NOSLEEP))) {
		EMLXS_MSGF(EMLXS_CONTEXT, &emlxs_event_debug_msg,
		    "%s: Unable to allocate buffer.", emlxs_fcoe_event.label);

		return;
	}

	bcopy((uint8_t *)init_rsp, bp, size);

	emlxs_event(port, &emlxs_fcoe_event, bp, size);

	return;

} /* emlxs_log_fcoe_event() */


extern void
emlxs_log_async_event(emlxs_port_t *port, IOCB *iocb)
{
	uint8_t *bp;
	uint32_t size;

	if (emlxs_event_check(port, &emlxs_async_event) == 0) {
		return;
	}

	/* ASYNC_STATUS_CN response size */
	size = 64;

	if (!(bp = (uint8_t *)kmem_alloc(size, KM_NOSLEEP))) {
		EMLXS_MSGF(EMLXS_CONTEXT, &emlxs_event_debug_msg,
		    "%s: Unable to allocate buffer.", emlxs_async_event.label);

		return;
	}

	bcopy((uint8_t *)iocb, bp, size);

	emlxs_event(port, &emlxs_async_event, bp, size);

	return;

} /* emlxs_log_async_event() */


extern uint32_t
emlxs_get_dfc_eventinfo(emlxs_port_t *port, HBA_EVENTINFO *eventinfo,
    uint32_t *eventcount, uint32_t *missed)
{
	emlxs_hba_t *hba = HBA;
	emlxs_event_queue_t *eventq = &EVENTQ;
	emlxs_event_entry_t *entry;
	uint32_t max_events;
	dfc_linkinfo_t *linkinfo;
	uint32_t *word;
	uint8_t *byte;
	uint8_t linkspeed;
	uint8_t liptype;
	fc_affected_id_t *aid;
	uint32_t events;
	uint8_t format;

	if (!eventinfo || !eventcount || !missed) {
		return (DFC_ARG_NULL);
	}

	max_events = *eventcount;
	*eventcount = 0;
	*missed = 0;

	mutex_enter(&eventq->lock);

	/* Account for missed events */
	if (hba->hba_event.new > hba->hba_event.missed) {
		hba->hba_event.new -= hba->hba_event.missed;
	} else {
		hba->hba_event.new = 0;
	}

	*missed = hba->hba_event.missed;
	hba->hba_event.missed = 0;

	if (!hba->hba_event.new) {
		hba->hba_event.last_id = eventq->next_id - 1;
		mutex_exit(&eventq->lock);
		return (0);
	}

	/* A new event has occurred since last acquisition */

	events = 0;
	entry = eventq->first;
	while (entry && (events < max_events)) {

		/* Skip old events */
		if (entry->id <= hba->hba_event.last_id) {
			entry = entry->next;
			continue;
		}

		/* Process this entry */
		switch (entry->evt->mask) {
		case EVT_LINK:
			byte = (uint8_t *)entry->bp;
			linkspeed = byte[0];
			liptype = byte[1];
			linkinfo = (dfc_linkinfo_t *)&byte[4];

			if (linkinfo->a_linkState == LNK_DOWN) {
				eventinfo->EventCode =
				    HBA_EVENT_LINK_DOWN;
				eventinfo->Event.Link_EventInfo.
				    PortFcId = linkinfo->a_DID;
				eventinfo->Event.Link_EventInfo.
				    Reserved[0] = 0;
				eventinfo->Event.Link_EventInfo.
				    Reserved[1] = 0;
				eventinfo->Event.Link_EventInfo.
				    Reserved[2] = 0;
			} else {
				eventinfo->EventCode =
				    HBA_EVENT_LINK_UP;
				eventinfo->Event.Link_EventInfo.
				    PortFcId = linkinfo->a_DID;

				if ((linkinfo->a_topology ==
				    LNK_PUBLIC_LOOP) ||
				    (linkinfo->a_topology ==
				    LNK_LOOP)) {
					eventinfo->Event.
					    Link_EventInfo.
					    Reserved[0] = 2;
				} else {
					eventinfo->Event.
					    Link_EventInfo.
					    Reserved[0] = 1;
				}

				eventinfo->Event.Link_EventInfo.
				    Reserved[1] = liptype;
				eventinfo->Event.Link_EventInfo.
				    Reserved[2] = linkspeed;
			}

			eventinfo++;
			events++;
			hba->hba_event.new--;
			break;

		case EVT_RSCN:
			word = (uint32_t *)entry->bp;
			eventinfo->EventCode = HBA_EVENT_RSCN;
			eventinfo->Event.RSCN_EventInfo.PortFcId =
			    word[0] & 0xFFFFFF;
			/* word[1] is the RSCN payload command */

			aid = (fc_affected_id_t *)&word[2];
			format = aid->aff_format;

			switch (format) {
			case 0:	/* Port */
				eventinfo->Event.RSCN_EventInfo.
				    NPortPage =
				    aid->aff_d_id & 0x00ffffff;
				break;

			case 1:	/* Area */
				eventinfo->Event.RSCN_EventInfo.
				    NPortPage =
				    aid->aff_d_id & 0x00ffff00;
				break;

			case 2:	/* Domain */
				eventinfo->Event.RSCN_EventInfo.
				    NPortPage =
				    aid->aff_d_id & 0x00ff0000;
				break;

			case 3:	/* Network */
				eventinfo->Event.RSCN_EventInfo.
				    NPortPage = 0;
				break;
			}

			eventinfo->Event.RSCN_EventInfo.Reserved[0] =
			    0;
			eventinfo->Event.RSCN_EventInfo.Reserved[1] =
			    0;

			eventinfo++;
			events++;
			hba->hba_event.new--;
			break;
		}

		hba->hba_event.last_id = entry->id;
		entry = entry->next;
	}

	/* Return number of events acquired */
	*eventcount = events;

	mutex_exit(&eventq->lock);

	return (0);

} /* emlxs_get_dfc_eventinfo() */


void
emlxs_get_dfc_event(emlxs_port_t *port, emlxs_dfc_event_t *dfc_event,
    uint32_t sleep)
{
	emlxs_hba_t *hba = HBA;
	emlxs_event_queue_t *eventq = &EVENTQ;
	emlxs_event_entry_t *entry;
	uint32_t found;
	uint32_t mask;
	uint32_t i;
	uint32_t size = 0;
	uint32_t rc;

	if (dfc_event->dataout && dfc_event->size) {
		size = dfc_event->size;
	}
	dfc_event->size = 0;

	/* Calculate the event index */
	mask = dfc_event->event;
	for (i = 0; i < 32; i++) {
		if (mask & 0x01) {
			break;
		}

		mask >>= 1;
	}

	if (i == 32) {
		return;
	}

	mutex_enter(&eventq->lock);

wait_for_event:

	/* Check if no new event has occurred */
	if (dfc_event->last_id == eventq->last_id[i]) {
		if (!sleep) {
			mutex_exit(&eventq->lock);
			return;
		}

		/* While event is still active and */
		/* no new event has been logged */
		while ((dfc_event->event & hba->event_mask) &&
		    (dfc_event->last_id == eventq->last_id[i])) {

			rc = cv_wait_sig(&eventq->lock_cv, &eventq->lock);

			/* Check if thread was killed by kernel */
			if (rc == 0) {
				dfc_event->pid = 0;
				dfc_event->event = 0;
				mutex_exit(&eventq->lock);
				return;
			}
		}

		/* If the event is no longer registered then */
		/* return immediately */
		if (!(dfc_event->event & hba->event_mask)) {
			mutex_exit(&eventq->lock);
			return;
		}
	}

	/* !!! An event has occurred since last_id !!! */

	/* Check if event data is not being requested */
	if (!size) {
		/* If so, then just return the last event id */
		dfc_event->last_id = eventq->last_id[i];

		mutex_exit(&eventq->lock);
		return;
	}

	/* !!! The requester wants the next event buffer !!! */

	found = 0;
	entry = eventq->first;
	while (entry) {
		if ((entry->id > dfc_event->last_id) &&
		    (entry->evt->mask == dfc_event->event)) {
			found = 1;
			break;
		}

		entry = entry->next;
	}

	if (!found) {
		/* Update last_id to the last known event */
		dfc_event->last_id = eventq->last_id[i];

		/* Try waiting again if we can */
		goto wait_for_event;
	}

	/* !!! Next event found !!! */

	/* Copy the context buffer to the buffer provided */
	if (entry->bp && entry->size) {
		if (entry->size < size) {
			size = entry->size;
		}

		bcopy((void *)entry->bp, dfc_event->dataout, size);

		/* Event has been retrieved by DFCLIB */
		entry->flag |= EMLXS_DFC_EVENT_DONE;

		dfc_event->size = size;
	}

	dfc_event->last_id = entry->id;

	mutex_exit(&eventq->lock);

	return;

} /* emlxs_get_dfc_event() */


uint32_t
emlxs_kill_dfc_event(emlxs_port_t *port, emlxs_dfc_event_t *dfc_event)
{
	emlxs_hba_t *hba = HBA;
	emlxs_event_queue_t *eventq = &EVENTQ;

	mutex_enter(&eventq->lock);
	dfc_event->pid = 0;
	dfc_event->event = 0;
	cv_broadcast(&eventq->lock_cv);
	mutex_exit(&eventq->lock);

	return (0);

} /* emlxs_kill_dfc_event() */


#ifdef SAN_DIAG_SUPPORT
extern void
emlxs_log_sd_basic_els_event(emlxs_port_t *port, uint32_t subcat,
    HBA_WWN *portname, HBA_WWN *nodename)
{
	struct sd_plogi_rcv_v0	*bp;
	uint32_t		size;

	/* Check if the event is being requested */
	if (emlxs_event_check(port, &emlxs_sd_els_event) == 0) {
		return;
	}

	size = sizeof (struct sd_plogi_rcv_v0);

	if (!(bp = (struct sd_plogi_rcv_v0 *)kmem_alloc(size, KM_NOSLEEP))) {
		EMLXS_MSGF(EMLXS_CONTEXT, &emlxs_event_debug_msg,
		    "%s: Unable to allocate buffer.", emlxs_sd_els_event.label);

		return;
	}

	/*
	 * we are using version field to store subtype, libdfc
	 * will fix this up before returning data to app.
	 */
	bp->sd_plogir_version = subcat;
	bcopy((uint8_t *)portname, (uint8_t *)&bp->sd_plogir_portname,
	    sizeof (HBA_WWN));
	bcopy((uint8_t *)nodename, (uint8_t *)&bp->sd_plogir_nodename,
	    sizeof (HBA_WWN));

	emlxs_event(port, &emlxs_sd_els_event, bp, size);

	return;

} /* emlxs_log_sd_basic_els_event() */


extern void
emlxs_log_sd_prlo_event(emlxs_port_t *port, HBA_WWN *remoteport)
{
	struct sd_prlo_rcv_v0	*bp;
	uint32_t		size;

	/* Check if the event is being requested */
	if (emlxs_event_check(port, &emlxs_sd_els_event) == 0) {
		return;
	}

	size = sizeof (struct sd_prlo_rcv_v0);

	if (!(bp = (struct sd_prlo_rcv_v0 *)kmem_alloc(size,
	    KM_NOSLEEP))) {
		EMLXS_MSGF(EMLXS_CONTEXT, &emlxs_event_debug_msg,
		    "%s PRLO: Unable to allocate buffer.",
		    emlxs_sd_els_event.label);

		return;
	}

	/*
	 * we are using version field to store subtype, libdfc
	 * will fix this up before returning data to app.
	 */
	bp->sd_prlor_version = SD_ELS_SUBCATEGORY_PRLO_RCV;
	bcopy((uint8_t *)remoteport, (uint8_t *)&bp->sd_prlor_remoteport,
	    sizeof (HBA_WWN));

	emlxs_event(port, &emlxs_sd_els_event, bp, size);

	return;

} /* emlxs_log_sd_prlo_event() */


extern void
emlxs_log_sd_lsrjt_event(emlxs_port_t *port, HBA_WWN *remoteport,
    uint32_t orig_cmd, uint32_t reason, uint32_t reason_expl)
{
	struct sd_lsrjt_rcv_v0	*bp;
	uint32_t		size;

	/* Check if the event is being requested */
	if (emlxs_event_check(port, &emlxs_sd_els_event) == 0) {
		return;
	}

	size = sizeof (struct sd_lsrjt_rcv_v0);

	if (!(bp = (struct sd_lsrjt_rcv_v0 *)kmem_alloc(size,
	    KM_NOSLEEP))) {
		EMLXS_MSGF(EMLXS_CONTEXT, &emlxs_event_debug_msg,
		    "%s LSRJT: Unable to allocate buffer.",
		    emlxs_sd_els_event.label);

		return;
	}

	/*
	 * we are using version field to store subtype, libdfc
	 * will fix this up before returning data to app.
	 */
	bp->sd_lsrjtr_version = SD_ELS_SUBCATEGORY_LSRJT_RCV;
	bcopy((uint8_t *)remoteport, (uint8_t *)&bp->sd_lsrjtr_remoteport,
	    sizeof (HBA_WWN));
	bp->sd_lsrjtr_original_cmd = orig_cmd;
	bp->sd_lsrjtr_reasoncode = reason;
	bp->sd_lsrjtr_reasoncodeexpl = reason_expl;

	emlxs_event(port, &emlxs_sd_els_event, bp, size);

	return;

} /* emlxs_log_sd_lsrjt_event() */


extern void
emlxs_log_sd_fc_bsy_event(emlxs_port_t *port, HBA_WWN *remoteport)
{
	struct sd_pbsy_rcv_v0	*bp;
	uint32_t		size;

	/* Check if the event is being requested */
	if (emlxs_event_check(port, &emlxs_sd_fabric_event) == 0) {
		return;
	}

	size = sizeof (struct sd_pbsy_rcv_v0);

	if (!(bp = (struct sd_pbsy_rcv_v0 *)kmem_alloc(size,
	    KM_NOSLEEP))) {
		EMLXS_MSGF(EMLXS_CONTEXT, &emlxs_event_debug_msg,
		    "%s BSY: Unable to allocate buffer.",
		    emlxs_sd_fabric_event.label);

		return;
	}

	/*
	 * we are using version field to store subtype, libdfc
	 * will fix this up before returning data to app.
	 */
	if (remoteport == NULL)
		bp->sd_pbsyr_evt_version = SD_FABRIC_SUBCATEGORY_FABRIC_BUSY;
	else
	{
		bp->sd_pbsyr_evt_version = SD_FABRIC_SUBCATEGORY_PORT_BUSY;
		bcopy((uint8_t *)remoteport, (uint8_t *)&bp->sd_pbsyr_rport,
		    sizeof (HBA_WWN));
	}

	emlxs_event(port, &emlxs_sd_fabric_event, bp, size);

	return;

} /* emlxs_log_sd_fc_bsy_event() */


extern void
emlxs_log_sd_fc_rdchk_event(emlxs_port_t *port, HBA_WWN *remoteport,
    uint32_t lun, uint32_t opcode, uint32_t fcp_param)
{
	struct sd_fcprdchkerr_v0	*bp;
	uint32_t			size;

	/* Check if the event is being requested */
	if (emlxs_event_check(port, &emlxs_sd_fabric_event) == 0) {
		return;
	}

	size = sizeof (struct sd_fcprdchkerr_v0);

	if (!(bp = (struct sd_fcprdchkerr_v0 *)kmem_alloc(size,
	    KM_NOSLEEP))) {
		EMLXS_MSGF(EMLXS_CONTEXT, &emlxs_event_debug_msg,
		    "%s RDCHK: Unable to allocate buffer.",
		    emlxs_sd_fabric_event.label);

		return;
	}

	/*
	 * we are using version field to store subtype, libdfc
	 * will fix this up before returning data to app.
	 */
	bp->sd_fcprdchkerr_version = SD_FABRIC_SUBCATEGORY_FCPRDCHKERR;
	bcopy((uint8_t *)remoteport, (uint8_t *)&bp->sd_fcprdchkerr_rport,
	    sizeof (HBA_WWN));
	bp->sd_fcprdchkerr_lun = lun;
	bp->sd_fcprdchkerr_opcode = opcode;
	bp->sd_fcprdchkerr_fcpiparam = fcp_param;

	emlxs_event(port, &emlxs_sd_fabric_event, bp, size);

	return;

} /* emlxs_log_sd_rdchk_event() */


extern void
emlxs_log_sd_scsi_event(emlxs_port_t *port, uint32_t type,
    HBA_WWN *remoteport, int32_t lun)
{
	struct sd_scsi_generic_v0	*bp;
	uint32_t			size;

	/* Check if the event is being requested */
	if (emlxs_event_check(port, &emlxs_sd_scsi_event) == 0) {
		return;
	}

	size = sizeof (struct sd_scsi_generic_v0);

	if (!(bp = (struct sd_scsi_generic_v0 *)kmem_alloc(size,
	    KM_NOSLEEP))) {
		EMLXS_MSGF(EMLXS_CONTEXT, &emlxs_event_debug_msg,
		    "%s: Unable to allocate buffer.",
		    emlxs_sd_scsi_event.label);

		return;
	}

	/*
	 * we are using version field to store subtype, libdfc
	 * will fix this up before returning data to app.
	 */
	bp->sd_scsi_generic_version = type;
	bcopy((uint8_t *)remoteport, (uint8_t *)&bp->sd_scsi_generic_rport,
	    sizeof (HBA_WWN));
	bp->sd_scsi_generic_lun = lun;

	emlxs_event(port, &emlxs_sd_scsi_event, bp, size);

	return;

} /* emlxs_log_sd_scsi_event() */


extern void
emlxs_log_sd_scsi_check_event(emlxs_port_t *port, HBA_WWN *remoteport,
    uint32_t lun, uint32_t cmdcode, uint32_t sensekey,
    uint32_t asc, uint32_t ascq)
{
	struct sd_scsi_checkcond_v0	*bp;
	uint32_t			size;

	/* Check if the event is being requested */
	if (emlxs_event_check(port, &emlxs_sd_scsi_event) == 0) {
		return;
	}

	size = sizeof (struct sd_scsi_checkcond_v0);

	if (!(bp = (struct sd_scsi_checkcond_v0 *)kmem_alloc(size,
	    KM_NOSLEEP))) {
		EMLXS_MSGF(EMLXS_CONTEXT, &emlxs_event_debug_msg,
		    "%s CHECK: Unable to allocate buffer.",
		    emlxs_sd_scsi_event.label);

		return;
	}

	/*
	 * we are using version field to store subtype, libdfc
	 * will fix this up before returning data to app.
	 */
	bp->sd_scsi_checkcond_version = SD_SCSI_SUBCATEGORY_CHECKCONDITION;
	bcopy((uint8_t *)remoteport, (uint8_t *)&bp->sd_scsi_checkcond_rport,
	    sizeof (HBA_WWN));
	bp->sd_scsi_checkcond_lun = lun;
	bp->sd_scsi_checkcond_cmdcode = cmdcode;
	bp->sd_scsi_checkcond_sensekey = sensekey;
	bp->sd_scsi_checkcond_asc = asc;
	bp->sd_scsi_checkcond_ascq = ascq;

	emlxs_event(port, &emlxs_sd_scsi_event, bp, size);

	return;

} /* emlxs_log_sd_scsi_check_event() */


void
emlxs_get_sd_event(emlxs_port_t *port, emlxs_dfc_event_t *dfc_event,
    uint32_t sleep)
{
	emlxs_hba_t *hba = HBA;
	emlxs_event_queue_t *eventq = &EVENTQ;
	emlxs_event_entry_t *entry;
	uint32_t found;
	uint32_t mask;
	uint32_t i;
	uint32_t size = 0;
	uint32_t rc;

	if (dfc_event->dataout && dfc_event->size) {
		size = dfc_event->size;
	}
	dfc_event->size = 0;

	/* Calculate the event index */
	mask = dfc_event->event;
	for (i = 0; i < 32; i++) {
		if (mask & 0x01) {
			break;
		}

		mask >>= 1;
	}

	if (i == 32) {
		return;
	}

	mutex_enter(&eventq->lock);

wait_for_event:

	/* Check if no new event has ocurred */
	if (dfc_event->last_id == eventq->last_id[i]) {
		if (!sleep) {
			mutex_exit(&eventq->lock);
			return;
		}

		/* While event is active and no new event has been logged */
		while ((dfc_event->event & port->sd_event_mask) &&
		    (dfc_event->last_id == eventq->last_id[i])) {
			rc = cv_wait_sig(&eventq->lock_cv, &eventq->lock);

			/* Check if thread was killed by kernel */
			if (rc == 0) {
				dfc_event->pid = 0;
				dfc_event->event = 0;
				mutex_exit(&eventq->lock);
				return;
			}
		}

		/* If the event is no longer registered then return */
		if (!(dfc_event->event & port->sd_event_mask)) {
			mutex_exit(&eventq->lock);
			return;
		}
	}

	/* !!! An event has occurred since last_id !!! */

	/* Check if event data is not being requested */
	if (!size) {
		/* If so, then just return the last event id */
		dfc_event->last_id = eventq->last_id[i];

		mutex_exit(&eventq->lock);
		return;
	}

	/* !!! The requester wants the next event buffer !!! */

	found = 0;
	entry = eventq->first;
	while (entry) {
		if ((entry->id > dfc_event->last_id) &&
		    (entry->port == (void *)port) &&
		    (entry->evt->mask == dfc_event->event)) {
			found = 1;
			break;
		}

		entry = entry->next;
	}

	if (!found) {
		/* Update last_id to the last known event */
		dfc_event->last_id = eventq->last_id[i];

		/* Try waiting again if we can */
		goto wait_for_event;
	}

	/* !!! Next event found !!! */

	/* Copy the context buffer to the buffer provided */
	if (entry->bp && entry->size) {
		if (entry->size < size) {
			size = entry->size;
		}

		bcopy((void *)entry->bp, dfc_event->dataout, size);

		/* Event has been retrieved by SANDIAG */
		entry->flag |= EMLXS_SD_EVENT_DONE;

		dfc_event->size = size;
	}

	dfc_event->last_id = entry->id;

	mutex_exit(&eventq->lock);

	return;

} /* emlxs_get_sd_event */
#endif /* SAN_DIAG_SUPPORT */
