root/usr/src/uts/common/io/ib/mgt/ibmf/ibmf_saa_events.c
/*
 * 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 2004 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <sys/ib/mgt/ibmf/ibmf_saa_impl.h>
#include <sys/ib/mgt/ibmf/ibmf_saa_utils.h>

extern  saa_state_t     *saa_statep;
extern  int     ibmf_trace_level;

static void
ibmf_saa_informinfo_cb(void *arg, size_t length, char *buffer,
    int status, uint32_t producer_type);

static int
ibmf_saa_send_informinfo(saa_port_t *saa_portp, uint32_t producer_type,
    boolean_t subscribe, boolean_t unseq_unsubscribe);

static void
ibmf_saa_notify_event_client_task(void *args);

static void
ibmf_saa_process_subnet_event(saa_port_t *saa_portp, ib_mad_notice_t *notice);

/*
 * ibmf_saa_subscribe_events:
 * Subscribe or unsubscribe to subnet events for a certain port.
 * ibmf_saa_subscribe_events() will send an InformInfo request for each of the
 * four notice producer types.
 *
 * Subscribes generally occur when the first client for a port opens a session
 * and when a port with registered ibmf_saa clients transitions to active.
 * Subscribes are done as asynchronous, sequenced transactions.
 *
 * ibmf_saa sends unsubscribe requests when the last client for a port
 * unregisters and when an CI_OFFLINE message is received from ibtf (via ibmf).
 * For the first case, the unsubscribe is done as an asynchronous, sequenced
 * transaction.  For the second case, the request is asynchronous, unsequenced.
 * This means that the unsubscribes will not be retried.  Because the port is
 * going away we cannot wait for responses.  Unsubscribes are not required
 * anyway as the SA will remove subscription records from ports it determines to
 * be down.
 *
 * For subscribe requests, clients are notified that the request failed through
 * the event notification mechanism.  For unsubscribe requests,  clients are not
 * notified if the request fails.  Therefore, this function returns void.
 *
 * Input Arguments
 * saa_portp            pointer to port state structure
 * subscribe            B_TRUE if request is a Subscribe, B_FALSE if unsubscribe
 * unseq_unsubscribe    B_TRUE if unsubscribe request should be unsequenced
 *                      (called from CI_OFFLINE event handler)
 *                      B_FALSE if sequenced (wait for response) or for all
 *                      subscribe requests
 *
 * Output Arguments
 * none
 *
 * Returns
 * void
 */
void
ibmf_saa_subscribe_events(saa_port_t *saa_portp, boolean_t subscribe,
    boolean_t unseq_unsubscribe)
{
        int                             res;
        ibmf_saa_event_details_t        event_details;
        boolean_t                       notify_clients = B_FALSE;
        uint8_t                         success_mask;

        IBMF_TRACE_0(IBMF_TNF_DEBUG, DPRINT_L4,
            ibmf_saa_subscribe_events_start, IBMF_TNF_TRACE, "",
            "ibmf_saa_subscribe_events() enter\n");

        /* subscribes should always be sychronous */
        ASSERT((subscribe == B_FALSE) || (unseq_unsubscribe == B_FALSE));

        /*
         * reset the arrive and success masks to indicate no responses have come
         * back; technically only used for subscriptions but reset the values
         * anyway
         */
        mutex_enter(&saa_portp->saa_pt_event_sub_mutex);

        success_mask = saa_portp->saa_pt_event_sub_success_mask;

        saa_portp->saa_pt_event_sub_arrive_mask = 0;
        saa_portp->saa_pt_event_sub_success_mask = 0;

        mutex_exit(&saa_portp->saa_pt_event_sub_mutex);

        /*
         * now subscribe/unsubscribe for each of the notice producer types;
         * send_informinfo returns 1 on success, 0 on failure.  If the "or" of
         * all four results is 0 then none of the informinfo's succeed and we
         * should notify the client.  If it's not 0, then informinfo_cb will be
         * called at least once, taking care of notifying the clients that there
         * was a failure.
         * For each producer type, send the request only if it's a subscribe or
         * if it's an unsubscribe for a subscribe which succeeded
         */

        /*
         * subscribe for all traps generated by the SM;
         * gid in service/out of service, mgid created/deleted, etc.
         */
        if ((success_mask & IBMF_SAA_EVENT_STATUS_MASK_PRODUCER_SM) ||
            (subscribe == B_TRUE))
                res = ibmf_saa_send_informinfo(saa_portp,
                    MAD_INFORMINFO_NODETYPE_SUBNET_MANAGEMENT, subscribe,
                    unseq_unsubscribe);

        /* subscribe for all traps generated by a CA */
        if ((success_mask & IBMF_SAA_EVENT_STATUS_MASK_PRODUCER_CA) ||
            (subscribe == B_TRUE))
                res |= ibmf_saa_send_informinfo(saa_portp,
                    MAD_INFORMINFO_NODETYPE_CA, subscribe, unseq_unsubscribe);

        /* subscribe for all traps generated by a switch */
        if ((success_mask & IBMF_SAA_EVENT_STATUS_MASK_PRODUCER_SWITCH) ||
            (subscribe == B_TRUE))
                res |= ibmf_saa_send_informinfo(saa_portp,
                    MAD_INFORMINFO_NODETYPE_SWITCH, subscribe,
                    unseq_unsubscribe);

        /* subscribe for all traps generated by a router */
        if ((success_mask & IBMF_SAA_EVENT_STATUS_MASK_PRODUCER_ROUTER) ||
            (subscribe == B_TRUE))
                res |= ibmf_saa_send_informinfo(saa_portp,
                    MAD_INFORMINFO_NODETYPE_ROUTER, subscribe,
                    unseq_unsubscribe);

        /* if none of the subscribe requests succeeded notify the clients */
        if ((res == 0) && (subscribe == B_TRUE)) {

                IBMF_TRACE_1(IBMF_TNF_NODEBUG, DPRINT_L1,
                    ibmf_saa_subscribe_events_err, IBMF_TNF_ERROR, "",
                    "ibmf_saa_subscribe_events: %s\n", tnf_string, msg,
                    "Could not subscribe for any of the four producer types");

                mutex_enter(&saa_portp->saa_pt_event_sub_mutex);

                /* all events should have "arrived" */
                ASSERT(saa_portp->saa_pt_event_sub_arrive_mask ==
                    IBMF_SAA_PORT_EVENT_SUB_ALL_ARRIVE);

                /* status mask should be 0 since all failed */
                ASSERT(saa_portp->saa_pt_event_sub_success_mask == 0);

                /* notify clients if success mask changed */
                if (saa_portp->saa_pt_event_sub_last_success_mask !=
                    saa_portp->saa_pt_event_sub_success_mask)
                        notify_clients = B_TRUE;

                /* update last mask for next set of subscription requests */
                saa_portp->saa_pt_event_sub_last_success_mask =
                    saa_portp->saa_pt_event_sub_arrive_mask = 0;

                mutex_exit(&saa_portp->saa_pt_event_sub_mutex);

                mutex_enter(&saa_portp->saa_pt_mutex);

                /*
                 * Sending the four InformInfos is treated as one port client
                 * reference.  Now that all have returned decrement the
                 * reference count.
                 */
                ASSERT(saa_portp->saa_pt_reference_count > 0);
                saa_portp->saa_pt_reference_count--;

                mutex_exit(&saa_portp->saa_pt_mutex);
        }

        /*
         * for unsequenced unsubscribes, decrement the reference count here
         * since no callbacks will ever do it
         */
        if (unseq_unsubscribe == B_TRUE) {

                mutex_enter(&saa_portp->saa_pt_mutex);

                /*
                 * Sending the four InformInfos is treated as one port client
                 * reference.  Now that all have returned decrement the
                 * reference count.
                 */
                ASSERT(saa_portp->saa_pt_reference_count > 0);
                saa_portp->saa_pt_reference_count--;

                mutex_exit(&saa_portp->saa_pt_mutex);
        }

        if (notify_clients == B_TRUE) {

                bzero(&event_details, sizeof (ibmf_saa_event_details_t));

                ibmf_saa_notify_event_clients(saa_portp, &event_details,
                    IBMF_SAA_EVENT_SUBSCRIBER_STATUS_CHG, NULL);
        }

        IBMF_TRACE_0(IBMF_TNF_DEBUG, DPRINT_L4, ibmf_saa_subscribe_events_end,
            IBMF_TNF_TRACE, "", "ibmf_saa_subscribe_events() exit\n");
}

