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