root/usr/src/uts/common/io/ib/mgt/ibdma/ibdma.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
 */

/*
 * Infiniband Device Management Agent for IB storage.
 */

#include <sys/conf.h>
#include <sys/file.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/modctl.h>
#include <sys/priv.h>
#include <sys/sysmacros.h>

#include <sys/ib/ibtl/ibti.h>           /* IB public interfaces */

#include <sys/ib/mgt/ibdma/ibdma.h>
#include <sys/ib/mgt/ibdma/ibdma_impl.h>

/*
 * NOTE: The IB Device Management Agent function, like other IB
 * managers and agents is best implemented as a kernel misc.
 * module.
 * Eventually we could modify IBT_DM_AGENT so that we don't need to
 * open each HCA to receive asynchronous events.
 */

#define IBDMA_NAME_VERSION      "IB Device Management Agent"

extern struct mod_ops mod_miscops;

static void ibdma_ibt_async_handler(void *clnt, ibt_hca_hdl_t hdl,
        ibt_async_code_t code, ibt_async_event_t *event);

static void ibdma_mad_recv_cb(ibmf_handle_t ibmf_hdl,
        ibmf_msg_t *msgp, void *args);
static void ibdma_create_resp_mad(ibmf_msg_t *msgp);

/*
 * Misc. kernel module for now.
 */
static struct modlmisc modlmisc = {
        &mod_miscops,
        IBDMA_NAME_VERSION
};

static struct modlinkage modlinkage = {
        MODREV_1, (void *)&modlmisc, NULL
};

static ibt_clnt_modinfo_t ibdma_ibt_modinfo = {
        IBTI_V_CURR,
        IBT_DM_AGENT,
        ibdma_ibt_async_handler,
        NULL,
        "ibdma"
};

/*
 * Module global state allocated at init().
 */
static ibdma_mod_state_t        *ibdma = NULL;

/*
 * Init/Fini handlers and IBTL HCA management prototypes.
 */
static int ibdma_init();
static int ibdma_fini();
static int ibdma_ibt_init();
static void ibdma_ibt_fini();
static ibdma_hca_t *ibdma_hca_init(ib_guid_t guid);
static void ibdma_hca_fini(ibdma_hca_t *hca);
static ibdma_hca_t *ibdma_find_hca(ib_guid_t guid);

/*
 * DevMgmt Agent MAD attribute handlers prototypes.
 */
static void ibdma_get_class_portinfo(ibmf_msg_t *msg);
static void ibdma_get_io_unitinfo(ibdma_hca_t *hca, ibmf_msg_t *msg);
static void ibdma_get_ioc_profile(ibdma_hca_t *hca, ibmf_msg_t *msg);
static void ibdma_get_ioc_services(ibdma_hca_t *hca, ibmf_msg_t *msg);

/*
 * _init()
 */
int
_init(void)
{
        int status;

        ASSERT(ibdma == NULL);

        ibdma = kmem_zalloc(sizeof (*ibdma), KM_SLEEP);
        ASSERT(ibdma != NULL);

        status = ibdma_init();
        if (status != DDI_SUCCESS) {
                kmem_free(ibdma, sizeof (*ibdma));
                ibdma = NULL;
                return (status);
        }

        status = mod_install(&modlinkage);
        if (status != DDI_SUCCESS) {
                cmn_err(CE_NOTE, "_init, mod_install error (%d)", status);
                (void) ibdma_fini();
                kmem_free(ibdma, sizeof (*ibdma));
                ibdma = NULL;
        }
        return (status);
}

/*
 * _info()
 */
int
_info(struct modinfo *modinfop)
{
        return (mod_info(&modlinkage, modinfop));
}

/*
 * _fini()
 */
int
_fini(void)
{
        int             status;
        int             slot;
        ibdma_hca_t     *hca;

        status = mod_remove(&modlinkage);
        if (status != DDI_SUCCESS) {
                cmn_err(CE_NOTE, "_fini, mod_remove error (%d)", status);
                return (status);
        }

        /*
         * Sanity check to see if anyone is not cleaning
         * up appropriately.
         */
        mutex_enter(&ibdma->ms_hca_list_lock);
        hca = list_head(&ibdma->ms_hca_list);
        while (hca != NULL) {
                for (slot = 0; slot < IBDMA_MAX_IOC; slot++) {
                        if (hca->ih_ioc[slot].ii_inuse) {
                                cmn_err(CE_NOTE, "_fini, IOC %d still attached"
                                    " for (0x%0llx)", slot+1,
                                    (u_longlong_t)hca->ih_iou_guid);
                        }
                }
                hca = list_next(&ibdma->ms_hca_list, hca);
        }
        mutex_exit(&ibdma->ms_hca_list_lock);

        (void) ibdma_fini();
        kmem_free(ibdma, sizeof (*ibdma));
        return (status);
}

/*
 * ibdma_init()
 *
 * Initialize I/O Unit structure, generate initial HCA list and register
 * it port with the IBMF.
 */
static int
ibdma_init()
{
        int             status;

        /*
         * Global lock and I/O Unit initialization.
         */
        mutex_init(&ibdma->ms_hca_list_lock, NULL, MUTEX_DRIVER, NULL);

        /*
         * Discover IB hardware and setup for device management agent
         * support.
         */
        status = ibdma_ibt_init();
        if (status != DDI_SUCCESS) {
                cmn_err(CE_NOTE, "ibdma_init, ibt_attach failed (%d)",
                    status);
                mutex_destroy(&ibdma->ms_hca_list_lock);
                return (status);
        }

        return (status);
}

