root/usr/src/uts/sun4v/io/dr_io.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 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * sun4v VIO DR Module
 */

#include <sys/modctl.h>
#include <sys/sunddi.h>
#include <sys/sunndi.h>
#include <sys/note.h>
#include <sys/sysevent/dr.h>
#include <sys/hypervisor_api.h>
#include <sys/mach_descrip.h>
#include <sys/mdesc.h>
#include <sys/mdesc_impl.h>
#include <sys/ds.h>
#include <sys/drctl.h>
#include <sys/dr_util.h>
#include <sys/dr_io.h>
#include <sys/promif.h>
#include <sys/machsystm.h>
#include <sys/ethernet.h>
#include <sys/hotplug/pci/pcicfg.h>


static struct modlmisc modlmisc = {
        &mod_miscops,
        "sun4v VIO DR"
};

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


/*
 * VIO DS Interface
 */

/*
 * Global DS Handle
 */
static ds_svc_hdl_t ds_vio_handle;

/*
 * Supported DS Capability Versions
 */
static ds_ver_t         dr_vio_vers[] = { { 1, 0 } };
#define DR_VIO_NVERS    (sizeof (dr_vio_vers) / sizeof (dr_vio_vers[0]))

/*
 * DS Capability Description
 */
static ds_capability_t dr_vio_cap = {
        DR_VIO_DS_ID,           /* svc_id */
        dr_vio_vers,            /* vers */
        DR_VIO_NVERS            /* nvers */
};

/*
 * DS Callbacks
 */
static void dr_vio_reg_handler(ds_cb_arg_t, ds_ver_t *, ds_svc_hdl_t);
static void dr_vio_unreg_handler(ds_cb_arg_t arg);
static void dr_vio_data_handler(ds_cb_arg_t arg, void *buf, size_t buflen);

/*
 * DS Client Ops Vector
 */
static ds_clnt_ops_t dr_vio_ops = {
        dr_vio_reg_handler,     /* ds_reg_cb */
        dr_vio_unreg_handler,   /* ds_unreg_cb */
        dr_vio_data_handler,    /* ds_data_cb */
        NULL                    /* cb_arg */
};


typedef struct {
        char            *name;
        uint64_t        devid;
        dev_info_t      *dip;
} dr_search_arg_t;

static int
dr_io_check_node(dev_info_t *dip, void *arg)
{
        char            *name;
        uint64_t        devid;
        dr_search_arg_t *sarg = (dr_search_arg_t *)arg;

        name = ddi_node_name(dip);

        if (strcmp(name, sarg->name) != 0)
                return (DDI_WALK_CONTINUE);

        devid = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
            "reg", -1);

        DR_DBG_IO("%s: found devid=%ld, looking for %ld\n",
            __func__, devid, sarg->devid);

        if (devid == sarg->devid) {
                DR_DBG_IO("%s: matched", __func__);

                /* matching node must be returned held */
                if (!e_ddi_branch_held(dip))
                        e_ddi_branch_hold(dip);

                sarg->dip = dip;
                return (DDI_WALK_TERMINATE);
        }

        return (DDI_WALK_CONTINUE);
}

/*
 * Walk the device tree to find the dip corresponding to the devid
 * passed in. If present, the dip is returned held. The caller must
 * release the hold on the dip once it is no longer required. If no
 * matching node if found, NULL is returned.
 */
static dev_info_t *
dr_io_find_node(char *name, uint64_t devid)
{
        dr_search_arg_t arg;

        DR_DBG_IO("dr_io_find_node...\n");

        arg.name = name;
        arg.devid = devid;
        arg.dip = NULL;

        ddi_walk_devs(ddi_root_node(), dr_io_check_node, &arg);

        ASSERT((arg.dip == NULL) || (e_ddi_branch_held(arg.dip)));

        return ((arg.dip) ? arg.dip : NULL);
}

/*
 * Look up a particular IO node in the MD. Returns the mde_cookie_t
 * representing that IO node if present, and MDE_INVAL_ELEM_COOKIE otherwise.
 * It is assumed the scratch array has already been allocated so that
 * it can accommodate the worst case scenario, every node in the MD.
 */
