xref: /freebsd/sys/kern/subr_hints.c (revision 5bf5ca772c6de2d53344a78cf461447cc322ccea)
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/malloc.h>
35 #include <sys/mutex.h>
36 #include <sys/sysctl.h>
37 #include <sys/systm.h>
38 #include <sys/bus.h>
39 
40 /*
41  * Access functions for device resources.
42  */
43 
44 static int checkmethod = 1;
45 static int use_kenv;
46 static char *hintp;
47 
48 /*
49  * Define kern.hintmode sysctl, which only accept value 2, that cause to
50  * switch from Static KENV mode to Dynamic KENV. So systems that have hints
51  * compiled into kernel will be able to see/modify KENV (and hints too).
52  */
53 
54 static int
55 sysctl_hintmode(SYSCTL_HANDLER_ARGS)
56 {
57 	const char *cp;
58 	char *line, *eq;
59 	int eqidx, error, from_kenv, i, value;
60 
61 	from_kenv = 0;
62 	cp = kern_envp;
63 	value = hintmode;
64 
65 	/* Fetch candidate for new hintmode value */
66 	error = sysctl_handle_int(oidp, &value, 0, req);
67 	if (error || req->newptr == NULL)
68 		return (error);
69 
70 	if (value != 2)
71 		/* Only accept swithing to hintmode 2 */
72 		return (EINVAL);
73 
74 	/* Migrate from static to dynamic hints */
75 	switch (hintmode) {
76 	case 0:
77 		if (dynamic_kenv) {
78 			/*
79 			 * Already here. But assign hintmode to 2, to not
80 			 * check it in the future.
81 			 */
82 			hintmode = 2;
83 			return (0);
84 		}
85 		from_kenv = 1;
86 		cp = kern_envp;
87 		break;
88 	case 1:
89 		cp = static_hints;
90 		break;
91 	case 2:
92 		/* Nothing to do, hintmode already 2 */
93 		return (0);
94 	}
95 
96 	while (cp) {
97 		i = strlen(cp);
98 		if (i == 0)
99 			break;
100 		if (from_kenv) {
101 			if (strncmp(cp, "hint.", 5) != 0)
102 				/* kenv can have not only hints */
103 				continue;
104 		}
105 		eq = strchr(cp, '=');
106 		if (eq == NULL)
107 			/* Bad hint value */
108 			continue;
109 		eqidx = eq - cp;
110 
111 		line = malloc(i+1, M_TEMP, M_WAITOK);
112 		strcpy(line, cp);
113 		line[eqidx] = '\0';
114 		kern_setenv(line, line + eqidx + 1);
115 		free(line, M_TEMP);
116 		cp += i + 1;
117 	}
118 
119 	hintmode = value;
120 	use_kenv = 1;
121 	return (0);
122 }
123 
124 SYSCTL_PROC(_kern, OID_AUTO, hintmode, CTLTYPE_INT|CTLFLAG_RW,
125     &hintmode, 0, sysctl_hintmode, "I", "Get/set current hintmode");
126 
127 /*
128  * Evil wildcarding resource string lookup.
129  * This walks the supplied env string table and returns a match.
130  * The start point can be remembered for incremental searches.
131  */
132 static int
133 res_find(int *line, int *startln,
134     const char *name, int *unit, const char *resname, const char *value,
135     const char **ret_name, int *ret_namelen, int *ret_unit,
136     const char **ret_resname, int *ret_resnamelen, const char **ret_value)
137 {
138 	int n = 0, hit, i = 0;
139 	char r_name[32];
140 	int r_unit;
141 	char r_resname[32];
142 	char r_value[128];
143 	const char *s, *cp;
144 	char *p;
145 
146 	if (checkmethod) {
147 		hintp = NULL;
148 
149 		switch (hintmode) {
150 		case 0:		/* loader hints in environment only */
151 			break;
152 		case 1:		/* static hints only */
153 			hintp = static_hints;
154 			checkmethod = 0;
155 			break;
156 		case 2:		/* fallback mode */
157 			if (dynamic_kenv) {
158 				mtx_lock(&kenv_lock);
159 				cp = kenvp[0];
160 				for (i = 0; cp != NULL; cp = kenvp[++i]) {
161 					if (!strncmp(cp, "hint.", 5)) {
162 						use_kenv = 1;
163 						checkmethod = 0;
164 						break;
165 					}
166 				}
167 				mtx_unlock(&kenv_lock);
168 			} else {
169 				cp = kern_envp;
170 				while (cp) {
171 					if (strncmp(cp, "hint.", 5) == 0) {
172 						cp = NULL;
173 						hintp = kern_envp;
174 						break;
175 					}
176 					while (*cp != '\0')
177 						cp++;
178 					cp++;
179 					if (*cp == '\0') {
180 						cp = NULL;
181 						hintp = static_hints;
182 						break;
183 					}
184 				}
185 			}
186 			break;
187 		default:
188 			break;
189 		}
190 		if (hintp == NULL) {
191 			if (dynamic_kenv) {
192 				use_kenv = 1;
193 				checkmethod = 0;
194 			} else
195 				hintp = kern_envp;
196 		}
197 	}
198 
199 	if (use_kenv) {
200 		mtx_lock(&kenv_lock);
201 		i = 0;
202 		cp = kenvp[0];
203 		if (cp == NULL) {
204 			mtx_unlock(&kenv_lock);
205 			return (ENOENT);
206 		}
207 	} else
208 		cp = hintp;
209 	while (cp) {
210 		hit = 1;
211 		(*line)++;
212 		if (strncmp(cp, "hint.", 5) != 0)
213 			hit = 0;
214 		else
215 			n = sscanf(cp, "hint.%32[^.].%d.%32[^=]=%127s",
216 			    r_name, &r_unit, r_resname, r_value);
217 		if (hit && n != 4) {
218 			printf("CONFIG: invalid hint '%s'\n", cp);
219 			p = strchr(cp, 'h');
220 			*p = 'H';
221 			hit = 0;
222 		}
223 		if (hit && startln && *startln >= 0 && *line < *startln)
224 			hit = 0;
225 		if (hit && name && strcmp(name, r_name) != 0)
226 			hit = 0;
227 		if (hit && unit && *unit != r_unit)
228 			hit = 0;
229 		if (hit && resname && strcmp(resname, r_resname) != 0)
230 			hit = 0;
231 		if (hit && value && strcmp(value, r_value) != 0)
232 			hit = 0;
233 		if (hit)
234 			break;
235 		if (use_kenv) {
236 			cp = kenvp[++i];
237 			if (cp == NULL)
238 				break;
239 		} else {
240 			while (*cp != '\0')
241 				cp++;
242 			cp++;
243 			if (*cp == '\0') {
244 				cp = NULL;
245 				break;
246 			}
247 		}
248 	}
249 	if (use_kenv)
250 		mtx_unlock(&kenv_lock);
251 	if (cp == NULL)
252 		return ENOENT;
253 
254 	s = cp;
255 	/* This is a bit of a hack, but at least is reentrant */
256 	/* Note that it returns some !unterminated! strings. */
257 	s = strchr(s, '.') + 1;		/* start of device */
258 	if (ret_name)
259 		*ret_name = s;
260 	s = strchr(s, '.') + 1;		/* start of unit */
261 	if (ret_namelen && ret_name)
262 		*ret_namelen = s - *ret_name - 1; /* device length */
263 	if (ret_unit)
264 		*ret_unit = r_unit;
265 	s = strchr(s, '.') + 1;		/* start of resname */
266 	if (ret_resname)
267 		*ret_resname = s;
268 	s = strchr(s, '=') + 1;		/* start of value */
269 	if (ret_resnamelen && ret_resname)
270 		*ret_resnamelen = s - *ret_resname - 1; /* value len */
271 	if (ret_value)
272 		*ret_value = s;
273 	if (startln)			/* line number for anchor */
274 		*startln = *line + 1;
275 	return 0;
276 }
277 
278 /*
279  * Search all the data sources for matches to our query.  We look for
280  * dynamic hints first as overrides for static or fallback hints.
281  */
282 static int
283 resource_find(int *line, int *startln,
284     const char *name, int *unit, const char *resname, const char *value,
285     const char **ret_name, int *ret_namelen, int *ret_unit,
286     const char **ret_resname, int *ret_resnamelen, const char **ret_value)
287 {
288 	int i;
289 	int un;
290 
291 	*line = 0;
292 
293 	/* Search for exact unit matches first */
294 	i = res_find(line, startln, name, unit, resname, value,
295 	    ret_name, ret_namelen, ret_unit, ret_resname, ret_resnamelen,
296 	    ret_value);
297 	if (i == 0)
298 		return 0;
299 	if (unit == NULL)
300 		return ENOENT;
301 	/* If we are still here, search for wildcard matches */
302 	un = -1;
303 	i = res_find(line, startln, name, &un, resname, value,
304 	    ret_name, ret_namelen, ret_unit, ret_resname, ret_resnamelen,
305 	    ret_value);
306 	if (i == 0)
307 		return 0;
308 	return ENOENT;
309 }
310 
311 int
312 resource_int_value(const char *name, int unit, const char *resname, int *result)
313 {
314 	int error;
315 	const char *str;
316 	char *op;
317 	unsigned long val;
318 	int line;
319 
320 	line = 0;
321 	error = resource_find(&line, NULL, name, &unit, resname, NULL,
322 	    NULL, NULL, NULL, NULL, NULL, &str);
323 	if (error)
324 		return error;
325 	if (*str == '\0')
326 		return EFTYPE;
327 	val = strtoul(str, &op, 0);
328 	if (*op != '\0')
329 		return EFTYPE;
330 	*result = val;
331 	return 0;
332 }
333 
334 int
335 resource_long_value(const char *name, int unit, const char *resname,
336     long *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_string_value(const char *name, int unit, const char *resname,
360     const char **result)
361 {
362 	int error;
363 	const char *str;
364 	int line;
365 
366 	line = 0;
367 	error = resource_find(&line, NULL, name, &unit, resname, NULL,
368 	    NULL, NULL, NULL, NULL, NULL, &str);
369 	if (error)
370 		return error;
371 	*result = str;
372 	return 0;
373 }
374 
375 /*
376  * This is a bit nasty, but allows us to not modify the env strings.
377  */
378 static const char *
379 resource_string_copy(const char *s, int len)
380 {
381 	static char stringbuf[256];
382 	static int offset = 0;
383 	const char *ret;
384 
385 	if (len == 0)
386 		len = strlen(s);
387 	if (len > 255)
388 		return NULL;
389 	if ((offset + len + 1) > 255)
390 		offset = 0;
391 	bcopy(s, &stringbuf[offset], len);
392 	stringbuf[offset + len] = '\0';
393 	ret = &stringbuf[offset];
394 	offset += len + 1;
395 	return ret;
396 }
397 
398 /*
399  * err = resource_find_match(&anchor, &name, &unit, resname, value)
400  * Iteratively fetch a list of devices wired "at" something
401  * res and value are restrictions.  eg: "at", "scbus0".
402  * For practical purposes, res = required, value = optional.
403  * *name and *unit are set.
404  * set *anchor to zero before starting.
405  */
406 int
407 resource_find_match(int *anchor, const char **name, int *unit,
408     const char *resname, const char *value)
409 {
410 	const char *found_name;
411 	int found_namelen;
412 	int found_unit;
413 	int ret;
414 	int newln;
415 
416 	newln = *anchor;
417 	ret = resource_find(anchor, &newln, NULL, NULL, resname, value,
418 	    &found_name, &found_namelen, &found_unit, NULL, NULL, NULL);
419 	if (ret == 0) {
420 		*name = resource_string_copy(found_name, found_namelen);
421 		*unit = found_unit;
422 	}
423 	*anchor = newln;
424 	return ret;
425 }
426 
427 
428 /*
429  * err = resource_find_dev(&anchor, name, &unit, res, value);
430  * Iterate through a list of devices, returning their unit numbers.
431  * res and value are optional restrictions.  eg: "at", "scbus0".
432  * *unit is set to the value.
433  * set *anchor to zero before starting.
434  */
435 int
436 resource_find_dev(int *anchor, const char *name, int *unit,
437     const char *resname, const char *value)
438 {
439 	int found_unit;
440 	int newln;
441 	int ret;
442 
443 	newln = *anchor;
444 	ret = resource_find(anchor, &newln, name, NULL, resname, value,
445 	    NULL, NULL, &found_unit, NULL, NULL, NULL);
446 	if (ret == 0) {
447 		*unit = found_unit;
448 	}
449 	*anchor = newln;
450 	return ret;
451 }
452 
453 /*
454  * Check to see if a device is disabled via a disabled hint.
455  */
456 int
457 resource_disabled(const char *name, int unit)
458 {
459 	int error, value;
460 
461 	error = resource_int_value(name, unit, "disabled", &value);
462 	if (error)
463 	       return (0);
464 	return (value);
465 }
466 
467 /*
468  * Clear a value associated with a device by removing it from
469  * the kernel environment.  This only removes a hint for an
470  * exact unit.
471  */
472 int
473 resource_unset_value(const char *name, int unit, const char *resname)
474 {
475 	char varname[128];
476 	const char *retname, *retvalue;
477 	int error, line;
478 	size_t len;
479 
480 	line = 0;
481 	error = resource_find(&line, NULL, name, &unit, resname, NULL,
482 	    &retname, NULL, NULL, NULL, NULL, &retvalue);
483 	if (error)
484 		return (error);
485 
486 	retname -= strlen("hint.");
487 	len = retvalue - retname - 1;
488 	if (len > sizeof(varname) - 1)
489 		return (ENAMETOOLONG);
490 	memcpy(varname, retname, len);
491 	varname[len] = '\0';
492 	return (kern_unsetenv(varname));
493 }
494