mws | 84ab085 | 2005-08-27 15:17:06 -0700 | [diff] [blame] | 1 | /* |
| 2 | * CDDL HEADER START |
| 3 | * |
| 4 | * The contents of this file are subject to the terms of the |
mec | 60946fe | 2008-02-19 13:14:17 -0800 | [diff] [blame] | 5 | * Common Development and Distribution License (the "License"). |
| 6 | * You may not use this file except in compliance with the License. |
mws | 84ab085 | 2005-08-27 15:17:06 -0700 | [diff] [blame] | 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 | /* |
mec | 60946fe | 2008-02-19 13:14:17 -0800 | [diff] [blame] | 23 | * Copyright 2008 Sun Microsystems, Inc. All rights reserved. |
mws | 84ab085 | 2005-08-27 15:17:06 -0700 | [diff] [blame] | 24 | * Use is subject to license terms. |
| 25 | */ |
| 26 | |
mws | 84ab085 | 2005-08-27 15:17:06 -0700 | [diff] [blame] | 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++) { |
Josef 'Jeff' Sipek | 75d9446 | 2014-08-08 10:27:20 -0400 | [diff] [blame] | 81 | if (atomic_cas_ptr(&smb_clones[c].c_hdl, NULL, ksmbios) == NULL) |
mws | 84ab085 | 2005-08-27 15:17:06 -0700 | [diff] [blame] | 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 | |
Toomas Soome | 1951a93 | 2016-12-10 01:03:02 +0200 | [diff] [blame] | 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 | } |
mws | 84ab085 | 2005-08-27 15:17:06 -0700 | [diff] [blame] | 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 |
| 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 |
| 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); |
mec | 60946fe | 2008-02-19 13:14:17 -0800 | [diff] [blame] | 178 | caddr_t addr = NULL; |
mws | 84ab085 | 2005-08-27 15:17:06 -0700 | [diff] [blame] | 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 |
| 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 |
| 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 |
| 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 */ |
Sherry Moore | 1939740 | 2008-09-22 16:30:26 -0700 | [diff] [blame] | 293 | NULL, /* bus ops */ |
| 294 | NULL, /* power */ |
| 295 | ddi_quiesce_not_needed, /* quiesce */ |
mws | 84ab085 | 2005-08-27 15:17:06 -0700 | [diff] [blame] | 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 |
| 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 |
| 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 |
| 334 | _info(struct modinfo *mip) |
| 335 | { |
| 336 | return (mod_info(&modlinkage, mip)); |
| 337 | } |