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 * We are expecting that the caller will pass us a hintp_cookie that 140 * they are tracking. Upon entry, if *hintp_cookie is *not* set, this 141 * indicates to us that we should be figuring out based on the current 142 * environment where to search. This keeps us sane throughout the 143 * entirety of a single search. 144 */ 145 if (*hintp_cookie == NULL) { 146 hintp = NULL; 147 if (hintenv_merged) { 148 /* 149 * static_hints, if it was previously used, has 150 * already been folded in to the environment 151 * by this point. 152 */ 153 mtx_lock(&kenv_lock); 154 cp = kenvp[0]; 155 for (i = 0; cp != NULL; cp = kenvp[++i]) { 156 if (!strncmp(cp, "hint.", 5)) { 157 hintp = kenvp[0]; 158 break; 159 } 160 } 161 mtx_unlock(&kenv_lock); 162 dyn_used = true; 163 } else { 164 /* 165 * We'll have a chance to keep coming back here until 166 * we've actually exhausted all of our possibilities. 167 * We might have chosen the MD/Static env because it 168 * had some kind of hints, but perhaps it didn't have 169 * the hint we are looking for. We don't provide any 170 * fallback when searching the dynamic environment. 171 */ 172 fallback: 173 if (dyn_used || fbacklvl >= FBACK_STATIC) 174 return (ENOENT); 175 176 switch (fbacklvl) { 177 case FBACK_MDENV: 178 fbacklvl++; 179 if (_res_checkenv(md_envp)) { 180 hintp = md_envp; 181 break; 182 } 183 184 /* FALLTHROUGH */ 185 case FBACK_STENV: 186 fbacklvl++; 187 if (!stenv_skip && _res_checkenv(kern_envp)) { 188 hintp = kern_envp; 189 break; 190 } else 191 stenv_skip = true; 192 193 /* FALLTHROUGH */ 194 case FBACK_STATIC: 195 fbacklvl++; 196 /* We'll fallback to static_hints if needed/can */ 197 if (!sthints_skip && 198 _res_checkenv(static_hints)) 199 hintp = static_hints; 200 else 201 sthints_skip = true; 202 203 break; 204 default: 205 return (ENOENT); 206 } 207 } 208 209 if (hintp == NULL) 210 return (ENOENT); 211 *hintp_cookie = hintp; 212 } else { 213 hintp = *hintp_cookie; 214 if (hintenv_merged && hintp == kenvp[0]) 215 dyn_used = true; 216 else 217 /* 218 * If we aren't using the dynamic environment, we need 219 * to run through the proper fallback procedure again. 220 * This is so that we do continuations right if we're 221 * working with *line and *startln. 222 */ 223 goto fallback; 224 } 225 226 if (dyn_used) { 227 mtx_lock(&kenv_lock); 228 i = 0; 229 } 230 231 cp = hintp; 232 while (cp) { 233 (*line)++; 234 if (strncmp(cp, "hint.", 5) != 0) 235 goto nexthint; 236 n = sscanf(cp, "hint.%32[^.].%d.%32[^=]=%127s", r_name, &r_unit, 237 r_resname, r_value); 238 if (n != 4) { 239 printf("CONFIG: invalid hint '%s'\n", cp); 240 p = strchr(cp, 'h'); 241 *p = 'H'; 242 goto nexthint; 243 } 244 if (startln && *startln >= 0 && *line < *startln) 245 goto nexthint; 246 if (name && strcmp(name, r_name) != 0) 247 goto nexthint; 248 if (unit && *unit != r_unit) 249 goto nexthint; 250 if (resname && strcmp(resname, r_resname) != 0) 251 goto nexthint; 252 if (value && strcmp(value, r_value) != 0) 253 goto nexthint; 254 /* Successfully found a hint matching all criteria */ 255 break; 256 nexthint: 257 if (dyn_used) { 258 cp = kenvp[++i]; 259 if (cp == NULL) 260 break; 261 } else { 262 while (*cp != '\0') 263 cp++; 264 cp++; 265 if (*cp == '\0') { 266 cp = NULL; 267 break; 268 } 269 } 270 } 271 if (dyn_used) 272 mtx_unlock(&kenv_lock); 273 if (cp == NULL) 274 goto fallback; 275 276 s = cp; 277 /* This is a bit of a hack, but at least is reentrant */ 278 /* Note that it returns some !unterminated! strings. */ 279 s = strchr(s, '.') + 1; /* start of device */ 280 if (ret_name) 281 *ret_name = s; 282 s = strchr(s, '.') + 1; /* start of unit */ 283 if (ret_namelen && ret_name) 284 *ret_namelen = s - *ret_name - 1; /* device length */ 285 if (ret_unit) 286 *ret_unit = r_unit; 287 s = strchr(s, '.') + 1; /* start of resname */ 288 if (ret_resname) 289 *ret_resname = s; 290 s = strchr(s, '=') + 1; /* start of value */ 291 if (ret_resnamelen && ret_resname) 292 *ret_resnamelen = s - *ret_resname - 1; /* value len */ 293 if (ret_value) 294 *ret_value = s; 295 if (startln) /* line number for anchor */ 296 *startln = *line + 1; 297 return 0; 298 } 299 300 /* 301 * Search all the data sources for matches to our query. We look for 302 * dynamic hints first as overrides for static or fallback hints. 303 */ 304 static int 305 resource_find(int *line, int *startln, 306 const char *name, int *unit, const char *resname, const char *value, 307 const char **ret_name, int *ret_namelen, int *ret_unit, 308 const char **ret_resname, int *ret_resnamelen, const char **ret_value) 309 { 310 int i; 311 int un; 312 char *hintp; 313 314 *line = 0; 315 hintp = NULL; 316 317 /* Search for exact unit matches first */ 318 i = res_find(&hintp, line, startln, name, unit, resname, value, 319 ret_name, ret_namelen, ret_unit, ret_resname, ret_resnamelen, 320 ret_value); 321 if (i == 0) 322 return 0; 323 if (unit == NULL) 324 return ENOENT; 325 /* If we are still here, search for wildcard matches */ 326 un = -1; 327 i = res_find(&hintp, line, startln, name, &un, resname, value, 328 ret_name, ret_namelen, ret_unit, ret_resname, ret_resnamelen, 329 ret_value); 330 if (i == 0) 331 return 0; 332 return ENOENT; 333 } 334 335 int 336 resource_int_value(const char *name, int unit, const char *resname, int *result) 337 { 338 int error; 339 const char *str; 340 char *op; 341 unsigned long val; 342 int line; 343 344 line = 0; 345 error = resource_find(&line, NULL, name, &unit, resname, NULL, 346 NULL, NULL, NULL, NULL, NULL, &str); 347 if (error) 348 return error; 349 if (*str == '\0') 350 return EFTYPE; 351 val = strtoul(str, &op, 0); 352 if (*op != '\0') 353 return EFTYPE; 354 *result = val; 355 return 0; 356 } 357 358 int 359 resource_long_value(const char *name, int unit, const char *resname, 360 long *result) 361 { 362 int error; 363 const char *str; 364 char *op; 365 unsigned long val; 366 int line; 367 368 line = 0; 369 error = resource_find(&line, NULL, name, &unit, resname, NULL, 370 NULL, NULL, NULL, NULL, NULL, &str); 371 if (error) 372 return error; 373 if (*str == '\0') 374 return EFTYPE; 375 val = strtoul(str, &op, 0); 376 if (*op != '\0') 377 return EFTYPE; 378 *result = val; 379 return 0; 380 } 381 382 int 383 resource_string_value(const char *name, int unit, const char *resname, 384 const char **result) 385 { 386 int error; 387 const char *str; 388 int line; 389 390 line = 0; 391 error = resource_find(&line, NULL, name, &unit, resname, NULL, 392 NULL, NULL, NULL, NULL, NULL, &str); 393 if (error) 394 return error; 395 *result = str; 396 return 0; 397 } 398 399 /* 400 * This is a bit nasty, but allows us to not modify the env strings. 401 */ 402 static const char * 403 resource_string_copy(const char *s, int len) 404 { 405 static char stringbuf[256]; 406 static int offset = 0; 407 const char *ret; 408 409 if (len == 0) 410 len = strlen(s); 411 if (len > 255) 412 return NULL; 413 if ((offset + len + 1) > 255) 414 offset = 0; 415 bcopy(s, &stringbuf[offset], len); 416 stringbuf[offset + len] = '\0'; 417 ret = &stringbuf[offset]; 418 offset += len + 1; 419 return ret; 420 } 421 422 /* 423 * err = resource_find_match(&anchor, &name, &unit, resname, value) 424 * Iteratively fetch a list of devices wired "at" something 425 * res and value are restrictions. eg: "at", "scbus0". 426 * For practical purposes, res = required, value = optional. 427 * *name and *unit are set. 428 * set *anchor to zero before starting. 429 */ 430 int 431 resource_find_match(int *anchor, const char **name, int *unit, 432 const char *resname, const char *value) 433 { 434 const char *found_name; 435 int found_namelen; 436 int found_unit; 437 int ret; 438 int newln; 439 440 newln = *anchor; 441 ret = resource_find(anchor, &newln, NULL, NULL, resname, value, 442 &found_name, &found_namelen, &found_unit, NULL, NULL, NULL); 443 if (ret == 0) { 444 *name = resource_string_copy(found_name, found_namelen); 445 *unit = found_unit; 446 } 447 *anchor = newln; 448 return ret; 449 } 450 451 /* 452 * err = resource_find_dev(&anchor, name, &unit, res, value); 453 * Iterate through a list of devices, returning their unit numbers. 454 * res and value are optional restrictions. eg: "at", "scbus0". 455 * *unit is set to the value. 456 * set *anchor to zero before starting. 457 */ 458 int 459 resource_find_dev(int *anchor, const char *name, int *unit, 460 const char *resname, const char *value) 461 { 462 int found_unit; 463 int newln; 464 int ret; 465 466 newln = *anchor; 467 ret = resource_find(anchor, &newln, name, NULL, resname, value, 468 NULL, NULL, &found_unit, NULL, NULL, NULL); 469 if (ret == 0) { 470 *unit = found_unit; 471 } 472 *anchor = newln; 473 return ret; 474 } 475 476 /* 477 * Check to see if a device is disabled via a disabled hint. 478 */ 479 int 480 resource_disabled(const char *name, int unit) 481 { 482 int error, value; 483 484 error = resource_int_value(name, unit, "disabled", &value); 485 if (error) 486 return (0); 487 return (value); 488 } 489 490 /* 491 * Clear a value associated with a device by removing it from 492 * the kernel environment. This only removes a hint for an 493 * exact unit. 494 */ 495 int 496 resource_unset_value(const char *name, int unit, const char *resname) 497 { 498 char varname[128]; 499 const char *retname, *retvalue; 500 int error, line; 501 size_t len; 502 503 line = 0; 504 error = resource_find(&line, NULL, name, &unit, resname, NULL, 505 &retname, NULL, NULL, NULL, NULL, &retvalue); 506 if (error) 507 return (error); 508 509 retname -= strlen("hint."); 510 len = retvalue - retname - 1; 511 if (len > sizeof(varname) - 1) 512 return (ENAMETOOLONG); 513 memcpy(varname, retname, len); 514 varname[len] = '\0'; 515 return (kern_unsetenv(varname)); 516 } 517