/*
 * ibdma_fini()
 *
 * Release resource if we are no longer in use.
 */
static int
ibdma_fini()
{
        ibdma_ibt_fini();
        mutex_destroy(&ibdma->ms_hca_list_lock);
        return (DDI_SUCCESS);
}

/*
 * ibdma_ibt_async_handler()
 */
/* ARGSUSED */
static void
ibdma_ibt_async_handler(void *clnt, ibt_hca_hdl_t hdl,
        ibt_async_code_t code, ibt_async_event_t *event)
{
        ibdma_hca_t     *hca;

        switch (code) {

        case IBT_EVENT_PORT_UP:
        case IBT_ERROR_PORT_DOWN:
        case IBT_PORT_CHANGE_EVENT:
        case IBT_CLNT_REREG_EVENT:
                break;

        case IBT_HCA_ATTACH_EVENT:
                mutex_enter(&ibdma->ms_hca_list_lock);
                hca = ibdma_hca_init(event->ev_hca_guid);
                if (hca != NULL) {
                        list_insert_tail(&ibdma->ms_hca_list, hca);
                        cmn_err(CE_NOTE, "hca ibt hdl (%p)",
                            (void *)hca->ih_ibt_hdl);
                        ibdma->ms_num_hcas++;
                }
                mutex_exit(&ibdma->ms_hca_list_lock);
                break;

        case IBT_HCA_DETACH_EVENT:
                mutex_enter(&ibdma->ms_hca_list_lock);
                hca = ibdma_find_hca(event->ev_hca_guid);
                if (hca != NULL) {
                        list_remove(&ibdma->ms_hca_list, hca);
                        cmn_err(CE_NOTE, "removing hca (%p) (0x%llx)",
                            (void *)hca, hca ?
                            (u_longlong_t)hca->ih_iou_guid : 0x0ll);
                        ibdma_hca_fini(hca);
                }
                mutex_exit(&ibdma->ms_hca_list_lock);
                break;

        default:
#ifdef  DEBUG
                cmn_err(CE_NOTE, "ibt_async_handler, unhandled event(%d)",
                    code);
#endif
                break;
        }

}

/*
 * ibdma_ibt_init()
 */
static int
ibdma_ibt_init()
{
        int             status;
        int             hca_cnt;
        int             hca_ndx;
        ib_guid_t       *guid;
        ibdma_hca_t     *hca;

        /*
         * Attach to IBTF and get HCA list.
         */
        status = ibt_attach(&ibdma_ibt_modinfo, NULL,
            ibdma, &ibdma->ms_ibt_hdl);
        if (status != DDI_SUCCESS) {
                cmn_err(CE_NOTE, "ibt_init, ibt_attach failed (%d)",
                    status);
                return (status);
        }

        list_create(&ibdma->ms_hca_list, sizeof (ibdma_hca_t),
            offsetof(ibdma_hca_t, ih_node));

        hca_cnt = ibt_get_hca_list(&guid);
        if (hca_cnt < 1) {
#ifdef  DEBUG_IBDMA
                cmn_err(CE_NOTE, "ibt_init, no HCA(s) found");
#endif
                /* not an error if no HCAs, but nothing more to do here */
                return (DDI_SUCCESS);
        }

        mutex_enter(&ibdma->ms_hca_list_lock);

        for (hca_ndx = 0; hca_ndx < hca_cnt; hca_ndx++) {
#ifdef  DEBUG_IBDMA
                cmn_err(CE_NOTE, "adding hca GUID(0x%llx)",
                    (u_longlong_t)guid[hca_ndx]);
#endif

                hca = ibdma_hca_init(guid[hca_ndx]);
                if (hca == NULL) {
                        cmn_err(CE_NOTE, "ibt_init, hca_init GUID(0x%llx)"
                            " failed", (u_longlong_t)guid[hca_ndx]);
                        continue;
                }
                list_insert_tail(&ibdma->ms_hca_list, hca);
                ibdma->ms_num_hcas++;
        }

        mutex_exit(&ibdma->ms_hca_list_lock);

        ibt_free_hca_list(guid, hca_cnt);
#ifdef  DEBUG_IBDMA
        cmn_err(CE_NOTE, "Added %d HCA(s)",
            ibdma->ms_num_hcas);
#endif
        return (DDI_SUCCESS);
}

/*
 * ibdma_ibt_fini()
 */
static void
ibdma_ibt_fini()
{
        ibdma_hca_t             *hca;
        ibdma_hca_t             *next;

        mutex_enter(&ibdma->ms_hca_list_lock);
        hca = list_head(&ibdma->ms_hca_list);
        while (hca != NULL) {
                next = list_next(&ibdma->ms_hca_list, hca);
                list_remove(&ibdma->ms_hca_list, hca);
#ifdef  DEBUG_IBDMA
                cmn_err(CE_NOTE, "removing hca (%p) (0x%llx)",
                    (void *)hca, hca ?
                    (u_longlong_t)hca->ih_iou_guid : 0x0ll);
                cmn_err(CE_NOTE, "hca ibt hdl (%p)",
                    (void *)hca->ih_ibt_hdl);
#endif
                ibdma_hca_fini(hca);
                hca = next;
        }
        list_destroy(&ibdma->ms_hca_list);

        (void) ibt_detach(ibdma->ms_ibt_hdl);
        ibdma->ms_ibt_hdl   = NULL;
        ibdma->ms_num_hcas  = 0;
        mutex_exit(&ibdma->ms_hca_list_lock);
}