static mde_cookie_t
dr_io_find_node_md(md_t *mdp, char *name, uint64_t id, mde_cookie_t *listp)
{
        int             i;
        int             nnodes;
        char            *devnm;
        uint64_t        devid;
        mde_cookie_t    rootnode;
        mde_cookie_t    result = MDE_INVAL_ELEM_COOKIE;

        DR_DBG_IO("%s: %s@%ld\n", __func__, name, id);

        rootnode = md_root_node(mdp);
        ASSERT(rootnode != MDE_INVAL_ELEM_COOKIE);

        /*
         * Scan the DAG for all candidate nodes.
         */
        nnodes = md_scan_dag(mdp, rootnode, md_find_name(mdp, "virtual-device"),
            md_find_name(mdp, "fwd"), listp);

        if (nnodes < 0) {
                DR_DBG_IO("%s: scan for "
                    "'virtual-device' nodes failed\n", __func__);
                return (result);
        }

        DR_DBG_IO("%s: found %d nodes in the MD\n", __func__, nnodes);

        /*
         * Find the node of interest
         */
        for (i = 0; i < nnodes; i++) {

                if (md_get_prop_str(mdp, listp[i], "name", &devnm)) {
                        DR_DBG_IO("%s: missing 'name' property for"
                            " IO node %d\n", __func__, i);
                        return (DDI_WALK_ERROR);
                }

                if (strcmp(devnm, name) != 0)
                        continue;

                if (md_get_prop_val(mdp, listp[i], "cfg-handle", &devid)) {
                        DR_DBG_IO("%s: missing 'cfg-handle' property for"
                            " IO node %d\n", __func__, i);
                        break;
                }

                if (devid == id) {
                        /* found a match */
                        DR_DBG_IO("%s: found IO node %s@%ld "
                            "in MD\n", __func__, name, id);
                        result = listp[i];
                        break;
                }
        }

        if (result == MDE_INVAL_ELEM_COOKIE)
                DR_DBG_IO("%s: IO node %ld not in MD\n", __func__, id);

        return (result);
}

typedef struct {
        md_t            *mdp;
        mde_cookie_t    node;
        dev_info_t      *dip;
} cb_arg_t;

#define STR_ARR_LEN     5

