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 (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 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 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 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); 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 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 */ 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 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 } 338