/*
 * ibdma_find_hca()
 */
static ibdma_hca_t *
ibdma_find_hca(ib_guid_t guid)
{
        ibdma_hca_t     *hca;

        ASSERT(mutex_owned(&ibdma->ms_hca_list_lock));

        hca = list_head(&ibdma->ms_hca_list);
        while (hca != NULL) {
                if (hca->ih_iou_guid == guid) {
                        break;
                }
                hca = list_next(&ibdma->ms_hca_list, hca);
        }
        return (hca);
}

/*
 * ibdma_hca_init()
 */
static ibdma_hca_t *
ibdma_hca_init(ib_guid_t guid)
{
        ibt_status_t            status;
        ibdma_hca_t             *hca;
        ibdma_port_t            *port;
        ibt_hca_attr_t          hca_attr;
        int                     ndx;

        ASSERT(mutex_owned(&ibdma->ms_hca_list_lock));

        status = ibt_query_hca_byguid(guid, &hca_attr);
        if (status != IBT_SUCCESS) {
                cmn_err(CE_NOTE, "hca_init HCA query error (%d)",
                    status);
                return (NULL);
        }

        if (ibdma_find_hca(guid) != NULL) {
#ifdef  DEBUG_IBDMA
                cmn_err(CE_NOTE, "hca_init HCA already exists");
#endif
                return (NULL);
        }

        hca = kmem_zalloc(sizeof (ibdma_hca_t) +
            (hca_attr.hca_nports-1)*sizeof (ibdma_port_t), KM_SLEEP);
        ASSERT(hca != NULL);

        hca->ih_nports   = hca_attr.hca_nports;

        rw_init(&hca->ih_iou_rwlock, NULL, RW_DRIVER, NULL);
        rw_enter(&hca->ih_iou_rwlock, RW_WRITER);
        hca->ih_iou_guid                = guid;
        hca->ih_iou.iou_changeid        = h2b16(1);
        hca->ih_iou.iou_num_ctrl_slots  = IBDMA_MAX_IOC;
        hca->ih_iou.iou_flag            = IB_DM_IOU_OPTIONROM_ABSENT;

        list_create(&hca->ih_hdl_list, sizeof (ibdma_hdl_impl_t),
            offsetof(ibdma_hdl_impl_t, ih_node));
        rw_exit(&hca->ih_iou_rwlock);

        /*
         * It would be better to not open, but IBTL is setup to only allow
         * certain managers to get async call backs if not open.
         */
        status = ibt_open_hca(ibdma->ms_ibt_hdl, guid, &hca->ih_ibt_hdl);
        if (status != IBT_SUCCESS) {
                cmn_err(CE_NOTE, "hca_init() IBT open failed (%d)",
                    status);

                list_destroy(&hca->ih_hdl_list);
                rw_destroy(&hca->ih_iou_rwlock);
                kmem_free(hca, sizeof (ibdma_hca_t) +
                    (hca_attr.hca_nports-1)*sizeof (ibdma_port_t));
                return (NULL);
        }

        /*
         * Register with the IB Management Framework and setup MAD call-back.
         */
        for (ndx = 0; ndx < hca->ih_nports; ndx++) {
                port = &hca->ih_port[ndx];
                port->ip_hcap = hca;
                port->ip_ibmf_reg.ir_ci_guid    = hca->ih_iou_guid;
                port->ip_ibmf_reg.ir_port_num   = ndx + 1;
                port->ip_ibmf_reg.ir_client_class = DEV_MGT_AGENT;

                status = ibmf_register(&port->ip_ibmf_reg, IBMF_VERSION,
                    0, NULL, NULL, &port->ip_ibmf_hdl, &port->ip_ibmf_caps);
                if (status != IBMF_SUCCESS) {
                        cmn_err(CE_NOTE, "hca_init, IBMF register failed (%d)",
                            status);
                        port->ip_ibmf_hdl = NULL;
                        ibdma_hca_fini(hca);
                        return (NULL);
                }

                status = ibmf_setup_async_cb(port->ip_ibmf_hdl,
                    IBMF_QP_HANDLE_DEFAULT, ibdma_mad_recv_cb, port, 0);
                if (status != IBMF_SUCCESS) {
                        cmn_err(CE_NOTE, "hca_init, IBMF cb setup failed (%d)",
                            status);
                        ibdma_hca_fini(hca);
                        return (NULL);
                }

                status = ibt_modify_port_byguid(hca->ih_iou_guid,
                    ndx+1, IBT_PORT_SET_DEVMGT, 0);
                if (status != IBT_SUCCESS) {
                        cmn_err(CE_NOTE, "hca_init, IBT modify port caps"
                            " error (%d)", status);
                        ibdma_hca_fini(hca);
                        return (NULL);
                }
        }
        return (hca);
}

/*
 * ibdma_hca_fini()
 */
