| /* |
| * 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. |
| * Copyright (c) 2016 by Delphix. All rights reserved. |
| */ |
| |
| #include <sys/types.h> |
| #include <sys/modctl.h> |
| #include <sys/stat.h> |
| #include <sys/proc.h> |
| #include <sys/ddi.h> |
| #include <sys/sunddi.h> |
| #include <sys/kmem.h> |
| #include <sys/file.h> |
| #include <sys/rsm/rsm_common.h> |
| #include <sys/rsm/rsmpi.h> |
| #include <sys/rsm/rsmpi_driver.h> |
| |
| /* lint -w2 */ |
| static struct modlmisc modlmisc = { |
| &mod_miscops, "RSMOPS module", |
| }; |
| |
| static struct modlinkage modlinkage = { |
| MODREV_1, (void *)&modlmisc, NULL |
| }; |
| |
| static kmutex_t rsmops_lock; |
| |
| static rsmops_drv_t *rsmops_drv_head = NULL; |
| |
| static int rsmops_threads_started = 0; |
| |
| int |
| _init(void) |
| { |
| int err; |
| |
| mutex_init(&rsmops_lock, NULL, MUTEX_DEFAULT, NULL); |
| |
| if ((err = mod_install(&modlinkage)) != 0) |
| mutex_destroy(&rsmops_lock); |
| |
| return (err); |
| } |
| |
| int |
| _fini(void) |
| { |
| int err; |
| |
| mutex_enter(&rsmops_lock); |
| if (rsmops_drv_head) { |
| /* Somebody is still registered with us - we cannot unload */ |
| mutex_exit(&rsmops_lock); |
| return (EBUSY); |
| } |
| if (rsmops_threads_started) { |
| /* |
| * Some threads have been started. We do not have any |
| * well-supported way of checking whether they have all |
| * exited. For now, fail attempt to unload if we have |
| * ever started any threads. This is overkill, but ... |
| */ |
| mutex_exit(&rsmops_lock); |
| return (EBUSY); |
| } |
| mutex_exit(&rsmops_lock); |
| |
| if ((err = mod_remove(&modlinkage)) == 0) |
| mutex_destroy(&rsmops_lock); |
| return (err); |
| } |
| |
| int |
| _info(struct modinfo *modinfop) |
| { |
| return (mod_info(&modlinkage, modinfop)); |
| } |
| |
| static void |
| rsmops_thread_entry(rsmops_drv_t *p_drv) |
| { |
| /* p_drv->ctrl_cnt has already been increased by the time we get here */ |
| ASSERT(p_drv->drv.rsm_thread_entry_pt); |
| |
| /* call the driver with the thread */ |
| (*(p_drv->drv.rsm_thread_entry_pt))(p_drv->drv.drv_name); |
| |
| /* thread has returned */ |
| mutex_enter(&rsmops_lock); |
| p_drv->ctrl_cnt--; |
| mutex_exit(&rsmops_lock); |
| } |
| |
| /* This is expected to be called from the driver's init function */ |
| int |
| rsm_register_driver(rsmops_registry_t *p_registry) |
| { |
| rsmops_drv_t **pp_tail; |
| rsmops_drv_t *p; |
| |
| if (p_registry->rsm_version > RSM_VERSION) { |
| /* The driver is up-rev than me. Fail attempt to register */ |
| return (RSMERR_BAD_DRIVER_VERSION); |
| } |
| |
| /* |
| * RSM_VERSION: Since this is the first version, there cannot be any |
| * down-rev drivers - this will be an issue in the future |
| */ |
| if (p_registry->rsm_version != RSM_VERSION) |
| return (RSMERR_BAD_DRIVER_VERSION); |
| |
| mutex_enter(&rsmops_lock); |
| /* First, search that this driver is not already registered */ |
| pp_tail = &rsmops_drv_head; |
| while (*pp_tail) { |
| if (strcmp((*pp_tail)->drv.drv_name, p_registry->drv_name) |
| == 0) { |
| mutex_exit(&rsmops_lock); |
| return (RSMERR_DRIVER_NAME_IN_USE); |
| } |
| pp_tail = &((*pp_tail)->next); |
| } |
| |
| p = kmem_alloc(sizeof (rsmops_drv_t), KM_SLEEP); |
| p->drv = *p_registry; /* copy entire rsmops_registry_t structure */ |
| p->next = NULL; |
| p->ctrl_cnt = 0; |
| p->ctrl_head = NULL; |
| |
| if (p->drv.rsm_thread_entry_pt) { |
| /* thread entry point is defined - we need to create a thread */ |
| extern pri_t minclsyspri; |
| |
| p->ctrl_cnt++; /* bump up the count right now */ |
| p->thread_id = thread_create(NULL, 0, rsmops_thread_entry, |
| p, 0, &p0, TS_RUN, minclsyspri); |
| rsmops_threads_started++; |
| } else |
| p->thread_id = NULL; |
| |
| *pp_tail = p; |
| mutex_exit(&rsmops_lock); |
| return (RSM_SUCCESS); |
| } |
| |
| /* |
| * This is expected to be called from the driver's fini function |
| * if this function returns EBUSY, the driver is supposed to fail |
| * its own fini operation |
| */ |
| int |
| rsm_unregister_driver(rsmops_registry_t *p_registry) |
| { |
| rsmops_drv_t **pp_tail; |
| rsmops_drv_t *p; |
| |
| mutex_enter(&rsmops_lock); |
| |
| /* Search for the driver */ |
| pp_tail = &rsmops_drv_head; |
| while (*pp_tail) { |
| if (strcmp((*pp_tail)->drv.drv_name, p_registry->drv_name)) { |
| pp_tail = &((*pp_tail)->next); |
| continue; |
| } |
| /* check ref count - if somebody is using it, return EBUSY */ |
| if ((*pp_tail)->ctrl_cnt) { |
| mutex_exit(&rsmops_lock); |
| return (RSMERR_CTLRS_REGISTERED); |
| } |
| /* Nobody is using it - we can allow the unregister to happen */ |
| p = *pp_tail; |
| |
| /* Stomp the guy out of our linked list */ |
| *pp_tail = (*pp_tail)->next; |
| |
| /* release the memory */ |
| kmem_free(p, sizeof (rsmops_drv_t)); |
| |
| mutex_exit(&rsmops_lock); |
| return (RSM_SUCCESS); |
| } |
| |
| /* Could not find the guy */ |
| mutex_exit(&rsmops_lock); |
| return (RSMERR_DRIVER_NOT_REGISTERED); |
| } |
| |
| /* Should be called holding the rsmops_lock mutex */ |
| static rsmops_drv_t * |
| find_rsmpi_driver(const char *name) |
| { |
| rsmops_drv_t *p_rsmops_list; |
| |
| ASSERT(MUTEX_HELD(&rsmops_lock)); |
| /* the name is of the form "sci", "wci" etc */ |
| |
| for (p_rsmops_list = rsmops_drv_head; p_rsmops_list != NULL; |
| p_rsmops_list = p_rsmops_list->next) { |
| |
| if (strcmp(name, p_rsmops_list->drv.drv_name) == 0) { |
| return (p_rsmops_list); |
| } |
| } |
| return (NULL); |
| } |
| |
| |
| /* Should be called holding the rsmops_lock mutex */ |
| static rsmops_ctrl_t * |
| find_rsmpi_controller(const char *name, uint_t number) |
| { |
| rsmops_drv_t *p_drv; |
| rsmops_ctrl_t *p; |
| |
| ASSERT(MUTEX_HELD(&rsmops_lock)); |
| |
| if ((p_drv = find_rsmpi_driver(name)) == NULL) |
| return (NULL); |
| |
| for (p = p_drv->ctrl_head; p != NULL; p = p->next) { |
| ASSERT(p->p_drv == p_drv); |
| if (p->number == number) |
| return (p); |
| } |
| return (NULL); |
| } |
| |
| /* Should be called holding the rsmops_lock mutex */ |
| static rsmops_ctrl_t * |
| find_rsmpi_controller_handle(rsm_controller_handle_t cntlr_handle) |
| { |
| rsmops_drv_t *p_drv; |
| rsmops_ctrl_t *p; |
| |
| ASSERT(MUTEX_HELD(&rsmops_lock)); |
| |
| for (p_drv = rsmops_drv_head; p_drv != NULL; p_drv = p_drv->next) { |
| for (p = p_drv->ctrl_head; p != NULL; p = p->next) { |
| if (p->handle == cntlr_handle) |
| return (p); |
| } |
| } |
| |
| return (NULL); |
| } |
| |
| static vnode_t * |
| rsmops_device_open(const char *major_name, const minor_t minor_num); |
| |
| int |
| rsm_get_controller(const char *name, uint_t number, |
| rsm_controller_object_t *controller, uint_t version) |
| { |
| rsmops_ctrl_t *p_ctrl; |
| rsmops_drv_t *p_drv; |
| vnode_t *vp; |
| int error; |
| int (*rsm_get_controller_handler) |
| (const char *name, uint_t number, |
| rsm_controller_object_t *pcontroller, uint_t version); |
| |
| mutex_enter(&rsmops_lock); |
| |
| /* check if the controller is already registered */ |
| if ((p_ctrl = find_rsmpi_controller(name, number)) == NULL) { |
| /* |
| * controller is not registered. We should try to load it |
| * First check if the driver is registered |
| */ |
| if ((p_drv = find_rsmpi_driver(name)) == NULL) { |
| /* Cannot find the driver. Try to load it */ |
| mutex_exit(&rsmops_lock); |
| if ((error = modload("drv", (char *)name)) == -1) { |
| return (RSMERR_CTLR_NOT_PRESENT); |
| } |
| mutex_enter(&rsmops_lock); |
| if ((p_drv = find_rsmpi_driver(name)) == NULL) { |
| mutex_exit(&rsmops_lock); |
| /* |
| * Cannot find yet - maybe the driver we loaded |
| * was not a RSMPI driver at all. We'll just |
| * fail this call. |
| */ |
| return (RSMERR_CTLR_NOT_PRESENT); |
| } |
| } |
| ASSERT(p_drv); |
| p_ctrl = find_rsmpi_controller(name, number); |
| if (p_ctrl == NULL) { |
| /* |
| * controller is not registered. |
| * try to do a VOP_OPEN to force it to get registered |
| */ |
| mutex_exit(&rsmops_lock); |
| vp = rsmops_device_open(name, number); |
| mutex_enter(&rsmops_lock); |
| if (vp != NULL) { |
| (void) VOP_CLOSE(vp, FREAD|FWRITE, 0, 0, |
| CRED(), NULL); |
| VN_RELE(vp); |
| } |
| p_ctrl = find_rsmpi_controller(name, number); |
| if (p_ctrl == NULL) { |
| mutex_exit(&rsmops_lock); |
| return (RSMERR_CTLR_NOT_PRESENT); |
| } |
| } |
| ASSERT(p_ctrl); |
| } else { |
| p_drv = p_ctrl->p_drv; |
| } |
| ASSERT(p_drv); |
| ASSERT(p_drv == p_ctrl->p_drv); |
| |
| rsm_get_controller_handler = p_drv->drv.rsm_get_controller_handler; |
| /* |
| * Increase the refcnt right now, so that attempts to deregister |
| * while we are using this entry will fail |
| */ |
| p_ctrl->refcnt++; |
| mutex_exit(&rsmops_lock); |
| |
| error = (*rsm_get_controller_handler)(name, number, controller, |
| version); |
| if (error != RSM_SUCCESS) { |
| /* We failed - drop the refcnt back */ |
| mutex_enter(&rsmops_lock); |
| /* |
| * Even though we had released the global lock, we can |
| * guarantee that p_ctrl is still meaningful (and has not |
| * been deregistered, freed whatever) because we were holding |
| * refcnt on it. So, it is okay to just use p_ctrl here |
| * after re-acquiring the global lock |
| */ |
| p_ctrl->refcnt--; |
| mutex_exit(&rsmops_lock); |
| } else { |
| /* |
| * Initialize the controller handle field |
| */ |
| mutex_enter(&rsmops_lock); |
| if ((p_ctrl = find_rsmpi_controller(name, number)) == NULL) { |
| mutex_exit(&rsmops_lock); |
| return (RSMERR_CTLR_NOT_PRESENT); |
| } |
| |
| p_ctrl->handle = controller->handle; |
| mutex_exit(&rsmops_lock); |
| } |
| return (error); |
| } |
| |
| int |
| rsm_release_controller(const char *name, uint_t number, |
| rsm_controller_object_t *controller) |
| { |
| rsmops_ctrl_t *p_ctrl; |
| rsmops_drv_t *p_drv; |
| int error; |
| int (*releaser)(const char *name, uint_t number, |
| rsm_controller_object_t *controller); |
| |
| mutex_enter(&rsmops_lock); |
| |
| if ((p_ctrl = find_rsmpi_controller(name, number)) == NULL) { |
| mutex_exit(&rsmops_lock); |
| return (RSMERR_CTLR_NOT_PRESENT); |
| } |
| p_drv = find_rsmpi_driver(name); |
| ASSERT(p_drv); /* If we found controller, there MUST be a driver */ |
| |
| /* Found the appropriate driver. Forward the call to it */ |
| releaser = p_drv->drv.rsm_release_controller_handler; |
| mutex_exit(&rsmops_lock); |
| |
| error = (*releaser)(name, number, controller); |
| if (error == RSM_SUCCESS) { |
| mutex_enter(&rsmops_lock); |
| p_ctrl->refcnt--; |
| mutex_exit(&rsmops_lock); |
| } |
| return (error); |
| } |
| |
| /* This is expected to be called from the driver's attach function */ |
| int |
| rsm_register_controller(const char *name, uint_t number, |
| rsm_controller_attr_t *attrp) |
| { |
| rsmops_drv_t *p_drv; |
| rsmops_ctrl_t *p_ctrl; |
| |
| if (strlen(name) > MAX_DRVNAME) |
| return (RSMERR_NAME_TOO_LONG); |
| |
| mutex_enter(&rsmops_lock); |
| |
| /* Check if the driver is registered with us */ |
| p_drv = find_rsmpi_driver(name); |
| if (p_drv == NULL) { |
| /* |
| * Hey! Driver is not registered, but we are getting a |
| * controller ?? |
| */ |
| mutex_exit(&rsmops_lock); |
| return (RSMERR_DRIVER_NOT_REGISTERED); |
| } |
| |
| /* Check if the controller is already registered with us */ |
| p_ctrl = find_rsmpi_controller(name, number); |
| if (p_ctrl) { |
| /* already registered */ |
| mutex_exit(&rsmops_lock); |
| return (RSMERR_CTLR_ALREADY_REGISTERED); |
| } |
| |
| /* WAIT: sanity check - verify that the dip matches up to name,number */ |
| |
| p_ctrl = kmem_alloc(sizeof (rsmops_ctrl_t), KM_SLEEP); |
| |
| /* bump up controller count on the driver */ |
| p_drv->ctrl_cnt++; |
| |
| p_ctrl->p_drv = p_drv; /* setup the back pointer */ |
| p_ctrl->number = number; |
| p_ctrl->refcnt = 0; |
| p_ctrl->attrp = attrp; |
| p_ctrl->handle = NULL; |
| |
| /* Now link to head of list */ |
| p_ctrl->next = p_drv->ctrl_head; |
| p_drv->ctrl_head = p_ctrl; |
| |
| mutex_exit(&rsmops_lock); |
| |
| return (RSM_SUCCESS); |
| } |
| |
| /* |
| * This is expected to be called from the driver's detach function |
| * if this function returns EBUSY, the driver is supposed to fail |
| * its own detach operation |
| */ |
| int |
| rsm_unregister_controller(const char *name, uint_t number) |
| { |
| rsmops_drv_t *p_drv; |
| rsmops_ctrl_t **p_prev; |
| rsmops_ctrl_t *found; |
| |
| mutex_enter(&rsmops_lock); |
| |
| /* Check if the driver is registered with us */ |
| p_drv = find_rsmpi_driver(name); |
| if (p_drv == NULL) { |
| /* Hey! Driver is not registered */ |
| mutex_exit(&rsmops_lock); |
| return (RSMERR_DRIVER_NOT_REGISTERED); |
| } |
| |
| /* Search for the controller in the list */ |
| for (p_prev = &p_drv->ctrl_head; *p_prev; p_prev = &((*p_prev)->next)) { |
| if ((*p_prev)->number == number) { |
| /* Found the controller. Check if it is busy */ |
| found = *p_prev; |
| |
| if (found->refcnt) { |
| /* Controller is busy - handles outstanding */ |
| mutex_exit(&rsmops_lock); |
| return (RSMERR_CTLR_IN_USE); |
| } |
| /* unlink it out */ |
| *p_prev = found->next; |
| /* bump down controller count on the driver */ |
| p_drv->ctrl_cnt--; |
| |
| mutex_exit(&rsmops_lock); |
| kmem_free(found, sizeof (rsmops_ctrl_t)); |
| return (RSM_SUCCESS); |
| } |
| } |
| mutex_exit(&rsmops_lock); |
| /* Could not find the right controller */ |
| return (RSMERR_CTLR_NOT_REGISTERED); |
| } |
| |
| |
| /* |
| * This opens and closes the appropriate device with minor number - |
| * hopefully, it will cause the driver to attach and register a controller |
| * with us |
| */ |
| static vnode_t * |
| rsmops_device_open(const char *major_name, const minor_t minor_num) |
| { |
| major_t maj; |
| vnode_t *vp; |
| int ret; |
| |
| if (minor_num == (minor_t)-1) { |
| return (NULL); |
| } |
| |
| maj = ddi_name_to_major((char *)major_name); |
| if (maj == (major_t)-1) { |
| return (NULL); |
| } |
| |
| vp = makespecvp(makedevice(maj, minor_num), VCHR); |
| |
| ret = VOP_OPEN(&vp, FREAD|FWRITE, CRED(), NULL); |
| if (ret == 0) { |
| return (vp); |
| } else { |
| VN_RELE(vp); |
| return (NULL); |
| } |
| } |
| |
| /* |
| * Attributes for controller identified by the handle are returned |
| * via *attrp. Modifications of attributes is prohibited by client! |
| */ |
| int |
| rsm_get_controller_attr(rsm_controller_handle_t handle, |
| rsm_controller_attr_t **attrp) |
| { |
| |
| rsmops_ctrl_t *p_ctrl; |
| |
| if (handle == NULL) |
| return (RSMERR_BAD_CTLR_HNDL); |
| |
| mutex_enter(&rsmops_lock); |
| |
| /* find controller */ |
| if ((p_ctrl = find_rsmpi_controller_handle(handle)) == NULL) { |
| /* can't supply attributes for invalid controller */ |
| mutex_exit(&rsmops_lock); |
| return (RSMERR_BAD_CTLR_HNDL); |
| } |
| *attrp = p_ctrl->attrp; |
| mutex_exit(&rsmops_lock); |
| |
| return (RSM_SUCCESS); |
| } |