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, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 23 /* 24 * Copyright 2006 Sun Microsystems, Inc. All rights reserved. 25 * Use is subject to license terms. 26 * Copyright (c) 2018, Joyent, Inc. 27 */ 28 29 #include <sys/types.h> 30 #include <sys/smbios_impl.h> 31 #include <sys/sysmacros.h> 32 #include <sys/stat.h> 33 #include <sys/mman.h> 34 35 #include <alloca.h> 36 #include <limits.h> 37 #include <unistd.h> 38 #include <strings.h> 39 #include <stdlib.h> 40 #include <errno.h> 41 #include <fcntl.h> 42 #include <libdevinfo.h> 43 44 #pragma init(smb_init) 45 static void 46 smb_init(void) 47 { 48 _smb_debug = getenv("SMB_DEBUG") != NULL; 49 } 50 51 static smbios_hdl_t * 52 smb_fileopen(int fd, int version, int flags, int *errp) 53 { 54 smbios_entry_t *ep = alloca(SMB_ENTRY_MAXLEN); 55 smbios_entry_point_t ep_type; 56 smbios_hdl_t *shp = NULL; 57 uint32_t smbe_stlen; 58 off64_t smbe_staddr; 59 ssize_t n, elen; 60 void *stbuf; 61 62 if ((n = pread64(fd, ep, sizeof (*ep), 0)) != sizeof (*ep)) 63 return (smb_open_error(shp, errp, n < 0 ? errno : ESMB_NOHDR)); 64 65 if (strncmp(ep->ep21.smbe_eanchor, SMB_ENTRY_EANCHOR, 66 SMB_ENTRY_EANCHORLEN) == 0) { 67 ep_type = SMBIOS_ENTRY_POINT_21; 68 elen = MIN(ep->ep21.smbe_elen, SMB_ENTRY_MAXLEN); 69 } else if (strncmp(ep->ep30.smbe_eanchor, SMB3_ENTRY_EANCHOR, 70 SMB3_ENTRY_EANCHORLEN) == 0) { 71 ep_type = SMBIOS_ENTRY_POINT_30; 72 elen = MIN(ep->ep30.smbe_elen, SMB_ENTRY_MAXLEN); 73 } else { 74 return (smb_open_error(shp, errp, ESMB_HEADER)); 75 } 76 77 if ((n = pread64(fd, ep, elen, 0)) != elen) 78 return (smb_open_error(shp, errp, n < 0 ? errno : ESMB_NOHDR)); 79 80 if (ep_type == SMBIOS_ENTRY_POINT_21) { 81 smbe_stlen = ep->ep21.smbe_stlen; 82 smbe_staddr = (off64_t)ep->ep21.smbe_staddr; 83 } else { 84 smbe_stlen = ep->ep30.smbe_stlen; 85 smbe_staddr = (off64_t)ep->ep30.smbe_staddr; 86 } 87 stbuf = smb_alloc(smbe_stlen); 88 89 if (stbuf == NULL) 90 return (smb_open_error(shp, errp, ESMB_NOMEM)); 91 92 if ((n = pread64(fd, stbuf, smbe_stlen, smbe_staddr)) != smbe_stlen) { 93 smb_free(stbuf, smbe_stlen); 94 return (smb_open_error(shp, errp, n < 0 ? errno : ESMB_NOSTAB)); 95 } 96 97 shp = smbios_bufopen(ep, stbuf, smbe_stlen, version, flags, errp); 98 99 if (shp != NULL) 100 shp->sh_flags |= SMB_FL_BUFALLOC; 101 else 102 smb_free(stbuf, smbe_stlen); 103 104 return (shp); 105 } 106 107 static smbios_hdl_t * 108 smb_biosopen(int fd, int version, int flags, int *errp) 109 { 110 smbios_entry_t *ep = alloca(SMB_ENTRY_MAXLEN); 111 smbios_entry_point_t ep_type; 112 smbios_hdl_t *shp = NULL; 113 size_t pgsize, pgmask, pgoff; 114 void *stbuf, *bios, *p, *q; 115 void *smb2, *smb3; 116 di_node_t root; 117 int64_t *val64; 118 uint32_t smbe_stlen; 119 off64_t smbe_staddr; 120 121 bios = MAP_FAILED; 122 if ((root = di_init("/", DINFOPROP)) != DI_NODE_NIL) { 123 if (di_prop_lookup_int64(DDI_DEV_T_ANY, root, 124 "smbios-address", &val64) == 1) { 125 bios = mmap(NULL, SMB_RANGE_LIMIT - SMB_RANGE_START + 1, 126 PROT_READ, MAP_SHARED, fd, (off_t)*val64); 127 } 128 di_fini(root); 129 } 130 if (bios == MAP_FAILED) { 131 bios = mmap(NULL, SMB_RANGE_LIMIT - SMB_RANGE_START + 1, 132 PROT_READ, MAP_SHARED, fd, (uint32_t)SMB_RANGE_START); 133 } 134 135 if (bios == MAP_FAILED) 136 return (smb_open_error(shp, errp, ESMB_MAPDEV)); 137 138 q = (void *)((uintptr_t)bios + SMB_RANGE_LIMIT - SMB_RANGE_START + 1); 139 140 smb2 = smb3 = NULL; 141 for (p = bios; p < q; p = (void *)((uintptr_t)p + SMB_SCAN_STEP)) { 142 if (smb2 != NULL && smb3 != NULL) 143 break; 144 if (smb3 == NULL && strncmp(p, SMB3_ENTRY_EANCHOR, 145 SMB3_ENTRY_EANCHORLEN) == 0) { 146 smb3 = p; 147 } else if (smb2 == NULL && strncmp(p, SMB_ENTRY_EANCHOR, 148 SMB_ENTRY_EANCHORLEN) == 0) { 149 smb2 = p; 150 } 151 } 152 153 /* 154 * While they're not supposed to (as per the SMBIOS 3.2 spec), some 155 * vendors end up having a newer version in one of the two entry points 156 * than the other. If we found multiple tables then we will prefer the 157 * one with the newer version. If they're equivalent, we prefer the 158 * 32-bit version. If only one is present, then we use that. 159 */ 160 if (smb2 != NULL && smb3 != NULL) { 161 uint8_t smb2maj, smb2min, smb3maj, smb3min; 162 163 bcopy(smb2, ep, sizeof (smbios_entry_t)); 164 smb2maj = ep->ep21.smbe_major; 165 smb2min = ep->ep21.smbe_minor; 166 bcopy(smb3, ep, sizeof (smbios_entry_t)); 167 smb3maj = ep->ep30.smbe_major; 168 smb3min = ep->ep30.smbe_minor; 169 170 if (smb3maj > smb2maj || 171 (smb3maj == smb2maj && smb3min > smb2min)) { 172 ep_type = SMBIOS_ENTRY_POINT_30; 173 p = smb3; 174 } else { 175 ep_type = SMBIOS_ENTRY_POINT_21; 176 p = smb2; 177 } 178 } else if (smb3 != NULL) { 179 ep_type = SMBIOS_ENTRY_POINT_30; 180 p = smb3; 181 } else if (smb2 != NULL) { 182 ep_type = SMBIOS_ENTRY_POINT_21; 183 p = smb2; 184 } else { 185 (void) munmap(bios, SMB_RANGE_LIMIT - SMB_RANGE_START + 1); 186 return (smb_open_error(NULL, errp, ESMB_NOTFOUND)); 187 } 188 189 bcopy(p, ep, sizeof (smbios_entry_t)); 190 191 switch (ep_type) { 192 case SMBIOS_ENTRY_POINT_21: 193 ep->ep21.smbe_elen = MIN(ep->ep21.smbe_elen, SMB_ENTRY_MAXLEN); 194 bcopy(p, ep, ep->ep21.smbe_elen); 195 smbe_stlen = ep->ep21.smbe_stlen; 196 smbe_staddr = ep->ep21.smbe_staddr; 197 break; 198 case SMBIOS_ENTRY_POINT_30: 199 ep->ep30.smbe_elen = MIN(ep->ep30.smbe_elen, SMB_ENTRY_MAXLEN); 200 bcopy(p, ep, ep->ep30.smbe_elen); 201 smbe_stlen = ep->ep30.smbe_stlen; 202 smbe_staddr = ep->ep30.smbe_staddr; 203 break; 204 default: 205 (void) munmap(bios, SMB_RANGE_LIMIT - SMB_RANGE_START + 1); 206 return (smb_open_error(NULL, errp, ESMB_VERSION)); 207 } 208 (void) munmap(bios, SMB_RANGE_LIMIT - SMB_RANGE_START + 1); 209 210 pgsize = getpagesize(); 211 pgmask = ~(pgsize - 1); 212 pgoff = smbe_staddr & ~pgmask; 213 214 bios = mmap(NULL, smbe_stlen + pgoff, 215 PROT_READ, MAP_SHARED, fd, smbe_staddr & pgmask); 216 217 if (bios == MAP_FAILED) 218 return (smb_open_error(shp, errp, ESMB_MAPDEV)); 219 220 if ((stbuf = smb_alloc(smbe_stlen)) == NULL) { 221 (void) munmap(bios, smbe_stlen + pgoff); 222 return (smb_open_error(shp, errp, ESMB_NOMEM)); 223 } 224 225 bcopy((char *)bios + pgoff, stbuf, smbe_stlen); 226 (void) munmap(bios, smbe_stlen + pgoff); 227 shp = smbios_bufopen(ep, stbuf, smbe_stlen, version, flags, errp); 228 229 if (shp != NULL) 230 shp->sh_flags |= SMB_FL_BUFALLOC; 231 else 232 smb_free(stbuf, smbe_stlen); 233 234 return (shp); 235 } 236 237 smbios_hdl_t * 238 smbios_fdopen(int fd, int version, int flags, int *errp) 239 { 240 struct stat64 st1, st2; 241 242 if (stat64(SMB_BIOS_DEVICE, &st1) == 0 && fstat64(fd, &st2) == 0 && 243 S_ISCHR(st2.st_mode) && st1.st_rdev == st2.st_rdev) 244 return (smb_biosopen(fd, version, flags, errp)); 245 else 246 return (smb_fileopen(fd, version, flags, errp)); 247 } 248 249 smbios_hdl_t * 250 smbios_open(const char *file, int version, int flags, int *errp) 251 { 252 smbios_hdl_t *shp; 253 int fd; 254 255 if ((fd = open64(file ? file : SMB_SMBIOS_DEVICE, O_RDONLY)) == -1) { 256 if ((errno == ENOENT || errno == ENXIO) && 257 (file == NULL || strcmp(file, SMB_SMBIOS_DEVICE) == 0)) 258 errno = ESMB_NOTFOUND; 259 return (smb_open_error(NULL, errp, errno)); 260 } 261 262 shp = smbios_fdopen(fd, version, flags, errp); 263 (void) close(fd); 264 return (shp); 265 } 266 267 static int 268 smbios_xwrite(smbios_hdl_t *shp, int fd, const void *buf, size_t buflen) 269 { 270 ssize_t resid = buflen; 271 ssize_t len; 272 273 while (resid != 0) { 274 if ((len = write(fd, buf, resid)) <= 0) 275 return (smb_set_errno(shp, errno)); 276 resid -= len; 277 buf = (uchar_t *)buf + len; 278 } 279 280 return (0); 281 } 282 283 int 284 smbios_write(smbios_hdl_t *shp, int fd) 285 { 286 smbios_entry_t ep; 287 off64_t off = lseek64(fd, 0, SEEK_CUR) + P2ROUNDUP(sizeof (ep), 16); 288 289 if (off > UINT32_MAX) 290 return (smb_set_errno(shp, EOVERFLOW)); 291 292 bcopy(&shp->sh_ent, &ep, sizeof (ep)); 293 if (shp->sh_ent_type == SMBIOS_ENTRY_POINT_21) 294 ep.ep21.smbe_staddr = (uint32_t)off; 295 else if (shp->sh_ent_type == SMBIOS_ENTRY_POINT_30) 296 ep.ep30.smbe_staddr = (uint64_t)off; 297 else 298 return (-1); 299 300 smbios_checksum(shp, &ep); 301 302 if (smbios_xwrite(shp, fd, &ep, sizeof (ep)) == -1 || 303 lseek64(fd, off, SEEK_SET) != off || 304 smbios_xwrite(shp, fd, shp->sh_buf, shp->sh_buflen) == -1) 305 return (-1); 306 307 return (0); 308 } 309