/*
 * ibmf_saa_send_informinfo:
 *
 * Sends an InformInfo request to the SA.  There are two types of request,
 * Subscribes and Unsubscribes.  This function is called from
 * ibmf_saa_subscribe_events.  See that function's comment for usage of
 * subscribe, unseq_unsubscribe booleans.
 *
 * This function generates a standard ibmf_saa transaction and sends using
 * ibmf_saa_impl_send_request().  For asynchronous callbacks, the function
 * ibmf_saa_informinfo_cb() will be called.
 *
 * This function blocks allocating resources, but not waiting for response
 * packets.
 *
 * Input Arguments
 * saa_portp            pointer to port data
 * producer_type        InformInfo producer type to subscribe for
 * subscribe            B_TRUE if subscribe request, B_FALSE if unsubscribe
 * unseq_unsubscribe    B_TRUE if unsubscribe request should be unsequenced
 *                      (called from CI_OFFLINE event handler)
 *                      B_FALSE if sequenced (wait for response) or for all
 *                      subscribe requests
 *
 * Output Arguments
 * none
 *
 * Returns
 * 1 if the transaction succeeded, 0 if it failed
 */
static int
ibmf_saa_send_informinfo(saa_port_t *saa_portp, uint32_t producer_type,
    boolean_t subscribe, boolean_t unseq_unsubscribe)
{
        ib_mad_informinfo_t     inform_info;
        saa_impl_trans_info_t   *trans_info;
        int                     res;
        uint8_t                 producer_type_mask;

        IBMF_TRACE_0(IBMF_TNF_DEBUG, DPRINT_L4,
            ibmf_saa_send_informinfo_start, IBMF_TNF_TRACE, "",
            "ibmf_saa_send_informinfo() enter\n");

        IBMF_TRACE_4(IBMF_TNF_DEBUG, DPRINT_L3, ibmf_saa_send_informinfo,
            IBMF_TNF_TRACE, "", "ibmf_saa_send_informinfo: %s, producer_type ="
            "%x, subscribe = %x, unseq_unsubscribe = %x\n",
            tnf_string, msg, "Sending informinfo request",
            tnf_opaque, producer_type, producer_type,
            tnf_int, subscribe, subscribe,
            tnf_int, unseq_unsubscribe, unseq_unsubscribe);

        bzero(&inform_info, sizeof (ib_mad_informinfo_t));

        /* initialize inform_info packet */
        inform_info.LIDRangeBegin = MAD_INFORMINFO_ALL_ENDPORTS_RANGE;
        inform_info.IsGeneric = MAD_INFORMINFO_FORWARD_GENERIC;

        if (subscribe == B_TRUE)
                inform_info.Subscribe = MAD_INFORMINFO_SUBSCRIBE;
        else {
                inform_info.Subscribe = MAD_INFORMINFO_UNSUBSCRIBE;
                inform_info.QPN = saa_portp->saa_pt_qpn;
        }

        inform_info.Type = MAD_INFORMINFO_TRAP_TYPE_FORWARD_ALL;
        inform_info.TrapNumber_DeviceID =
            MAD_INFORMINFO_TRAP_NUMBER_FORWARD_ALL;
        inform_info.ProducerType_VendorID = producer_type;

        trans_info = kmem_zalloc(sizeof (saa_impl_trans_info_t), KM_SLEEP);

        /* no specific client associated with this transaction */
        trans_info->si_trans_client_data = NULL;
        trans_info->si_trans_port = saa_portp;
        trans_info->si_trans_method = SA_SUBN_ADM_SET;
        trans_info->si_trans_attr_id = SA_INFORMINFO_ATTRID;
        trans_info->si_trans_component_mask = 0;
        trans_info->si_trans_template = &inform_info;
        trans_info->si_trans_template_length = sizeof (ib_mad_informinfo_t);
        trans_info->si_trans_unseq_unsubscribe = unseq_unsubscribe;

        /*
         * if this isn't an unsequenced unsubscribe (the only synchronous
         * request) then set up the callback
         */
        if (unseq_unsubscribe == B_FALSE) {
                trans_info->si_trans_sub_callback =
                    ibmf_saa_informinfo_cb;
                trans_info->si_trans_callback_arg = saa_portp;

                /*
                 * if this is a subscribe, set the producer type so we can know
                 * which one's failed
                 */
                if (subscribe == B_TRUE) {
                        trans_info->si_trans_sub_producer_type = producer_type;
                }
        }

        mutex_enter(&saa_portp->saa_pt_kstat_mutex);

        IBMF_SAA_ADD32_KSTATS(saa_portp, outstanding_requests, 1);
        IBMF_SAA_ADD32_KSTATS(saa_portp, total_requests, 1);

        mutex_exit(&saa_portp->saa_pt_kstat_mutex);

        res = ibmf_saa_impl_send_request(trans_info);
        if (res != IBMF_SUCCESS) {

                IBMF_TRACE_2(IBMF_TNF_NODEBUG, DPRINT_L1,
                    ibmf_saa_send_informinfo, IBMF_TNF_ERROR, "",
                    "ibmf_saa_send_informinfo: %s, ibmf_status = %d\n",
                    tnf_string, msg, "ibmf_saa_impl_send_request() failed",
                    tnf_int, ibmf_status, res);

                res = 0;

        } else {

                IBMF_TRACE_1(IBMF_TNF_DEBUG, DPRINT_L3,
                    ibmf_saa_send_informinfo, IBMF_TNF_TRACE, "",
                    "ibmf_saa_send_informinfo: %s\n", tnf_string, msg,
                    "Request sent successfully");

                res = 1;

                /*
                 * if this was an asynchronous transaction (not the unsequenced
                 * unsubscribe case) return here.
                 * The callback will clean up everything.
                 */
                if (unseq_unsubscribe == B_FALSE) {

                        goto bail;
                }
        }

        kmem_free(trans_info, sizeof (saa_impl_trans_info_t));

        mutex_enter(&saa_portp->saa_pt_kstat_mutex);

        IBMF_SAA_SUB32_KSTATS(saa_portp, outstanding_requests, 1);
        IBMF_SAA_ADD32_KSTATS(saa_portp, failed_requests, 1);

        mutex_exit(&saa_portp->saa_pt_kstat_mutex);

        /*
         * if subscribe transaction failed, update status mask
         * to indicate "response"
         */
        if ((res == 0) && (subscribe == B_TRUE)) {

                mutex_enter(&saa_portp->saa_pt_event_sub_mutex);

                saa_portp->saa_pt_event_sub_arrive_mask = 0;
                saa_portp->saa_pt_event_sub_success_mask = 0;

                switch (producer_type) {

                        case MAD_INFORMINFO_NODETYPE_CA:
                                producer_type_mask =
                                    IBMF_SAA_EVENT_STATUS_MASK_PRODUCER_CA;
                        break;
                        case MAD_INFORMINFO_NODETYPE_SWITCH:
                                producer_type_mask =
                                    IBMF_SAA_EVENT_STATUS_MASK_PRODUCER_SWITCH;
                        break;
                        case MAD_INFORMINFO_NODETYPE_ROUTER:
                                producer_type_mask =
                                    IBMF_SAA_EVENT_STATUS_MASK_PRODUCER_ROUTER;
                        break;
                        case MAD_INFORMINFO_NODETYPE_SUBNET_MANAGEMENT:
                                producer_type_mask =
                                    IBMF_SAA_EVENT_STATUS_MASK_PRODUCER_SM;
                        break;
                        default:
                                IBMF_TRACE_2(IBMF_TNF_NODEBUG, DPRINT_L1,
                                    ibmf_saa_send_informinfo, IBMF_TNF_ERROR,
                                    "", "ibmf_saa_send_informinfo: %s, "
                                    "producer_type = 0x%x\n",
                                    tnf_string, msg, "Unknown producer type",
                                    tnf_opaque, producer_type, producer_type);

                                ASSERT(0);
                                producer_type_mask = 0;
                        break;
                }

                saa_portp->saa_pt_event_sub_arrive_mask |= producer_type_mask;

                mutex_exit(&saa_portp->saa_pt_event_sub_mutex);
        }

bail:
        IBMF_TRACE_1(IBMF_TNF_DEBUG, DPRINT_L3,
            ibmf_saa_send_informinfo_end, IBMF_TNF_TRACE, "",
            "ibmf_saa_send_informinfo() exit: result = 0x%x\n",
            tnf_opaque, result, res);

        return (res);
}

