xref: /illumos-gate/usr/src/uts/common/io/smbios.c (revision bbf21555)
184ab085aSmws /*
284ab085aSmws  * CDDL HEADER START
384ab085aSmws  *
484ab085aSmws  * The contents of this file are subject to the terms of the
560946fe0Smec  * Common Development and Distribution License (the "License").
660946fe0Smec  * You may not use this file except in compliance with the License.
784ab085aSmws  *
884ab085aSmws  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
984ab085aSmws  * or http://www.opensolaris.org/os/licensing.
1084ab085aSmws  * See the License for the specific language governing permissions
1184ab085aSmws  * and limitations under the License.
1284ab085aSmws  *
1384ab085aSmws  * When distributing Covered Code, include this CDDL HEADER in each
1484ab085aSmws  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
1584ab085aSmws  * If applicable, add the following below this CDDL HEADER, with the
1684ab085aSmws  * fields enclosed by brackets "[]" replaced with your own identifying
1784ab085aSmws  * information: Portions Copyright [yyyy] [name of copyright owner]
1884ab085aSmws  *
1984ab085aSmws  * CDDL HEADER END
2084ab085aSmws  */
2184ab085aSmws 
2284ab085aSmws /*
2360946fe0Smec  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
2484ab085aSmws  * Use is subject to license terms.
2584ab085aSmws  */
2684ab085aSmws 
2784ab085aSmws 
2884ab085aSmws /*
29*bbf21555SRichard Lowe  * smbios(4D) driver
3084ab085aSmws  *
3184ab085aSmws  * This pseudo-driver makes available a snapshot of the system's SMBIOS image
3284ab085aSmws  * that can be accessed using libsmbios.  Clients may access a snapshot using
3384ab085aSmws  * either read(2) or mmap(2).  The driver returns the SMBIOS entry point data
3484ab085aSmws  * followed by the SMBIOS structure table.  The entry point has its 'staddr'
3584ab085aSmws  * field set to indicate the byte offset of the structure table.  The driver
3684ab085aSmws  * uses the common SMBIOS API defined in <sys/smbios.h> to access the image.
3784ab085aSmws  *
3884ab085aSmws  * At present, the kernel takes a single snapshot of SMBIOS at boot time and
3984ab085aSmws  * stores a handle for this snapshot in 'ksmbios'.  To keep track of driver
4084ab085aSmws  * opens, we simply compare-and-swap this handle into an 'smb_clones' array.
4184ab085aSmws  * Future x86 systems may need to support dynamic SMBIOS updates: when that
4284ab085aSmws  * happens the SMBIOS API can be extended to support reference counting and
4384ab085aSmws  * handles for different snapshots can be stored in smb_clones[].
4484ab085aSmws  */
4584ab085aSmws 
4684ab085aSmws #include <sys/smbios.h>
4784ab085aSmws #include <sys/sysmacros.h>
4884ab085aSmws #include <sys/cmn_err.h>
4984ab085aSmws #include <sys/vmsystm.h>
5084ab085aSmws #include <vm/seg_vn.h>
5184ab085aSmws #include <sys/ddi.h>
5284ab085aSmws #include <sys/sunddi.h>
5384ab085aSmws #include <sys/modctl.h>
5484ab085aSmws #include <sys/conf.h>
5584ab085aSmws #include <sys/stat.h>
5684ab085aSmws 
5784ab085aSmws typedef struct smb_clone {
5884ab085aSmws 	smbios_hdl_t *c_hdl;
5984ab085aSmws 	size_t c_eplen;
6084ab085aSmws 	size_t c_stlen;
6184ab085aSmws } smb_clone_t;
6284ab085aSmws 
6384ab085aSmws static dev_info_t *smb_devi;
6484ab085aSmws static smb_clone_t *smb_clones;
6584ab085aSmws static int smb_nclones;
6684ab085aSmws 
6784ab085aSmws /*ARGSUSED*/
6884ab085aSmws static int
smb_open(dev_t * dp,int flag,int otyp,cred_t * cred)6984ab085aSmws smb_open(dev_t *dp, int flag, int otyp, cred_t *cred)
7084ab085aSmws {
7184ab085aSmws 	minor_t c;
7284ab085aSmws 
7384ab085aSmws 	if (ksmbios == NULL)
7484ab085aSmws 		return (ENXIO);
7584ab085aSmws 
7684ab085aSmws 	/*
7784ab085aSmws 	 * Locate and reserve a clone structure.  We skip clone 0 as that is
7884ab085aSmws 	 * the real minor number, and we assign a new minor to each clone.
7984ab085aSmws 	 */
8084ab085aSmws 	for (c = 1; c < smb_nclones; c++) {
8175d94465SJosef 'Jeff' Sipek 		if (atomic_cas_ptr(&smb_clones[c].c_hdl, NULL, ksmbios) == NULL)
8284ab085aSmws 			break;
8384ab085aSmws 	}
8484ab085aSmws 
8584ab085aSmws 	if (c >= smb_nclones)
8684ab085aSmws 		return (EAGAIN);
8784ab085aSmws 
8884ab085aSmws 	smb_clones[c].c_eplen = P2ROUNDUP(sizeof (smbios_entry_t), 16);
8984ab085aSmws 	smb_clones[c].c_stlen = smbios_buflen(smb_clones[c].c_hdl);
9084ab085aSmws 
9184ab085aSmws 	*dp = makedevice(getemajor(*dp), c);
9284ab085aSmws 
9384ab085aSmws 	(void) ddi_prop_update_int(*dp, smb_devi, "size",
9484ab085aSmws 	    smb_clones[c].c_eplen + smb_clones[c].c_stlen);
9584ab085aSmws 
9684ab085aSmws 	return (0);
9784ab085aSmws }
9884ab085aSmws 
9984ab085aSmws /*ARGSUSED*/
10084ab085aSmws static int
smb_close(dev_t dev,int flag,int otyp,cred_t * cred)10184ab085aSmws smb_close(dev_t dev, int flag, int otyp, cred_t *cred)
10284ab085aSmws {
10384ab085aSmws 	(void) ddi_prop_remove(dev, smb_devi, "size");
10484ab085aSmws 	smb_clones[getminor(dev)].c_hdl = NULL;
10584ab085aSmws 	return (0);
10684ab085aSmws }
10784ab085aSmws 
10884ab085aSmws /*
10984ab085aSmws  * Common code to copy out the SMBIOS snapshot used for both read and mmap.
11084ab085aSmws  * The caller must validate uio_offset for us since semantics differ there.
11184ab085aSmws  * The copy is done in two stages, either of which can be skipped based on the
11284ab085aSmws  * offset and length: first we copy the entry point, with 'staddr' recalculated
11384ab085aSmws  * to indicate the offset of the data buffer, and second we copy the table.
11484ab085aSmws  */
11584ab085aSmws static int
smb_uiomove(smb_clone_t * cp,uio_t * uio)11684ab085aSmws smb_uiomove(smb_clone_t *cp, uio_t *uio)
11784ab085aSmws {
11884ab085aSmws 	off_t off = uio->uio_offset;
11984ab085aSmws 	size_t len = uio->uio_resid;
12084ab085aSmws 	int err = 0;
12184ab085aSmws 
12284ab085aSmws 	if (off + len > cp->c_eplen + cp->c_stlen)
12384ab085aSmws 		len = cp->c_eplen + cp->c_stlen - off;
12484ab085aSmws 
12584ab085aSmws 	if (off < cp->c_eplen) {
12684ab085aSmws 		smbios_entry_t *ep = kmem_zalloc(cp->c_eplen, KM_SLEEP);
12784ab085aSmws 		size_t eprlen = MIN(len, cp->c_eplen - off);
12884ab085aSmws 
1291951a933SToomas Soome 		switch (smbios_info_smbios(cp->c_hdl, ep)) {
1301951a933SToomas Soome 		case SMBIOS_ENTRY_POINT_21:
1311951a933SToomas Soome 			ep->ep21.smbe_staddr = (uint32_t)cp->c_eplen;
1321951a933SToomas Soome 			break;
1331951a933SToomas Soome 		case SMBIOS_ENTRY_POINT_30:
1341951a933SToomas Soome 			ep->ep30.smbe_staddr = (uint64_t)cp->c_eplen;
1351951a933SToomas Soome 			break;
1361951a933SToomas Soome 		}
13784ab085aSmws 		smbios_checksum(cp->c_hdl, ep);
13884ab085aSmws 
13984ab085aSmws 		err = uiomove((char *)ep + off, eprlen, UIO_READ, uio);
14084ab085aSmws 		kmem_free(ep, cp->c_eplen);
14184ab085aSmws 
14284ab085aSmws 		off += eprlen;
14384ab085aSmws 		len -= eprlen;
14484ab085aSmws 	}
14584ab085aSmws 
14684ab085aSmws 	if (err == 0 && off >= cp->c_eplen) {
14784ab085aSmws 		char *buf = (char *)smbios_buf(cp->c_hdl);
14884ab085aSmws 		size_t bufoff = off - cp->c_eplen;
14984ab085aSmws 
15084ab085aSmws 		err = uiomove(buf + bufoff,
15184ab085aSmws 		    MIN(len, cp->c_stlen - bufoff), UIO_READ, uio);
15284ab085aSmws 	}
15384ab085aSmws 
15484ab085aSmws 	return (err);
15584ab085aSmws }
15684ab085aSmws 
15784ab085aSmws /*ARGSUSED*/
15884ab085aSmws static int
smb_read(dev_t dev,uio_t * uio,cred_t * cred)15984ab085aSmws smb_read(dev_t dev, uio_t *uio, cred_t *cred)
16084ab085aSmws {
16184ab085aSmws 	smb_clone_t *cp = &smb_clones[getminor(dev)];
16284ab085aSmws 
16384ab085aSmws 	if (uio->uio_offset < 0 ||
16484ab085aSmws 	    uio->uio_offset >= cp->c_eplen + cp->c_stlen)
16584ab085aSmws 		return (0);
16684ab085aSmws 
16784ab085aSmws 	return (smb_uiomove(cp, uio));
16884ab085aSmws }
16984ab085aSmws 
17084ab085aSmws /*ARGSUSED*/
17184ab085aSmws 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)17284ab085aSmws smb_segmap(dev_t dev, off_t off, struct as *as, caddr_t *addrp, off_t len,
17384ab085aSmws     uint_t prot, uint_t maxprot, uint_t flags, cred_t *cred)
17484ab085aSmws {
17584ab085aSmws 	smb_clone_t *cp = &smb_clones[getminor(dev)];
17684ab085aSmws 
17784ab085aSmws 	size_t alen = P2ROUNDUP(len, PAGESIZE);
17860946fe0Smec 	caddr_t addr = NULL;
17984ab085aSmws 
18084ab085aSmws 	iovec_t iov;
18184ab085aSmws 	uio_t uio;
18284ab085aSmws 	int err;
18384ab085aSmws 
18484ab085aSmws 	if (len <= 0 || (flags & MAP_FIXED))
18584ab085aSmws 		return (EINVAL);
18684ab085aSmws 
18784ab085aSmws 	if ((prot & PROT_WRITE) && (flags & MAP_SHARED))
18884ab085aSmws 		return (EACCES);
18984ab085aSmws 
19084ab085aSmws 	if (off < 0 || off + len < off || off + len > cp->c_eplen + cp->c_stlen)
19184ab085aSmws 		return (ENXIO);
19284ab085aSmws 
19384ab085aSmws 	as_rangelock(as);
19484ab085aSmws 	map_addr(&addr, alen, 0, 1, 0);
19584ab085aSmws 
19684ab085aSmws 	if (addr != NULL)
19784ab085aSmws 		err = as_map(as, addr, alen, segvn_create, zfod_argsp);
19884ab085aSmws 	else
19984ab085aSmws 		err = ENOMEM;
20084ab085aSmws 
20184ab085aSmws 	as_rangeunlock(as);
20284ab085aSmws 	*addrp = addr;
20384ab085aSmws 
20484ab085aSmws 	if (err != 0)
20584ab085aSmws 		return (err);
20684ab085aSmws 
20784ab085aSmws 	iov.iov_base = addr;
20884ab085aSmws 	iov.iov_len = len;
20984ab085aSmws 
21084ab085aSmws 	bzero(&uio, sizeof (uio_t));
21184ab085aSmws 	uio.uio_iov = &iov;
21284ab085aSmws 	uio.uio_iovcnt = 1;
21384ab085aSmws 	uio.uio_offset = off;
21484ab085aSmws 	uio.uio_segflg = UIO_USERSPACE;
21584ab085aSmws 	uio.uio_extflg = UIO_COPY_DEFAULT;
21684ab085aSmws 	uio.uio_resid = len;
21784ab085aSmws 
21884ab085aSmws 	if ((err = smb_uiomove(cp, &uio)) != 0)
21984ab085aSmws 		(void) as_unmap(as, addr, alen);
22084ab085aSmws 
22184ab085aSmws 	return (err);
22284ab085aSmws }
22384ab085aSmws 
22484ab085aSmws /*ARGSUSED*/
22584ab085aSmws static int
smb_info(dev_info_t * dip,ddi_info_cmd_t infocmd,void * arg,void ** result)22684ab085aSmws smb_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
22784ab085aSmws {
22884ab085aSmws 	switch (infocmd) {
22984ab085aSmws 	case DDI_INFO_DEVT2DEVINFO:
23084ab085aSmws 		*result = smb_devi;
23184ab085aSmws 		return (DDI_SUCCESS);
23284ab085aSmws 	case DDI_INFO_DEVT2INSTANCE:
23384ab085aSmws 		*result = 0;
23484ab085aSmws 		return (DDI_SUCCESS);
23584ab085aSmws 	}
23684ab085aSmws 	return (DDI_FAILURE);
23784ab085aSmws }
23884ab085aSmws 
23984ab085aSmws static int
smb_attach(dev_info_t * devi,ddi_attach_cmd_t cmd)24084ab085aSmws smb_attach(dev_info_t *devi, ddi_attach_cmd_t cmd)
24184ab085aSmws {
24284ab085aSmws 	if (cmd != DDI_ATTACH)
24384ab085aSmws 		return (DDI_FAILURE);
24484ab085aSmws 
24584ab085aSmws 	if (ddi_create_minor_node(devi, "smbios",
24684ab085aSmws 	    S_IFCHR, 0, DDI_PSEUDO, 0) == DDI_FAILURE) {
24784ab085aSmws 		ddi_remove_minor_node(devi, NULL);
24884ab085aSmws 		return (DDI_FAILURE);
24984ab085aSmws 	}
25084ab085aSmws 
25184ab085aSmws 	smb_devi = devi;
25284ab085aSmws 	return (DDI_SUCCESS);
25384ab085aSmws }
25484ab085aSmws 
25584ab085aSmws static int
smb_detach(dev_info_t * devi,ddi_detach_cmd_t cmd)25684ab085aSmws smb_detach(dev_info_t *devi, ddi_detach_cmd_t cmd)
25784ab085aSmws {
25884ab085aSmws 	if (cmd != DDI_DETACH)
25984ab085aSmws 		return (DDI_FAILURE);
26084ab085aSmws 
26184ab085aSmws 	ddi_remove_minor_node(devi, NULL);
26284ab085aSmws 	return (DDI_SUCCESS);
26384ab085aSmws }
26484ab085aSmws 
26584ab085aSmws static struct cb_ops smb_cb_ops = {
26684ab085aSmws 	smb_open,		/* open */
26784ab085aSmws 	smb_close,		/* close */
26884ab085aSmws 	nodev,			/* strategy */
26984ab085aSmws 	nodev,			/* print */
27084ab085aSmws 	nodev,			/* dump */
27184ab085aSmws 	smb_read,		/* read */
27284ab085aSmws 	nodev,			/* write */
27384ab085aSmws 	nodev,			/* ioctl */
27484ab085aSmws 	nodev,			/* devmap */
27584ab085aSmws 	nodev,			/* mmap */
27684ab085aSmws 	smb_segmap,		/* segmap */
27784ab085aSmws 	nochpoll,		/* poll */
27884ab085aSmws 	ddi_prop_op,		/* prop_op */
27984ab085aSmws 	NULL,			/* streamtab */
28084ab085aSmws 	D_NEW | D_MP		/* flags */
28184ab085aSmws };
28284ab085aSmws 
28384ab085aSmws static struct dev_ops smb_ops = {
28484ab085aSmws 	DEVO_REV,		/* rev */
28584ab085aSmws 	0,			/* refcnt */
28684ab085aSmws 	smb_info,		/* info */
28784ab085aSmws 	nulldev,		/* identify */
28884ab085aSmws 	nulldev,		/* probe */
28984ab085aSmws 	smb_attach,		/* attach */
29084ab085aSmws 	smb_detach,		/* detach */
29184ab085aSmws 	nodev,			/* reset */
29284ab085aSmws 	&smb_cb_ops,		/* cb ops */
29319397407SSherry Moore 	NULL,			/* bus ops */
29419397407SSherry Moore 	NULL,			/* power */
29519397407SSherry Moore 	ddi_quiesce_not_needed,		/* quiesce */
29684ab085aSmws };
29784ab085aSmws 
29884ab085aSmws static struct modldrv modldrv = {
29984ab085aSmws 	&mod_driverops, "System Management BIOS driver", &smb_ops,
30084ab085aSmws };
30184ab085aSmws 
30284ab085aSmws static struct modlinkage modlinkage = {
30384ab085aSmws 	MODREV_1, { (void *)&modldrv }
30484ab085aSmws };
30584ab085aSmws 
30684ab085aSmws int
_init(void)30784ab085aSmws _init(void)
30884ab085aSmws {
30984ab085aSmws 	int err;
31084ab085aSmws 
31184ab085aSmws 	if (smb_nclones <= 0)
31284ab085aSmws 		smb_nclones = maxusers;
31384ab085aSmws 
31484ab085aSmws 	smb_clones = kmem_zalloc(sizeof (smb_clone_t) * smb_nclones, KM_SLEEP);
31584ab085aSmws 
31684ab085aSmws 	if ((err = mod_install(&modlinkage)) != 0)
31784ab085aSmws 		kmem_free(smb_clones, sizeof (smb_clone_t) * smb_nclones);
31884ab085aSmws 
31984ab085aSmws 	return (err);
32084ab085aSmws }
32184ab085aSmws 
32284ab085aSmws int
_fini(void)32384ab085aSmws _fini(void)
32484ab085aSmws {
32584ab085aSmws 	int err;
32684ab085aSmws 
32784ab085aSmws 	if ((err = mod_remove(&modlinkage)) == 0)
32884ab085aSmws 		kmem_free(smb_clones, sizeof (smb_clone_t) * smb_nclones);
32984ab085aSmws 
33084ab085aSmws 	return (err);
33184ab085aSmws }
33284ab085aSmws 
33384ab085aSmws int
_info(struct modinfo * mip)33484ab085aSmws _info(struct modinfo *mip)
33584ab085aSmws {
33684ab085aSmws 	return (mod_info(&modlinkage, mip));
33784ab085aSmws }
338