1/*	$OpenBSD: diskprobe.c,v 1.49 2024/06/04 20:31:35 krw Exp $	*/
2
3/*
4 * Copyright (c) 1997 Tobias Weingartner
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 ``AS IS'' AND ANY EXPRESS OR
17 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS 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
30/* We want the disk type names from disklabel.h */
31#undef DKTYPENAMES
32
33#include <sys/param.h>
34#include <sys/queue.h>
35#include <sys/reboot.h>
36#include <sys/disklabel.h>
37#include <sys/hibernate.h>
38
39#include <lib/libz/zlib.h>
40#include <machine/biosvar.h>
41#include <stand/boot/bootarg.h>
42
43#include "disk.h"
44#include "biosdev.h"
45#include "libsa.h"
46
47#ifdef SOFTRAID
48#include "softraid_i386.h"
49#endif
50#ifdef EFIBOOT
51#include "efidev.h"
52#endif
53
54#define MAX_CKSUMLEN MAXBSIZE / DEV_BSIZE	/* Max # of blks to cksum */
55
56/* Local Prototypes */
57static int disksum(int);
58
59int bootdev_has_hibernate(void);		/* export for loadfile() */
60
61/* List of disk devices we found/probed */
62struct disklist_lh disklist;
63
64/* Pointer to boot device */
65struct diskinfo *bootdev_dip;
66
67extern int debug;
68extern int bios_bootdev;
69extern int bios_cddev;
70
71#ifndef EFIBOOT
72static void
73diskinfo_init(struct diskinfo *dip)
74{
75	bzero(dip, sizeof(*dip));
76	dip->diskio = biosd_diskio;
77	dip->strategy = biosstrategy;
78}
79
80/* Probe for all BIOS floppies */
81static void
82floppyprobe(void)
83{
84	struct diskinfo *dip;
85	int i;
86
87	/* Floppies */
88	for (i = 0; i < 4; i++) {
89		dip = alloc(sizeof(struct diskinfo));
90		diskinfo_init(dip);
91
92		if (bios_getdiskinfo(i, &dip->bios_info)) {
93#ifdef BIOS_DEBUG
94			if (debug)
95				printf(" <!fd%u>", i);
96#endif
97			free(dip, sizeof(*dip));
98			break;
99		}
100
101		printf(" fd%u", i);
102
103		/* Fill out best we can - (fd?) */
104		dip->bios_info.bsd_dev = MAKEBOOTDEV(2, 0, 0, i, RAW_PART);
105
106		/*
107		 * Delay reading the disklabel until we're sure we want
108		 * to boot from the floppy. Doing this avoids a delay
109		 * (sometimes very long) when trying to read the label
110		 * and the drive is unplugged.
111		 */
112		dip->bios_info.flags |= BDI_BADLABEL;
113
114		/* Add to queue of disks */
115		TAILQ_INSERT_TAIL(&disklist, dip, list);
116	}
117}
118
119/* Probe for all BIOS hard disks */
120static void
121hardprobe(void)
122{
123	struct diskinfo *dip;
124	int i;
125	u_int bsdunit, type;
126	u_int scsi = 0, ide = 0;
127	const char *dc = (const char *)((0x40 << 4) + 0x75);
128
129	/* Hard disks */
130	for (i = 0x80; i < (0x80 + *dc); i++) {
131		dip = alloc(sizeof(struct diskinfo));
132		diskinfo_init(dip);
133
134		if (bios_getdiskinfo(i, &dip->bios_info)) {
135#ifdef BIOS_DEBUG
136			if (debug)
137				printf(" <!hd%u>", i&0x7f);
138#endif
139			free(dip, sizeof(*dip));
140			break;
141		}
142
143		printf(" hd%u%s", i&0x7f, (dip->bios_info.bios_edd > 0?"+":""));
144
145		/* Try to find the label, to figure out device type */
146		if ((bios_getdisklabel(&dip->bios_info, &dip->disklabel)) ) {
147			printf("*");
148			bsdunit = ide++;
149			type = 0;	/* XXX let it be IDE */
150		} else {
151			/* Best guess */
152			switch (dip->disklabel.d_type) {
153			case DTYPE_SCSI:
154				type = 4;
155				bsdunit = scsi++;
156				dip->bios_info.flags |= BDI_GOODLABEL;
157				break;
158
159			case DTYPE_ESDI:
160			case DTYPE_ST506:
161				type = 0;
162				bsdunit = ide++;
163				dip->bios_info.flags |= BDI_GOODLABEL;
164				break;
165
166			default:
167				dip->bios_info.flags |= BDI_BADLABEL;
168				type = 0;	/* XXX Suggest IDE */
169				bsdunit = ide++;
170			}
171		}
172
173		dip->bios_info.checksum = 0; /* just in case */
174		/* Fill out best we can */
175		dip->bsddev = dip->bios_info.bsd_dev =
176		    MAKEBOOTDEV(type, 0, 0, bsdunit, RAW_PART);
177		check_hibernate(dip);
178
179		/* Add to queue of disks */
180		TAILQ_INSERT_TAIL(&disklist, dip, list);
181	}
182}
183#endif
184
185#ifdef EFIBOOT
186static void
187efi_hardprobe(void)
188{
189	int		 n;
190	struct diskinfo	*dip, *dipt;
191	u_int		 bsdunit, type = 0;
192	u_int		 scsi= 0, ide = 0;
193	extern struct disklist_lh
194			 efi_disklist;
195
196	n = 0;
197	TAILQ_FOREACH_SAFE(dip, &efi_disklist, list, dipt) {
198		TAILQ_REMOVE(&efi_disklist, dip, list);
199		printf(" hd%u", n);
200
201		dip->bios_info.bios_number = 0x80 | n;
202		/* Try to find the label, to figure out device type */
203		if ((efi_getdisklabel(dip->efi_info, &dip->disklabel))) {
204			printf("*");
205			bsdunit = ide++;
206		} else {
207			/* Best guess */
208			switch (dip->disklabel.d_type) {
209			case DTYPE_SCSI:
210				type = 4;
211				bsdunit = scsi++;
212				dip->bios_info.flags |= BDI_GOODLABEL;
213				break;
214
215			case DTYPE_ESDI:
216			case DTYPE_ST506:
217				type = 0;
218				bsdunit = ide++;
219				dip->bios_info.flags |= BDI_GOODLABEL;
220				break;
221
222			default:
223				dip->bios_info.flags |= BDI_BADLABEL;
224				type = 0;	/* XXX Suggest IDE */
225				bsdunit = ide++;
226			}
227		}
228
229		dip->bios_info.checksum = 0; /* just in case */
230		/* Fill out best we can */
231		dip->bsddev = dip->bios_info.bsd_dev =
232		    MAKEBOOTDEV(type, 0, 0, bsdunit, RAW_PART);
233		check_hibernate(dip);
234
235		/* Add to queue of disks */
236		TAILQ_INSERT_TAIL(&disklist, dip, list);
237		n++;
238	}
239}
240#endif
241
242/* Probe for all BIOS supported disks */
243u_int32_t bios_cksumlen;
244void
245diskprobe(void)
246{
247	struct diskinfo *dip;
248	int i;
249
250	/* These get passed to kernel */
251	bios_diskinfo_t *bios_diskinfo;
252
253	/* Init stuff */
254	TAILQ_INIT(&disklist);
255
256#ifndef EFIBOOT
257	/* Do probes */
258	floppyprobe();
259#ifdef BIOS_DEBUG
260	if (debug)
261		printf(";");
262#endif
263	hardprobe();
264#else
265	efi_hardprobe();
266#endif
267
268#ifdef SOFTRAID
269	srprobe();
270#endif
271
272	/* Checksumming of hard disks */
273	for (i = 0; disksum(i++) && i < MAX_CKSUMLEN; )
274		;
275	bios_cksumlen = i;
276
277	/* Get space for passing bios_diskinfo stuff to kernel */
278	for (i = 0, dip = TAILQ_FIRST(&disklist); dip;
279	    dip = TAILQ_NEXT(dip, list))
280		i++;
281	bios_diskinfo = alloc(++i * sizeof(bios_diskinfo_t));
282
283	/* Copy out the bios_diskinfo stuff */
284	for (i = 0, dip = TAILQ_FIRST(&disklist); dip;
285	    dip = TAILQ_NEXT(dip, list))
286		bios_diskinfo[i++] = dip->bios_info;
287
288	bios_diskinfo[i++].bios_number = -1;
289	/* Register for kernel use */
290	addbootarg(BOOTARG_CKSUMLEN, sizeof(u_int32_t), &bios_cksumlen);
291	addbootarg(BOOTARG_DISKINFO, i * sizeof(bios_diskinfo_t),
292	    bios_diskinfo);
293}
294
295#ifndef EFIBOOT
296void
297cdprobe(void)
298{
299	struct diskinfo *dip;
300	int cddev = bios_cddev & 0xff;
301
302	/* Another BIOS boot device... */
303
304	if (bios_cddev == -1)			/* Not been set, so don't use */
305		return;
306
307	dip = alloc(sizeof(struct diskinfo));
308	diskinfo_init(dip);
309
310#if 0
311	if (bios_getdiskinfo(cddev, &dip->bios_info)) {
312		printf(" <!cd0>");	/* XXX */
313		free(dip, sizeof(*dip));
314		return;
315	}
316#endif
317
318	printf(" cd0");
319
320	dip->bios_info.bios_number = cddev;
321	dip->bios_info.bios_edd = 1;		/* Use the LBA calls */
322	dip->bios_info.flags |= BDI_GOODLABEL | BDI_EL_TORITO;
323	dip->bios_info.checksum = 0;		 /* just in case */
324	dip->bios_info.bsd_dev =
325	    MAKEBOOTDEV(6, 0, 0, 0, RAW_PART);
326
327	/* Create an imaginary disk label */
328	dip->disklabel.d_secsize = 2048;
329	dip->disklabel.d_ntracks = 1;
330	dip->disklabel.d_nsectors = 100;
331	dip->disklabel.d_ncylinders = 1;
332	dip->disklabel.d_secpercyl = dip->disklabel.d_ntracks *
333	    dip->disklabel.d_nsectors;
334
335	strncpy(dip->disklabel.d_typename, "ATAPI CD-ROM",
336	    sizeof(dip->disklabel.d_typename));
337	dip->disklabel.d_type = DTYPE_ATAPI;
338
339	strncpy(dip->disklabel.d_packname, "fictitious",
340	    sizeof(dip->disklabel.d_packname));
341	DL_SETDSIZE(&dip->disklabel, 100);
342
343	/* 'a' partition covering the "whole" disk */
344	DL_SETPOFFSET(&dip->disklabel.d_partitions[0], 0);
345	DL_SETPSIZE(&dip->disklabel.d_partitions[0], 100);
346	dip->disklabel.d_partitions[0].p_fstype = FS_UNUSED;
347
348	/* The raw partition is special */
349	DL_SETPOFFSET(&dip->disklabel.d_partitions[RAW_PART], 0);
350	DL_SETPSIZE(&dip->disklabel.d_partitions[RAW_PART], 100);
351	dip->disklabel.d_partitions[RAW_PART].p_fstype = FS_UNUSED;
352
353	dip->disklabel.d_npartitions = MAXPARTITIONS;
354
355	dip->disklabel.d_magic = DISKMAGIC;
356	dip->disklabel.d_magic2 = DISKMAGIC;
357	dip->disklabel.d_checksum = dkcksum(&dip->disklabel);
358
359	/* Add to queue of disks */
360	TAILQ_INSERT_TAIL(&disklist, dip, list);
361}
362#endif
363
364
365/* Find info on given BIOS disk */
366struct diskinfo *
367dklookup(int dev)
368{
369	struct diskinfo *dip;
370
371	for (dip = TAILQ_FIRST(&disklist); dip; dip = TAILQ_NEXT(dip, list))
372		if (dip->bios_info.bios_number == dev)
373			return dip;
374
375	return NULL;
376}
377
378void
379dump_diskinfo(void)
380{
381	struct diskinfo *dip;
382
383	printf("Disk\tBIOS#\tType\tCyls\tHeads\tSecs\tFlags\tChecksum\n");
384	for (dip = TAILQ_FIRST(&disklist); dip; dip = TAILQ_NEXT(dip, list)) {
385		bios_diskinfo_t *bdi = &dip->bios_info;
386		int d = bdi->bios_number;
387		int u = d & 0x7f;
388		char c;
389
390		if (bdi->flags & BDI_EL_TORITO) {
391			c = 'c';
392			u = 0;
393		} else {
394		    	c = (d & 0x80) ? 'h' : 'f';
395		}
396
397		printf("%cd%d\t0x%x\t%s\t%d\t%d\t%d\t0x%x\t0x%x\n",
398		    c, u, d,
399		    (bdi->flags & BDI_BADLABEL)?"*none*":"label",
400		    bdi->bios_cylinders, bdi->bios_heads, bdi->bios_sectors,
401		    bdi->flags, bdi->checksum);
402	}
403}
404
405/* Find BIOS portion on given BIOS disk
406 * XXX - Use dklookup() instead.
407 */
408bios_diskinfo_t *
409bios_dklookup(int dev)
410{
411	struct diskinfo *dip;
412
413	dip = dklookup(dev);
414	if (dip)
415		return &dip->bios_info;
416
417	return NULL;
418}
419
420/*
421 * Checksum one more block on all harddrives
422 *
423 * Use the adler32() function from libz,
424 * as it is quick, small, and available.
425 */
426int
427disksum(int blk)
428{
429	struct diskinfo *dip, *dip2;
430	int st, reprobe = 0;
431	char buf[DEV_BSIZE];
432
433	for (dip = TAILQ_FIRST(&disklist); dip; dip = TAILQ_NEXT(dip, list)) {
434		bios_diskinfo_t *bdi = &dip->bios_info;
435
436		/* Skip this disk if it is not a HD or has had an I/O error */
437		if (!(bdi->bios_number & 0x80) || bdi->flags & BDI_INVALID)
438			continue;
439
440		/* Adler32 checksum */
441		st = dip->diskio(F_READ, dip, blk, 1, buf);
442		if (st) {
443			bdi->flags |= BDI_INVALID;
444			continue;
445		}
446		bdi->checksum = adler32(bdi->checksum, buf, DEV_BSIZE);
447
448		for (dip2 = TAILQ_FIRST(&disklist); dip2 != dip;
449				dip2 = TAILQ_NEXT(dip2, list)) {
450			bios_diskinfo_t *bd = &dip2->bios_info;
451			if ((bd->bios_number & 0x80) &&
452			    !(bd->flags & BDI_INVALID) &&
453			    bdi->checksum == bd->checksum)
454				reprobe = 1;
455		}
456	}
457
458	return reprobe;
459}
460
461int
462bootdev_has_hibernate(void)
463{
464	return ((bootdev_dip->bios_info.flags & BDI_HIBVALID)? 1 : 0);
465}
466
467void
468check_hibernate(struct diskinfo *dip)
469{
470	uint8_t buf[DEV_BSIZE];
471	daddr_t sec;
472	int error;
473	union hibernate_info *hib = (union hibernate_info *)&buf;
474
475	/* read hibernate */
476	if (dip->disklabel.d_partitions[1].p_fstype != FS_SWAP ||
477	    DL_GETPSIZE(&dip->disklabel.d_partitions[1]) == 0)
478		return;
479
480	sec = DL_GETPOFFSET(&dip->disklabel.d_partitions[1]) +
481	    DL_GETPSIZE(&dip->disklabel.d_partitions[1]) - 1;
482
483	error = dip->strategy(dip, F_READ, DL_SECTOBLK(&dip->disklabel, sec),
484	    sizeof buf, &buf, NULL);
485	if (error == 0 && hib->magic == HIBERNATE_MAGIC)
486		dip->bios_info.flags |= BDI_HIBVALID; /* Hibernate present */
487}
488