/*
* 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.
*/
#include <sys/stat.h>
#include <sys/conf.h>
#include <sys/modctl.h>
#include <sys/ddi.h>
#include <sys/rmc_comm_dp.h>
#include <sys/rmc_comm_dp_boot.h>
#include <sys/rmc_comm_drvintf.h>
#include <sys/cyclic.h>
#include <sys/rmc_comm.h>
#include <sys/machsystm.h>
#include <sys/file.h>
#include <sys/rmcadm.h>
/*
* functions local to this driver.
*/
static int rmcadm_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg,
void **resultp);
static int rmcadm_attach(dev_info_t *dip, ddi_attach_cmd_t cmd);
static int rmcadm_detach(dev_info_t *dip, ddi_detach_cmd_t cmd);
static int rmcadm_open(dev_t *dev_p, int flag, int otyp, cred_t *cred_p);
static int rmcadm_close(dev_t dev, int flag, int otyp, cred_t *cred_p);
static int rmcadm_ioctl(dev_t dev, int cmd, intptr_t arg, int mode,
cred_t *cred_p, int *rval_p);
/*
* Driver entry points
*/
static struct cb_ops rmcadm_cb_ops = {
rmcadm_open, /* open */
rmcadm_close, /* close */
nodev, /* strategy() */
nodev, /* print() */
nodev, /* dump() */
nodev, /* read() */
nodev, /* write() */
rmcadm_ioctl, /* ioctl() */
nodev, /* devmap() */
nodev, /* mmap() */
ddi_segmap, /* segmap() */
nochpoll, /* poll() */
ddi_prop_op, /* prop_op() */
NULL, /* cb_str */
D_NEW | D_MP /* cb_flag */
};
static struct dev_ops rmcadm_ops = {
DEVO_REV,
0, /* ref count */
rmcadm_getinfo, /* getinfo() */
nulldev, /* identify() */
nulldev, /* probe() */
rmcadm_attach, /* attach() */
rmcadm_detach, /* detach */
nodev, /* reset */
&rmcadm_cb_ops, /* pointer to cb_ops structure */
(struct bus_ops *)NULL,
nulldev, /* power() */
ddi_quiesce_not_needed, /* quiesce */
};
/*
* Loadable module support.
*/
extern struct mod_ops mod_driverops;
static struct modldrv modldrv = {
&mod_driverops, /* Type of module. This is a driver */
"rmcadm control driver", /* Name of the module */
&rmcadm_ops /* pointer to the dev_ops structure */
};
static struct modlinkage modlinkage = {
MODREV_1,
&modldrv,
NULL
};
static dev_info_t *rmcadm_dip = NULL;
extern void pmugpio_reset();
/*
* Utilities...
*/
/*
* to return the errno from the rmc_comm error status
*/
int
rmcadm_get_errno(int status)
{
int retval = EIO;
/* errors from RMC */
switch (status) {
case RCENOSOFTSTATE:
/* invalid/NULL soft state structure */
retval = EIO;
break;
case RCENODATALINK:
/* data protocol not available (down) */
retval = EIO;
break;
case RCENOMEM:
/* memory problems */
retval = ENOMEM;
break;
case RCECANTRESEND:
/* resend failed */
retval = EIO;
break;
case RCEMAXRETRIES:
/* reply not received - retries exceeded */
retval = EINTR;
break;
case RCETIMEOUT:
/* reply not received - command has timed out */
retval = EINTR;
break;
case RCEINVCMD:
/* data protocol cmd not supported */
retval = ENOTSUP;
break;
case RCEINVARG:
/* invalid argument(s) */
retval = ENOTSUP;
break;
case RCEGENERIC:
/* generic error */
retval = EIO;
break;
default:
retval = EIO;
break;
}
return (retval);
}
int
_init(void)
{
int error = 0;
error = mod_install(&modlinkage);
return (error);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
int
_fini(void)
{
int error = 0;
error = mod_remove(&modlinkage);
if (error)
return (error);
return (error);
}
/* ARGSUSED */
static int
rmcadm_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **resultp)
{
minor_t m = getminor((dev_t)arg);
switch (cmd) {
case DDI_INFO_DEVT2DEVINFO:
if ((m != 0) || (rmcadm_dip == NULL)) {
*resultp = NULL;
return (DDI_FAILURE);
}
*resultp = rmcadm_dip;
return (DDI_SUCCESS);
case DDI_INFO_DEVT2INSTANCE:
*resultp = (void *)(uintptr_t)m;
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
}
static int
rmcadm_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
int instance;
int err;
switch (cmd) {
case DDI_ATTACH:
/*
* only allow one instance
*/
instance = ddi_get_instance(dip);
if (instance != 0)
return (DDI_FAILURE);
err = ddi_create_minor_node(dip, "rmcadm", S_IFCHR,
instance, DDI_PSEUDO, NULL);
if (err != DDI_SUCCESS)
return (DDI_FAILURE);
/*
* Register with rmc_comm to prevent it being detached
*/
err = rmc_comm_register();
if (err != DDI_SUCCESS) {
ddi_remove_minor_node(dip, NULL);
return (DDI_FAILURE);
}
/* Remember the dev info */
rmcadm_dip = dip;
ddi_report_dev(dip);
return (DDI_SUCCESS);
case DDI_RESUME:
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
}
static int
rmcadm_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
int instance;
switch (cmd) {
case DDI_DETACH:
instance = ddi_get_instance(dip);
if (instance != 0)
return (DDI_FAILURE);
rmcadm_dip = NULL;
ddi_remove_minor_node(dip, NULL);
rmc_comm_unregister();
return (DDI_SUCCESS);
case DDI_SUSPEND:
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
}
/*ARGSUSED*/
static int
rmcadm_open(dev_t *dev_p, int flag, int otyp, cred_t *cred_p)
{
int error = 0;
int instance = getminor(*dev_p);
if (instance != 0)
return (ENXIO);
if ((error = drv_priv(cred_p)) != 0) {
cmn_err(CE_WARN, "rmcadm: inst %d drv_priv failed",
instance);
return (error);
}
return (error);
}
/*ARGSUSED*/
static int
rmcadm_close(dev_t dev, int flag, int otyp, cred_t *cred_p)
{
return (DDI_SUCCESS);
}
/*ARGSUSED*/
static int
rmcadm_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *cred_p,
int *rval_p)
{
int instance = getminor(dev);
int retval = 0;
rmcadm_request_response_t rr;
rmcadm_send_srecord_bp_t ssbp;
rmc_comm_msg_t rmc_req, *rmc_reqp = &rmc_req;
rmc_comm_msg_t rmc_resp, *rmc_respp = &rmc_resp;
caddr_t user_req_buf;
caddr_t user_data_buf;
caddr_t user_resp_buf;
if (instance != 0)
return (ENXIO);
switch (cmd) {
case RMCADM_REQUEST_RESPONSE:
case RMCADM_REQUEST_RESPONSE_BP:
/*
* first copy in the request_response structure
*/
#ifdef _MULTI_DATAMODEL
switch (ddi_model_convert_from(mode & FMODELS)) {
case DDI_MODEL_ILP32:
{
/*
* For use when a 32 bit app makes a call into a
* 64 bit ioctl
*/
rmcadm_request_response32_t rr32;
if (ddi_copyin((caddr_t)arg, (caddr_t)&rr32,
sizeof (rr32), mode)) {
return (EFAULT);
}
rr.req.msg_type = rr32.req.msg_type;
rr.req.msg_len = rr32.req.msg_len;
rr.req.msg_bytes = rr32.req.msg_bytes;
rr.req.msg_buf = (caddr_t)(uintptr_t)rr32.req.msg_buf;
rr.resp.msg_type = rr32.resp.msg_type;
rr.resp.msg_len = rr32.resp.msg_len;
rr.resp.msg_bytes = rr32.resp.msg_bytes;
rr.resp.msg_buf = (caddr_t)(uintptr_t)rr32.resp.msg_buf;
rr.wait_time = rr32.wait_time;
break;
}
case DDI_MODEL_NONE:
if (ddi_copyin((caddr_t)arg, (caddr_t)&rr,
sizeof (rr), mode)) {
return (EFAULT);
}
break;
}
#else /* ! _MULTI_DATAMODEL */
if (ddi_copyin((caddr_t)arg, (caddr_t)&rr,
sizeof (rr), mode) != 0) {
return (EFAULT);
}
#endif /* _MULTI_DATAMODEL */
/*
* save the user request buffer pointer
*/
user_req_buf = rr.req.msg_buf;
if (user_req_buf != NULL) {
/*
* copy in the request data
*/
rr.req.msg_buf = kmem_alloc(rr.req.msg_len, KM_SLEEP);
if (ddi_copyin(user_req_buf, rr.req.msg_buf,
rr.req.msg_len, mode) != 0) {
kmem_free(rr.req.msg_buf, rr.req.msg_len);
rr.req.msg_buf = user_req_buf;
return (EFAULT);
}
} else {
if (rr.req.msg_len > 0)
/*
* msg_len should be 0 if buffer is NULL!
*/
return (EINVAL);
}
/*
* save the user request buffer pointer
*/
user_resp_buf = rr.resp.msg_buf;
if (user_resp_buf != NULL) {
rr.resp.msg_buf = kmem_alloc(rr.resp.msg_len, KM_SLEEP);
}
/*
* send the request (or BP request) via the rmc_comm driver
*/
rmc_reqp->msg_type = rr.req.msg_type;
rmc_reqp->msg_buf = rr.req.msg_buf;
rmc_reqp->msg_len = rr.req.msg_len;
rmc_reqp->msg_bytes = rr.req.msg_bytes;
if (cmd == RMCADM_REQUEST_RESPONSE) {
/*
* check if response is expected. If so, fill in
* the response data structure
*/
if (rr.resp.msg_type != DP_NULL_MSG) {
rmc_respp->msg_type = rr.resp.msg_type;
rmc_respp->msg_buf = rr.resp.msg_buf;
rmc_respp->msg_len = rr.resp.msg_len;
rmc_respp->msg_bytes = rr.resp.msg_bytes;
} else {
rmc_respp = (rmc_comm_msg_t *)NULL;
}
rr.status = rmc_comm_request_response(
rmc_reqp, rmc_respp, rr.wait_time);
} else { /* RMCADM_REQUEST_RESPONSE_BP */
/*
* check if a BP message is expected back. If so,
* fill in the response data structure
*/
if (rr.resp.msg_buf != NULL) {
rmc_respp->msg_type = rr.resp.msg_type;
rmc_respp->msg_buf = rr.resp.msg_buf;
rmc_respp->msg_len = rr.resp.msg_len;
rmc_respp->msg_bytes = rr.resp.msg_bytes;
} else {
rmc_respp = (rmc_comm_msg_t *)NULL;
}
rr.status = rmc_comm_request_response_bp(
rmc_reqp, rmc_respp, rr.wait_time);
}
/*
* if a response was expected, copy back the (actual) number
* of bytes of the response returned by the
* rmc_comm_request_response function (msg_bytes field)
*/
if (rmc_respp != NULL) {
rr.resp.msg_bytes = rmc_respp->msg_bytes;
}
if (rr.status != RCNOERR) {
retval = rmcadm_get_errno(rr.status);
} else if (user_resp_buf != NULL) {
/*
* copy out the user response buffer
*/
if (ddi_copyout(rr.resp.msg_buf, user_resp_buf,
rr.resp.msg_bytes, mode) != 0) {
retval = EFAULT;
}
}
/*
* now copy out the updated request_response structure
*/
if (rr.req.msg_buf)
kmem_free(rr.req.msg_buf, rr.req.msg_len);
if (rr.resp.msg_buf)
kmem_free(rr.resp.msg_buf, rr.resp.msg_len);
rr.req.msg_buf = user_req_buf;
rr.resp.msg_buf = user_resp_buf;
#ifdef _MULTI_DATAMODEL
switch (ddi_model_convert_from(mode & FMODELS)) {
case DDI_MODEL_ILP32:
{
/*
* For use when a 32 bit app makes a call into a
* 64 bit ioctl
*/
rmcadm_request_response32_t rr32;
rr32.req.msg_type = rr.req.msg_type;
rr32.req.msg_len = rr.req.msg_len;
rr32.req.msg_bytes = rr.req.msg_bytes;
rr32.req.msg_buf = (caddr32_t)(uintptr_t)rr.req.msg_buf;
rr32.resp.msg_type = rr.resp.msg_type;
rr32.resp.msg_len = rr.resp.msg_len;
rr32.resp.msg_bytes = rr.resp.msg_bytes;
rr32.resp.msg_buf =
(caddr32_t)(uintptr_t)rr.resp.msg_buf;
rr32.wait_time = rr.wait_time;
rr32.status = rr.status;
if (ddi_copyout((caddr_t)&rr32, (caddr_t)arg,
sizeof (rr32), mode)) {
return (EFAULT);
}
break;
}
case DDI_MODEL_NONE:
if (ddi_copyout((caddr_t)&rr, (caddr_t)arg,
sizeof (rr), mode))
return (EFAULT);
break;
}
#else /* ! _MULTI_DATAMODEL */
if (ddi_copyout((caddr_t)&rr, (caddr_t)arg, sizeof (rr),
mode) != 0)
return (EFAULT);
#endif /* _MULTI_DATAMODEL */
break;
case RMCADM_SEND_SRECORD_BP:
/*
* first copy in the request_response structure
*/
#ifdef _MULTI_DATAMODEL
switch (ddi_model_convert_from(mode & FMODELS)) {
case DDI_MODEL_ILP32:
{
/*
* For use when a 32 bit app makes a call into a
* 64 bit ioctl
*/
rmcadm_send_srecord_bp32_t ssbp32;
if (ddi_copyin((caddr_t)arg, (caddr_t)&ssbp32,
sizeof (ssbp32), mode)) {
return (EFAULT);
}
ssbp.data_len = ssbp32.data_len;
ssbp.data_buf = (caddr_t)(uintptr_t)ssbp32.data_buf;
ssbp.resp_bp.msg_type = ssbp32.resp_bp.msg_type;
ssbp.resp_bp.msg_len = ssbp32.resp_bp.msg_len;
ssbp.resp_bp.msg_bytes = ssbp32.resp_bp.msg_bytes;
ssbp.resp_bp.msg_buf =
(caddr_t)(uintptr_t)ssbp32.resp_bp.msg_buf;
ssbp.wait_time = ssbp32.wait_time;
break;
}
case DDI_MODEL_NONE:
if (ddi_copyin((caddr_t)arg, (caddr_t)&ssbp,
sizeof (ssbp), mode))
return (EFAULT);
break;
}
#else /* ! _MULTI_DATAMODEL */
if (ddi_copyin((caddr_t)arg, (caddr_t)&ssbp,
sizeof (ssbp), mode) != 0)
return (EFAULT);
#endif /* _MULTI_DATAMODEL */
/*
* save the user data buffer pointer
*/
user_data_buf = ssbp.data_buf;
if (user_data_buf != NULL) {
/*
* copy in the srecord data
*/
ssbp.data_buf = kmem_alloc(ssbp.data_len, KM_SLEEP);
if (ddi_copyin(user_data_buf, ssbp.data_buf,
ssbp.data_len, mode) != 0) {
kmem_free(ssbp.data_buf, ssbp.data_len);
ssbp.data_buf = user_data_buf;
return (EFAULT);
}
} else {
return (EINVAL); /* request can't be NULL! */
}
/*
* save the user request buffer pointer
*/
user_resp_buf = ssbp.resp_bp.msg_buf;
if (user_resp_buf != NULL) {
ssbp.resp_bp.msg_buf =
kmem_alloc(ssbp.resp_bp.msg_len, KM_SLEEP);
} else {
kmem_free(ssbp.data_buf, ssbp.data_len);
return (EINVAL);
}
/*
* send the srecord via the rmc_comm driver and get the reply
* back (BP message)
*/
rmc_respp->msg_type = ssbp.resp_bp.msg_type;
rmc_respp->msg_buf = ssbp.resp_bp.msg_buf;
rmc_respp->msg_len = ssbp.resp_bp.msg_len;
rmc_respp->msg_bytes = ssbp.resp_bp.msg_bytes;
ssbp.status = rmc_comm_send_srecord_bp(ssbp.data_buf,
ssbp.data_len, rmc_respp, ssbp.wait_time);
/*
* copy back the actual size of the returned message
*/
ssbp.resp_bp.msg_bytes = rmc_respp->msg_bytes;
if (ssbp.status != RCNOERR) {
retval = rmcadm_get_errno(ssbp.status);
} else if (user_resp_buf != NULL) {
/*
* copy out the user BP response buffer
*/
if (ddi_copyout(ssbp.resp_bp.msg_buf, user_resp_buf,
ssbp.resp_bp.msg_bytes, mode) != 0) {
retval = EFAULT;
}
}
/*
* now copy out the updated request_response structure
*/
if (ssbp.data_buf)
kmem_free(ssbp.data_buf, ssbp.data_len);
if (ssbp.resp_bp.msg_buf)
kmem_free(ssbp.resp_bp.msg_buf, ssbp.resp_bp.msg_len);
ssbp.data_buf = user_data_buf;
ssbp.resp_bp.msg_buf = user_resp_buf;
#ifdef _MULTI_DATAMODEL
switch (ddi_model_convert_from(mode & FMODELS)) {
case DDI_MODEL_ILP32:
{
/*
* For use when a 32 bit app makes a call into a
* 64 bit ioctl
*/
rmcadm_send_srecord_bp32_t ssbp32;
ssbp32.data_len = ssbp.data_len;
ssbp32.data_buf = (caddr32_t)(uintptr_t)ssbp.data_buf;
ssbp32.resp_bp.msg_type = ssbp.resp_bp.msg_type;
ssbp32.resp_bp.msg_len = ssbp.resp_bp.msg_len;
ssbp32.resp_bp.msg_bytes = ssbp.resp_bp.msg_bytes;
ssbp32.resp_bp.msg_buf =
(caddr32_t)(uintptr_t)ssbp.resp_bp.msg_buf;
ssbp32.wait_time = ssbp.wait_time;
if (ddi_copyout((caddr_t)&ssbp32, (caddr_t)arg,
sizeof (ssbp32), mode)) {
return (EFAULT);
}
break;
}
case DDI_MODEL_NONE:
if (ddi_copyout((caddr_t)&ssbp, (caddr_t)arg,
sizeof (ssbp), mode))
return (EFAULT);
break;
}
#else /* ! _MULTI_DATAMODEL */
if (ddi_copyout((caddr_t)&ssbp, (caddr_t)arg, sizeof (ssbp),
mode) != 0)
return (EFAULT);
#endif /* _MULTI_DATAMODEL */
break;
case RMCADM_RESET_SP:
pmugpio_reset();
retval = 0;
break;
default:
retval = ENOTSUP;
break;
}
return (retval);
}