static int
new_dev_node(dev_info_t *new_node, void *arg, uint_t flags)
{
        _NOTE(ARGUNUSED(flags))

        cb_arg_t        *cba;
        char            *devnm, *devtype;
        char            *compat;
        uint64_t        devid;
        int             len = 0;
        char            *curr;
        int             i = 0;
        char            *str_arr[STR_ARR_LEN];

        cba = (cb_arg_t *)arg;

        /*
         * Add 'name' property
         */
        if (md_get_prop_str(cba->mdp, cba->node, "name", &devnm)) {
                DR_DBG_IO("%s: failed to read 'name' prop from MD\n", __func__);
                return (DDI_WALK_ERROR);
        }
        DR_DBG_IO("%s: device name is %s\n", __func__, devnm);

        if (ndi_prop_update_string(DDI_DEV_T_NONE, new_node,
            "name", devnm) != DDI_SUCCESS) {
                DR_DBG_IO("%s: failed to create 'name' prop\n", __func__);
                return (DDI_WALK_ERROR);
        }

        /*
         * Add 'compatible' property
         */
        if (md_get_prop_data(cba->mdp, cba->node, "compatible",
            (uint8_t **)&compat, &len)) {
                DR_DBG_IO("%s: failed to read "
                    "'compatible' prop from MD\n", __func__);
                return (DDI_WALK_ERROR);
        }

        /* parse the MD string array */
        curr = compat;
        while (curr < (compat + len)) {

                DR_DBG_IO("%s: adding '%s' to "
                    "'compatible' prop\n", __func__, curr);

                str_arr[i++] = curr;
                curr += strlen(curr) + 1;

                if (i == STR_ARR_LEN) {
                        DR_DBG_CPU("exceeded str_arr len (%d)\n", STR_ARR_LEN);
                        break;
                }
        }


        if (ndi_prop_update_string_array(DDI_DEV_T_NONE, new_node,
            "compatible", str_arr, i) != DDI_SUCCESS) {
                DR_DBG_IO("%s: cannot create 'compatible' prop\n", __func__);
                return (DDI_WALK_ERROR);
        }

        /*
         * Add 'device_type' property
         */
        if (md_get_prop_str(cba->mdp, cba->node, "device-type", &devtype)) {
                DR_DBG_IO("%s: failed to read "
                    "'device-type' prop from MD\n", __func__);
                return (DDI_WALK_ERROR);
        }
        if (ndi_prop_update_string(DDI_DEV_T_NONE, new_node,
            "device_type", devtype) != DDI_SUCCESS) {
                DR_DBG_IO("%s: failed to create "
                    "'device-type' prop\n", __func__);
                return (DDI_WALK_ERROR);
        }

        DR_DBG_IO("%s: device type is %s\n", __func__, devtype);

        /*
         * Add 'reg' (cfg-handle) property
         */
        if (md_get_prop_val(cba->mdp, cba->node, "cfg-handle", &devid)) {
                DR_DBG_IO("%s: failed to read "
                    "'cfg-handle' prop from MD\n", __func__);
                return (DDI_WALK_ERROR);
        }

        DR_DBG_IO("%s: new device is %s@%ld\n", __func__, devnm, devid);

        if (ndi_prop_update_int(DDI_DEV_T_NONE, new_node, "reg", devid)
            != DDI_SUCCESS) {
                DR_DBG_IO("%s: failed to create 'reg' prop\n", __func__);
                return (DDI_WALK_ERROR);
        }

        /* if vnet/vswitch, probe and add mac-address and mtu properties */
        if (strcmp(devnm, "vsw") == 0 || strcmp(devnm, "network") == 0) {

                int i, j;
                uint64_t mtu, macaddr;
                uchar_t maddr_arr[ETHERADDRL];

                if (md_get_prop_val(cba->mdp, cba->node, "local-mac-address",
                    &macaddr)) {
                        DR_DBG_IO("%s: failed to read "
                            "'local-mac-address' prop from MD\n", __func__);
                        return (DDI_WALK_ERROR);
                }

                for (i = 0, j = (ETHERADDRL - 1); i < ETHERADDRL; i++, j--)
                        maddr_arr[j] = (macaddr >> (i * 8)) & 0xff;

                if (ndi_prop_update_byte_array(DDI_DEV_T_NONE, new_node,
                    "local-mac-address", maddr_arr, ETHERADDRL)
                    != DDI_SUCCESS) {
                        DR_DBG_IO("%s: failed to create "
                            "'local-mac-address' prop\n", __func__);
                        return (DDI_WALK_ERROR);
                }

                if (md_get_prop_val(cba->mdp, cba->node, "mtu", &mtu)) {
                        DR_DBG_IO("%s: failed to read "
                            "'mtu' prop from MD\n", __func__);
                        return (DDI_WALK_ERROR);
                }

                if (ndi_prop_update_int64(DDI_DEV_T_NONE, new_node, "mtu",
                    mtu) != DDI_SUCCESS) {
                        DR_DBG_IO("%s: failed to "
                            "create 'mtu' prop\n", __func__);
                        return (DDI_WALK_ERROR);
                }

                DR_DBG_IO("%s: Added properties for %s@%ld, "
                    "mac=%ld, mtu=%ld\n", __func__, devnm, devid, macaddr, mtu);
        }

        cba->dip = new_node;

        return (DDI_WALK_TERMINATE);
}

/*
 * Find the parent node of the argument virtual device node in
 * the MD.  For virtual devices, the parent is always
 * "channel-devices", so scan the MD using the "back" arcs
 * looking for a node with that name.
 */
static mde_cookie_t
dr_vio_find_parent_md(md_t *mdp, mde_cookie_t node)
{
        int             max_nodes;
        int             num_nodes;
        int             listsz;
        mde_cookie_t    *listp;
        mde_cookie_t    pnode = MDE_INVAL_ELEM_COOKIE;

        max_nodes = md_node_count(mdp);
        listsz = max_nodes * sizeof (mde_cookie_t);
        listp = kmem_zalloc(listsz, KM_SLEEP);
        DR_DBG_KMEM("%s: alloc addr %p size %d\n",
            __func__, (void *)listp, listsz);

        num_nodes = md_scan_dag(mdp, node,
            md_find_name(mdp, "channel-devices"),
            md_find_name(mdp, "back"), listp);

        ASSERT(num_nodes == 1);

        if (num_nodes == 1)
                pnode = listp[0];

        DR_DBG_KMEM("%s: free addr %p size %d\n",
            __func__, (void *)listp, listsz);
        kmem_free(listp, listsz);

        return (pnode);
}