/*
 * ibmf_saa_informinfo_cb:
 *
 * Called when the asynchronous informinfo request receives its response.
 * Checks the status (whether the ibmf_saa was able to subscribe with the SA for
 * events) and updates the status mask for the specific producer.  If all four
 * producer types have arrived then the event clients are notified if there has
 * been a change in the status.
 *
 * Input Arguments
 * arg          user-specified pointer (points to the current port data)
 * length       length of payload returned (should be size of informinfo_rec)
 * buffer       pointer to informinfo response returned (should not be null)
 * status       status of sa access request
 * producer_type for subscriptions, indicates the notice producer type that was
 *              requested; ignored for unsubscribes
 *
 * Output Arguments
 * none
 *
 * Returns void
 */
static void
ibmf_saa_informinfo_cb(void *arg, size_t length, char *buffer,
    int status, uint32_t producer_type)
{
        saa_port_t                      *saa_portp;
        uint8_t                         producer_type_mask;
        boolean_t                       notify_clients;
        uint8_t                         event_status_mask;
        ibmf_saa_event_details_t        event_details;

        IBMF_TRACE_2(IBMF_TNF_DEBUG, DPRINT_L3, ibmf_saa_informinfo_cb_start,
            IBMF_TNF_TRACE, "", "ibmf_saa_informinfo_cb() enter: producer_type "
            "= 0x%x, status = %d\n", tnf_opaque, producer_type, producer_type,
            tnf_int, status, status);

        saa_portp = (saa_port_t *)arg;

        notify_clients = B_FALSE;

        /* if producer type is 0 this was an unsubscribe */
        if (producer_type == 0) {

                IBMF_TRACE_1(IBMF_TNF_DEBUG, DPRINT_L3,
                    ibmf_saa_informinfo_cb, IBMF_TNF_TRACE, "",
                    "ibmf_saa_informinfo_cb(): %s",
                    tnf_string, msg, "handling unsubscribe");

                if (buffer != NULL)
                        kmem_free(buffer, length);

                goto bail;
        }

        /* determine which event it was */
        switch (producer_type) {

                case MAD_INFORMINFO_NODETYPE_CA:
                        producer_type_mask =
                            IBMF_SAA_EVENT_STATUS_MASK_PRODUCER_CA;
                break;
                case MAD_INFORMINFO_NODETYPE_SWITCH:
                        producer_type_mask =
                            IBMF_SAA_EVENT_STATUS_MASK_PRODUCER_SWITCH;
                break;
                case MAD_INFORMINFO_NODETYPE_ROUTER:
                        producer_type_mask =
                            IBMF_SAA_EVENT_STATUS_MASK_PRODUCER_ROUTER;
                break;
                case MAD_INFORMINFO_NODETYPE_SUBNET_MANAGEMENT:
                        producer_type_mask =
                            IBMF_SAA_EVENT_STATUS_MASK_PRODUCER_SM;
                break;

                default:
                        IBMF_TRACE_2(IBMF_TNF_NODEBUG, DPRINT_L1,
                            ibmf_saa_send_informinfo_cb, IBMF_TNF_ERROR,
                            "", "ibmf_saa_informinfo_cb: %s, "
                            "producer_type = 0x%x\n",
                            tnf_string, msg, "Unknown producer type",
                            tnf_opaque, producer_type, producer_type);

                        producer_type_mask = 0;
                break;
        }

        mutex_enter(&saa_portp->saa_pt_event_sub_mutex);

        if (saa_portp->saa_pt_event_sub_arrive_mask & producer_type_mask) {

                mutex_exit(&saa_portp->saa_pt_event_sub_mutex);

                IBMF_TRACE_2(IBMF_TNF_DEBUG, DPRINT_L2,
                    ibmf_saa_informinfo_cb, IBMF_TNF_TRACE, "",
                    "ibmf_saa_informinfo_cb(): %s, prod_type_mask = 0x%x",
                    tnf_string, msg, "Received duplicate response",
                    tnf_opaque, producer_type_mask, producer_type_mask);

                if (buffer != NULL)
                        kmem_free(buffer, length);

                goto bail;
        }

        saa_portp->saa_pt_event_sub_arrive_mask |= producer_type_mask;

        /* process response */
        if ((status != IBMF_SUCCESS) || (buffer == NULL)) {

                IBMF_TRACE_4(IBMF_TNF_NODEBUG, DPRINT_L1,
                    ibmf_saa_informinfo_cb, IBMF_TNF_ERROR, "",
                    "ibmf_saa_informinfo_cb: %s, status = %d,"
                    " buffer = 0x%p, length = %d\n",
                    tnf_string, msg, "could not get informinfo resp",
                    tnf_int, status, status, tnf_opaque, buffer, buffer,
                    tnf_uint, length, length);

        } else if (buffer != NULL) {

                kmem_free(buffer, length);
                saa_portp->saa_pt_event_sub_success_mask |= producer_type_mask;
        }

        /* if all four InformInfo responses have arrived */
        if (saa_portp->saa_pt_event_sub_arrive_mask ==
            IBMF_SAA_PORT_EVENT_SUB_ALL_ARRIVE) {

                IBMF_TRACE_3(IBMF_TNF_DEBUG, DPRINT_L3,
                    ibmf_saa_informinfo_cb, IBMF_TNF_TRACE, "",
                    "ibmf_saa_informinfo_cb(): %s, success mask = 0x%x,"
                    " last success mask = 0x%x\n",
                    tnf_string, msg, "all informinfo responses have arrived",
                    tnf_opaque, success_mask,
                    saa_portp->saa_pt_event_sub_success_mask,
                    tnf_opaque, last_success_mask,
                    saa_portp->saa_pt_event_sub_last_success_mask);

                mutex_enter(&saa_portp->saa_pt_mutex);

                /*
                 * Sending the four InformInfos is treated as one port client
                 * reference.  Now that all have returned decrement the
                 * reference count.
                 */
                ASSERT(saa_portp->saa_pt_reference_count > 0);
                saa_portp->saa_pt_reference_count--;

                mutex_exit(&saa_portp->saa_pt_mutex);

                if (saa_portp->saa_pt_event_sub_last_success_mask !=
                    saa_portp->saa_pt_event_sub_success_mask) {

                        IBMF_TRACE_1(IBMF_TNF_DEBUG, DPRINT_L2,
                            ibmf_saa_informinfo_cb, IBMF_TNF_TRACE, "",
                            "ibmf_saa_informinfo_cb(): %s\n",
                            tnf_string, msg,
                            "success mask different - notifying clients");

                        /*
                         * save status mask to give to clients and update last
                         * mask for next set of subscription requests
                         */
                        event_status_mask =
                            saa_portp->saa_pt_event_sub_last_success_mask =
                            saa_portp->saa_pt_event_sub_success_mask;

                        notify_clients = B_TRUE;
                }
        }

        mutex_exit(&saa_portp->saa_pt_event_sub_mutex);

        if (notify_clients == B_TRUE) {

                bzero(&event_details, sizeof (ibmf_saa_event_details_t));

                event_details.ie_producer_event_status_mask =
                    event_status_mask;

                ibmf_saa_notify_event_clients(saa_portp, &event_details,
                    IBMF_SAA_EVENT_SUBSCRIBER_STATUS_CHG, NULL);
        }

bail:
        IBMF_TRACE_0(IBMF_TNF_DEBUG, DPRINT_L4,
            ibmf_saa_informinfo_cb_end,
            IBMF_TNF_TRACE, "", "ibmf_saa_informinfo_cb() exit\n");
}

