xref: /illumos-gate/usr/src/uts/common/io/smbios.c (revision 92ae099e204069d2fec11f099863387b5317d849)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 
28 /*
29  * smbios(4D) driver
30  *
31  * This pseudo-driver makes available a snapshot of the system's SMBIOS image
32  * that can be accessed using libsmbios.  Clients may access a snapshot using
33  * either read(2) or mmap(2).  The driver returns the SMBIOS entry point data
34  * followed by the SMBIOS structure table.  The entry point has its 'staddr'
35  * field set to indicate the byte offset of the structure table.  The driver
36  * uses the common SMBIOS API defined in <sys/smbios.h> to access the image.
37  *
38  * At present, the kernel takes a single snapshot of SMBIOS at boot time and
39  * stores a handle for this snapshot in 'ksmbios'.  To keep track of driver
40  * opens, we simply compare-and-swap this handle into an 'smb_clones' array.
41  * Future x86 systems may need to support dynamic SMBIOS updates: when that
42  * happens the SMBIOS API can be extended to support reference counting and
43  * handles for different snapshots can be stored in smb_clones[].
44  */
45 
46 #include <sys/smbios.h>
47 #include <sys/sysmacros.h>
48 #include <sys/cmn_err.h>
49 #include <sys/vmsystm.h>
50 #include <vm/seg_vn.h>
51 #include <sys/ddi.h>
52 #include <sys/sunddi.h>
53 #include <sys/modctl.h>
54 #include <sys/conf.h>
55 #include <sys/stat.h>
56 
57 typedef struct smb_clone {
58 	smbios_hdl_t *c_hdl;
59 	size_t c_eplen;
60 	size_t c_stlen;
61 } smb_clone_t;
62 
63 static dev_info_t *smb_devi;
64 static smb_clone_t *smb_clones;
65 static int smb_nclones;
66 
67 /*ARGSUSED*/
68 static int
smb_open(dev_t * dp,int flag,int otyp,cred_t * cred)69 smb_open(dev_t *dp, int flag, int otyp, cred_t *cred)
70 {
71 	minor_t c;
72 
73 	if (ksmbios == NULL)
74 		return (ENXIO);
75 
76 	/*
77 	 * Locate and reserve a clone structure.  We skip clone 0 as that is
78 	 * the real minor number, and we assign a new minor to each clone.
79 	 */
80 	for (c = 1; c < smb_nclones; c++) {
81 		if (atomic_cas_ptr(&smb_clones[c].c_hdl, NULL, ksmbios) == NULL)
82 			break;
83 	}
84 
85 	if (c >= smb_nclones)
86 		return (EAGAIN);
87 
88 	smb_clones[c].c_eplen = P2ROUNDUP(sizeof (smbios_entry_t), 16);
89 	smb_clones[c].c_stlen = smbios_buflen(smb_clones[c].c_hdl);
90 
91 	*dp = makedevice(getemajor(*dp), c);
92 
93 	(void) ddi_prop_update_int(*dp, smb_devi, "size",
94 	    smb_clones[c].c_eplen + smb_clones[c].c_stlen);
95 
96 	return (0);
97 }
98 
99 /*ARGSUSED*/
100 static int
smb_close(dev_t dev,int flag,int otyp,cred_t * cred)101 smb_close(dev_t dev, int flag, int otyp, cred_t *cred)
102 {
103 	(void) ddi_prop_remove(dev, smb_devi, "size");
104 	smb_clones[getminor(dev)].c_hdl = NULL;
105 	return (0);
106 }
107 
108 /*
109  * Common code to copy out the SMBIOS snapshot used for both read and mmap.
110  * The caller must validate uio_offset for us since semantics differ there.
111  * The copy is done in two stages, either of which can be skipped based on the
112  * offset and length: first we copy the entry point, with 'staddr' recalculated
113  * to indicate the offset of the data buffer, and second we copy the table.
114  */
115 static int
smb_uiomove(smb_clone_t * cp,uio_t * uio)116 smb_uiomove(smb_clone_t *cp, uio_t *uio)
117 {
118 	off_t off = uio->uio_offset;
119 	size_t len = uio->uio_resid;
120 	int err = 0;
121 
122 	if (off + len > cp->c_eplen + cp->c_stlen)
123 		len = cp->c_eplen + cp->c_stlen - off;
124 
125 	if (off < cp->c_eplen) {
126 		smbios_entry_t *ep = kmem_zalloc(cp->c_eplen, KM_SLEEP);
127 		size_t eprlen = MIN(len, cp->c_eplen - off);
128 
129 		switch (smbios_info_smbios(cp->c_hdl, ep)) {
130 		case SMBIOS_ENTRY_POINT_21:
131 			ep->ep21.smbe_staddr = (uint32_t)cp->c_eplen;
132 			break;
133 		case SMBIOS_ENTRY_POINT_30:
134 			ep->ep30.smbe_staddr = (uint64_t)cp->c_eplen;
135 			break;
136 		}
137 		smbios_checksum(cp->c_hdl, ep);
138 
139 		err = uiomove((char *)ep + off, eprlen, UIO_READ, uio);
140 		kmem_free(ep, cp->c_eplen);
141 
142 		off += eprlen;
143 		len -= eprlen;
144 	}
145 
146 	if (err == 0 && off >= cp->c_eplen) {
147 		char *buf = (char *)smbios_buf(cp->c_hdl);
148 		size_t bufoff = off - cp->c_eplen;
149 
150 		err = uiomove(buf + bufoff,
151 		    MIN(len, cp->c_stlen - bufoff), UIO_READ, uio);
152 	}
153 
154 	return (err);
155 }
156 
157 /*ARGSUSED*/
158 static int
smb_read(dev_t dev,uio_t * uio,cred_t * cred)159 smb_read(dev_t dev, uio_t *uio, cred_t *cred)
160 {
161 	smb_clone_t *cp = &smb_clones[getminor(dev)];
162 
163 	if (uio->uio_offset < 0 ||
164 	    uio->uio_offset >= cp->c_eplen + cp->c_stlen)
165 		return (0);
166 
167 	return (smb_uiomove(cp, uio));
168 }
169 
170 /*ARGSUSED*/
171 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)172 smb_segmap(dev_t dev, off_t off, struct as *as, caddr_t *addrp, off_t len,
173     uint_t prot, uint_t maxprot, uint_t flags, cred_t *cred)
174 {
175 	smb_clone_t *cp = &smb_clones[getminor(dev)];
176 
177 	size_t alen = P2ROUNDUP(len, PAGESIZE);
178 	caddr_t addr = NULL;
179 
180 	iovec_t iov;
181 	uio_t uio;
182 	int err;
183 
184 	if (len <= 0 || (flags & MAP_FIXED))
185 		return (EINVAL);
186 
187 	if ((prot & PROT_WRITE) && (flags & MAP_SHARED))
188 		return (EACCES);
189 
190 	if (off < 0 || sum_overflows_off(off, len) ||
191 	    off + len > cp->c_eplen + cp->c_stlen) {
192 		return (ENXIO);
193 	}
194 
195 	as_rangelock(as);
196 	map_addr(&addr, alen, 0, 1, 0);
197 
198 	if (addr != NULL)
199 		err = as_map(as, addr, alen, segvn_create, zfod_argsp);
200 	else
201 		err = ENOMEM;
202 
203 	as_rangeunlock(as);
204 	*addrp = addr;
205 
206 	if (err != 0)
207 		return (err);
208 
209 	iov.iov_base = addr;
210 	iov.iov_len = len;
211 
212 	bzero(&uio, sizeof (uio_t));
213 	uio.uio_iov = &iov;
214 	uio.uio_iovcnt = 1;
215 	uio.uio_offset = off;
216 	uio.uio_segflg = UIO_USERSPACE;
217 	uio.uio_extflg = UIO_COPY_DEFAULT;
218 	uio.uio_resid = len;
219 
220 	if ((err = smb_uiomove(cp, &uio)) != 0)
221 		(void) as_unmap(as, addr, alen);
222 
223 	return (err);
224 }
225 
226 /*ARGSUSED*/
227 static int
smb_info(dev_info_t * dip,ddi_info_cmd_t infocmd,void * arg,void ** result)228 smb_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
229 {
230 	switch (infocmd) {
231 	case DDI_INFO_DEVT2DEVINFO:
232 		*result = smb_devi;
233 		return (DDI_SUCCESS);
234 	case DDI_INFO_DEVT2INSTANCE:
235 		*result = 0;
236 		return (DDI_SUCCESS);
237 	}
238 	return (DDI_FAILURE);
239 }
240 
241 static int
smb_attach(dev_info_t * devi,ddi_attach_cmd_t cmd)242 smb_attach(dev_info_t *devi, ddi_attach_cmd_t cmd)
243 {
244 	if (cmd != DDI_ATTACH)
245 		return (DDI_FAILURE);
246 
247 	if (ddi_create_minor_node(devi, "smbios",
248 	    S_IFCHR, 0, DDI_PSEUDO, 0) == DDI_FAILURE) {
249 		ddi_remove_minor_node(devi, NULL);
250 		return (DDI_FAILURE);
251 	}
252 
253 	smb_devi = devi;
254 	return (DDI_SUCCESS);
255 }
256 
257 static int
smb_detach(dev_info_t * devi,ddi_detach_cmd_t cmd)258 smb_detach(dev_info_t *devi, ddi_detach_cmd_t cmd)
259 {
260 	if (cmd != DDI_DETACH)
261 		return (DDI_FAILURE);
262 
263 	ddi_remove_minor_node(devi, NULL);
264 	return (DDI_SUCCESS);
265 }
266 
267 static struct cb_ops smb_cb_ops = {
268 	smb_open,		/* open */
269 	smb_close,		/* close */
270 	nodev,			/* strategy */
271 	nodev,			/* print */
272 	nodev,			/* dump */
273 	smb_read,		/* read */
274 	nodev,			/* write */
275 	nodev,			/* ioctl */
276 	nodev,			/* devmap */
277 	nodev,			/* mmap */
278 	smb_segmap,		/* segmap */
279 	nochpoll,		/* poll */
280 	ddi_prop_op,		/* prop_op */
281 	NULL,			/* streamtab */
282 	D_NEW | D_MP		/* flags */
283 };
284 
285 static struct dev_ops smb_ops = {
286 	DEVO_REV,		/* rev */
287 	0,			/* refcnt */
288 	smb_info,		/* info */
289 	nulldev,		/* identify */
290 	nulldev,		/* probe */
291 	smb_attach,		/* attach */
292 	smb_detach,		/* detach */
293 	nodev,			/* reset */
294 	&smb_cb_ops,		/* cb ops */
295 	NULL,			/* bus ops */
296 	NULL,			/* power */
297 	ddi_quiesce_not_needed,		/* quiesce */
298 };
299 
300 static struct modldrv modldrv = {
301 	&mod_driverops, "System Management BIOS driver", &smb_ops,
302 };
303 
304 static struct modlinkage modlinkage = {
305 	MODREV_1, { (void *)&modldrv }
306 };
307 
308 int
_init(void)309 _init(void)
310 {
311 	int err;
312 
313 	if (smb_nclones <= 0)
314 		smb_nclones = maxusers;
315 
316 	smb_clones = kmem_zalloc(sizeof (smb_clone_t) * smb_nclones, KM_SLEEP);
317 
318 	if ((err = mod_install(&modlinkage)) != 0)
319 		kmem_free(smb_clones, sizeof (smb_clone_t) * smb_nclones);
320 
321 	return (err);
322 }
323 
324 int
_fini(void)325 _fini(void)
326 {
327 	int err;
328 
329 	if ((err = mod_remove(&modlinkage)) == 0)
330 		kmem_free(smb_clones, sizeof (smb_clone_t) * smb_nclones);
331 
332 	return (err);
333 }
334 
335 int
_info(struct modinfo * mip)336 _info(struct modinfo *mip)
337 {
338 	return (mod_info(&modlinkage, mip));
339 }
340