static int
dr_io_configure(dr_vio_req_t *req, dr_vio_res_t *res)
{
        int             rv = ENXIO;
        int             listsz;
        int             nnodes;
        uint64_t        devid = req->dev_id;
        uint64_t        pdevid;
        char            *name = req->name;
        char            *pname;
        md_t            *mdp = NULL;
        mde_cookie_t    *listp = NULL;
        mde_cookie_t    node;
        mde_cookie_t    pnode;
        dev_info_t      *pdip = NULL;
        dev_info_t      *dip;
        devi_branch_t   br;
        cb_arg_t        cba;
        int             drctl_cmd;
        int             drctl_flags = 0;
        drctl_rsrc_t    *drctl_req;
        size_t          drctl_req_len;
        drctl_rsrc_t    *drctl_rsrc = NULL;
        drctl_cookie_t  drctl_res_ck;
        char            *p;
        drctl_resp_t    *drctl_resp;
        size_t          drctl_resp_len = 0;

        res->result = DR_VIO_RES_FAILURE;

        if ((dip = dr_io_find_node(name, devid)) != NULL) {
                DR_DBG_IO("%s: %s@%ld already configured\n",
                    __func__, name, devid);

                /* Return success if resources is already there. */
                res->result = DR_VIO_RES_OK;
                res->status = DR_VIO_STAT_CONFIGURED;
                e_ddi_branch_rele(dip);
                return (0);
        }

        /* Assume we fail to find the node to be added. */
        res->status = DR_VIO_STAT_NOT_PRESENT;

        if ((mdp = md_get_handle()) == NULL) {
                DR_DBG_IO("%s: unable to initialize MD\n", __func__);
                return (ENXIO);
        }

        nnodes = md_node_count(mdp);
        ASSERT(nnodes > 0);

        listsz = nnodes * sizeof (mde_cookie_t);
        listp = kmem_zalloc(listsz, KM_SLEEP);
        DR_DBG_KMEM("%s: alloc addr %p size %d\n",
            __func__, (void *)listp, listsz);

        /*
         * Get the MD device node.
         */
        node = dr_io_find_node_md(mdp, name, devid, listp);

        if (node == MDE_INVAL_ELEM_COOKIE) {
                DR_DBG_IO("%s: scan for %s name node failed\n", __func__, name);
                res->result = DR_VIO_RES_NOT_IN_MD;
                goto done;
        }

        /*
         * Get the MD parent node.
         */
        pnode = dr_vio_find_parent_md(mdp, node);
        if (pnode == MDE_INVAL_ELEM_COOKIE) {
                DR_DBG_IO("%s: failed to find MD parent of %lx\n",
                    __func__, pnode);
                goto done;
        }

        if (md_get_prop_str(mdp, pnode, "name", &pname)) {
                DR_DBG_IO("%s: failed to read "
                    "'name' for pnode %lx from MD\n", __func__, pnode);
                goto done;
        }

        if (md_get_prop_val(mdp, pnode, "cfg-handle", &pdevid)) {
                DR_DBG_IO("%s: failed to read 'cfg-handle' "
                    "for pnode '%s' from MD\n", __func__, pname);
                goto done;
        }

        DR_DBG_IO("%s: parent device %s@%lx\n", __func__, pname, pdevid);

        /*
         * Get the devinfo parent node.
         */
        if ((pdip = dr_io_find_node(pname, pdevid)) == NULL) {
                DR_DBG_IO("%s: parent device %s@%ld not found\n",
                    __func__, pname, pdevid);
                goto done;
        }

        drctl_req_len = sizeof (drctl_rsrc_t) + MAXPATHLEN;
        drctl_req = kmem_zalloc(drctl_req_len, KM_SLEEP);
        DR_DBG_KMEM("%s: alloc addr %p size %ld\n",
            __func__, (void *)drctl_req, drctl_req_len);
        drctl_req->status = DRCTL_STATUS_INIT;

        drctl_cmd = DRCTL_IO_CONFIG_REQUEST;

        /*
         * Construct the path of the device as it will be if it
         * is successfully added.
         */
        p = drctl_req->res_dev_path;
        (void) sprintf(p, "/devices");
        (void) ddi_pathname(pdip, p + strlen(p));
        (void) sprintf(p + strlen(p), "/%s@%ld", name, devid);
        DR_DBG_IO("%s: devpath=%s\n", __func__, drctl_req->res_dev_path);

        rv = drctl_config_init(drctl_cmd, drctl_flags, drctl_req,
            1, &drctl_resp, &drctl_resp_len, &drctl_res_ck);

        ASSERT((drctl_resp != NULL) && (drctl_resp_len != 0));

        drctl_rsrc = drctl_resp->resp_resources;

        if (rv != 0) {
                DR_DBG_IO("%s: drctl_config_init failed: %d\n", __func__, rv);

                ASSERT(drctl_resp->resp_type == DRCTL_RESP_ERR);

                (void) strlcpy(res->reason,
                    drctl_resp->resp_err_msg, DR_VIO_MAXREASONLEN);

                DR_DBG_IO("%s: %s\n", __func__, res->reason);

                goto done;

        }

        ASSERT(drctl_resp->resp_type == DRCTL_RESP_OK);

        if (drctl_rsrc->status == DRCTL_STATUS_DENY) {

                res->result = DR_VIO_RES_BLOCKED;

                DR_DBG_IO("%s: drctl_config_init denied\n", __func__);
                p = (char *)drctl_rsrc + drctl_rsrc->offset;

                (void) strlcpy(res->reason, p, DR_VIO_MAXREASONLEN);

                DR_DBG_IO("%s: %s\n", __func__, res->reason);

                drctl_req->status = DRCTL_STATUS_CONFIG_FAILURE;

                rv = EPERM;
        } else {
                cba.mdp = mdp;
                cba.node = node;

                br.arg = (void *)&cba;
                br.type = DEVI_BRANCH_SID;
                br.create.sid_branch_create = new_dev_node;
                br.devi_branch_callback = NULL;

                rv = e_ddi_branch_create(pdip,
                    &br, NULL, DEVI_BRANCH_CONFIGURE);

                drctl_req->status = (rv == 0) ?
                    DRCTL_STATUS_CONFIG_SUCCESS : DRCTL_STATUS_CONFIG_FAILURE;

                DR_DBG_IO("%s: %s@%ld = %d\n", __func__, name, devid, rv);
        }

        if (drctl_config_fini(&drctl_res_ck, drctl_req, 1) != 0)
                DR_DBG_IO("%s: drctl_config_fini returned: %d\n", __func__, rv);

done:
        if (listp) {
                DR_DBG_KMEM("%s: free addr %p size %d\n",
                    __func__, (void *)listp, listsz);
                kmem_free(listp, listsz);
        }

        if (mdp)
                (void) md_fini_handle(mdp);

        if (pdip)
                e_ddi_branch_rele(pdip);

        DR_DBG_KMEM("%s: free addr %p size %ld\n",
            __func__, (void *)drctl_req, drctl_req_len);
        kmem_free(drctl_req, drctl_req_len);

        if (drctl_resp) {
                DR_DBG_KMEM("%s: free addr %p size %ld\n",
                    __func__, (void *)drctl_resp, drctl_resp_len);
                kmem_free(drctl_resp, drctl_resp_len);
        }

        if (rv == 0) {
                res->result = DR_VIO_RES_OK;
                res->status = DR_VIO_STAT_CONFIGURED;

                /* notify interested parties about the operation */
                dr_generate_event(DR_TYPE_VIO, SE_HINT_INSERT);
        } else {
                res->status = DR_VIO_STAT_UNCONFIGURED;
        }

        return (rv);
}