/*
 * ibmf_saa_notify_event_client_task
 *
 * Calls the event notification callback for a registered saa client.  Called
 * from ibmf_saa_notify_event_clients() for each client that has registered for
 * events.  ibmf_saa_notify_event_clients() will dispatch this task on the
 * saa_event_taskq so the client's callback can be invoked directly.
 *
 * Input Arguments
 * args                 pointer to ibmf_saa_event_taskq_args_t
 *                      this function will free memory associated with args
 *
 * Output Arguments
 * none
 *
 * Returns
 * void
 */
static void
ibmf_saa_notify_event_client_task(void *args)
{
        ibmf_saa_event_taskq_args_t     *event_taskq_args;
        saa_client_data_t               *client;

        IBMF_TRACE_0(IBMF_TNF_DEBUG, DPRINT_L3,
            ibmf_saa_notify_event_client_task_start,
            IBMF_TNF_TRACE, "", "ibmf_saa_notify_event_client_task() enter\n");

        event_taskq_args = (ibmf_saa_event_taskq_args_t *)args;

        client = event_taskq_args->et_client;

        /* call client's callback (client pointer is ibmf_saa_handle) */
        (event_taskq_args->et_callback)((ibmf_saa_handle_t)client,
            event_taskq_args->et_subnet_event,
            event_taskq_args->et_event_details,
            event_taskq_args->et_callback_arg);

        kmem_free(event_taskq_args->et_event_details,
            sizeof (ibmf_saa_event_details_t));

        kmem_free(event_taskq_args, sizeof (ibmf_saa_event_taskq_args_t));

        /* decrement the callback count and signal a waiting client */
        mutex_enter(&client->saa_client_mutex);

        client->saa_client_event_cb_num_active--;

        if (client->saa_client_event_cb_num_active == 0) {

                cv_signal(&client->saa_client_event_cb_cv);

        }

        mutex_exit(&client->saa_client_mutex);

        IBMF_TRACE_0(IBMF_TNF_DEBUG, DPRINT_L3,
            ibmf_saa_notify_event_client_task_end,
            IBMF_TNF_TRACE, "", "ibmf_saa_notify_event_client_task() exit\n");
}