static void
ibdma_hca_fini(ibdma_hca_t *hca)
{
        int                     status;
        int                     ndx;
        ibdma_port_t            *port;
        ibdma_hdl_impl_t        *hdl;
        ibdma_hdl_impl_t        *hdl_next;

        ASSERT(mutex_owned(&ibdma->ms_hca_list_lock));
        ASSERT(hca != NULL);

        rw_enter(&hca->ih_iou_rwlock, RW_WRITER);

        /*
         * All handles should have been de-registered, but release
         * any that are outstanding.
         */
        hdl = list_head(&hca->ih_hdl_list);
        while (hdl != NULL) {
                hdl_next = list_next(&hca->ih_hdl_list, hdl);
                list_remove(&hca->ih_hdl_list, hdl);
                cmn_err(CE_NOTE, "hca_fini, unexpected ibdma user handle"
                    " exists");
                kmem_free(hdl, sizeof (*hdl));
                hdl = hdl_next;
        }
        list_destroy(&hca->ih_hdl_list);

        /*
         * Un-register with the IBMF.
         */
        for (ndx = 0; ndx < hca->ih_nports; ndx++) {
                port = &hca->ih_port[ndx];
                port->ip_hcap = NULL;

                status = ibt_modify_port_byguid(hca->ih_iou_guid,
                    ndx+1, IBT_PORT_RESET_DEVMGT, 0);
                if (status != IBT_SUCCESS)
                        cmn_err(CE_NOTE, "hca_fini, IBT modify port caps"
                            " error (%d)", status);

                if (port->ip_ibmf_hdl == NULL)
                        continue;

                status = ibmf_tear_down_async_cb(port->ip_ibmf_hdl,
                    IBMF_QP_HANDLE_DEFAULT, 0);
                if (status != IBMF_SUCCESS)
                        cmn_err(CE_NOTE, "hca_fini, IBMF tear down cb"
                            " error (%d)", status);

                status = ibmf_unregister(&port->ip_ibmf_hdl, 0);
                if (status != IBMF_SUCCESS)
                        cmn_err(CE_NOTE, "hca_fini, IBMF un-register"
                            " error (%d)", status);
                port->ip_ibmf_hdl = NULL;
        }

        status = ibt_close_hca(hca->ih_ibt_hdl);
        if (status != IBT_SUCCESS)
                cmn_err(CE_NOTE, "hca_fini close error (%d)", status);

        rw_exit(&hca->ih_iou_rwlock);
        rw_destroy(&hca->ih_iou_rwlock);
        kmem_free(hca, sizeof (ibdma_hca_t) +
            (hca->ih_nports-1) * sizeof (ibdma_port_t));
}

/* DM IBMF MAD handlers */
/*
 * ibdma_create_resp_mad()
 */
static void
ibdma_create_resp_mad(ibmf_msg_t *msgp)
{
        /*
         * Allocate send buffer fix up hdr for response.
         */
        msgp->im_msgbufs_send.im_bufs_mad_hdr =
            kmem_zalloc(IBDMA_MAD_SIZE, KM_SLEEP);

        msgp->im_msgbufs_send.im_bufs_cl_hdr = (uchar_t *)
            msgp->im_msgbufs_send.im_bufs_mad_hdr + sizeof (ib_mad_hdr_t);
        msgp->im_msgbufs_send.im_bufs_cl_hdr_len = IBDMA_DM_MAD_HDR_SIZE;
        msgp->im_msgbufs_send.im_bufs_cl_data =
            ((char *)msgp->im_msgbufs_send.im_bufs_cl_hdr +
            IBDMA_DM_MAD_HDR_SIZE);
        msgp->im_msgbufs_send.im_bufs_cl_data_len =
            IBDMA_MAD_SIZE - sizeof (ib_mad_hdr_t) - IBDMA_DM_MAD_HDR_SIZE;
        (void) memcpy(msgp->im_msgbufs_send.im_bufs_mad_hdr,
            msgp->im_msgbufs_recv.im_bufs_mad_hdr, IBDMA_MAD_SIZE);

        /*
         * We may want to support a GRH since this is a GMP; not
         * required for current SRP device manager platforms.
         */
#if 0
        if (msgp->im_msg_flags & IBMF_MSG_FLAGS_GLOBAL_ADDRESS) {
                ib_gid_t        temp = msgp->im_global_addr.ig_recver_gid;

                msgp->im_global_addr.ig_recver_gid =
                    msgp->im_global_addr.ig_sender_gid;
                msgp->im_global_addr.ig_sender_gid = temp;
        }
#endif
}

/*
 * ibdma_mad_send_cb()
 */
/* ARGSUSED */
static void
ibdma_mad_send_cb(ibmf_handle_t ibmf_hdl, ibmf_msg_t *msgp, void *arg)
{
        /*
         * Just free the buffers and release the message.
         */
        if (msgp->im_msgbufs_send.im_bufs_mad_hdr != NULL) {
                kmem_free(msgp->im_msgbufs_send.im_bufs_mad_hdr,
                    IBDMA_MAD_SIZE);
                msgp->im_msgbufs_send.im_bufs_mad_hdr = NULL;
        }
        if (ibmf_free_msg(ibmf_hdl, &msgp) != IBMF_SUCCESS) {
                cmn_err(CE_NOTE, "mad_send_cb, IBMF message free error");
        }
}

/*
 * ibdma_mad_recv_cb()
 */