static int
dr_io_unconfigure(dr_vio_req_t *req, dr_vio_res_t *res)
{
        int             rv;
        char            *name = req->name;
        char            *p;
        uint64_t        devid = req->dev_id;
        dev_info_t      *dip;
        dev_info_t      *fdip = NULL;
        int             drctl_cmd;
        int             drctl_flags = 0;
        drctl_rsrc_t    *drctl_req;
        size_t          drctl_req_len;
        drctl_rsrc_t    *drctl_rsrc = NULL;
        drctl_cookie_t  drctl_res_ck;
        drctl_resp_t    *drctl_resp;
        size_t          drctl_resp_len;

        if ((dip = dr_io_find_node(name, devid)) == NULL) {
                DR_DBG_IO("%s: %s@%ld already unconfigured\n",
                    __func__, name, devid);
                res->result = DR_VIO_RES_OK;
                res->status = DR_VIO_STAT_NOT_PRESENT;
                return (0);
        }

        res->result = DR_VIO_RES_FAILURE;

        ASSERT(e_ddi_branch_held(dip));

        /* Assume we fail to unconfigure the resource. */
        res->status = DR_VIO_STAT_CONFIGURED;

        drctl_req_len = sizeof (drctl_rsrc_t) + MAXPATHLEN;
        drctl_req = kmem_zalloc(drctl_req_len, KM_SLEEP);
        DR_DBG_KMEM("%s: alloc addr %p size %ld\n",
            __func__, (void *)drctl_req, drctl_req_len);
        drctl_req->status = DRCTL_STATUS_INIT;

        drctl_cmd = DRCTL_IO_UNCONFIG_REQUEST;

        if (req->msg_type == DR_VIO_FORCE_UNCONFIG)
                drctl_flags = DRCTL_FLAG_FORCE;

        p = drctl_req->res_dev_path;
        (void) sprintf(p, "/devices");
        (void) ddi_pathname(dip, p + strlen(p));
        DR_DBG_IO("%s: devpath=%s\n", __func__, drctl_req->res_dev_path);

        rv = drctl_config_init(drctl_cmd, drctl_flags, drctl_req,
            1, &drctl_resp, &drctl_resp_len, &drctl_res_ck);

        ASSERT((drctl_resp != NULL) && (drctl_resp_len != 0));

        drctl_rsrc = drctl_resp->resp_resources;

        if (rv != 0) {

                DR_DBG_IO("%s: drctl_config_init failed: %d\n", __func__, rv);

                ASSERT(drctl_resp->resp_type == DRCTL_RESP_ERR);

                (void) strlcpy(res->reason,
                    drctl_resp->resp_err_msg, DR_VIO_MAXREASONLEN);

                DR_DBG_IO("%s: %s\n", __func__, res->reason);

                goto done;
        }

        if (drctl_rsrc->status == DRCTL_STATUS_DENY) {
                res->result = DR_VIO_RES_BLOCKED;

                DR_DBG_IO("%s: drctl_config_init denied\n", __func__);
                p = (char *)drctl_rsrc + drctl_rsrc->offset;

                (void) strlcpy(res->reason, p, DR_VIO_MAXREASONLEN);

                DR_DBG_IO("%s: %s\n", __func__, res->reason);

                drctl_req->status = DRCTL_STATUS_CONFIG_FAILURE;

                rv = EPERM;
        } else if (rv = e_ddi_branch_destroy(dip, &fdip, 0)) {
                char *path = kmem_alloc(MAXPATHLEN, KM_SLEEP);

                DR_DBG_KMEM("%s: alloc addr %p size %d\n",
                    __func__, (void *)path, MAXPATHLEN);
                /*
                 * If non-NULL, fdip is held and must be released.
                 */
                if (fdip != NULL) {
                        (void) ddi_pathname(fdip, path);
                        ddi_release_devi(fdip);
                } else {
                        (void) ddi_pathname(dip, path);
                }

                DR_DBG_IO("%s: node removal failed: %s (%p)",
                    __func__, path, (fdip) ? (void *)fdip : (void *)dip);

                drctl_req->status = DRCTL_STATUS_CONFIG_FAILURE;

                DR_DBG_KMEM("%s: free addr %p size %d\n",
                    __func__, (void *)path, MAXPATHLEN);
                kmem_free(path, MAXPATHLEN);
        } else {
                drctl_req->status = DRCTL_STATUS_CONFIG_SUCCESS;
        }

        if (drctl_config_fini(&drctl_res_ck, drctl_req, 1) != 0)
                DR_DBG_IO("%s: drctl_config_fini returned: %d\n", __func__, rv);

        DR_DBG_IO("%s: (%s@%ld) = %d\n", __func__, name, devid, rv);

        if (rv == 0) {
                res->result = DR_VIO_RES_OK;
                res->status = DR_VIO_STAT_UNCONFIGURED;

                /* Notify interested parties about the operation. */
                dr_generate_event(DR_TYPE_VIO, SE_HINT_REMOVE);
        }
done:
        DR_DBG_KMEM("%s: free addr %p size %ld\n",
            __func__, (void *)drctl_req, drctl_req_len);
        kmem_free(drctl_req, drctl_req_len);

        if (drctl_resp) {
                DR_DBG_KMEM("%s: free addr %p size %ld\n",
                    __func__, (void *)drctl_resp, drctl_resp_len);
                kmem_free(drctl_resp, drctl_resp_len);
        }

        return (rv);
}

