/* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2019 Joyent, Inc. */ /* * This is a test driver used for exercising the DDI UFM subsystem. * * Most of the test cases depend on the ufmtest driver being loaded. * On SmartOS, this driver will need to be manually installed, as it is not * part of the platform image. */ #include #include #include #include #include #include #include #include #include #include #include "ufmtest.h" typedef struct ufmtest { dev_info_t *ufmt_devi; nvlist_t *ufmt_nvl; ddi_ufm_handle_t *ufmt_ufmh; uint32_t ufmt_failflags; } ufmtest_t; static ufmtest_t ufmt = { 0 }; static int ufmtest_open(dev_t *, int, int, cred_t *); static int ufmtest_close(dev_t, int, int, cred_t *); static int ufmtest_ioctl(dev_t, int, intptr_t, int, cred_t *, int *); static struct cb_ops ufmtest_cb_ops = { .cb_open = ufmtest_open, .cb_close = ufmtest_close, .cb_strategy = nodev, .cb_print = nodev, .cb_dump = nodev, .cb_read = nodev, .cb_write = nodev, .cb_ioctl = ufmtest_ioctl, .cb_devmap = nodev, .cb_mmap = nodev, .cb_segmap = nodev, .cb_chpoll = nochpoll, .cb_prop_op = ddi_prop_op, .cb_str = NULL, .cb_flag = D_NEW | D_MP, .cb_rev = CB_REV, .cb_aread = nodev, .cb_awrite = nodev }; static int ufmtest_info(dev_info_t *, ddi_info_cmd_t, void *, void **); static int ufmtest_attach(dev_info_t *, ddi_attach_cmd_t); static int ufmtest_detach(dev_info_t *, ddi_detach_cmd_t); static struct dev_ops ufmtest_ops = { .devo_rev = DEVO_REV, .devo_refcnt = 0, .devo_getinfo = ufmtest_info, .devo_identify = nulldev, .devo_probe = nulldev, .devo_attach = ufmtest_attach, .devo_detach = ufmtest_detach, .devo_reset = nodev, .devo_cb_ops = &ufmtest_cb_ops, .devo_bus_ops = NULL, .devo_power = NULL, .devo_quiesce = ddi_quiesce_not_needed }; static struct modldrv modldrv = { .drv_modops = &mod_driverops, .drv_linkinfo = "DDI UFM test driver", .drv_dev_ops = &ufmtest_ops }; static struct modlinkage modlinkage = { .ml_rev = MODREV_1, .ml_linkage = { (void *)&modldrv, NULL } }; static int ufmtest_nimages(ddi_ufm_handle_t *, void *, uint_t *); static int ufmtest_fill_image(ddi_ufm_handle_t *, void *, uint_t, ddi_ufm_image_t *); static int ufmtest_fill_slot(ddi_ufm_handle_t *, void *, uint_t, uint_t, ddi_ufm_slot_t *); static int ufmtest_getcaps(ddi_ufm_handle_t *, void *, ddi_ufm_cap_t *); static ddi_ufm_ops_t ufmtest_ufm_ops = { ufmtest_nimages, ufmtest_fill_image, ufmtest_fill_slot, ufmtest_getcaps }; int _init(void) { return (mod_install(&modlinkage)); } int _fini(void) { return (mod_remove(&modlinkage)); } int _info(struct modinfo *modinfop) { return (mod_info(&modlinkage, modinfop)); } static int ufmtest_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result) { switch (infocmd) { case DDI_INFO_DEVT2DEVINFO: *result = ufmt.ufmt_devi; return (DDI_SUCCESS); case DDI_INFO_DEVT2INSTANCE: *result = (void *)(uintptr_t)ddi_get_instance(dip); return (DDI_SUCCESS); } return (DDI_FAILURE); } static int ufmtest_attach(dev_info_t *devi, ddi_attach_cmd_t cmd) { if (cmd != DDI_ATTACH || ufmt.ufmt_devi != NULL) return (DDI_FAILURE); if (ddi_create_minor_node(devi, "ufmtest", S_IFCHR, 0, DDI_PSEUDO, 0) == DDI_FAILURE) { ddi_remove_minor_node(devi, NULL); return (DDI_FAILURE); } ufmt.ufmt_devi = devi; if (ddi_ufm_init(ufmt.ufmt_devi, DDI_UFM_CURRENT_VERSION, &ufmtest_ufm_ops, &ufmt.ufmt_ufmh, NULL) != 0) { dev_err(ufmt.ufmt_devi, CE_WARN, "failed to initialize UFM " "subsystem"); return (DDI_FAILURE); } return (DDI_SUCCESS); } static int ufmtest_detach(dev_info_t *devi, ddi_detach_cmd_t cmd) { if (cmd != DDI_DETACH) return (DDI_FAILURE); if (devi != NULL) ddi_remove_minor_node(devi, NULL); ddi_ufm_fini(ufmt.ufmt_ufmh); if (ufmt.ufmt_nvl != NULL) { nvlist_free(ufmt.ufmt_nvl); ufmt.ufmt_nvl = NULL; } return (DDI_SUCCESS); } static int ufmtest_open(dev_t *devp, int flag, int otyp, cred_t *credp) { const int inv_flags = FWRITE | FEXCL | FNDELAY | FNONBLOCK; if (otyp != OTYP_CHR) return (EINVAL); if (flag & inv_flags) return (EINVAL); if (drv_priv(credp) != 0) return (EPERM); if (getzoneid() != GLOBAL_ZONEID) return (EPERM); return (0); } static int ufmtest_close(dev_t dev, int flag, int otyp, cred_t *credp) { return (0); } /* * By default, this pseudo test driver contains no hardcoded UFM data to * report. This ioctl takes a packed nvlist, representing a UFM report. * This data is then used as a source for firmware information by this * driver when it's UFM callback are called. * * External test programs can use this ioctl to effectively seed this * driver with arbitrary firmware information which it will report up to the * DDI UFM subsystem. */ static int ufmtest_do_setfw(intptr_t data, int mode) { int ret; uint_t model; ufmtest_ioc_setfw_t setfw; char *nvlbuf = NULL; #ifdef _MULTI_DATAMODEL ufmtest_ioc_setfw32_t setfw32; #endif model = ddi_model_convert_from(mode); switch (model) { #ifdef _MULTI_DATAMODEL case DDI_MODEL_ILP32: if (ddi_copyin((void *)data, &setfw32, sizeof (ufmtest_ioc_setfw32_t), mode) != 0) return (EFAULT); setfw.utsw_bufsz = setfw32.utsw_bufsz; setfw.utsw_buf = (caddr_t)(uintptr_t)setfw32.utsw_buf; break; #endif /* _MULTI_DATAMODEL */ case DDI_MODEL_NONE: default: if (ddi_copyin((void *)data, &setfw, sizeof (ufmtest_ioc_setfw_t), mode) != 0) return (EFAULT); } if (ufmt.ufmt_nvl != NULL) { nvlist_free(ufmt.ufmt_nvl); ufmt.ufmt_nvl = NULL; } nvlbuf = kmem_zalloc(setfw.utsw_bufsz, KM_NOSLEEP_LAZY); if (nvlbuf == NULL) return (ENOMEM); if (ddi_copyin(setfw.utsw_buf, nvlbuf, setfw.utsw_bufsz, mode) != 0) { kmem_free(nvlbuf, setfw.utsw_bufsz); return (EFAULT); } ret = nvlist_unpack(nvlbuf, setfw.utsw_bufsz, &ufmt.ufmt_nvl, KM_NOSLEEP); kmem_free(nvlbuf, setfw.utsw_bufsz); if (ret != 0) return (ret); /* * Notify the UFM subsystem that our firmware information has changed. */ ddi_ufm_update(ufmt.ufmt_ufmh); return (0); } static int ufmtest_do_toggle_fails(intptr_t data, int mode) { ufmtest_ioc_fails_t fails; if (ddi_copyin((void *)data, &fails, sizeof (ufmtest_ioc_fails_t), mode) != 0) return (EFAULT); if (fails.utfa_flags > UFMTEST_MAX_FAILFLAGS) return (EINVAL); ufmt.ufmt_failflags = fails.utfa_flags; return (0); } /* ARGSUSED */ static int ufmtest_ioctl(dev_t dev, int cmd, intptr_t data, int mode, cred_t *credp, int *rvalp) { int ret = 0; if (drv_priv(credp) != 0) return (EPERM); switch (cmd) { case UFMTEST_IOC_SET_FW: ret = ufmtest_do_setfw(data, mode); break; case UFMTEST_IOC_TOGGLE_FAILS: ret = ufmtest_do_toggle_fails(data, mode); break; case UFMTEST_IOC_DO_UPDATE: ddi_ufm_update(ufmt.ufmt_ufmh); break; default: return (ENOTTY); } return (ret); } static int ufmtest_nimages(ddi_ufm_handle_t *ufmh, void *arg, uint_t *nimgs) { nvlist_t **imgs; uint_t ni; if (ufmt.ufmt_failflags & UFMTEST_FAIL_NIMAGES || ufmt.ufmt_nvl == NULL) return (EINVAL); if (nvlist_lookup_nvlist_array(ufmt.ufmt_nvl, DDI_UFM_NV_IMAGES, &imgs, &ni) != 0) return (EINVAL); *nimgs = ni; return (0); } static int ufmtest_fill_image(ddi_ufm_handle_t *ufmh, void *arg, uint_t imgno, ddi_ufm_image_t *img) { nvlist_t **images, *misc, *miscdup = NULL, **slots; char *desc; uint_t ni, ns; if (ufmt.ufmt_failflags & UFMTEST_FAIL_FILLIMAGE || ufmt.ufmt_nvl == NULL || nvlist_lookup_nvlist_array(ufmt.ufmt_nvl, DDI_UFM_NV_IMAGES, &images, &ni) != 0) goto err; if (imgno >= ni) goto err; if (nvlist_lookup_string(images[imgno], DDI_UFM_NV_IMAGE_DESC, &desc) != 0 || nvlist_lookup_nvlist_array(images[imgno], DDI_UFM_NV_IMAGE_SLOTS, &slots, &ns) != 0) goto err; ddi_ufm_image_set_desc(img, desc); ddi_ufm_image_set_nslots(img, ns); if (nvlist_lookup_nvlist(images[imgno], DDI_UFM_NV_IMAGE_MISC, &misc) == 0) { if (nvlist_dup(misc, &miscdup, 0) != 0) return (ENOMEM); ddi_ufm_image_set_misc(img, miscdup); } return (0); err: return (EINVAL); } static int ufmtest_fill_slot(ddi_ufm_handle_t *ufmh, void *arg, uint_t imgno, uint_t slotno, ddi_ufm_slot_t *slot) { nvlist_t **images, *misc, *miscdup = NULL, **slots; char *vers; uint32_t attrs; uint_t ni, ns; if (ufmt.ufmt_failflags & UFMTEST_FAIL_FILLSLOT || ufmt.ufmt_nvl == NULL || nvlist_lookup_nvlist_array(ufmt.ufmt_nvl, DDI_UFM_NV_IMAGES, &images, &ni) != 0) goto err; if (imgno >= ni) goto err; if (nvlist_lookup_nvlist_array(images[imgno], DDI_UFM_NV_IMAGE_SLOTS, &slots, &ns) != 0) goto err; if (slotno >= ns) goto err; if (nvlist_lookup_uint32(slots[slotno], DDI_UFM_NV_SLOT_ATTR, &attrs) != 0) goto err; ddi_ufm_slot_set_attrs(slot, attrs); if (attrs & DDI_UFM_ATTR_EMPTY) return (0); if (nvlist_lookup_string(slots[slotno], DDI_UFM_NV_SLOT_VERSION, &vers) != 0) goto err; ddi_ufm_slot_set_version(slot, vers); if (nvlist_lookup_nvlist(slots[slotno], DDI_UFM_NV_SLOT_MISC, &misc) == 0) { if (nvlist_dup(misc, &miscdup, 0) != 0) return (ENOMEM); ddi_ufm_slot_set_misc(slot, miscdup); } return (0); err: return (EINVAL); } static int ufmtest_getcaps(ddi_ufm_handle_t *ufmh, void *arg, ddi_ufm_cap_t *caps) { if (ufmt.ufmt_failflags & UFMTEST_FAIL_GETCAPS) return (EINVAL); *caps = DDI_UFM_CAP_REPORT; return (0); }