/* * 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 #include #include #include #include #include #include #include #include #include #include #include /* * 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, 0); 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); }