1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3 * 4 * Copyright (c) 2000,2001 Peter Wemm <peter@FreeBSD.org> 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * SUCH DAMAGE. 27 */ 28 29 #include <sys/cdefs.h> 30 __FBSDID("$FreeBSD$"); 31 32 #include <sys/param.h> 33 #include <sys/lock.h> 34 #include <sys/kernel.h> 35 #include <sys/malloc.h> 36 #include <sys/mutex.h> 37 #include <sys/sysctl.h> 38 #include <sys/systm.h> 39 #include <sys/bus.h> 40 41 #define FBACK_MDENV 0 /* MD env (e.g. loader.conf) */ 42 #define FBACK_STENV 1 /* Static env */ 43 #define FBACK_STATIC 2 /* static_hints */ 44 45 /* 46 * We'll use hintenv_merged to indicate that the dynamic environment has been 47 * properly prepared for hint usage. This implies that the dynamic environment 48 * has already been setup (dynamic_kenv) and that we have added any supplied 49 * static_hints to the dynamic environment. 50 */ 51 static bool hintenv_merged; 52 /* Static environment and static hints cannot change, so we'll skip known bad */ 53 static bool stenv_skip; 54 static bool sthints_skip; 55 /* 56 * Access functions for device resources. 57 */ 58 59 static void 60 static_hints_to_env(void *data __unused) 61 { 62 const char *cp; 63 char *line, *eq; 64 int eqidx, i; 65 66 cp = static_hints; 67 while (cp && *cp != '\0') { 68 eq = strchr(cp, '='); 69 if (eq == NULL) 70 /* Bad hint value */ 71 continue; 72 eqidx = eq - cp; 73 74 i = strlen(cp); 75 line = malloc(i + 1, M_TEMP, M_WAITOK); 76 strcpy(line, cp); 77 line[eqidx] = line[i] = '\0'; 78 /* 79 * Before adding a hint to the dynamic environment, check if 80 * another value for said hint has already been added. This is 81 * needed because static environment overrides static hints and 82 * dynamic environment overrides all. 83 */ 84 if (testenv(line) == 0) 85 kern_setenv(line, line + eqidx + 1); 86 free(line, M_TEMP); 87 cp += i + 1; 88 } 89 hintenv_merged = true; 90 } 91 92 /* Any time after dynamic env is setup */ 93 SYSINIT(hintenv, SI_SUB_KMEM + 1, SI_ORDER_SECOND, static_hints_to_env, NULL); 94 95 /* 96 * Checks the environment to see if we even have any hints. If it has no hints, 97 * then res_find can take the hint that there's no point in searching it and 98 * either move on to the next environment or fail early. 99 */ 100 static bool 101 _res_checkenv(char *envp) 102 { 103 char *cp; 104 105 cp = envp; 106 while (cp) { 107 if (strncmp(cp, "hint.", 5) == 0) 108 return (true); 109 while (*cp != '\0') 110 cp++; 111 cp++; 112 if (*cp == '\0') 113 break; 114 } 115 return (false); 116 } 117 118 /* 119 * Evil wildcarding resource string lookup. 120 * This walks the supplied env string table and returns a match. 121 * The start point can be remembered for incremental searches. 122 */ 123 static int 124 res_find(char **hintp_cookie, int *line, int *startln, 125 const char *name, int *unit, const char *resname, const char *value, 126 const char **ret_name, int *ret_namelen, int *ret_unit, 127 const char **ret_resname, int *ret_resnamelen, const char **ret_value) 128 { 129 int fbacklvl = FBACK_MDENV, i = 0, n = 0; 130 char r_name[32]; 131 int r_unit; 132 char r_resname[32]; 133 char r_value[128]; 134 const char *s, *cp; 135 char *hintp, *p; 136 bool dyn_used = false; 137 138 139 /* 140 * We are expecting that the caller will pass us a hintp_cookie that 141 * they are tracking. Upon entry, if *hintp_cookie is *not* set, this 142 * indicates to us that we should be figuring out based on the current 143 * environment where to search. This keeps us sane throughout the 144 * entirety of a single search. 145 */ 146 if (*hintp_cookie == NULL) { 147 hintp = NULL; 148 if (hintenv_merged) { 149 /* 150 * static_hints, if it was previously used, has 151 * already been folded in to the environment 152 * by this point. 153 */ 154 mtx_lock(&kenv_lock); 155 cp = kenvp[0]; 156 for (i = 0; cp != NULL; cp = kenvp[++i]) { 157 if (!strncmp(cp, "hint.", 5)) { 158 hintp = kenvp[0]; 159 break; 160 } 161 } 162 mtx_unlock(&kenv_lock); 163 dyn_used = true; 164 } else { 165 /* 166 * We'll have a chance to keep coming back here until 167 * we've actually exhausted all of our possibilities. 168 * We might have chosen the MD/Static env because it 169 * had some kind of hints, but perhaps it didn't have 170 * the hint we are looking for. We don't provide any 171 * fallback when searching the dynamic environment. 172 */ 173 fallback: 174 if (dyn_used || fbacklvl >= FBACK_STATIC) 175 return (ENOENT); 176 177 if (fbacklvl <= FBACK_MDENV && 178 _res_checkenv(md_envp)) { 179 hintp = md_envp; 180 goto found; 181 } 182 fbacklvl++; 183 184 if (!stenv_skip && fbacklvl <= FBACK_STENV && 185 _res_checkenv(kern_envp)) { 186 hintp = kern_envp; 187 goto found; 188 } else 189 stenv_skip = true; 190 191 fbacklvl++; 192 193 /* We'll fallback to static_hints if needed/can */ 194 if (!sthints_skip && fbacklvl <= FBACK_STATIC && 195 _res_checkenv(static_hints)) 196 hintp = static_hints; 197 else 198 sthints_skip = true; 199 found: 200 fbacklvl++; 201 } 202 203 if (hintp == NULL) 204 return (ENOENT); 205 *hintp_cookie = hintp; 206 } else { 207 hintp = *hintp_cookie; 208 if (hintenv_merged && hintp == kenvp[0]) 209 dyn_used = true; 210 else 211 /* 212 * If we aren't using the dynamic environment, we need 213 * to run through the proper fallback procedure again. 214 * This is so that we do continuations right if we're 215 * working with *line and *startln. 216 */ 217 goto fallback; 218 } 219 220 if (dyn_used) { 221 mtx_lock(&kenv_lock); 222 i = 0; 223 } 224 225 cp = hintp; 226 while (cp) { 227 (*line)++; 228 if (strncmp(cp, "hint.", 5) != 0) 229 goto nexthint; 230 n = sscanf(cp, "hint.%32[^.].%d.%32[^=]=%127s", r_name, &r_unit, 231 r_resname, r_value); 232 if (n != 4) { 233 printf("CONFIG: invalid hint '%s'\n", cp); 234 p = strchr(cp, 'h'); 235 *p = 'H'; 236 goto nexthint; 237 } 238 if (startln && *startln >= 0 && *line < *startln) 239 goto nexthint; 240 if (name && strcmp(name, r_name) != 0) 241 goto nexthint; 242 if (unit && *unit != r_unit) 243 goto nexthint; 244 if (resname && strcmp(resname, r_resname) != 0) 245 goto nexthint; 246 if (value && strcmp(value, r_value) != 0) 247 goto nexthint; 248 /* Successfully found a hint matching all criteria */ 249 break; 250 nexthint: 251 if (dyn_used) { 252 cp = kenvp[++i]; 253 if (cp == NULL) 254 break; 255 } else { 256 while (*cp != '\0') 257 cp++; 258 cp++; 259 if (*cp == '\0') { 260 cp = NULL; 261 break; 262 } 263 } 264 } 265 if (dyn_used) 266 mtx_unlock(&kenv_lock); 267 if (cp == NULL) 268 goto fallback; 269 270 s = cp; 271 /* This is a bit of a hack, but at least is reentrant */ 272 /* Note that it returns some !unterminated! strings. */ 273 s = strchr(s, '.') + 1; /* start of device */ 274 if (ret_name) 275 *ret_name = s; 276 s = strchr(s, '.') + 1; /* start of unit */ 277 if (ret_namelen && ret_name) 278 *ret_namelen = s - *ret_name - 1; /* device length */ 279 if (ret_unit) 280 *ret_unit = r_unit; 281 s = strchr(s, '.') + 1; /* start of resname */ 282 if (ret_resname) 283 *ret_resname = s; 284 s = strchr(s, '=') + 1; /* start of value */ 285 if (ret_resnamelen && ret_resname) 286 *ret_resnamelen = s - *ret_resname - 1; /* value len */ 287 if (ret_value) 288 *ret_value = s; 289 if (startln) /* line number for anchor */ 290 *startln = *line + 1; 291 return 0; 292 } 293 294 /* 295 * Search all the data sources for matches to our query. We look for 296 * dynamic hints first as overrides for static or fallback hints. 297 */ 298 static int 299 resource_find(int *line, int *startln, 300 const char *name, int *unit, const char *resname, const char *value, 301 const char **ret_name, int *ret_namelen, int *ret_unit, 302 const char **ret_resname, int *ret_resnamelen, const char **ret_value) 303 { 304 int i; 305 int un; 306 char *hintp; 307 308 *line = 0; 309 hintp = NULL; 310 311 /* Search for exact unit matches first */ 312 i = res_find(&hintp, line, startln, name, unit, resname, value, 313 ret_name, ret_namelen, ret_unit, ret_resname, ret_resnamelen, 314 ret_value); 315 if (i == 0) 316 return 0; 317 if (unit == NULL) 318 return ENOENT; 319 /* If we are still here, search for wildcard matches */ 320 un = -1; 321 i = res_find(&hintp, line, startln, name, &un, resname, value, 322 ret_name, ret_namelen, ret_unit, ret_resname, ret_resnamelen, 323 ret_value); 324 if (i == 0) 325 return 0; 326 return ENOENT; 327 } 328 329 int 330 resource_int_value(const char *name, int unit, const char *resname, int *result) 331 { 332 int error; 333 const char *str; 334 char *op; 335 unsigned long val; 336 int line; 337 338 line = 0; 339 error = resource_find(&line, NULL, name, &unit, resname, NULL, 340 NULL, NULL, NULL, NULL, NULL, &str); 341 if (error) 342 return error; 343 if (*str == '\0') 344 return EFTYPE; 345 val = strtoul(str, &op, 0); 346 if (*op != '\0') 347 return EFTYPE; 348 *result = val; 349 return 0; 350 } 351 352 int 353 resource_long_value(const char *name, int unit, const char *resname, 354 long *result) 355 { 356 int error; 357 const char *str; 358 char *op; 359 unsigned long val; 360 int line; 361 362 line = 0; 363 error = resource_find(&line, NULL, name, &unit, resname, NULL, 364 NULL, NULL, NULL, NULL, NULL, &str); 365 if (error) 366 return error; 367 if (*str == '\0') 368 return EFTYPE; 369 val = strtoul(str, &op, 0); 370 if (*op != '\0') 371 return EFTYPE; 372 *result = val; 373 return 0; 374 } 375 376 int 377 resource_string_value(const char *name, int unit, const char *resname, 378 const char **result) 379 { 380 int error; 381 const char *str; 382 int line; 383 384 line = 0; 385 error = resource_find(&line, NULL, name, &unit, resname, NULL, 386 NULL, NULL, NULL, NULL, NULL, &str); 387 if (error) 388 return error; 389 *result = str; 390 return 0; 391 } 392 393 /* 394 * This is a bit nasty, but allows us to not modify the env strings. 395 */ 396 static const char * 397 resource_string_copy(const char *s, int len) 398 { 399 static char stringbuf[256]; 400 static int offset = 0; 401 const char *ret; 402 403 if (len == 0) 404 len = strlen(s); 405 if (len > 255) 406 return NULL; 407 if ((offset + len + 1) > 255) 408 offset = 0; 409 bcopy(s, &stringbuf[offset], len); 410 stringbuf[offset + len] = '\0'; 411 ret = &stringbuf[offset]; 412 offset += len + 1; 413 return ret; 414 } 415 416 /* 417 * err = resource_find_match(&anchor, &name, &unit, resname, value) 418 * Iteratively fetch a list of devices wired "at" something 419 * res and value are restrictions. eg: "at", "scbus0". 420 * For practical purposes, res = required, value = optional. 421 * *name and *unit are set. 422 * set *anchor to zero before starting. 423 */ 424 int 425 resource_find_match(int *anchor, const char **name, int *unit, 426 const char *resname, const char *value) 427 { 428 const char *found_name; 429 int found_namelen; 430 int found_unit; 431 int ret; 432 int newln; 433 434 newln = *anchor; 435 ret = resource_find(anchor, &newln, NULL, NULL, resname, value, 436 &found_name, &found_namelen, &found_unit, NULL, NULL, NULL); 437 if (ret == 0) { 438 *name = resource_string_copy(found_name, found_namelen); 439 *unit = found_unit; 440 } 441 *anchor = newln; 442 return ret; 443 } 444 445 446 /* 447 * err = resource_find_dev(&anchor, name, &unit, res, value); 448 * Iterate through a list of devices, returning their unit numbers. 449 * res and value are optional restrictions. eg: "at", "scbus0". 450 * *unit is set to the value. 451 * set *anchor to zero before starting. 452 */ 453 int 454 resource_find_dev(int *anchor, const char *name, int *unit, 455 const char *resname, const char *value) 456 { 457 int found_unit; 458 int newln; 459 int ret; 460 461 newln = *anchor; 462 ret = resource_find(anchor, &newln, name, NULL, resname, value, 463 NULL, NULL, &found_unit, NULL, NULL, NULL); 464 if (ret == 0) { 465 *unit = found_unit; 466 } 467 *anchor = newln; 468 return ret; 469 } 470 471 /* 472 * Check to see if a device is disabled via a disabled hint. 473 */ 474 int 475 resource_disabled(const char *name, int unit) 476 { 477 int error, value; 478 479 error = resource_int_value(name, unit, "disabled", &value); 480 if (error) 481 return (0); 482 return (value); 483 } 484 485 /* 486 * Clear a value associated with a device by removing it from 487 * the kernel environment. This only removes a hint for an 488 * exact unit. 489 */ 490 int 491 resource_unset_value(const char *name, int unit, const char *resname) 492 { 493 char varname[128]; 494 const char *retname, *retvalue; 495 int error, line; 496 size_t len; 497 498 line = 0; 499 error = resource_find(&line, NULL, name, &unit, resname, NULL, 500 &retname, NULL, NULL, NULL, NULL, &retvalue); 501 if (error) 502 return (error); 503 504 retname -= strlen("hint."); 505 len = retvalue - retname - 1; 506 if (len > sizeof(varname) - 1) 507 return (ENAMETOOLONG); 508 memcpy(varname, retname, len); 509 varname[len] = '\0'; 510 return (kern_unsetenv(varname)); 511 } 512