/*
 * ibmf_saa_process_subnet_event:
 *
 * Called when the ibmf_saa is notified of a forwarded notice.  Converts the
 * notice into an ibmf_saa_event_details structure and calls
 * ibmf_saa_notify_event_clients() which will notify each interested client.
 *
 * Input Arguments
 * saa_portp            pointer to saa_port data
 * notice               notice that was forwarded from SA
 *
 * Output Arguments
 * none
 *
 * Returns
 * void
 */
static void
ibmf_saa_process_subnet_event(saa_port_t *saa_portp, ib_mad_notice_t *notice)
{
        ibmf_saa_event_details_t        event_details;
        sm_trap_64_t                    trap_data_details;
        sm_trap_144_t                   cap_mask_trap_data_details;
        sm_trap_145_t                   sys_img_trap_data_details;
        ibmf_saa_subnet_event_t         subnet_event;

        IBMF_TRACE_1(IBMF_TNF_DEBUG, DPRINT_L3,
            ibmf_saa_process_subnet_event_start,
            IBMF_TNF_TRACE, "", "ibmf_saa_process_subnet_event() enter: "
            "trap_number = 0x%x\n",
            tnf_opaque, trap_number, notice->TrapNumber_DeviceID);

        bzero(&event_details, sizeof (ibmf_saa_event_details_t));

        /*
         * fill in the appropriate fields of event_details depending on
         * the trap number
         */
        switch (notice->TrapNumber_DeviceID) {

                case SM_GID_IN_SERVICE_TRAP:

                        ibmf_saa_gid_trap_parse_buffer(notice->DataDetails,
                            &trap_data_details);

                        event_details.ie_gid = trap_data_details.GIDADDR;

                        subnet_event = IBMF_SAA_EVENT_GID_AVAILABLE;
                break;


                case SM_GID_OUT_OF_SERVICE_TRAP:

                        ibmf_saa_gid_trap_parse_buffer(notice->DataDetails,
                            &trap_data_details);

                        event_details.ie_gid = trap_data_details.GIDADDR;

                        subnet_event = IBMF_SAA_EVENT_GID_UNAVAILABLE;
                break;

                case SM_MGID_CREATED_TRAP:

                        ibmf_saa_gid_trap_parse_buffer(notice->DataDetails,
                            &trap_data_details);

                        event_details.ie_gid = trap_data_details.GIDADDR;

                        subnet_event = IBMF_SAA_EVENT_MCG_CREATED;
                break;

                case SM_MGID_DESTROYED_TRAP:

                        ibmf_saa_gid_trap_parse_buffer(notice->DataDetails,
                            &trap_data_details);

                        event_details.ie_gid = trap_data_details.GIDADDR;

                        subnet_event = IBMF_SAA_EVENT_MCG_DELETED;
                break;

                case SM_CAP_MASK_CHANGED_TRAP:

                        ibmf_saa_capmask_chg_trap_parse_buffer(
                            notice->DataDetails, &cap_mask_trap_data_details);

                        event_details.ie_lid =
                            cap_mask_trap_data_details.LIDADDR;
                        event_details.ie_capability_mask =
                            cap_mask_trap_data_details.CAPABILITYMASK;

                        subnet_event = IBMF_SAA_EVENT_CAP_MASK_CHG;
                break;

                case SM_SYS_IMG_GUID_CHANGED_TRAP:

                        ibmf_saa_sysimg_guid_chg_trap_parse_buffer(
                            notice->DataDetails, &sys_img_trap_data_details);

                        event_details.ie_lid =
                            sys_img_trap_data_details.LIDADDR;
                        event_details.ie_sysimg_guid =
                            sys_img_trap_data_details.SYSTEMIMAGEGUID;

                        subnet_event = IBMF_SAA_EVENT_SYS_IMG_GUID_CHG;
                break;

                default:
                        /*
                         * do nothing if it's not one of the traps we care about
                         */
                        IBMF_TRACE_1(IBMF_TNF_DEBUG, DPRINT_L3,
                            ibmf_saa_process_subnet_event_end,
                            IBMF_TNF_TRACE, "",
                            "ibmf_saa_process_subnet_event() exit: %s\n",
                            tnf_string, msg,
                            "not one of the six ibmf_saa subnet events");

                        return;
        }

        ibmf_saa_notify_event_clients(saa_portp, &event_details, subnet_event,
            NULL);

        IBMF_TRACE_0(IBMF_TNF_DEBUG, DPRINT_L4,
            ibmf_saa_process_subnet_event_end,
            IBMF_TNF_TRACE, "", "ibmf_saa_process_subnet_event() exit\n");
}

/*
 * ibmf_saa_notify_event_clients:
 *
 * Called when a trap for one of the six saa subnet events arrives or there is a
 * change in the status of event subscriptions.  Searches the list of clients
 * with callbacks and dispatches a taskq thread to notify the client that the
 * event occured.
 *
 * If some subscription request fails and a subsequent client registers for
 * events that client needs to know that it may not receive all events.  To
 * facilitate this, notify_event_clients() takes an optional parameter which
 * specifies a specific client.  If registering_client is non-NULL only this
 * client is notified.  If the parameter is NULL, all clients in the list are
 * notified.
 *
 * Input Arguments
 * saa_portp            pointer to saa_port data
 * event_details        pointer to ibmf_saa_event_details_t for this event
 * subnet_event         type of event that occured
 * registering_client   pointer to client_data_t if notification should go to a
 *                      specific client; NULL if notification should go to all
 *                      clients which subscribed for events
 *
 * Output Arguments
 * none
 *
 * Returns
 * none
 */