static void
ibdma_mad_recv_cb(ibmf_handle_t ibmf_hdl, ibmf_msg_t *msgp, void *args)
{
        int             status;
        ib_mad_hdr_t    *in_mad;
        ib_mad_hdr_t    *out_mad;
        ibdma_port_t    *port = args;

        ASSERT(msgp != NULL);
        ASSERT(port != NULL);

        if (msgp->im_msg_status != IBMF_SUCCESS) {
                cmn_err(CE_NOTE, "mad_recv_cb, bad MAD receive status (%d)",
                    msgp->im_msg_status);
                goto drop;
        }

        in_mad = msgp->im_msgbufs_recv.im_bufs_mad_hdr;

        if (in_mad->MgmtClass != MAD_MGMT_CLASS_DEV_MGT) {
#ifdef  DEBUG_IBDMA
                cmn_err(CE_NOTE, "mad_recv_cb, MAD not of Dev Mgmt Class");
#endif
                goto drop;
        }

        ibdma_create_resp_mad(msgp);
        out_mad = msgp->im_msgbufs_send.im_bufs_mad_hdr;

        out_mad->R_Method = IB_DM_DEVMGT_METHOD_GET_RESP;
        out_mad->Status   = 0;

        if (in_mad->R_Method == MAD_METHOD_SET) {
#ifdef  DEBUG_IBDMA
                cmn_err(CE_NOTE, "mad_recv_cb, no attributes supported"
                    " for set");
#endif
                out_mad->Status = MAD_STATUS_UNSUPP_METHOD_ATTR;
                goto send_resp;
        }

        if (in_mad->R_Method != MAD_METHOD_GET) {
#ifdef  DEBUG_IBDMA
                cmn_err(CE_NOTE, "mad_recv_cb, no attributes supported"
                    " for set");
#endif
                out_mad->Status = MAD_STATUS_UNSUPP_METHOD;
                goto send_resp;
        }

        /*
         * Process a GET method.
         */
        switch (b2h16(in_mad->AttributeID)) {

        case IB_DM_ATTR_CLASSPORTINFO:
                ibdma_get_class_portinfo(msgp);
                break;

        case IB_DM_ATTR_IO_UNITINFO:
                ibdma_get_io_unitinfo(port->ip_hcap, msgp);
                break;

        case IB_DM_ATTR_IOC_CTRL_PROFILE:
                ibdma_get_ioc_profile(port->ip_hcap, msgp);
                break;

        case IB_DM_ATTR_SERVICE_ENTRIES:
                ibdma_get_ioc_services(port->ip_hcap, msgp);
                break;

        default:
                out_mad->Status = MAD_STATUS_UNSUPP_METHOD_ATTR;
                break;
        }

send_resp:
        status = ibmf_msg_transport(ibmf_hdl, IBMF_QP_HANDLE_DEFAULT,
            msgp, NULL, ibdma_mad_send_cb, NULL, 0);
        if (status != IBMF_SUCCESS) {
                cmn_err(CE_NOTE, "mad_recv_cb, send error (%d)", status);
                ibdma_mad_send_cb(ibmf_hdl, msgp, NULL);
        }
        return;

drop:
        status = ibmf_free_msg(ibmf_hdl, &msgp);
        if (status != IBMF_SUCCESS) {
                cmn_err(CE_NOTE, "mad_recv_cb, error dropping (%d)",
                    status);
        }
}

/*
 * ibdma_get_class_portinfo()
 */
static void
ibdma_get_class_portinfo(ibmf_msg_t *msg)
{
        ib_mad_classportinfo_t  *cpip;

        cpip = (ib_mad_classportinfo_t *)msg->im_msgbufs_send.im_bufs_cl_data;
        bzero(cpip, sizeof (*cpip));
        cpip->BaseVersion   = MAD_CLASS_BASE_VERS_1;
        cpip->ClassVersion  = IB_DM_CLASS_VERSION_1;
        cpip->RespTimeValue = h2b32(IBDMA_DM_RESP_TIME);
}

/*
 * ibdma_get_io_unitinfo()
 */
static void
ibdma_get_io_unitinfo(ibdma_hca_t *hca, ibmf_msg_t *msg)
{
        ib_dm_io_unitinfo_t     *uip;

        uip = (ib_dm_io_unitinfo_t *)msg->im_msgbufs_send.im_bufs_cl_data;
        rw_enter(&hca->ih_iou_rwlock, RW_READER);
        bcopy(&hca->ih_iou, uip, sizeof (ib_dm_io_unitinfo_t));
        rw_exit(&hca->ih_iou_rwlock);
}

/*
 * ibdma_get_ioc_profile()
 */
static void
ibdma_get_ioc_profile(ibdma_hca_t *hca, ibmf_msg_t *msg)
{
        ib_dm_ioc_ctrl_profile_t        *iocp;
        uint32_t                        slot;

        ASSERT(msg != NULL);

        slot = b2h32(msg->im_msgbufs_recv.im_bufs_mad_hdr->AttributeModifier);
        iocp = (ib_dm_ioc_ctrl_profile_t *)
            msg->im_msgbufs_send.im_bufs_cl_data;
        if (slot == 0 || slot > IBDMA_MAX_IOC) {
                msg->im_msgbufs_send.im_bufs_mad_hdr->Status =
                    MAD_STATUS_INVALID_FIELD;
                return;
        }

        slot--;
        rw_enter(&hca->ih_iou_rwlock, RW_READER);
        if (ibdma_get_ioc_state(hca, slot) == IBDMA_IOC_PRESENT) {
                bcopy(&hca->ih_ioc[slot].ii_profile, iocp,
                    sizeof (ib_dm_ioc_ctrl_profile_t));
        } else {
                msg->im_msgbufs_send.im_bufs_mad_hdr->Status =
                    IB_DM_DEVMGT_MAD_STAT_NORESP;
        }
        rw_exit(&hca->ih_iou_rwlock);
}

/*
 * ibdma_get_ioc_services()
 */