static void
dr_vio_data_handler(ds_cb_arg_t arg, void *buf, size_t buflen)
{
        _NOTE(ARGUNUSED(arg))

        size_t          res_len;
        dr_vio_res_t    *res;
        dr_vio_req_t    *req;

        /*
         * Allocate a response buffer, because we always want to
         * send back a response message.
         */
        res_len = sizeof (dr_vio_res_t) + DR_VIO_MAXREASONLEN;
        res = kmem_zalloc(res_len, KM_SLEEP);
        DR_DBG_KMEM("%s: alloc addr %p size %ld\n",
            __func__, (void *)res, res_len);
        res->result = DR_VIO_RES_FAILURE;

        /*
         * Sanity check the message
         */
        if (buf == NULL) {
                DR_DBG_IO("empty message: expected at least %ld bytes\n",
                    sizeof (dr_vio_req_t));
                goto done;
        }
        if (buflen < sizeof (dr_vio_req_t)) {
                DR_DBG_IO("incoming message short: expected at least %ld "
                    "bytes, received %ld\n", sizeof (dr_vio_req_t), buflen);
                goto done;
        }

        DR_DBG_TRANS("incoming request:\n");
        DR_DBG_DUMP_MSG(buf, buflen);

        req = buf;
        switch (req->msg_type) {
        case DR_VIO_CONFIGURE:
                (void) dr_io_configure(req, res);
                break;
        case DR_VIO_FORCE_UNCONFIG:
        case DR_VIO_UNCONFIGURE:
                (void) dr_io_unconfigure(req, res);
                break;
        default:
                cmn_err(CE_NOTE, "bad msg_type %d\n", req->msg_type);
                break;
        }
done:
        res->req_num = (req) ? req->req_num : 0;

        DR_DBG_TRANS("outgoing response:\n");
        DR_DBG_DUMP_MSG(res, res_len);

        /* send back the response */
        if (ds_cap_send(ds_vio_handle, res, res_len) != 0)
                DR_DBG_IO("ds_send failed\n");

        if (res) {
                DR_DBG_KMEM("%s: free addr %p size %ld\n",
                    __func__, (void *)res, res_len);
                kmem_free(res, res_len);
        }
}