void
ibmf_saa_notify_event_clients(saa_port_t *saa_portp,
    ibmf_saa_event_details_t *event_details,
    ibmf_saa_subnet_event_t subnet_event, saa_client_data_t *registering_client)
{
        saa_client_data_t               *client;
        ibmf_saa_event_taskq_args_t     *event_taskq_args;
        int                             status;

        IBMF_TRACE_0(IBMF_TNF_DEBUG, DPRINT_L4,
            ibmf_saa_notify_event_clients_start,
            IBMF_TNF_TRACE, "", "ibmf_saa_notify_event_clients() enter\n");

        mutex_enter(&saa_portp->saa_pt_event_sub_mutex);

        if (registering_client != NULL)
                client = registering_client;
        else
                client = saa_portp->saa_pt_event_sub_client_list;

        while (client != NULL) {

                _NOTE(NOW_INVISIBLE_TO_OTHER_THREADS(*event_taskq_args))

                event_taskq_args = kmem_zalloc(
                    sizeof (ibmf_saa_event_taskq_args_t), KM_NOSLEEP);
                if (event_taskq_args == NULL) {

                        IBMF_TRACE_2(IBMF_TNF_NODEBUG, DPRINT_L1,
                            ibmf_saa_notify_event_clients_err, IBMF_TNF_ERROR,
                            "", "ibmf_saa_notify_event_clients: %s, client = "
                            "0x%x\n", tnf_string, msg,
                            "could not allocate memory for taskq args",
                            tnf_opaque, client, client);

                        /*
                         * if a particular client was not specified continue
                         * processing the client list
                         */
                        if (registering_client == NULL)
                                client = client->next;
                        else
                                client = NULL;

                        continue;
                }

                /*
                 * each task needs its own pointer, the task will free
                 * up this memory
                 */
                event_taskq_args->et_event_details = kmem_zalloc(
                    sizeof (ibmf_saa_event_details_t), KM_NOSLEEP);
                if (event_taskq_args->et_event_details == NULL) {

                        IBMF_TRACE_2(IBMF_TNF_NODEBUG, DPRINT_L1,
                            ibmf_saa_notify_event_clients_err, IBMF_TNF_ERROR,
                            "", "ibmf_saa_notify_event_clients: %s, client = "
                            "0x%x\n", tnf_string, msg,
                            "could not allocate memory for taskq event details",
                            tnf_opaque, client, client);

                        kmem_free(event_taskq_args,
                            sizeof (ibmf_saa_event_taskq_args_t));

                        /*
                         * if a particular client was not specified continue
                         * processing the client list
                         */
                        client =
                            (registering_client == NULL) ? client->next: NULL;

                        continue;
                }

                mutex_enter(&client->saa_client_mutex);

                /*
                 * don't generate callbacks if client is not active
                 * (it's probably closing the session)
                 */
                if (client->saa_client_state != SAA_CLIENT_STATE_ACTIVE) {

                        IBMF_TRACE_3(IBMF_TNF_DEBUG, DPRINT_L2,
                            ibmf_saa_notify_event_clients, IBMF_TNF_TRACE,
                            "", "ibmf_saa_notify_event_clients: %s, client = "
                            "0x%x, state = 0x%x\n", tnf_string, msg,
                            "client state not active",
                            tnf_opaque, client, client,
                            tnf_opaque, state, client->saa_client_state);

                        mutex_exit(&client->saa_client_mutex);

                        kmem_free(event_taskq_args->et_event_details,
                            sizeof (ibmf_saa_event_details_t));

                        kmem_free(event_taskq_args,
                            sizeof (ibmf_saa_event_taskq_args_t));

                        /*
                         * if a particular client was not specified continue
                         * processing the client list
                         */
                        client =
                            (registering_client == NULL) ? client->next: NULL;

                        continue;
                }

                /*
                 * increment the callback count so the client cannot close the
                 * session while callbacks are active
                 */
                client->saa_client_event_cb_num_active++;

                mutex_exit(&client->saa_client_mutex);

                event_taskq_args->et_client = client;
                event_taskq_args->et_subnet_event = subnet_event;

                bcopy(event_details, event_taskq_args->et_event_details,
                    sizeof (ibmf_saa_event_details_t));

                event_taskq_args->et_callback = client->saa_client_event_cb;
                event_taskq_args->et_callback_arg =
                    client->saa_client_event_cb_arg;

                _NOTE(NOW_VISIBLE_TO_OTHER_THREADS(*event_taskq_args))

                /* dispatch taskq thread to notify client */
                status = taskq_dispatch(saa_statep->saa_event_taskq,
                    ibmf_saa_notify_event_client_task, event_taskq_args,
                    KM_NOSLEEP);
                if (status == TASKQID_INVALID) {

                        IBMF_TRACE_2(IBMF_TNF_NODEBUG, DPRINT_L1,
                            ibmf_saa_notify_event_clients_err, IBMF_TNF_ERROR,
                            "", "ibmf_saa_notify_event_clients: %s, client = "
                            "0x%x\n",
                            tnf_string, msg, "Could not dispatch event taskq",
                            tnf_opaque, client, client);

                        kmem_free(event_taskq_args->et_event_details,
                            sizeof (ibmf_saa_event_details_t));

                        kmem_free(event_taskq_args,
                            sizeof (ibmf_saa_event_taskq_args_t));

                        /*
                         * decrement the callback count and signal a waiting
                         * client
                         */
                        mutex_enter(&client->saa_client_mutex);

                        client->saa_client_event_cb_num_active--;

                        if (client->saa_client_event_cb_num_active == 0) {

                                cv_signal(&client->saa_client_event_cb_cv);

                        }

                        mutex_exit(&client->saa_client_mutex);

                } else {

                        IBMF_TRACE_2(IBMF_TNF_DEBUG, DPRINT_L3,
                            ibmf_saa_notify_event_clients_err, IBMF_TNF_ERROR,
                            "", "ibmf_saa_notify_event_clients: %s, client = "
                            "0x%x\n",
                            tnf_string, msg, "Dispatched task to notify client",
                            tnf_opaque, client, client);
                }


                /*
                 * if a particular client was not specified continue processing
                 * the client list
                 */
                client = (registering_client == NULL) ? client->next: NULL;
        }

        mutex_exit(&saa_portp->saa_pt_event_sub_mutex);

        IBMF_TRACE_0(IBMF_TNF_DEBUG, DPRINT_L4,
            ibmf_saa_notify_event_clients_end,
            IBMF_TNF_TRACE, "", "ibmf_saa_notify_event_clients() exit\n");
}

/*
 * ibmf_saa_report_cb:
 *
 * Called when a forwarded notice Report is received by ibmf_saa from the SA.
 * Converts the Report into an ib_mad_notice_t and calls
 * ibmf_saa_notify_event_clients() which will notify each subscribed ibmf_saa
 * client.  Also sends a response to the report to acknowledge to the SA that
 * this port is still up.
 *
 * This is the registered async callback with ibmf.  Only Reports should come
 * through this interface as all other transactions with ibmf_saa are sequenced
 * (ibmf_saa makes the initial request).
 *
 * This function cannot block since it is called from an ibmf callback.
 *
 * Input Arguments
 * ibmf_handle                  ibmf handle
 * msgp                         pointer to ibmf_msg_t
 * args                         pointer to saa_port data
 *
 * Output Arguments
 * none
 *
 * Returns
 * none
 */
