xref: /netbsd-src/sys/arch/x86/x86/x86_autoconf.c (revision 271b2a532810f2ad9518c59b4032829ca0caaa05)
1 /*	$NetBSD: x86_autoconf.c,v 1.89 2024/12/06 10:53:41 bouyer Exp $	*/
2 
3 /*-
4  * Copyright (c) 1990 The Regents of the University of California.
5  * All rights reserved.
6  *
7  * This code is derived from software contributed to Berkeley by
8  * William Jolitz.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  * 3. Neither the name of the University nor the names of its contributors
19  *    may be used to endorse or promote products derived from this software
20  *    without specific prior written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  *
34  *	@(#)autoconf.c	7.1 (Berkeley) 5/9/91
35  */
36 
37 #include <sys/cdefs.h>
38 __KERNEL_RCSID(0, "$NetBSD: x86_autoconf.c,v 1.89 2024/12/06 10:53:41 bouyer Exp $");
39 
40 #include <sys/param.h>
41 #include <sys/systm.h>
42 #include <sys/device.h>
43 #include <sys/disklabel.h>
44 #include <sys/conf.h>
45 #include <sys/malloc.h>
46 #include <sys/vnode.h>
47 #include <sys/fcntl.h>
48 #include <sys/disk.h>
49 #include <sys/proc.h>
50 #include <sys/md5.h>
51 #include <sys/kauth.h>
52 
53 #include <machine/autoconf.h>
54 #include <machine/bootinfo.h>
55 #include <machine/pio.h>
56 
57 #include <xen/xen.h>
58 
59 #include <dev/i2c/i2cvar.h>
60 
61 #include "acpica.h"
62 #include "wsdisplay.h"
63 #ifndef XENPV
64 #include "hyperv.h"
65 #endif
66 
67 #if NACPICA > 0
68 #include <dev/acpi/acpivar.h>
69 #endif
70 #if NHYPERV > 0
71 #include <x86/x86/hypervvar.h>
72 #endif
73 
74 struct disklist *x86_alldisks;
75 int x86_ndisks;
76 int x86_found_console;
77 
78 #ifdef DEBUG_GEOM
79 #define DPRINTF(a) printf a
80 #else
81 #define DPRINTF(a)
82 #endif
83 
84 static void
85 dmatch(const char *func, device_t dv, const char *method)
86 {
87 
88 	printf("WARNING: %s: double match for boot device (%s:%s %s:%s)\n",
89 	    func, booted_method, device_xname(booted_device),
90 	    method, device_xname(dv));
91 }
92 
93 static int
94 is_valid_disk(device_t dv)
95 {
96 
97 	if (device_class(dv) != DV_DISK)
98 		return 0;
99 
100 	return (device_is_a(dv, "dk") ||
101 		device_is_a(dv, "sd") ||
102 		device_is_a(dv, "wd") ||
103 		device_is_a(dv, "ld") ||
104 		device_is_a(dv, "xbd") ||
105 		device_is_a(dv, "ed"));
106 }
107 
108 /*
109  * XXX Ugly bit of code.  But, this is the only safe time that the
110  * match between BIOS disks and native disks can be done.
111  */
112 static void
113 matchbiosdisks(void)
114 {
115 	struct btinfo_biosgeom *big;
116 	struct bi_biosgeom_entry *be;
117 	device_t dv;
118 	deviter_t di;
119 	int i, ck, error, m, n;
120 	struct vnode *tv;
121 	char mbr[DEV_BSIZE];
122 	int dklist_size;
123 	int numbig;
124 
125 	if (x86_ndisks)
126 		return;
127 	big = lookup_bootinfo(BTINFO_BIOSGEOM);
128 
129 	numbig = big ? big->num : 0;
130 
131 	/* First, count all native disks. */
132 	for (dv = deviter_first(&di, DEVITER_F_ROOT_FIRST); dv != NULL;
133 	     dv = deviter_next(&di)) {
134 		if (is_valid_disk(dv))
135 			x86_ndisks++;
136 	}
137 	deviter_release(&di);
138 
139 	dklist_size = sizeof(struct disklist) + (x86_ndisks - 1) *
140 	    sizeof(struct nativedisk_info);
141 
142 	/* XXX M_TEMP is wrong */
143 	x86_alldisks = malloc(dklist_size, M_TEMP, M_WAITOK | M_ZERO);
144 	x86_alldisks->dl_nnativedisks = x86_ndisks;
145 	x86_alldisks->dl_nbiosdisks = numbig;
146 	for (i = 0; i < numbig; i++) {
147 		x86_alldisks->dl_biosdisks[i].bi_dev = big->disk[i].dev;
148 		x86_alldisks->dl_biosdisks[i].bi_sec = big->disk[i].sec;
149 		x86_alldisks->dl_biosdisks[i].bi_head = big->disk[i].head;
150 		x86_alldisks->dl_biosdisks[i].bi_cyl = big->disk[i].cyl;
151 		x86_alldisks->dl_biosdisks[i].bi_lbasecs = big->disk[i].totsec;
152 		x86_alldisks->dl_biosdisks[i].bi_flags = big->disk[i].flags;
153 		DPRINTF(("%s: disk %x: flags %x",
154 		    __func__, big->disk[i].dev, big->disk[i].flags));
155 #ifdef BIOSDISK_EXTINFO_V3
156 		DPRINTF((", interface %x, device %llx",
157 		    big->disk[i].interface_path, big->disk[i].device_path));
158 #endif
159 		DPRINTF(("\n"));
160 	}
161 
162 	/* XXX Code duplication from findroot(). */
163 	n = -1;
164 	for (dv = deviter_first(&di, DEVITER_F_ROOT_FIRST); dv != NULL;
165 	     dv = deviter_next(&di)) {
166 		if (!is_valid_disk(dv))
167 			continue;
168 		DPRINTF(("%s: trying to match (%s) %s: ", __func__,
169 		    device_xname(dv), device_cfdata(dv)->cf_name));
170 		n++;
171 		snprintf(x86_alldisks->dl_nativedisks[n].ni_devname,
172 		    sizeof(x86_alldisks->dl_nativedisks[n].ni_devname),
173 		    "%s", device_xname(dv));
174 
175 		if ((tv = opendisk(dv)) == NULL) {
176 			DPRINTF(("cannot open\n"));
177 			continue;
178 		}
179 
180 		error = vn_rdwr(UIO_READ, tv, mbr, DEV_BSIZE, 0, UIO_SYSSPACE,
181 		    IO_NODELOCKED, NOCRED, NULL, NULL);
182 		VOP_CLOSE(tv, FREAD, NOCRED);
183 		vput(tv);
184 		if (error) {
185 			DPRINTF(("MBR read failure %d\n", error));
186 			continue;
187 		}
188 
189 		for (ck = i = 0; i < DEV_BSIZE; i++)
190 			ck += mbr[i];
191 		for (m = i = 0; i < numbig; i++) {
192 			be = &big->disk[i];
193 			if (be->flags & BI_GEOM_INVALID)
194 				continue;
195 			DPRINTF(("matched with %d dev ck %x bios ck %x\n",
196 			    i, ck, be->cksum));
197 			if (be->cksum == ck && memcmp(&mbr[MBR_PART_OFFSET],
198 			    be->mbrparts, MBR_PART_COUNT
199 			    * sizeof(struct mbr_partition)) == 0) {
200 				DPRINTF(("%s: matched BIOS disk %x with %s\n",
201 				    __func__, be->dev, device_xname(dv)));
202 				x86_alldisks->dl_nativedisks[n].
203 				    ni_biosmatches[m++] = i;
204 			}
205 		}
206 		x86_alldisks->dl_nativedisks[n].ni_nmatches = m;
207 	}
208 	deviter_release(&di);
209 }
210 
211 /*
212  * Helper function for findroot():
213  * Return non-zero if wedge device matches bootinfo.
214  */
215 static int
216 match_bootwedge(device_t dv, struct btinfo_bootwedge *biw)
217 {
218 	MD5_CTX ctx;
219 	struct vnode *tmpvn;
220 	int error;
221 	uint8_t bf[DEV_BSIZE];
222 	uint8_t hash[16];
223 	int found = 0;
224 	daddr_t blk;
225 	uint64_t nblks;
226 
227 	/*
228 	 * If the boot loader didn't specify the sector, abort.
229 	 */
230 	if (biw->matchblk == -1) {
231 		DPRINTF(("%s: no sector specified for %s\n", __func__,
232 			device_xname(dv)));
233 		return 0;
234 	}
235 
236 	if ((tmpvn = opendisk(dv)) == NULL) {
237 		DPRINTF(("%s: can't open %s\n", __func__, device_xname(dv)));
238 		return 0;
239 	}
240 
241 	MD5Init(&ctx);
242 	for (blk = biw->matchblk, nblks = biw->matchnblks;
243 	     nblks != 0; nblks--, blk++) {
244 		error = vn_rdwr(UIO_READ, tmpvn, (void *) bf,
245 		    sizeof(bf), blk * DEV_BSIZE, UIO_SYSSPACE,
246 		    IO_NODELOCKED, NOCRED, NULL, NULL);
247 		if (error) {
248 			if (error != EINVAL) {
249 				aprint_error("%s: unable to read block %"
250 				    PRId64 " " "of dev %s (%d)\n", __func__,
251 				    blk, device_xname(dv), error);
252 			}
253 			goto closeout;
254 		}
255 		MD5Update(&ctx, bf, sizeof(bf));
256 	}
257 	MD5Final(hash, &ctx);
258 
259 	/* Compare with the provided hash. */
260 	found = memcmp(biw->matchhash, hash, sizeof(hash)) == 0;
261 	DPRINTF(("%s: %s found=%d\n", __func__, device_xname(dv), found));
262 
263  closeout:
264 	VOP_CLOSE(tmpvn, FREAD, NOCRED);
265 	vput(tmpvn);
266 	return found;
267 }
268 
269 /*
270  * Helper function for findroot():
271  * Return non-zero if disk device matches bootinfo.
272  */
273 static int
274 match_bootdisk(device_t dv, struct btinfo_bootdisk *bid)
275 {
276 	struct vnode *tmpvn;
277 	int error;
278 	struct disklabel label;
279 	int found = 0;
280 
281 	if (device_is_a(dv, "dk")) {
282 		DPRINTF(("%s: dk %s\n", __func__, device_xname(dv)));
283 		return 0;
284 	}
285 
286 	/*
287 	 * A disklabel is required here.  The boot loader doesn't refuse
288 	 * to boot from a disk without a label, but this is normally not
289 	 * wanted.
290 	 */
291 	if (bid->labelsector == -1) {
292 		DPRINTF(("%s: no label %s\n", __func__, device_xname(dv)));
293 		return 0;
294 	}
295 
296 	if ((tmpvn = opendisk(dv)) == NULL) {
297 		DPRINTF(("%s: can't open %s\n", __func__, device_xname(dv)));
298 		return 0;
299 	}
300 
301 	VOP_UNLOCK(tmpvn);
302 	error = VOP_IOCTL(tmpvn, DIOCGDINFO, &label, FREAD, NOCRED);
303 	vn_lock(tmpvn, LK_EXCLUSIVE | LK_RETRY);
304 	if (error) {
305 		/*
306 		 * XXX Can't happen -- open() would have errored out
307 		 * or faked one up.
308 		 */
309 		printf("%s: can't get label for dev %s (%d)\n", __func__,
310 		    device_xname(dv), error);
311 		goto closeout;
312 	}
313 
314 	/* Compare with our data. */
315 	if (label.d_type == bid->label.type &&
316 	    label.d_checksum == bid->label.checksum &&
317 	    strncmp(label.d_packname, bid->label.packname, 16) == 0)
318 		found = 1;
319 
320 	DPRINTF(("%s: %s found=%d\n", __func__, device_xname(dv), found));
321  closeout:
322 	VOP_CLOSE(tmpvn, FREAD, NOCRED);
323 	vput(tmpvn);
324 	return found;
325 }
326 
327 /*
328  * Attempt to find the device from which we were booted.  If we can do so,
329  * and not instructed not to do so, change rootdev to correspond to the
330  * load device.
331  */
332 static void
333 findroot(void)
334 {
335 	struct btinfo_rootdevice *biv;
336 	struct btinfo_bootdisk *bid;
337 	struct btinfo_bootwedge *biw;
338 	struct btinfo_biosgeom *big;
339 	device_t dv;
340 	deviter_t di;
341 	static char bootspecbuf[sizeof(biv->devname)+1];
342 
343 	if (booted_device)
344 		return;
345 
346 	if (lookup_bootinfo(BTINFO_NETIF) != NULL) {
347 		/*
348 		 * We got netboot interface information, but device_register()
349 		 * failed to match it to a configured device.  Boot disk
350 		 * information cannot be present at the same time, so give
351 		 * up.
352 		 */
353 		printf("%s: netboot interface not found.\n", __func__);
354 		return;
355 	}
356 
357 	if ((biv = lookup_bootinfo(BTINFO_ROOTDEVICE)) != NULL) {
358 		for (dv = deviter_first(&di, DEVITER_F_ROOT_FIRST);
359 		     dv != NULL;
360 		     dv = deviter_next(&di)) {
361 			cfdata_t cd;
362 			size_t len;
363 
364 			if (device_class(dv) != DV_DISK)
365 				continue;
366 
367 			cd = device_cfdata(dv);
368 			len = strlen(cd->cf_name);
369 
370 			if (strncmp(cd->cf_name, biv->devname, len) == 0 &&
371 			    biv->devname[len] - '0' == device_unit(dv)) {
372 				booted_device = dv;
373 				booted_method = "bootinfo/rootdevice";
374 				booted_partition = biv->devname[len + 1] - 'a';
375 				booted_nblks = 0;
376 				break;
377 			}
378 		}
379 		DPRINTF(("%s: BTINFO_ROOTDEVICE %s\n", __func__,
380 		    booted_device ? device_xname(booted_device) : "not found"));
381 		deviter_release(&di);
382 		if (dv != NULL)
383 			return;
384 
385 		if (biv->devname[0] != '\0') {
386 			strlcpy(bootspecbuf, biv->devname, sizeof(bootspecbuf));
387 			bootspec = bootspecbuf;
388 			return;
389 		}
390 	}
391 
392 	bid = lookup_bootinfo(BTINFO_BOOTDISK);
393 	biw = lookup_bootinfo(BTINFO_BOOTWEDGE);
394 
395 	if (biw != NULL) {
396 		/*
397 		 * Scan all disk devices for ones that match the passed data.
398 		 * Don't break if one is found, to get possible multiple
399 		 * matches - for problem tracking.  Use the first match anyway
400 		 * because lower devices numbers are more likely to be the
401 		 * boot device.
402 		 */
403 		for (dv = deviter_first(&di, DEVITER_F_ROOT_FIRST);
404 		     dv != NULL;
405 		     dv = deviter_next(&di)) {
406 			if (is_valid_disk(dv)) {
407 				/*
408 				 * Don't trust BIOS device numbers, try
409 				 * to match the information passed by the
410 				 * boot loader instead.
411 				 */
412 				if ((biw->biosdev & 0x80) == 0 ||
413 				    match_bootwedge(dv, biw) == 0)
414 					continue;
415 				goto bootwedge_found;
416 			}
417 
418 			continue;
419  bootwedge_found:
420 			if (booted_device) {
421 				dmatch(__func__, dv, "bootinfo/bootwedge");
422 				continue;
423 			}
424 			booted_device = dv;
425 			booted_method = "bootinfo/bootwedge";
426 			booted_partition = bid != NULL ? bid->partition : 0;
427 			booted_nblks = biw->nblks;
428 			booted_startblk = biw->startblk;
429 		}
430 		deviter_release(&di);
431 
432 		DPRINTF(("%s: BTINFO_BOOTWEDGE %s\n", __func__,
433 		    booted_device ? device_xname(booted_device) : "not found"));
434 		if (booted_nblks)
435 			return;
436 	}
437 
438 	if (bid != NULL) {
439 		/*
440 		 * Scan all disk devices for ones that match the passed data.
441 		 * Don't break if one is found, to get possible multiple
442 		 * matches - for problem tracking.  Use the first match anyway
443 		 * because lower device numbers are more likely to be the
444 		 * boot device.
445 		 */
446 		for (dv = deviter_first(&di, DEVITER_F_ROOT_FIRST);
447 		     dv != NULL;
448 		     dv = deviter_next(&di)) {
449 
450 			if (device_is_a(dv, "fd") &&
451 			    device_class(dv) == DV_DISK) {
452 				/*
453 				 * Assume the configured unit number matches
454 				 * the BIOS device number.  (This is the old
455 				 * behavior.)  Needs some ideas how to handle
456 				 * the BIOS's "swap floppy drive" options.
457 				 */
458 				/* XXX device_unit() abuse */
459 				if ((bid->biosdev & 0x80) != 0 ||
460 				    device_unit(dv) != bid->biosdev)
461 					continue;
462 				goto bootdisk_found;
463 			}
464 
465 			if (is_valid_disk(dv)) {
466 				/*
467 				 * Don't trust BIOS device numbers, try
468 				 * to match the information passed by the
469 				 * boot loader instead.
470 				 */
471 				if ((bid->biosdev & 0x80) == 0 ||
472 				    match_bootdisk(dv, bid) == 0)
473 					continue;
474 				goto bootdisk_found;
475 			}
476 
477 			continue;
478  bootdisk_found:
479 			if (booted_device) {
480 				dmatch(__func__, dv, "bootinfo/bootdisk");
481 				continue;
482 			}
483 			booted_device = dv;
484 			booted_method = "bootinfo/bootdisk";
485 			booted_partition = bid->partition;
486 			booted_nblks = 0;
487 		}
488 		deviter_release(&di);
489 
490 		DPRINTF(("%s: BTINFO_BOOTDISK %s\n", __func__,
491 		    booted_device ? device_xname(booted_device) : "not found"));
492 		if (booted_device)
493 			return;
494 
495 		/*
496 		 * No booted device found; check CD-ROM boot at last.
497 		 *
498 		 * Our bootloader assumes CD-ROM boot if biosdev is larger
499 		 * or equal than the number of hard drives recognized by the
500 		 * BIOS. The number of drives can be found in BTINFO_BIOSGEOM
501 		 * here. For example, if we have wd0, wd1, and cd0:
502 		 *
503 		 *	big->num = 2 (for wd0 and wd1)
504 		 *	bid->biosdev = 0x80 (wd0)
505 		 *	bid->biosdev = 0x81 (wd1)
506 		 *	bid->biosdev = 0x82 (cd0)
507 		 *
508 		 * See src/sys/arch/i386/stand/boot/devopen.c and
509 		 * src/sys/arch/i386/stand/lib/bootinfo_biosgeom.c .
510 		 */
511 		if ((big = lookup_bootinfo(BTINFO_BIOSGEOM)) != NULL &&
512 		    bid->biosdev >= 0x80 + big->num) {
513 			/*
514 			 * XXX
515 			 * There is no proper way to detect which unit is
516 			 * recognized as a bootable CD-ROM drive by the BIOS.
517 			 * Assume the first unit is the one.
518 			 */
519 			for (dv = deviter_first(&di, DEVITER_F_ROOT_FIRST);
520 			     dv != NULL;
521 			     dv = deviter_next(&di)) {
522 				if (device_class(dv) == DV_DISK &&
523 				    device_is_a(dv, "cd")) {
524 					booted_device = dv;
525 					booted_method = "bootinfo/biosgeom";
526 					booted_partition = 0;
527 					booted_nblks = 0;
528 					break;
529 				}
530 			}
531 			deviter_release(&di);
532 			DPRINTF(("%s: BTINFO_BIOSGEOM %s\n", __func__,
533 			    booted_device ? device_xname(booted_device) :
534 			    "not found"));
535 		}
536 	}
537 }
538 
539 void
540 cpu_bootconf(void)
541 {
542 #ifdef XEN
543 	if (vm_guest_is_pvh()) {
544 		xen_bootconf();
545 		return;
546 	}
547 #endif
548 	findroot();
549 	matchbiosdisks();
550 }
551 
552 void
553 cpu_rootconf(void)
554 {
555 	cpu_bootconf();
556 
557 	aprint_normal("boot device: %s\n",
558 	    booted_device ? device_xname(booted_device) : "<unknown>");
559 	rootconf();
560 }
561 
562 void
563 device_register(device_t dev, void *aux)
564 {
565 	device_t isaboot, pciboot;
566 
567 	/*
568 	 * The Intel Integrated Memory Controller has a built-in i2c
569 	 * controller that's rather limited in capability; it is intended
570 	 * only for reading memory module EERPOMs and sensors.
571 	 */
572 	if (device_is_a(dev, "iic") &&
573 	    device_is_a(device_parent(dev), "imcsmb")) {
574 		static const char *imcsmb_device_permitlist[] = {
575 			"spdmem",
576 			"sdtemp",
577 			NULL,
578 		};
579 		prop_array_t permitlist = prop_array_create();
580 		prop_dictionary_t props = device_properties(dev);
581 		int i;
582 
583 		for (i = 0; imcsmb_device_permitlist[i] != NULL; i++) {
584 			prop_string_t pstr = prop_string_create_nocopy(
585 			    imcsmb_device_permitlist[i]);
586 			(void) prop_array_add(permitlist, pstr);
587 			prop_object_release(pstr);
588 		}
589 		(void) prop_dictionary_set(props,
590 					   I2C_PROP_INDIRECT_DEVICE_PERMITLIST,
591 					   permitlist);
592 		(void) prop_dictionary_set_string_nocopy(props,
593 					   I2C_PROP_INDIRECT_PROBE_STRATEGY,
594 					   I2C_PROBE_STRATEGY_NONE);
595 	}
596 
597 	device_acpi_register(dev, aux);
598 
599 	isaboot = device_isa_register(dev, aux);
600 	pciboot = device_pci_register(dev, aux);
601 #if NHYPERV > 0
602 	(void)device_hyperv_register(dev, aux);
603 #endif
604 
605 	if (isaboot == NULL && pciboot == NULL)
606 		return;
607 
608 	if (booted_device != NULL) {
609 		/* XXX should be a panic() */
610 		dmatch(__func__, dev, "device/register");
611 	} else {
612 		booted_device = (isaboot != NULL) ? isaboot : pciboot;
613 		booted_method = "device/register";
614 	}
615 }
616