/*	$OpenBSD: diskprobe.c,v 1.6 1997/10/24 22:22:57 mickey Exp $	*/

/*
 * Copyright (c) 1997 Tobias Weingartner
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by Tobias Weingartner.
 * 4. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 */

#include <sys/param.h>
#include <sys/reboot.h>
#include <sys/disklabel.h>
#include <stand/boot/bootarg.h>
#include <machine/biosvar.h>
#include <lib/libz/zlib.h>
#include "biosdev.h"
#include "libsa.h"

#define MAX_CKSUMLEN 32		/* Max amount of diskblocks used in cksum */

/* Local Prototypes */
static int disksum __P((int));

/* These get passed to kernel */
bios_diskinfo_t bios_diskinfo[16];
u_int32_t bios_cksumlen;

/* Probe for all BIOS disks */
void
diskprobe()
{
	u_int drive, i = 0, rv;
	struct disklabel label;
	u_int unit, type;

	printf("Probing disks:");

	/* Floppies */
	for(drive = 0; drive < 4; drive++) {
		rv = bios_getinfo(drive, &bios_diskinfo[i]);

		if(rv) {
#ifdef BIOS_DEBUG
			printf(" <!fd%u>", drive);
#endif
			break;
		}

		printf(" fd%u", drive);

		/* Fill out best we can - (fd?) */
		bios_diskinfo[i].bsd_dev = MAKEBOOTDEV(2, 0, 0, drive, 0);
		i++;
	}

	/* Hard disks */
	for(drive = 0x80; drive < 0x88; drive++) {
		rv = bios_getinfo(drive, &bios_diskinfo[i]);

		if(rv) {
#ifdef BIOS_DEBUG
			printf(" <!hd%u>", drive);
#endif
			break;
		}

		unit = drive - 0x80;
		printf(" hd%u%s", unit, (bios_diskinfo[i].bios_edd > 0?"+":""));

		/* Try to find the label, to figure out device type */
		if((bios_getdisklabel(drive, &label)) ) {
			printf("*");
			type = 0;	/* XXX let it be IDE */
		} else {
			/* Best guess */
			switch (label.d_type) {
			case DTYPE_SCSI:
				type = 4;
				bios_diskinfo[i].flags |= BDI_GOODLABEL;
				break;

			case DTYPE_ESDI:
			case DTYPE_ST506:
				type = 0;
				bios_diskinfo[i].flags |= BDI_GOODLABEL;
				break;

			default:
				bios_diskinfo[i].flags |= BDI_BADLABEL;
				type = 0;	/* XXX Suggest IDE */
			}
		}

		/* Fill out best we can */
		bios_diskinfo[i].bsd_dev = MAKEBOOTDEV(type, 0, 0, unit, 0);
		i++;
	}

	/* End of list */
	bios_diskinfo[i].bios_number = -1;
	addbootarg(BOOTARG_DISKINFO,
		   (i + 1) * sizeof(bios_diskinfo[0]), bios_diskinfo);

	/* Checksumming of hard disks */
	for (i = 0; disksum(i) && i < MAX_CKSUMLEN; i++)
		;
	bios_cksumlen = i + 1;
	addbootarg(BOOTARG_CKSUMLEN, sizeof(u_int32_t), &bios_cksumlen);

	printf("\n");
}

/* Find info on given BIOS disk */
bios_diskinfo_t *
bios_dklookup(dev)
	register int dev;
{
	register int i;

	for(i = 0; bios_diskinfo[i].bios_number != -1; i++)
		if(bios_diskinfo[i].bios_number == dev)
			return(&bios_diskinfo[i]);

	return(NULL);
}

/*
 * Checksum one more block on all harddrives
 *
 * Use the adler32() function from libz,
 * as it is quick, small, and available.
 */
static int
disksum(blk)
	int blk;
{
	bios_diskinfo_t *bdi, *bd;
	int st, reprobe = 0;
	int hpc, spt, dev;
	char *buf;
	int cyl, head, sect;

	buf = alloca(DEV_BSIZE);
	for (bdi = bios_diskinfo; bdi->bios_number != -1; bdi++) {
		/* Skip this disk if it is not a HD or has had an I/O error */
		if (!(bdi->bios_number & 0x80) || bdi->flags & BDI_INVALID)
			continue;

		dev = bdi->bios_number;
		hpc = bdi->bios_heads;
		spt = bdi->bios_sectors;

		/* Adler32 checksum */
		btochs(blk, cyl, head, sect, hpc, spt);
		st = biosd_io(F_READ, dev, cyl, head, sect, 1, buf);
		if (st) {
			bdi->flags |= BDI_INVALID;
			continue;
		}
		bdi->checksum = adler32(bdi->checksum, buf, DEV_BSIZE);

		for (bd = bios_diskinfo; bd != bdi; bd++)
			if (bdi->checksum == bd->checksum)
				reprobe = 1;
	}

	return (reprobe);
}