static void
ibdma_get_ioc_services(ibdma_hca_t *hca, ibmf_msg_t *msg)
{
        ib_dm_srv_t     *to_svcp;
        ib_dm_srv_t     *from_svcp;
        uint32_t        slot;
        uint8_t         hi;
        uint8_t         low;

        ASSERT(msg != NULL);

        slot = b2h32(msg->im_msgbufs_recv.im_bufs_mad_hdr->AttributeModifier);
        hi   = (slot >> 8) & 0x00FF;
        low  = slot  & 0x00FF;
        slot = (slot >> 16) & 0x0FFFF;
        if (slot == 0 || slot > IBDMA_MAX_IOC) {
                msg->im_msgbufs_send.im_bufs_mad_hdr->Status =
                    MAD_STATUS_INVALID_FIELD;
                return;
        }

        slot--;

        rw_enter(&hca->ih_iou_rwlock, RW_READER);
        if (ibdma_get_ioc_state(hca, slot) != IBDMA_IOC_PRESENT) {
                msg->im_msgbufs_send.im_bufs_mad_hdr->Status =
                    IB_DM_DEVMGT_MAD_STAT_NORESP;
                rw_exit(&hca->ih_iou_rwlock);
                return;
        }

        if ((low > hi) || (hi - low > 4)) {
                msg->im_msgbufs_send.im_bufs_mad_hdr->Status =
                    MAD_STATUS_INVALID_FIELD;
                rw_exit(&hca->ih_iou_rwlock);
                return;
        }

        if (hi > hca->ih_ioc[slot].ii_profile.ioc_service_entries) {
                msg->im_msgbufs_send.im_bufs_mad_hdr->Status =
                    MAD_STATUS_INVALID_FIELD;
                rw_exit(&hca->ih_iou_rwlock);
                return;
        }

        to_svcp = (ib_dm_srv_t *)msg->im_msgbufs_send.im_bufs_cl_data;
        from_svcp = hca->ih_ioc[slot].ii_srvcs + low;
        bcopy(from_svcp, to_svcp, sizeof (ib_dm_srv_t) * (hi - low + 1));
        rw_exit(&hca->ih_iou_rwlock);
}


/*
 * Client API internal helpers
 */

/*
 * ibdma_hdl_to_ioc()
 */
ibdma_hdl_impl_t *
ibdma_get_hdl_impl(ibdma_hdl_t hdl)
{
        ibdma_hca_t             *hca;
        ibdma_hdl_impl_t        *hdl_tmp = hdl;
        ibdma_hdl_impl_t        *hdl_impl;

        ASSERT(mutex_owned(&ibdma->ms_hca_list_lock));

        if (hdl_tmp == NULL) {
                cmn_err(CE_NOTE, "get_hdl_impl, NULL handle");
                return (NULL);
        }

        hca = ibdma_find_hca(hdl_tmp->ih_iou_guid);
        if (hca == NULL) {
                cmn_err(CE_NOTE, "get_hdl_impl, invalid handle, bad IOU");
                return (NULL);
        }

        hdl_impl = list_head(&hca->ih_hdl_list);
        while (hdl_impl != NULL) {
                if (hdl_impl == hdl_tmp) {
                        break;
                }
                hdl_impl = list_next(&hca->ih_hdl_list, hdl_impl);
        }
        return (hdl_impl);
}

/*
 * ibdma_set_ioc_state()
 *
 * slot should be 0 based (not DM 1 based slot).
 *
 * I/O Unit write lock should be held outside of this function.
 */
static void
ibdma_set_ioc_state(ibdma_hca_t *hca, int slot, ibdma_ioc_state_t state)
{
        uint8_t         cur;
        uint16_t        id;

        cur = hca->ih_iou.iou_ctrl_list[slot >> 1];
        if (slot & 1) {
                cur = (cur & 0xF0) | state;
        } else {
                cur = (cur & 0x0F) | (state << 4);
        }
        hca->ih_iou.iou_ctrl_list[slot >> 1] = cur;
        id = b2h16(hca->ih_iou.iou_changeid);
        id++;
        hca->ih_iou.iou_changeid = h2b16(id);
#ifdef  DEBUG_IBDMA
        cmn_err(CE_NOTE, "set_ioc_state, slot offset(%d), value(%d)",
            slot, hca->ih_iou.iou_ctrl_list[slot >> 1]);
#endif
}

/*
 * ibdma_get_ioc_state()
 *
 * slot should be 0 based (not DM 1 based slot).
 *
 * I/O Unit read lock should be held outside of this function.
 */
static ibdma_ioc_state_t
ibdma_get_ioc_state(ibdma_hca_t *hca, int slot)
{
        uint8_t         cur;

        if (slot >= IBDMA_MAX_IOC)
                return (0xFF);

        cur = hca->ih_iou.iou_ctrl_list[slot >> 1];
        cur = slot & 1 ?  cur & 0x0F : cur >> 4;
        return (cur);
}

/* CLIENT API Implementation */
/*
 * ibdma_ioc_register()
 *
 */