void
ibmf_saa_report_cb(ibmf_handle_t ibmf_handle, ibmf_msg_t *msgp,
    void *args)
{
        ib_mad_hdr_t            *req_mad_hdr, *resp_mad_hdr;
        saa_port_t              *saa_portp, *saa_port_list_entry;
        ibmf_retrans_t          ibmf_retrans;
        int                     ibmf_status;
        ib_mad_notice_t         *notice_report;
        saa_impl_trans_info_t   *trans_info;
        boolean_t               port_valid;
        uint16_t                mad_status;
        uint16_t                attr_id;
        boolean_t               response_sent = B_FALSE;
        size_t                  length;
        int                     status;

        IBMF_TRACE_0(IBMF_TNF_DEBUG, DPRINT_L4,
            ibmf_saa_report_cb_start,
            IBMF_TNF_TRACE, "", "ibmf_saa_report_cb() enter\n");

        _NOTE(ASSUMING_PROTECTED(*msgp))

        saa_portp = (saa_port_t *)args;

        port_valid = B_FALSE;

        /* check whether this portp is still valid */
        mutex_enter(&saa_statep->saa_port_list_mutex);

        saa_port_list_entry = saa_statep->saa_port_list;
        while (saa_port_list_entry != NULL) {

                if (saa_port_list_entry == saa_portp) {

                        port_valid = ibmf_saa_is_valid(saa_portp, B_FALSE);

                        break;
                }
                saa_port_list_entry = saa_port_list_entry->next;
        }

        mutex_exit(&saa_statep->saa_port_list_mutex);

        if (port_valid == B_FALSE) {

                IBMF_TRACE_2(IBMF_TNF_DEBUG, DPRINT_L2,
                    ibmf_saa_report_cb, IBMF_TNF_TRACE, "",
                    "ibmf_saa_report_cb: %s, saa_port = 0x%p\n",
                    tnf_string, msg, "port no longer valid",
                    tnf_opaque, saa_portp, saa_portp);

                goto bail;
        }

        req_mad_hdr = msgp->im_msgbufs_recv.im_bufs_mad_hdr;

        /* drop packet if status is bad */
        if ((msgp->im_msg_status != IBMF_SUCCESS) ||
            (req_mad_hdr == NULL) ||
            ((mad_status = b2h16(req_mad_hdr->Status)) != SA_STATUS_NO_ERROR)) {

                IBMF_TRACE_4(IBMF_TNF_DEBUG, DPRINT_L1,
                    ibmf_saa_report_cb, IBMF_TNF_TRACE, "",
                    "ibmf_saa_report_cb: %s, msg_status = 0x%x,"
                    " req_mad_hdr = 0x%p, mad_status = 0x%x\n",
                    tnf_string, msg, "Bad ibmf status",
                    tnf_opaque, msg_status, msgp->im_msg_status,
                    tnf_opaque, req_mad_hdr, req_mad_hdr,
                    tnf_opaque, mad_status,
                    (req_mad_hdr == NULL ? 0 : mad_status));

                goto bail;
        }

        /* drop packet if class version is not correct */
        if (req_mad_hdr->ClassVersion != SAA_MAD_CLASS_VERSION) {

                IBMF_TRACE_3(IBMF_TNF_DEBUG, DPRINT_L1,
                    ibmf_saa_report_cb, IBMF_TNF_TRACE, "",
                    "ibmf_saa_report_cb: %s, msg_class_ver = 0x%x,"
                    " ibmf_saa_class_ver = 0x%x\n",
                    tnf_string, msg, "Bad class version",
                    tnf_opaque, msg_class_ver, req_mad_hdr->ClassVersion,
                    tnf_opaque, ibmf_saa_class_ver, SAA_MAD_CLASS_VERSION);

                goto bail;
        }


        /*
         * only care about notice reports(); should not get any other type
         * of method or attribute
         */
        if (((attr_id = b2h16(req_mad_hdr->AttributeID)) != SA_NOTICE_ATTRID) ||
            (req_mad_hdr->R_Method != SA_SUBN_ADM_REPORT)) {

                IBMF_TRACE_3(IBMF_TNF_DEBUG, DPRINT_L2,
                    ibmf_saa_report_cb, IBMF_TNF_TRACE, "",
                    "ibmf_saa_report_cb: %s, attr_id = 0x%x, "
                    "method = 0x%x\n",
                    tnf_string, msg, "Unsolicited message not notice report",
                    tnf_opaque, attr_id, attr_id,
                    tnf_opaque, method, req_mad_hdr->R_Method);

                goto bail;
        }

        /*
         * unpack the data into a ib_mad_notice_t; the data details are left
         * as packed data and will be unpacked by process_subnet_event()
         * is_get_resp parameter is set to B_TRUE since cl_data_len will
         * probably be set to 200 bytes by ibmf (it's not an RMPP trans)
         */
        status = ibmf_saa_utils_unpack_payload(
            msgp->im_msgbufs_recv.im_bufs_cl_data,
            msgp->im_msgbufs_recv.im_bufs_cl_data_len, SA_NOTICE_ATTRID,
            (void **)&notice_report, &length, 0, B_TRUE, KM_NOSLEEP);
        if (status != IBMF_SUCCESS) {

                IBMF_TRACE_2(IBMF_TNF_DEBUG, DPRINT_L2,
                    ibmf_saa_report_cb, IBMF_TNF_TRACE, "",
                    "ibmf_saa_report_cb: %s, status = %d",
                    tnf_string, msg, "Could not unpack data",
                    tnf_int, status, status);

                goto bail;
        }

        ASSERT(length == sizeof (ib_mad_notice_t));

        ibmf_saa_process_subnet_event(saa_portp, notice_report);

        kmem_free(notice_report, length);

        _NOTE(NOW_INVISIBLE_TO_OTHER_THREADS(*resp_mad_hdr))

        /* send ReportResp */
        resp_mad_hdr = kmem_zalloc(sizeof (ib_mad_hdr_t), KM_SLEEP);

        bcopy(req_mad_hdr, resp_mad_hdr, sizeof (ib_mad_hdr_t));

        resp_mad_hdr->R_Method = SA_SUBN_ADM_REPORT_RESP;

        msgp->im_msgbufs_send.im_bufs_mad_hdr = resp_mad_hdr;
        msgp->im_msgbufs_send.im_bufs_cl_hdr = kmem_zalloc(
            msgp->im_msgbufs_recv.im_bufs_cl_hdr_len, KM_SLEEP);
        msgp->im_msgbufs_send.im_bufs_cl_hdr_len =
            msgp->im_msgbufs_recv.im_bufs_cl_hdr_len;

        /* only headers are needed */
        msgp->im_msgbufs_send.im_bufs_cl_data = NULL;
        msgp->im_msgbufs_send.im_bufs_cl_data_len = 0;

        /*
         * report_cb cannot block because it's in the context of an ibmf
         * callback.  So the response needs to be sent asynchronously.
         * ibmf_saa_async_cb is an appropriate callback to use for the response.
         * Set up a trans_info structure as saa_async_cb expects.  But don't use
         * ibmf_saa_impl_send_request() to send the response since that function
         * does unncessary steps in this case (like allocating a new ibmf msg).
         * Only the si_trans_port field needs to be filled in.
         */
        trans_info = kmem_zalloc(sizeof (saa_impl_trans_info_t), KM_NOSLEEP);
        if (trans_info == NULL) {

                IBMF_TRACE_1(IBMF_TNF_NODEBUG, DPRINT_L1,
                    ibmf_saa_report_cb_err, IBMF_TNF_TRACE, "",
                    "ibmf_saa_report_cb: %s",
                    tnf_string, msg, "could not allocate trans_info structure");

                goto bail;
        }

        trans_info->si_trans_port = saa_portp;

        mutex_enter(&saa_portp->saa_pt_mutex);

        bcopy(&saa_portp->saa_pt_ibmf_retrans, &ibmf_retrans,
            sizeof (ibmf_retrans_t));

        saa_portp->saa_pt_num_outstanding_trans++;

        mutex_exit(&saa_portp->saa_pt_mutex);

        ASSERT(ibmf_handle == saa_portp->saa_pt_ibmf_handle);

        ibmf_status = ibmf_msg_transport(ibmf_handle,
            saa_portp->saa_pt_qp_handle, msgp, &ibmf_retrans, ibmf_saa_async_cb,
            trans_info, 0);
        if (ibmf_status != IBMF_SUCCESS) {

                IBMF_TRACE_2(IBMF_TNF_DEBUG, DPRINT_L1,
                    ibmf_saa_report_cb, IBMF_TNF_TRACE, "",
                    "ibmf_saa_report_cb: %s, msg_status = 0x%x\n",
                    tnf_string, msg, "Could not send report response",
                    tnf_int, ibmf_status, ibmf_status);

                mutex_enter(&saa_portp->saa_pt_mutex);

                ASSERT(saa_portp->saa_pt_num_outstanding_trans > 0);
                saa_portp->saa_pt_num_outstanding_trans--;

                mutex_exit(&saa_portp->saa_pt_mutex);

                kmem_free(trans_info, sizeof (saa_impl_trans_info_t));

        } else {

                IBMF_TRACE_1(IBMF_TNF_DEBUG, DPRINT_L3,
                    ibmf_saa_report_cb, IBMF_TNF_TRACE, "",
                    "ibmf_saa_report_cb: %s\n",
                    tnf_string, msg, "Asynchronous Report response sent");

                response_sent = B_TRUE;
        }

bail:
        if (response_sent == B_FALSE) {
                ibmf_status = ibmf_free_msg(ibmf_handle, &msgp);
                ASSERT(ibmf_status == IBMF_SUCCESS);
        }

        IBMF_TRACE_0(IBMF_TNF_DEBUG, DPRINT_L4,
            ibmf_saa_informinfo_cb_end, IBMF_TNF_TRACE, "",
            "ibmf_saa_report_cb() exit\n");
}

