/* * 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 #include /* * The kernel pools subsystem is accessed and manipulated through the pool * device, which has two minor nodes /dev/pool, and /dev/poolctl. User * processes can comminicate with pools through ioctls on these devices. * * The poolctl device (POOL_CTL_PARENT) can be used to modify and take * snapshot of the current configuration. Only one process on the system * can have it open at any given time. This device is also used to enable * or disable pools. If pools are disabled, the pool driver can be unloaded * and completely removed from the system. * * The pool "info" device (POOL_INFO_PARENT) can only be used to obtain * snapshots of the current configuration and change/query pool bindings. * While some reconfiguration transaction via the poolctl device is in * progress, all processes using this "info" device will be provided with * the snapshot taken at the beginning of that transaction. */ #define POOL_CTL_PARENT 0 #define POOL_INFO_PARENT 1 static dev_info_t *pool_devi; /* pool device information */ static int pool_openctl; /* poolctl device is already open */ /*ARGSUSED*/ static int pool_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result) { int error = DDI_FAILURE; switch (infocmd) { case DDI_INFO_DEVT2DEVINFO: *result = pool_devi; error = DDI_SUCCESS; break; case DDI_INFO_DEVT2INSTANCE: /* * All dev_t's map to the same, single instance. */ *result = NULL; error = DDI_SUCCESS; break; default: break; } return (error); } static int pool_detach(dev_info_t *devi, ddi_detach_cmd_t cmd) { int ret = DDI_SUCCESS; switch (cmd) { case DDI_DETACH: pool_lock(); if (pool_state == POOL_ENABLED) { ret = DDI_FAILURE; pool_unlock(); break; } ddi_remove_minor_node(devi, NULL); pool_devi = NULL; pool_unlock(); break; default: ret = DDI_FAILURE; } return (ret); } static int pool_attach(dev_info_t *devi, ddi_attach_cmd_t cmd) { switch (cmd) { case DDI_ATTACH: if (pool_devi != NULL) return (DDI_FAILURE); if (ddi_create_minor_node(devi, "poolctl", S_IFCHR, POOL_CTL_PARENT, DDI_PSEUDO, 0) == DDI_FAILURE || ddi_create_minor_node(devi, "pool", S_IFCHR, POOL_INFO_PARENT, DDI_PSEUDO, 0) == DDI_FAILURE) { ddi_remove_minor_node(devi, NULL); return (DDI_FAILURE); } pool_devi = devi; ddi_report_dev(devi); break; case DDI_RESUME: break; default: return (DDI_FAILURE); } return (DDI_SUCCESS); } /* * There is only one instance of the pool control device, poolctl, * and multiple instances of the pool info device, pool. */ /*ARGSUSED*/ static int pool_open(dev_t *devp, int flag, int otype, cred_t *credp) { minor_t minor = getminor(*devp); if (otype != OTYP_CHR) return (EINVAL); switch (minor) { case POOL_CTL_PARENT: if (secpolicy_pool(CRED()) != 0) return (EPERM); if (pool_lock_intr() != 0) return (EINTR); if (pool_openctl == 1) { pool_unlock(); return (EBUSY); } pool_openctl = 1; pool_unlock(); break; case POOL_INFO_PARENT: break; default: return (ENXIO); } return (0); } /*ARGSUSED*/ static int pool_close(dev_t dev, int flag, int otype, cred_t *credp) { if (otype != OTYP_CHR) return (EINVAL); if (getminor(dev) == 0) { /* * We could be closing the poolctl device without finishing * the commit transaction first, so do that now. */ pool_lock(); (void) pool_commit(0); /* cannot fail since arg is 0 */ pool_openctl = 0; pool_unlock(); } return (0); } /* * Main pool interface. */ /* ARGSUSED4 */ static int pool_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, int *rvalp) { pool_xtransfer_t xtransfer; pool_transfer_t transfer; pool_destroy_t destroy; pool_propget_t propget; pool_propput_t propput; pool_proprm_t proprm; pool_status_t status; pool_dissoc_t dissoc; pool_create_t create; pool_assoc_t assoc; pool_bindq_t bindq; pool_query_t query; pool_bind_t bind; #ifdef _MULTI_DATAMODEL pool_xtransfer32_t xtransfer32; pool_propput32_t propput32; pool_propget32_t propget32; pool_proprm32_t proprm32; pool_query32_t query32; #endif /* _MULTI_DATAMODEL */ char *kbuf = NULL; size_t kbufsz = 0; int snapshot = 0; char *prop_name; size_t size = 0; nvlist_t *list; nvpair_t *pair; char *listbuf; minor_t minor; uint_t model; id_t *id_buf; int ret = 0; model = ddi_model_convert_from(mode & FMODELS); minor = getminor(dev); /* * Check basic permissions first. */ switch (cmd) { case POOL_STATUS: case POOL_CREATE: case POOL_ASSOC: case POOL_DISSOC: case POOL_DESTROY: case POOL_TRANSFER: case POOL_XTRANSFER: case POOL_PROPPUT: case POOL_PROPRM: case POOL_COMMIT: if (minor != POOL_CTL_PARENT) return (EINVAL); /*FALLTHROUGH*/ case POOL_BIND: if (secpolicy_pool(CRED()) != 0) return (EPERM); break; } switch (cmd) { case POOL_STATUS: if (ddi_copyin((void *)arg, &status, sizeof (pool_status_t), mode) != 0) return (EFAULT); if (pool_lock_intr() != 0) return (EINTR); ret = pool_status(status.ps_io_state); pool_unlock(); break; case POOL_STATUSQ: /* * No need to grab pool_lock() to look at the current state. */ status.ps_io_state = pool_state; if (ddi_copyout(&status, (void *)arg, sizeof (pool_status_t), mode) != 0) return (EFAULT); break; case POOL_QUERY: switch (model) { #ifdef _MULTI_DATAMODEL case DDI_MODEL_ILP32: if (ddi_copyin((void *)arg, &query32, sizeof (pool_query32_t), mode) != 0) return (EFAULT); query.pq_io_bufsize = query32.pq_io_bufsize; query.pq_io_buf = (char *)(uintptr_t)query32.pq_io_buf; break; #endif /* _MULTI_DATAMODEL */ default: case DDI_MODEL_NONE: if (ddi_copyin((void *)arg, &query, sizeof (pool_query_t), mode) != 0) return (EFAULT); } if (pool_lock_intr() != 0) return (EINTR); if (pool_state == POOL_DISABLED) { pool_unlock(); return (ENOTACTIVE); } if (minor != 0 && pool_buf != NULL) { /* * Return last snapshot if some * transaction is still in progress */ if (kbufsz != 0 && pool_bufsz > kbufsz) { pool_unlock(); return (ENOMEM); } kbuf = pool_buf; kbufsz = size = pool_bufsz; snapshot = 1; } else if (query.pq_io_bufsize != 0) { kbufsz = query.pq_io_bufsize; kbuf = kmem_alloc(kbufsz, KM_NOSLEEP); if (kbuf == NULL) { pool_unlock(); return (ENOMEM); } ret = pool_pack_conf(kbuf, kbufsz, &size); } else { ret = pool_pack_conf(NULL, 0, &size); } if (ret == 0) { switch (model) { #ifdef _MULTI_DATAMODEL case DDI_MODEL_ILP32: query32.pq_io_bufsize = size; if (ddi_copyout((caddr_t)&query32, (void *)arg, sizeof (pool_query32_t), mode) != 0) ret = EFAULT; break; #endif /* _MULTI_DATAMODEL */ default: case DDI_MODEL_NONE: query.pq_io_bufsize = size; if (ddi_copyout(&query, (void *)arg, sizeof (pool_query_t), mode) != 0) ret = EFAULT; } if (ret == 0 && query.pq_io_buf != NULL && ddi_copyout(kbuf, query.pq_io_buf, size, mode) != 0) ret = EFAULT; } pool_unlock(); if (snapshot == 0) kmem_free(kbuf, kbufsz); break; case POOL_CREATE: if (ddi_copyin((void *)arg, &create, sizeof (pool_create_t), mode) != 0) return (EFAULT); if (pool_lock_intr() != 0) return (EINTR); ret = pool_create(create.pc_o_type, create.pc_o_sub_type, &create.pc_i_id); pool_unlock(); if (ret == 0 && ddi_copyout(&create, (void *)arg, sizeof (pool_create_t), mode) != 0) ret = EFAULT; break; case POOL_ASSOC: if (ddi_copyin((void *)arg, &assoc, sizeof (pool_assoc_t), mode) != 0) return (EFAULT); if (pool_lock_intr() != 0) return (EINTR); ret = pool_assoc(assoc.pa_o_pool_id, assoc.pa_o_id_type, assoc.pa_o_res_id); pool_unlock(); break; case POOL_DISSOC: if (ddi_copyin((void *)arg, &dissoc, sizeof (pool_dissoc_t), mode) != 0) return (EFAULT); if (pool_lock_intr() != 0) return (EINTR); ret = pool_dissoc(dissoc.pd_o_pool_id, dissoc.pd_o_id_type); pool_unlock(); break; case POOL_DESTROY: if (ddi_copyin((void *)arg, &destroy, sizeof (pool_destroy_t), mode) != 0) return (EFAULT); if (pool_lock_intr() != 0) return (EINTR); ret = pool_destroy(destroy.pd_o_type, destroy.pd_o_sub_type, destroy.pd_o_id); pool_unlock(); break; case POOL_TRANSFER: if (ddi_copyin((void *)arg, &transfer, sizeof (pool_transfer_t), mode) != 0) return (EFAULT); if (pool_lock_intr() != 0) return (EINTR); ret = pool_transfer(transfer.pt_o_id_type, transfer.pt_o_src_id, transfer.pt_o_tgt_id, transfer.pt_o_qty); pool_unlock(); break; case POOL_XTRANSFER: switch (model) { #ifdef _MULTI_DATAMODEL case DDI_MODEL_ILP32: if (ddi_copyin((void *)arg, &xtransfer32, sizeof (pool_xtransfer32_t), mode) != 0) return (EFAULT); xtransfer.px_o_id_type = xtransfer32.px_o_id_type; xtransfer.px_o_src_id = xtransfer32.px_o_src_id; xtransfer.px_o_tgt_id = xtransfer32.px_o_tgt_id; xtransfer.px_o_complist_size = xtransfer32.px_o_complist_size; xtransfer.px_o_comp_list = (id_t *)(uintptr_t)xtransfer32.px_o_comp_list; break; #endif /* _MULTI_DATAMODEL */ default: case DDI_MODEL_NONE: if (ddi_copyin((void *)arg, &xtransfer, sizeof (pool_xtransfer_t), mode) != 0) return (EFAULT); } /* * Copy in IDs to transfer from the userland */ if (xtransfer.px_o_complist_size > POOL_IDLIST_SIZE) return (EINVAL); id_buf = kmem_alloc(xtransfer.px_o_complist_size * sizeof (id_t), KM_SLEEP); if (ddi_copyin((void *)xtransfer.px_o_comp_list, id_buf, xtransfer.px_o_complist_size * sizeof (id_t), mode) != 0) { kmem_free(id_buf, xtransfer.px_o_complist_size * sizeof (id_t)); return (EFAULT); } if (pool_lock_intr() != 0) { kmem_free(id_buf, xtransfer.px_o_complist_size * sizeof (id_t)); return (EINTR); } ret = pool_xtransfer(xtransfer.px_o_id_type, xtransfer.px_o_src_id, xtransfer.px_o_tgt_id, xtransfer.px_o_complist_size, id_buf); pool_unlock(); kmem_free(id_buf, xtransfer.px_o_complist_size * sizeof (id_t)); break; case POOL_BIND: if (ddi_copyin((void *)arg, &bind, sizeof (pool_bind_t), mode) != 0) return (EFAULT); if (pool_lock_intr() != 0) return (EINTR); ret = pool_bind(bind.pb_o_pool_id, bind.pb_o_id_type, bind.pb_o_id); pool_unlock(); break; case POOL_BINDQ: if (ddi_copyin((void *)arg, &bindq, sizeof (pool_bindq_t), mode) != 0) { return (EFAULT); } if (pool_lock_intr() != 0) return (EINTR); if ((ret = pool_query_binding(bindq.pb_o_id_type, bindq.pb_o_id, &bindq.pb_i_id)) == 0 && ddi_copyout(&bindq, (void *)arg, sizeof (pool_bindq_t), mode) != 0) ret = EFAULT; pool_unlock(); break; case POOL_PROPGET: switch (model) { #ifdef _MULTI_DATAMODEL case DDI_MODEL_ILP32: if (ddi_copyin((void *)arg, &propget32, sizeof (pool_propget32_t), mode) != 0) return (EFAULT); propget.pp_o_id = propget32.pp_o_id; propget.pp_o_id_type = propget32.pp_o_id_type; propget.pp_o_id_subtype = propget32.pp_o_id_subtype; propget.pp_o_prop_name = (char *)(uintptr_t)propget32.pp_o_prop_name; propget.pp_o_prop_name_size = propget32.pp_o_prop_name_size; propget.pp_i_buf = (char *)(uintptr_t)propget32.pp_i_buf; propget.pp_i_bufsize = propget32.pp_i_bufsize; break; #endif /* _MULTI_DATAMODEL */ default: case DDI_MODEL_NONE: if (ddi_copyin((void *)arg, &propget, sizeof (pool_propget_t), mode) != 0) return (EFAULT); } if (propget.pp_o_prop_name_size + 1 > POOL_PROPNAME_SIZE) return (EINVAL); prop_name = kmem_alloc(propget.pp_o_prop_name_size + 1, KM_SLEEP); if (ddi_copyin(propget.pp_o_prop_name, prop_name, propget.pp_o_prop_name_size + 1, mode) != 0) { kmem_free(prop_name, propget.pp_o_prop_name_size + 1); return (EFAULT); } list = NULL; if (pool_lock_intr() != 0) { kmem_free(prop_name, propget.pp_o_prop_name_size + 1); return (EINTR); } ret = pool_propget(prop_name, propget.pp_o_id_type, propget.pp_o_id_subtype, propget.pp_o_id, &list); pool_unlock(); kmem_free(prop_name, propget.pp_o_prop_name_size + 1); if (ret != 0) return (ret); ret = nvlist_pack(list, &kbuf, &kbufsz, NV_ENCODE_NATIVE, 0); if (ret != 0) { nvlist_free(list); return (ret); } switch (model) { #ifdef _MULTI_DATAMODEL case DDI_MODEL_ILP32: propget32.pp_i_bufsize = kbufsz; if (ddi_copyout((caddr_t)&propget32, (void *)arg, sizeof (pool_propget32_t), mode) != 0) ret = EFAULT; break; #endif /* _MULTI_DATAMODEL */ default: case DDI_MODEL_NONE: if (ddi_copyout(&propget, (void *)arg, sizeof (pool_propget_t), mode) != 0) ret = EFAULT; } if (ret == 0) { if (propget.pp_i_buf == NULL) { ret = 0; } else if (propget.pp_i_bufsize >= kbufsz) { if (ddi_copyout(kbuf, propget.pp_i_buf, kbufsz, mode) != 0) ret = EFAULT; } else { ret = ENOMEM; } } kmem_free(kbuf, kbufsz); nvlist_free(list); break; case POOL_PROPPUT: switch (model) { #ifdef _MULTI_DATAMODEL case DDI_MODEL_ILP32: if (ddi_copyin((void *)arg, &propput32, sizeof (pool_propput32_t), mode) != 0) return (EFAULT); propput.pp_o_id_type = propput32.pp_o_id_type; propput.pp_o_id_sub_type = propput32.pp_o_id_sub_type; propput.pp_o_id = propput32.pp_o_id; propput.pp_o_bufsize = propput32.pp_o_bufsize; propput.pp_o_buf = (char *)(uintptr_t)propput32.pp_o_buf; break; #endif /* _MULTI_DATAMODEL */ default: case DDI_MODEL_NONE: if (ddi_copyin((void *)arg, &propput, sizeof (pool_propput_t), mode) != 0) return (EFAULT); } if (propput.pp_o_bufsize > POOL_PROPBUF_SIZE) return (EINVAL); listbuf = kmem_alloc(propput.pp_o_bufsize, KM_SLEEP); if (ddi_copyin(propput.pp_o_buf, listbuf, propput.pp_o_bufsize, mode) != 0) { kmem_free(listbuf, propput.pp_o_bufsize); return (EFAULT); } if (nvlist_unpack(listbuf, propput.pp_o_bufsize, &list, KM_SLEEP) != 0) { kmem_free(listbuf, propput.pp_o_bufsize); return (EFAULT); } if (pool_lock_intr() != 0) { nvlist_free(list); kmem_free(listbuf, propput.pp_o_bufsize); return (EINTR); } /* * Extract the nvpair from the list. The list may * contain multiple properties. */ for (pair = nvlist_next_nvpair(list, NULL); pair != NULL; pair = nvlist_next_nvpair(list, pair)) { if ((ret = pool_propput(propput.pp_o_id_type, propput.pp_o_id_sub_type, propput.pp_o_id, pair)) != 0) break; } pool_unlock(); nvlist_free(list); kmem_free(listbuf, propput.pp_o_bufsize); break; case POOL_PROPRM: switch (model) { #ifdef _MULTI_DATAMODEL case DDI_MODEL_ILP32: if (ddi_copyin((void *)arg, &proprm32, sizeof (pool_proprm32_t), mode) != 0) return (EFAULT); proprm.pp_o_id_type = proprm32.pp_o_id_type; proprm.pp_o_id_sub_type = proprm32.pp_o_id_sub_type; proprm.pp_o_id = proprm32.pp_o_id; proprm.pp_o_prop_name_size = proprm32.pp_o_prop_name_size; proprm.pp_o_prop_name = (void *)(uintptr_t)proprm32.pp_o_prop_name; break; #endif /* _MULTI_DATAMODEL */ default: case DDI_MODEL_NONE: if (ddi_copyin((void *)arg, &proprm, sizeof (pool_proprm_t), mode) != 0) return (EFAULT); } if (proprm.pp_o_prop_name_size + 1 > POOL_PROPNAME_SIZE) return (EINVAL); prop_name = kmem_alloc(proprm.pp_o_prop_name_size + 1, KM_SLEEP); if (ddi_copyin(proprm.pp_o_prop_name, prop_name, proprm.pp_o_prop_name_size + 1, mode) != 0) { kmem_free(prop_name, proprm.pp_o_prop_name_size + 1); return (EFAULT); } if (pool_lock_intr() != 0) { kmem_free(prop_name, proprm.pp_o_prop_name_size + 1); return (EINTR); } ret = pool_proprm(proprm.pp_o_id_type, proprm.pp_o_id_sub_type, proprm.pp_o_id, prop_name); pool_unlock(); kmem_free(prop_name, proprm.pp_o_prop_name_size + 1); break; case POOL_COMMIT: if (pool_lock_intr() != 0) return (EINTR); ret = pool_commit((int)arg); pool_unlock(); break; default: return (EINVAL); } return (ret); } static struct cb_ops pool_cb_ops = { pool_open, /* open */ pool_close, /* close */ nodev, /* strategy */ nodev, /* print */ nodev, /* dump */ nodev, /* read */ nodev, /* write */ pool_ioctl, /* ioctl */ nodev, /* devmap */ nodev, /* mmap */ nodev, /* segmap */ nochpoll, /* poll */ nodev, /* cb_prop_op */ (struct streamtab *)0, /* streamtab */ D_NEW | D_MP /* driver compatibility flags */ }; static struct dev_ops pool_ops = { DEVO_REV, /* devo_rev */ 0, /* refcnt */ pool_info, /* info */ nulldev, /* identify */ nulldev, /* probe */ pool_attach, /* attach */ pool_detach, /* detach */ nodev, /* reset */ &pool_cb_ops, /* cb_ops */ (struct bus_ops *)NULL, /* bus_ops */ nulldev, /* power */ ddi_quiesce_not_needed, /* quiesce */ }; /* * Module linkage information for the kernel */ static struct modldrv modldrv = { &mod_driverops, /* this one is a pseudo driver */ "pool driver", &pool_ops }; static struct modlinkage modlinkage = { MODREV_1, &modldrv, NULL }; int _init(void) { return (mod_install(&modlinkage)); } int _fini(void) { return (mod_remove(&modlinkage)); } int _info(struct modinfo *modinfop) { return (mod_info(&modlinkage, modinfop)); }