ibdma_hdl_t
ibdma_ioc_register(ib_guid_t iou_guid, ib_dm_ioc_ctrl_profile_t *profile,
        ib_dm_srv_t *services)
{
        int                     free_slot = -1;
        int                     svc_entries;
        int                     slot;
        ibdma_hca_t             *hca;
        ibdma_hdl_impl_t        *hdl;

        if (profile == NULL || services == NULL) {
                cmn_err(CE_NOTE, "ioc_register, bad parameter");
                return (NULL);
        }

        svc_entries = profile->ioc_service_entries;
        if (svc_entries == 0) {
                cmn_err(CE_NOTE, "ioc_register, bad profile no service");
                return (NULL);
        }

        /*
         * Find the associated I/O Unit.
         */
        mutex_enter(&ibdma->ms_hca_list_lock);
        hca = ibdma_find_hca(iou_guid);
        if (hca == NULL) {
                mutex_exit(&ibdma->ms_hca_list_lock);
                cmn_err(CE_NOTE, "ioc_register, bad I/O Unit GUID (0x%llx)",
                    (u_longlong_t)iou_guid);
                return (NULL);
        }

        rw_enter(&hca->ih_iou_rwlock, RW_WRITER);
        for (slot = 0; slot < IBDMA_MAX_IOC; slot++) {
                if (hca->ih_ioc[slot].ii_inuse == 0) {
                        if (free_slot == -1) {
                                free_slot = slot;
                        }
                        continue;
                }

                if (profile->ioc_guid ==
                    hca->ih_ioc[slot].ii_profile.ioc_guid) {
                        rw_exit(&hca->ih_iou_rwlock);
                        mutex_exit(&ibdma->ms_hca_list_lock);
#ifdef  DEBUG_IBDMA
                        cmn_err(CE_NOTE, "ioc_register, IOC previously"
                            " registered");
#endif
                        return (NULL);
                }
        }

        if (free_slot < 0) {
                rw_exit(&hca->ih_iou_rwlock);
                cmn_err(CE_NOTE, "ioc_register, error - I/O Unit full");
                return (NULL);
        }
#ifdef  DEBUG_IBDMA
        cmn_err(CE_NOTE, "ibdma_ioc_register, assigned to 0 based slot (%d)",
            free_slot);
#endif

        hca->ih_ioc[free_slot].ii_inuse = 1;
        hca->ih_ioc[free_slot].ii_slot  = free_slot;
        hca->ih_ioc[free_slot].ii_hcap  = hca;

        /*
         * Allocate local copy of profile and services.
         */
        hca->ih_ioc[free_slot].ii_srvcs =
            kmem_zalloc(sizeof (ib_dm_srv_t) * svc_entries, KM_SLEEP);
        bcopy(profile, &hca->ih_ioc[free_slot].ii_profile,
            sizeof (ib_dm_ioc_ctrl_profile_t));
        bcopy(services, hca->ih_ioc[free_slot].ii_srvcs,
            sizeof (ib_dm_srv_t) * svc_entries);

        /*
         * Update the profile copy with the I/O controller slot assigned.
         * The slot occupies the lower 8 biths of the vendor ID/slot 32bit
         * field.
         */
        profile->ioc_vendorid |= h2b32(free_slot);

        ibdma_set_ioc_state(hca, free_slot, IBDMA_IOC_PRESENT);

        hdl = kmem_alloc(sizeof (*hdl), KM_SLEEP);
        hdl->ih_iou_guid = hca->ih_iou_guid;
        hdl->ih_ioc_ndx = (uint8_t)free_slot;
        list_insert_tail(&hca->ih_hdl_list, hdl);

        rw_exit(&hca->ih_iou_rwlock);
        mutex_exit(&ibdma->ms_hca_list_lock);

        return ((ibdma_hdl_t)hdl);
}

/*
 * ibdma_ioc_unregister()
 *
 */
ibdma_status_t
ibdma_ioc_unregister(ibdma_hdl_t hdl)
{
        ibdma_ioc_t             *ioc;
        ibdma_hca_t             *hca;
        int                     slot;
        ibdma_hdl_impl_t        *hdl_tmp = hdl;
        ibdma_hdl_impl_t        *hdl_impl;

        if (hdl == NULL) {
                cmn_err(CE_NOTE, "ioc_unregister, NULL handle");
                return (IBDMA_BAD_PARAM);
        }

        mutex_enter(&ibdma->ms_hca_list_lock);
        hca = ibdma_find_hca(hdl_tmp->ih_iou_guid);
        if (hca == NULL) {
                cmn_err(CE_NOTE, "ioc_unregsiter, invalid handle, IOU"
                    " not found");
                mutex_exit(&ibdma->ms_hca_list_lock);
                return (IBDMA_BAD_PARAM);
        }

        hdl_impl = list_head(&hca->ih_hdl_list);
        while (hdl_impl != NULL) {
                if (hdl_impl == hdl_tmp) {
                        break;
                }
                hdl_impl = list_next(&hca->ih_hdl_list, hdl_impl);
        }

        if (hdl_impl == NULL) {
                cmn_err(CE_NOTE, "ioc_unregsiter, invalid handle, not found");
                mutex_exit(&ibdma->ms_hca_list_lock);
                return (IBDMA_BAD_PARAM);
        }

        list_remove(&hca->ih_hdl_list, hdl_impl);

        if (hdl_impl->ih_ioc_ndx >= IBDMA_MAX_IOC) {
                cmn_err(CE_NOTE, "ioc_unregister, corrupted handle");
                kmem_free(hdl_impl, sizeof (*hdl_impl));
                mutex_exit(&ibdma->ms_hca_list_lock);
                return (IBDMA_BAD_PARAM);
        }
        ioc = &hca->ih_ioc[hdl_impl->ih_ioc_ndx];
        kmem_free(hdl_impl, sizeof (*hdl_impl));

        if (ioc->ii_slot > IBDMA_MAX_IOC) {
                cmn_err(CE_NOTE, "ioc_unregister, IOC corrupted, bad"
                    " slot in IOC");
                mutex_exit(&ibdma->ms_hca_list_lock);
                return (IBDMA_BAD_PARAM);
        }

        rw_enter(&ioc->ii_hcap->ih_iou_rwlock, RW_WRITER);
        if (ioc->ii_inuse == 0) {
                rw_exit(&ioc->ii_hcap->ih_iou_rwlock);
                mutex_exit(&ibdma->ms_hca_list_lock);
                cmn_err(CE_NOTE, "ioc_unregister, slot not in use (%d)",
                    ioc->ii_slot+1);
                return (IBDMA_BAD_PARAM);
        }

        ASSERT(ioc->ii_srvcs != NULL);

        slot = ioc->ii_slot;
        hca  = ioc->ii_hcap;
        kmem_free(ioc->ii_srvcs, sizeof (ib_dm_srv_t) *
            ioc->ii_profile.ioc_service_entries);
        bzero(ioc, sizeof (ibdma_ioc_t));
        ibdma_set_ioc_state(hca, slot, IBDMA_IOC_NOT_INSTALLED);

        rw_exit(&hca->ih_iou_rwlock);
        mutex_exit(&ibdma->ms_hca_list_lock);

        return (IBDMA_SUCCESS);
}