/*
 * ibmf_saa_add_event_subscriber:
 *
 * Adds an interested client to the list of subscribers for events for a port.
 * If it's the first client, generates the subscription requests.
 * This function must only be called if event_args is not null
 *
 * Input Arguments
 *
 * client               pointer to client data (client->saa_port should be set)
 * event_args           pointer to event_args passed in from client (non-NULL)
 *
 * Output Arguments
 * none
 *
 * Returns
 * void
 */
void
ibmf_saa_add_event_subscriber(saa_client_data_t *client,
    ibmf_saa_subnet_event_args_t *event_args)
{
        saa_port_t                      *saa_portp;
        boolean_t                       first_client;
        uint8_t                         producer_status_mask;
        ibmf_saa_event_details_t        event_details;

        IBMF_TRACE_0(IBMF_TNF_DEBUG, DPRINT_L4,
            ibmf_saa_add_event_subscriber_start, IBMF_TNF_TRACE, "",
            "ibmf_saa_add_event_subscriber() enter\n");

        /* event_args should be checked before calling this function */
        ASSERT(event_args != NULL);

        /* don't add client if no callback function is specified */
        if (event_args->is_event_callback == NULL)
                return;

        saa_portp = client->saa_client_port;

        client->saa_client_event_cb = event_args->is_event_callback;
        client->saa_client_event_cb_arg = event_args->is_event_callback_arg;

        /*
         * insert this client onto the list; this list is used when a
         * Report arrives to call each client's callback
         */
        mutex_enter(&saa_portp->saa_pt_event_sub_mutex);

        IBMF_TRACE_2(IBMF_TNF_DEBUG, DPRINT_L3,
            ibmf_sa_session_open, IBMF_TNF_TRACE, "",
            "ibmf_saa_add_event_subscriber: %s, client = 0x%x\n",
            tnf_string, msg, "Adding client to event subscriber list",
            tnf_opaque, client, client);

        if (saa_portp->saa_pt_event_sub_client_list == NULL)
                first_client = B_TRUE;
        else {
                first_client = B_FALSE;
                producer_status_mask =
                    saa_portp->saa_pt_event_sub_last_success_mask;
        }

        client->next = saa_portp->saa_pt_event_sub_client_list;
        saa_portp->saa_pt_event_sub_client_list = client;

        mutex_exit(&saa_portp->saa_pt_event_sub_mutex);

        if (first_client == B_TRUE) {

                /*
                 * increment the reference count by one to account for
                 * the subscription requests.  All four InformInfo's are
                 * sent as one port client reference.
                 */
                mutex_enter(&saa_portp->saa_pt_mutex);

                saa_portp->saa_pt_reference_count++;

                mutex_exit(&saa_portp->saa_pt_mutex);

                /* subscribe for subnet events */
                ibmf_saa_subscribe_events(saa_portp, B_TRUE, B_FALSE);

        } else if (producer_status_mask != IBMF_SAA_PORT_EVENT_SUB_ALL_ARRIVE) {

                /*
                 * if this is not the first client and the producer status mask
                 * is not all success, generate a callback to indicate to the
                 * client that not all events will be forwarded
                 */
                bzero(&event_details, sizeof (ibmf_saa_event_details_t));

                event_details.ie_producer_event_status_mask =
                    producer_status_mask;

                ibmf_saa_notify_event_clients(saa_portp, &event_details,
                    IBMF_SAA_EVENT_SUBSCRIBER_STATUS_CHG, client);
        }

        IBMF_TRACE_0(IBMF_TNF_DEBUG, DPRINT_L4,
            ibmf_saa_add_event_subscriber_end, IBMF_TNF_TRACE, "",
            "ibmf_saa_add_event_subscriber() exit\n");
}