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