dr_io.c revision 99c7e85592b5d673f1469b2767f876710004dc23
/*
* 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);
}