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                 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 }