xref: /illumos-gate/usr/src/uts/common/io/smbios.c (revision 89b2a9fbeabf42fa54594df0e5927bcc50a07cc9)
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(7D) 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
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 (casptr(&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
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
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  		smbios_info_smbios(cp->c_hdl, ep);
130  		ep->smbe_staddr = (uint32_t)cp->c_eplen;
131  		smbios_checksum(cp->c_hdl, ep);
132  
133  		err = uiomove((char *)ep + off, eprlen, UIO_READ, uio);
134  		kmem_free(ep, cp->c_eplen);
135  
136  		off += eprlen;
137  		len -= eprlen;
138  	}
139  
140  	if (err == 0 && off >= cp->c_eplen) {
141  		char *buf = (char *)smbios_buf(cp->c_hdl);
142  		size_t bufoff = off - cp->c_eplen;
143  
144  		err = uiomove(buf + bufoff,
145  		    MIN(len, cp->c_stlen - bufoff), UIO_READ, uio);
146  	}
147  
148  	return (err);
149  }
150  
151  /*ARGSUSED*/
152  static int
153  smb_read(dev_t dev, uio_t *uio, cred_t *cred)
154  {
155  	smb_clone_t *cp = &smb_clones[getminor(dev)];
156  
157  	if (uio->uio_offset < 0 ||
158  	    uio->uio_offset >= cp->c_eplen + cp->c_stlen)
159  		return (0);
160  
161  	return (smb_uiomove(cp, uio));
162  }
163  
164  /*ARGSUSED*/
165  static int
166  smb_segmap(dev_t dev, off_t off, struct as *as, caddr_t *addrp, off_t len,
167      uint_t prot, uint_t maxprot, uint_t flags, cred_t *cred)
168  {
169  	smb_clone_t *cp = &smb_clones[getminor(dev)];
170  
171  	size_t alen = P2ROUNDUP(len, PAGESIZE);
172  	caddr_t addr = NULL;
173  
174  	iovec_t iov;
175  	uio_t uio;
176  	int err;
177  
178  	if (len <= 0 || (flags & MAP_FIXED))
179  		return (EINVAL);
180  
181  	if ((prot & PROT_WRITE) && (flags & MAP_SHARED))
182  		return (EACCES);
183  
184  	if (off < 0 || off + len < off || off + len > cp->c_eplen + cp->c_stlen)
185  		return (ENXIO);
186  
187  	as_rangelock(as);
188  	map_addr(&addr, alen, 0, 1, 0);
189  
190  	if (addr != NULL)
191  		err = as_map(as, addr, alen, segvn_create, zfod_argsp);
192  	else
193  		err = ENOMEM;
194  
195  	as_rangeunlock(as);
196  	*addrp = addr;
197  
198  	if (err != 0)
199  		return (err);
200  
201  	iov.iov_base = addr;
202  	iov.iov_len = len;
203  
204  	bzero(&uio, sizeof (uio_t));
205  	uio.uio_iov = &iov;
206  	uio.uio_iovcnt = 1;
207  	uio.uio_offset = off;
208  	uio.uio_segflg = UIO_USERSPACE;
209  	uio.uio_extflg = UIO_COPY_DEFAULT;
210  	uio.uio_resid = len;
211  
212  	if ((err = smb_uiomove(cp, &uio)) != 0)
213  		(void) as_unmap(as, addr, alen);
214  
215  	return (err);
216  }
217  
218  /*ARGSUSED*/
219  static int
220  smb_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
221  {
222  	switch (infocmd) {
223  	case DDI_INFO_DEVT2DEVINFO:
224  		*result = smb_devi;
225  		return (DDI_SUCCESS);
226  	case DDI_INFO_DEVT2INSTANCE:
227  		*result = 0;
228  		return (DDI_SUCCESS);
229  	}
230  	return (DDI_FAILURE);
231  }
232  
233  static int
234  smb_attach(dev_info_t *devi, ddi_attach_cmd_t cmd)
235  {
236  	if (cmd != DDI_ATTACH)
237  		return (DDI_FAILURE);
238  
239  	if (ddi_create_minor_node(devi, "smbios",
240  	    S_IFCHR, 0, DDI_PSEUDO, 0) == DDI_FAILURE) {
241  		ddi_remove_minor_node(devi, NULL);
242  		return (DDI_FAILURE);
243  	}
244  
245  	smb_devi = devi;
246  	return (DDI_SUCCESS);
247  }
248  
249  static int
250  smb_detach(dev_info_t *devi, ddi_detach_cmd_t cmd)
251  {
252  	if (cmd != DDI_DETACH)
253  		return (DDI_FAILURE);
254  
255  	ddi_remove_minor_node(devi, NULL);
256  	return (DDI_SUCCESS);
257  }
258  
259  static struct cb_ops smb_cb_ops = {
260  	smb_open,		/* open */
261  	smb_close,		/* close */
262  	nodev,			/* strategy */
263  	nodev,			/* print */
264  	nodev,			/* dump */
265  	smb_read,		/* read */
266  	nodev,			/* write */
267  	nodev,			/* ioctl */
268  	nodev,			/* devmap */
269  	nodev,			/* mmap */
270  	smb_segmap,		/* segmap */
271  	nochpoll,		/* poll */
272  	ddi_prop_op,		/* prop_op */
273  	NULL,			/* streamtab */
274  	D_NEW | D_MP		/* flags */
275  };
276  
277  static struct dev_ops smb_ops = {
278  	DEVO_REV,		/* rev */
279  	0,			/* refcnt */
280  	smb_info,		/* info */
281  	nulldev,		/* identify */
282  	nulldev,		/* probe */
283  	smb_attach,		/* attach */
284  	smb_detach,		/* detach */
285  	nodev,			/* reset */
286  	&smb_cb_ops,		/* cb ops */
287  	NULL,			/* bus ops */
288  	NULL,			/* power */
289  	ddi_quiesce_not_needed,		/* quiesce */
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