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 || off + len < off || off + len > cp->c_eplen + cp->c_stlen)
191 return (ENXIO);
192
193 as_rangelock(as);
194 map_addr(&addr, alen, 0, 1, 0);
195
196 if (addr != NULL)
197 err = as_map(as, addr, alen, segvn_create, zfod_argsp);
198 else
199 err = ENOMEM;
200
201 as_rangeunlock(as);
202 *addrp = addr;
203
204 if (err != 0)
205 return (err);
206
207 iov.iov_base = addr;
208 iov.iov_len = len;
209
210 bzero(&uio, sizeof (uio_t));
211 uio.uio_iov = &iov;
212 uio.uio_iovcnt = 1;
213 uio.uio_offset = off;
214 uio.uio_segflg = UIO_USERSPACE;
215 uio.uio_extflg = UIO_COPY_DEFAULT;
216 uio.uio_resid = len;
217
218 if ((err = smb_uiomove(cp, &uio)) != 0)
219 (void) as_unmap(as, addr, alen);
220
221 return (err);
222 }
223
224 /*ARGSUSED*/
225 static int
smb_info(dev_info_t * dip,ddi_info_cmd_t infocmd,void * arg,void ** result)226 smb_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
227 {
228 switch (infocmd) {
229 case DDI_INFO_DEVT2DEVINFO:
230 *result = smb_devi;
231 return (DDI_SUCCESS);
232 case DDI_INFO_DEVT2INSTANCE:
233 *result = 0;
234 return (DDI_SUCCESS);
235 }
236 return (DDI_FAILURE);
237 }
238
239 static int
smb_attach(dev_info_t * devi,ddi_attach_cmd_t cmd)240 smb_attach(dev_info_t *devi, ddi_attach_cmd_t cmd)
241 {
242 if (cmd != DDI_ATTACH)
243 return (DDI_FAILURE);
244
245 if (ddi_create_minor_node(devi, "smbios",
246 S_IFCHR, 0, DDI_PSEUDO, 0) == DDI_FAILURE) {
247 ddi_remove_minor_node(devi, NULL);
248 return (DDI_FAILURE);
249 }
250
251 smb_devi = devi;
252 return (DDI_SUCCESS);
253 }
254
255 static int
smb_detach(dev_info_t * devi,ddi_detach_cmd_t cmd)256 smb_detach(dev_info_t *devi, ddi_detach_cmd_t cmd)
257 {
258 if (cmd != DDI_DETACH)
259 return (DDI_FAILURE);
260
261 ddi_remove_minor_node(devi, NULL);
262 return (DDI_SUCCESS);
263 }
264
265 static struct cb_ops smb_cb_ops = {
266 smb_open, /* open */
267 smb_close, /* close */
268 nodev, /* strategy */
269 nodev, /* print */
270 nodev, /* dump */
271 smb_read, /* read */
272 nodev, /* write */
273 nodev, /* ioctl */
274 nodev, /* devmap */
275 nodev, /* mmap */
276 smb_segmap, /* segmap */
277 nochpoll, /* poll */
278 ddi_prop_op, /* prop_op */
279 NULL, /* streamtab */
280 D_NEW | D_MP /* flags */
281 };
282
283 static struct dev_ops smb_ops = {
284 DEVO_REV, /* rev */
285 0, /* refcnt */
286 smb_info, /* info */
287 nulldev, /* identify */
288 nulldev, /* probe */
289 smb_attach, /* attach */
290 smb_detach, /* detach */
291 nodev, /* reset */
292 &smb_cb_ops, /* cb ops */
293 NULL, /* bus ops */
294 NULL, /* power */
295 ddi_quiesce_not_needed, /* quiesce */
296 };
297
298 static struct modldrv modldrv = {
299 &mod_driverops, "System Management BIOS driver", &smb_ops,
300 };
301
302 static struct modlinkage modlinkage = {
303 MODREV_1, { (void *)&modldrv }
304 };
305
306 int
_init(void)307 _init(void)
308 {
309 int err;
310
311 if (smb_nclones <= 0)
312 smb_nclones = maxusers;
313
314 smb_clones = kmem_zalloc(sizeof (smb_clone_t) * smb_nclones, KM_SLEEP);
315
316 if ((err = mod_install(&modlinkage)) != 0)
317 kmem_free(smb_clones, sizeof (smb_clone_t) * smb_nclones);
318
319 return (err);
320 }
321
322 int
_fini(void)323 _fini(void)
324 {
325 int err;
326
327 if ((err = mod_remove(&modlinkage)) == 0)
328 kmem_free(smb_clones, sizeof (smb_clone_t) * smb_nclones);
329
330 return (err);
331 }
332
333 int
_info(struct modinfo * mip)334 _info(struct modinfo *mip)
335 {
336 return (mod_info(&modlinkage, mip));
337 }
338