/*

	hdtypes.c  -- scan BIOS ROM for hard disk table containing 
	cylinder, head, sector, write precompensation and land zone
	values.  Table seems to be at 0xFE400 on most machines, FWIW.
	Some machines apparently start with a type 2 drive. hence the
	two signatures to search for.  Must be root to open /dev/mem.

	Note that user type (type 47) values are hidden in CMOS RAM.
	Also type 15 will nearly always be unused, as type 15 is used 
	as a flag in CMOS byte 0x12 to indicate drive type > 15 which
	is stored in CMOS 0x19 (and 0x1A for 2nd drive).

	BUGS: Guaranteed to not find anything on newer systems
	with a compressed ROM, or without a HD table whatsoever.
	HD tables are stone age, along with the MFM/RLL drives that
	they supported.  Consider this a stone age program only
	useful on stone age (386/486) computers.

					Paul Gortmaker, 08/2000.

	This code is released under the GPL, blah blah blah...

*/

#define _GNU_SOURCE		/* for memmem */
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>

#define ROM_SIZE 0xffff
#define ROM_OFFSET 0xF0000
#define NR_ENTRIES 47

int
main(int argc, char **argv) {
	/* 
	 * Type 1 disk: 0x132 cyl (306), and 4 heads.
	 * Type 2 disk: 0x267 cyl (615), and 4 heads.
	 */

	unsigned char type1sig[] = {0x32, 0x01, 0x04};
	unsigned char type2sig[] = {0x67, 0x02, 0x04};
	size_t sig_len = 3;

	int i, fd;
	void *bios_rom, *loc;
	char *file = "/dev/mem";
	off_t offset = ROM_OFFSET;

	if (argc > 1) {
		file = argv[1];
		offset = 0;
	}

	fd = open(file, O_RDONLY);
	if (fd == -1) {
		perror("open");
		exit(-1);
	}

	bios_rom = mmap(0, ROM_SIZE, PROT_READ, MAP_SHARED, fd, offset);

	if ((long)bios_rom == -1) {
		perror("memmap");
		exit(-1);
	}

	/* Check std location 1st before scanning whole ROM. */
	loc = memmem(bios_rom+0xe400, ROM_SIZE-0xe400, type1sig, sig_len);
	if (loc == NULL)
		loc = memmem(bios_rom, ROM_SIZE, type1sig, sig_len);

	/* Still nothing? Hrrm. Try type 2... */
	if (loc == NULL) {
		loc = memmem(bios_rom, ROM_SIZE, type2sig, sig_len);
		if (loc == NULL) {
			printf("Couldn't find type 1 or type 2 drive in BIOS ROM - giving up\n");
			exit(-1);
		}
	}

	fprintf(stderr, "\nDisk table at %#x\n", ROM_OFFSET+loc-bios_rom-1);

	printf("\n\tType\tCyl.\tHead\tSect.\tWrite\tLand\n\t\t\t\t\tp-comp\tZone\n");
	printf("\t--------------------------------------------\n");
	for (i=0; i<NR_ENTRIES; i++) {
		unsigned char *drive = loc + 16*i;
		int cyl = drive[1]*0x100 + drive[0];
		int heads = drive[2];
		int wpcom = drive[6]*0x100 + drive[5];
		int lz = drive[13]*0x100 + drive[12];
		int sect = drive[14];

		if (wpcom == 65535)
			wpcom = -1;
		if (cyl == 0)
			printf((i+1 == 15) ? "\t%2d\t-- reserved --\n" :
			       "\t%2d\t-- not used or user defined --\n", i+1);
		else if (cyl < 2048) 
			printf("\t%2d\t%4d\t%2d\t%2d\t%4d\t%4d\n",
				i+1, cyl, heads, sect, wpcom, lz);
	}

	if (munmap(bios_rom, ROM_SIZE) == -1) {
		perror("munmap");
		exit(-1);
	}

	if (close(fd) == -1) {
		perror("close");
		exit(-1);
	}

	return 0;
}
