/* * 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. */ /* * smbios(4D) driver * * This pseudo-driver makes available a snapshot of the system's SMBIOS image * that can be accessed using libsmbios. Clients may access a snapshot using * either read(2) or mmap(2). The driver returns the SMBIOS entry point data * followed by the SMBIOS structure table. The entry point has its 'staddr' * field set to indicate the byte offset of the structure table. The driver * uses the common SMBIOS API defined in to access the image. * * At present, the kernel takes a single snapshot of SMBIOS at boot time and * stores a handle for this snapshot in 'ksmbios'. To keep track of driver * opens, we simply compare-and-swap this handle into an 'smb_clones' array. * Future x86 systems may need to support dynamic SMBIOS updates: when that * happens the SMBIOS API can be extended to support reference counting and * handles for different snapshots can be stored in smb_clones[]. */ #include #include #include #include #include #include #include #include #include #include typedef struct smb_clone { smbios_hdl_t *c_hdl; size_t c_eplen; size_t c_stlen; } smb_clone_t; static dev_info_t *smb_devi; static smb_clone_t *smb_clones; static int smb_nclones; /*ARGSUSED*/ static int smb_open(dev_t *dp, int flag, int otyp, cred_t *cred) { minor_t c; if (ksmbios == NULL) return (ENXIO); /* * Locate and reserve a clone structure. We skip clone 0 as that is * the real minor number, and we assign a new minor to each clone. */ for (c = 1; c < smb_nclones; c++) { if (atomic_cas_ptr(&smb_clones[c].c_hdl, NULL, ksmbios) == NULL) break; } if (c >= smb_nclones) return (EAGAIN); smb_clones[c].c_eplen = P2ROUNDUP(sizeof (smbios_entry_t), 16); smb_clones[c].c_stlen = smbios_buflen(smb_clones[c].c_hdl); *dp = makedevice(getemajor(*dp), c); (void) ddi_prop_update_int(*dp, smb_devi, "size", smb_clones[c].c_eplen + smb_clones[c].c_stlen); return (0); } /*ARGSUSED*/ static int smb_close(dev_t dev, int flag, int otyp, cred_t *cred) { (void) ddi_prop_remove(dev, smb_devi, "size"); smb_clones[getminor(dev)].c_hdl = NULL; return (0); } /* * Common code to copy out the SMBIOS snapshot used for both read and mmap. * The caller must validate uio_offset for us since semantics differ there. * The copy is done in two stages, either of which can be skipped based on the * offset and length: first we copy the entry point, with 'staddr' recalculated * to indicate the offset of the data buffer, and second we copy the table. */ static int smb_uiomove(smb_clone_t *cp, uio_t *uio) { off_t off = uio->uio_offset; size_t len = uio->uio_resid; int err = 0; if (off + len > cp->c_eplen + cp->c_stlen) len = cp->c_eplen + cp->c_stlen - off; if (off < cp->c_eplen) { smbios_entry_t *ep = kmem_zalloc(cp->c_eplen, KM_SLEEP); size_t eprlen = MIN(len, cp->c_eplen - off); switch (smbios_info_smbios(cp->c_hdl, ep)) { case SMBIOS_ENTRY_POINT_21: ep->ep21.smbe_staddr = (uint32_t)cp->c_eplen; break; case SMBIOS_ENTRY_POINT_30: ep->ep30.smbe_staddr = (uint64_t)cp->c_eplen; break; } smbios_checksum(cp->c_hdl, ep); err = uiomove((char *)ep + off, eprlen, UIO_READ, uio); kmem_free(ep, cp->c_eplen); off += eprlen; len -= eprlen; } if (err == 0 && off >= cp->c_eplen) { char *buf = (char *)smbios_buf(cp->c_hdl); size_t bufoff = off - cp->c_eplen; err = uiomove(buf + bufoff, MIN(len, cp->c_stlen - bufoff), UIO_READ, uio); } return (err); } /*ARGSUSED*/ static int smb_read(dev_t dev, uio_t *uio, cred_t *cred) { smb_clone_t *cp = &smb_clones[getminor(dev)]; if (uio->uio_offset < 0 || uio->uio_offset >= cp->c_eplen + cp->c_stlen) return (0); return (smb_uiomove(cp, uio)); } /*ARGSUSED*/ static int smb_segmap(dev_t dev, off_t off, struct as *as, caddr_t *addrp, off_t len, uint_t prot, uint_t maxprot, uint_t flags, cred_t *cred) { smb_clone_t *cp = &smb_clones[getminor(dev)]; size_t alen = P2ROUNDUP(len, PAGESIZE); caddr_t addr = NULL; iovec_t iov; uio_t uio; int err; if (len <= 0 || (flags & MAP_FIXED)) return (EINVAL); if ((prot & PROT_WRITE) && (flags & MAP_SHARED)) return (EACCES); if (off < 0 || off + len < off || off + len > cp->c_eplen + cp->c_stlen) return (ENXIO); as_rangelock(as); map_addr(&addr, alen, 0, 1, 0); if (addr != NULL) err = as_map(as, addr, alen, segvn_create, zfod_argsp); else err = ENOMEM; as_rangeunlock(as); *addrp = addr; if (err != 0) return (err); iov.iov_base = addr; iov.iov_len = len; bzero(&uio, sizeof (uio_t)); uio.uio_iov = &iov; uio.uio_iovcnt = 1; uio.uio_offset = off; uio.uio_segflg = UIO_USERSPACE; uio.uio_extflg = UIO_COPY_DEFAULT; uio.uio_resid = len; if ((err = smb_uiomove(cp, &uio)) != 0) (void) as_unmap(as, addr, alen); return (err); } /*ARGSUSED*/ static int smb_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result) { switch (infocmd) { case DDI_INFO_DEVT2DEVINFO: *result = smb_devi; return (DDI_SUCCESS); case DDI_INFO_DEVT2INSTANCE: *result = 0; return (DDI_SUCCESS); } return (DDI_FAILURE); } static int smb_attach(dev_info_t *devi, ddi_attach_cmd_t cmd) { if (cmd != DDI_ATTACH) return (DDI_FAILURE); if (ddi_create_minor_node(devi, "smbios", S_IFCHR, 0, DDI_PSEUDO, 0) == DDI_FAILURE) { ddi_remove_minor_node(devi, NULL); return (DDI_FAILURE); } smb_devi = devi; return (DDI_SUCCESS); } static int smb_detach(dev_info_t *devi, ddi_detach_cmd_t cmd) { if (cmd != DDI_DETACH) return (DDI_FAILURE); ddi_remove_minor_node(devi, NULL); return (DDI_SUCCESS); } static struct cb_ops smb_cb_ops = { smb_open, /* open */ smb_close, /* close */ nodev, /* strategy */ nodev, /* print */ nodev, /* dump */ smb_read, /* read */ nodev, /* write */ nodev, /* ioctl */ nodev, /* devmap */ nodev, /* mmap */ smb_segmap, /* segmap */ nochpoll, /* poll */ ddi_prop_op, /* prop_op */ NULL, /* streamtab */ D_NEW | D_MP /* flags */ }; static struct dev_ops smb_ops = { DEVO_REV, /* rev */ 0, /* refcnt */ smb_info, /* info */ nulldev, /* identify */ nulldev, /* probe */ smb_attach, /* attach */ smb_detach, /* detach */ nodev, /* reset */ &smb_cb_ops, /* cb ops */ NULL, /* bus ops */ NULL, /* power */ ddi_quiesce_not_needed, /* quiesce */ }; static struct modldrv modldrv = { &mod_driverops, "System Management BIOS driver", &smb_ops, }; static struct modlinkage modlinkage = { MODREV_1, { (void *)&modldrv } }; int _init(void) { int err; if (smb_nclones <= 0) smb_nclones = maxusers; smb_clones = kmem_zalloc(sizeof (smb_clone_t) * smb_nclones, KM_SLEEP); if ((err = mod_install(&modlinkage)) != 0) kmem_free(smb_clones, sizeof (smb_clone_t) * smb_nclones); return (err); } int _fini(void) { int err; if ((err = mod_remove(&modlinkage)) == 0) kmem_free(smb_clones, sizeof (smb_clone_t) * smb_nclones); return (err); } int _info(struct modinfo *mip) { return (mod_info(&modlinkage, mip)); }