/*
 * ibdma_ioc_update()
 *
 */
ibdma_status_t
ibdma_ioc_update(ibdma_hdl_t hdl, ib_dm_ioc_ctrl_profile_t *profile,
        ib_dm_srv_t *services)
{
        ibdma_ioc_t             *ioc;
        ibdma_hca_t             *hca;
        ibdma_hdl_impl_t        *hdl_tmp = hdl;
        ibdma_hdl_impl_t        *hdl_impl;

        if (hdl == NULL) {
                cmn_err(CE_NOTE, "ioc_update, NULL handle");
                return (IBDMA_BAD_PARAM);
        }

        if (profile == NULL || services == NULL) {
                cmn_err(CE_NOTE, "ioc_update, NULL parameter");
                return (IBDMA_BAD_PARAM);
        }

        mutex_enter(&ibdma->ms_hca_list_lock);
        hca = ibdma_find_hca(hdl_tmp->ih_iou_guid);
        if (hca == NULL) {
                cmn_err(CE_NOTE, "ioc_update, invalid handle, IOU not found");
                mutex_exit(&ibdma->ms_hca_list_lock);
                return (IBDMA_BAD_PARAM);
        }

        hdl_impl = list_head(&hca->ih_hdl_list);
        while (hdl_impl != NULL) {
                if (hdl_impl == hdl_tmp) {
                        break;
                }
                hdl_impl = list_next(&hca->ih_hdl_list, hdl_impl);
        }

        if (hdl_impl == NULL) {
                cmn_err(CE_NOTE, "ioc_update, invalid handle, not found");
                mutex_exit(&ibdma->ms_hca_list_lock);
                return (IBDMA_BAD_PARAM);
        }

        if (hdl_impl->ih_ioc_ndx >= IBDMA_MAX_IOC) {
                cmn_err(CE_NOTE, "ioc_update, corrupted handle");
                mutex_exit(&ibdma->ms_hca_list_lock);
                return (IBDMA_BAD_PARAM);
        }
        ioc = &hca->ih_ioc[hdl_impl->ih_ioc_ndx];

        if (ioc->ii_slot >= IBDMA_MAX_IOC || ioc->ii_hcap == NULL) {
                cmn_err(CE_NOTE, "ioc_update, bad handle (%p)",
                    (void *)hdl);
                mutex_exit(&ibdma->ms_hca_list_lock);
                return (IBDMA_BAD_PARAM);
        }

        rw_enter(&ioc->ii_hcap->ih_iou_rwlock, RW_WRITER);
        if (ioc->ii_inuse == 0) {
                rw_exit(&ioc->ii_hcap->ih_iou_rwlock);
                mutex_exit(&ibdma->ms_hca_list_lock);
                cmn_err(CE_NOTE, "ioc_udate slot not in use (%d)",
                    ioc->ii_slot+1);
                return (IBDMA_BAD_PARAM);
        }

        ASSERT(ioc->ii_srvcs != NULL);

        kmem_free(ioc->ii_srvcs, ioc->ii_profile.ioc_service_entries *
            sizeof (ib_dm_srv_t));
        ioc->ii_srvcs = kmem_zalloc(profile->ioc_service_entries  *
            sizeof (ib_dm_srv_t), KM_SLEEP);

        bcopy(profile, &ioc->ii_profile, sizeof (ib_dm_ioc_ctrl_profile_t));
        bcopy(services, ioc->ii_srvcs, sizeof (ib_dm_srv_t) *
            profile->ioc_service_entries);
        /*
         * Update the profile copy with the I/O controller slot assigned.
         * The slot occupies the lower 8 biths of the vendor ID/slot 32bit
         * field.
         */
        profile->ioc_vendorid |= h2b32(ioc->ii_slot);
        ibdma_set_ioc_state(ioc->ii_hcap, ioc->ii_slot, IBDMA_IOC_PRESENT);
        rw_exit(&ioc->ii_hcap->ih_iou_rwlock);
        mutex_exit(&ibdma->ms_hca_list_lock);

        return (IBDMA_SUCCESS);
}