static void
dr_vio_reg_handler(ds_cb_arg_t arg, ds_ver_t *ver, ds_svc_hdl_t hdl)
{
        DR_DBG_IO("vio_reg_handler: arg=0x%p, ver=%d.%d, hdl=0x%lx\n",
            arg, ver->major, ver->minor, hdl);

        ds_vio_handle = hdl;
}

static void
dr_vio_unreg_handler(ds_cb_arg_t arg)
{
        DR_DBG_IO("vio_unreg_handler: arg=0x%p\n", arg);

        ds_vio_handle = DS_INVALID_HDL;
}

static int
dr_io_init(void)
{
        int     rv;

        if ((rv = ds_cap_init(&dr_vio_cap, &dr_vio_ops)) != 0) {
                cmn_err(CE_NOTE, "ds_cap_init vio failed: %d", rv);
                return (-1);
        }

        return (0);
}

static int
dr_io_fini(void)
{
        int     rv;

        if ((rv = ds_cap_fini(&dr_vio_cap)) != 0) {
                cmn_err(CE_NOTE, "ds_cap_fini vio failed: %d", rv);
                return (-1);
        }

        return (0);
}

int
_init(void)
{
        int     status;

        /* check that IO DR is enabled */
        if (dr_is_disabled(DR_TYPE_VIO)) {
                cmn_err(CE_CONT, "!VIO DR is disabled\n");
                return (-1);
        }

        if ((status = dr_io_init()) != 0) {
                cmn_err(CE_NOTE, "VIO DR initialization failed");
                return (status);
        }

        if ((status = mod_install(&modlinkage)) != 0) {
                (void) dr_io_fini();
        }

        return (status);
}

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

int dr_io_allow_unload = 0;

int
_fini(void)
{
        int     status;

        if (dr_io_allow_unload == 0)
                return (EBUSY);

        if ((status = mod_remove(&modlinkage)) == 0) {
                (void) dr_io_fini();
        }

        return (status);
}