/*
 * Copyright 2003-2006, Axel Dörfler, axeld@pinc-software.de.
 * Distributed under the terms of the MIT License.
 */
 
 
#include "bios.h"
 
#include <KernelExport.h>
#include <boot/platform.h>
#include <boot/partitions.h>
#include <boot/stdio.h>
#include <boot/stage2.h>
 
#include <string.h>
 
//#define TRACE_DEVICES
#ifdef TRACE_DEVICES
#	define TRACE(x) dprintf x
#else
#	define TRACE(x) ;
#endif
 
 
// exported from shell.S
extern uint8 gBootedFromImage;
extern uint8 gBootDriveID;
extern uint32 gBootPartitionOffset;
 
// int 0x13 definitions
#define BIOS_RESET_DISK_SYSTEM			0x0000
#define BIOS_READ						0x0200
#define BIOS_GET_DRIVE_PARAMETERS		0x0800
#define BIOS_IS_EXT_PRESENT				0x4100
#define BIOS_EXT_READ					0x4200
#define BIOS_EXT_WRITE					0x4300
#define BIOS_GET_EXT_DRIVE_PARAMETERS	0x4800
#define BIOS_BOOT_CD_GET_STATUS			0x4b01
 
struct real_addr {
	uint16	offset;
	uint16	segment;
};
 
struct disk_address_packet {
	uint8		size;
	uint8		reserved;
	uint16		number_of_blocks;
	uint32		buffer;
	uint64		lba;
} _PACKED;
 
static const uint16 kParametersSizeVersion1 = 0x1a;
static const uint16 kParametersSizeVersion2 = 0x1e;
static const uint16 kParametersSizeVersion3 = 0x42;
 
static const uint16 kDevicePathSignature = 0xbedd;
 
struct drive_parameters {
	uint16		parameters_size;
	uint16		flags;
	uint32		cylinders;
	uint32		heads;
	uint32		sectors_per_track;
	uint64		sectors;
	uint16		bytes_per_sector;
	/* edd 2.0 */
	real_addr	device_table;
	/* edd 3.0 */
	uint16		device_path_signature;
	uint8		device_path_size;
	uint8		reserved1[3];
	char		host_bus[4];
	char		interface_type[8];
	union {
		struct {
			uint16	base_address;
		} legacy;
		struct {
			uint8	bus;
			uint8	slot;
			uint8	function;
		} pci;
		uint8		reserved[8];
	} interface;
	union {
		struct {
			uint8	slave;
		} ata;
		struct {
			uint8	slave;
			uint8	logical_unit;
		} atapi;
		struct {
			uint8	logical_unit;
		} scsi;
		struct {
			uint8	tbd;
		} usb;
		struct {
			uint64	guid;
		} firewire;
		struct {
			uint64	wwd;
		} fibre;
	} device;
	uint8		reserved2;
	uint8		checksum;
} _PACKED;
 
struct device_table {
	uint16	base_address;
	uint16	control_port_address;
	uint8	_reserved1 : 4;
	uint8	is_slave : 1;
	uint8	_reserved2 : 1;
	uint8	lba_enabled : 1;
} _PACKED;
 
struct specification_packet {
	uint8	size;
	uint8	media_type;
	uint8	drive_number;
	uint8	controller_index;
	uint32	start_emulation;
	uint16	device_specification;
	uint8	_more_[9];
} _PACKED;
 
class BIOSDrive : public Node {
	public:
		BIOSDrive(uint8 driveID);
		virtual ~BIOSDrive();
 
		status_t InitCheck() const;
 
		virtual ssize_t ReadAt(void *cookie, off_t pos, void *buffer, size_t bufferSize);
		virtual ssize_t WriteAt(void *cookie, off_t pos, const void *buffer, size_t bufferSize);
 
		virtual off_t Size() const;
 
		uint32 BlockSize() const { return fBlockSize; }
 
		status_t FillIdentifier();
 
		bool HasParameters() const { return fHasParameters; }
		const drive_parameters &Parameters() const { return fParameters; }
 
		disk_identifier &Identifier() { return fIdentifier; }
		uint8 DriveID() const { return fDriveID; }
 
	protected:
		uint8	fDriveID;
		bool	fLBA;
		uint64	fSize;
		uint32	fBlockSize;
		bool	fHasParameters;
		drive_parameters fParameters;
		disk_identifier fIdentifier;
};
 
 
static bool sBlockDevicesAdded = false;
 
 
static void
check_cd_boot(BIOSDrive *drive)
{
	gBootVolume.SetInt32(BOOT_METHOD, BOOT_METHOD_HARD_DISK);
 
	if (drive->DriveID() != 0)
		return;
 
	struct bios_regs regs;
	regs.eax = BIOS_BOOT_CD_GET_STATUS;
	regs.edx = 0;
	regs.esi = kDataSegmentScratch;
	call_bios(0x13, &regs);
 
	if ((regs.flags & CARRY_FLAG) != 0)
		return;
 
	// we obviously were booted from CD!
 
	specification_packet *packet = (specification_packet *)kDataSegmentScratch;
	if (packet->media_type != 0)
		gBootVolume.SetInt32(BOOT_METHOD, BOOT_METHOD_CD);
 
#if 0
	dprintf("got CD boot spec:\n");
	dprintf("  size: %#x\n", packet->size);
	dprintf("  media type: %u\n", packet->media_type);
	dprintf("  drive_number: %u\n", packet->drive_number);
	dprintf("  controller index: %u\n", packet->controller_index);
	dprintf("  start emulation: %lu\n", packet->start_emulation);
	dprintf("  device_specification: %u\n", packet->device_specification);
#endif
}
 
 
static bool
are_extensions_available(uint8 drive)
{
	struct bios_regs regs;
	regs.eax = BIOS_IS_EXT_PRESENT;
	regs.ebx = 0x55aa;
	regs.edx = drive;
	call_bios(0x13, &regs);
 
	TRACE(("checking extensions: carry: %u; ebx: 0x%08lx; ecx: 0x%08lx\n",
		regs.flags & CARRY_FLAG, regs.ebx, regs.ecx));
	return (regs.flags & CARRY_FLAG) == 0 && regs.ebx == 0xaa55
		&& (regs.ecx & 0x01 /* supports device access using packet */) != 0;
}
 
 
static status_t
get_ext_drive_parameters(uint8 drive, drive_parameters *targetParameters)
{
	drive_parameters *parameter = (drive_parameters *)kDataSegmentScratch;
 
	memset(parameter, 0, sizeof(drive_parameters));
	parameter->parameters_size = sizeof(drive_parameters);
 
	struct bios_regs regs;
	regs.eax = BIOS_GET_EXT_DRIVE_PARAMETERS;
	regs.edx = drive;
	regs.esi = (addr_t)parameter - kDataSegmentBase;
	call_bios(0x13, &regs);
 
	// filter out faulty BIOS return codes
	if ((regs.flags & CARRY_FLAG) != 0
		|| parameter->sectors == 0)
		return B_ERROR;
 
	memcpy(targetParameters, parameter, sizeof(drive_parameters));
	return B_OK;
}
 
 
static status_t
get_drive_parameters(uint8 drive, drive_parameters *parameters)
{
	struct bios_regs regs;
	regs.eax = BIOS_GET_DRIVE_PARAMETERS;
	regs.edx = drive;
	regs.es = 0;
	regs.edi = 0;	// guard against faulty BIOS, see Ralf Brown's interrupt list
	call_bios(0x13, &regs);
 
	if ((regs.flags & CARRY_FLAG) != 0 || (regs.ecx & 0x3f) == 0)
		return B_ERROR;
 
	// fill drive_parameters structure with useful values
	parameters->parameters_size = kParametersSizeVersion1;
	parameters->flags = 0;
	parameters->cylinders = (((regs.ecx & 0xc0) << 2) | ((regs.ecx >> 8) & 0xff)) + 1;
	parameters->heads = ((regs.edx >> 8) & 0xff) + 1;
		// heads and cylinders start counting from 0
	parameters->sectors_per_track = regs.ecx & 0x3f;
	parameters->sectors = parameters->cylinders * parameters->heads
		* parameters->sectors_per_track;
	parameters->bytes_per_sector = 512;
 
	return B_OK;
}
 
 
static status_t
get_number_of_drives(uint8 *_count)
{
	struct bios_regs regs;
	regs.eax = BIOS_GET_DRIVE_PARAMETERS;
	regs.edx = 0x80;
	regs.es = 0;
	regs.edi = 0;
	call_bios(0x13, &regs);
 
	if (regs.flags & CARRY_FLAG)
		return B_ERROR;
 
	*_count = regs.edx & 0xff;
	return B_OK;
}
 
 
/** parse EDD 3.0 drive path information */
 
static status_t
fill_disk_identifier_v3(disk_identifier &disk, const drive_parameters &parameters)
{
	if (parameters.parameters_size < kParametersSizeVersion3
		|| parameters.device_path_signature != kDevicePathSignature)
		return B_BAD_TYPE;
 
	// parse host bus
 
	if (!strncmp(parameters.host_bus, "PCI", 3)) {
		disk.bus_type = PCI_BUS;
 
		disk.bus.pci.bus = parameters.interface.pci.bus;
		disk.bus.pci.slot = parameters.interface.pci.slot;
		disk.bus.pci.function = parameters.interface.pci.function;
	} else if (!strncmp(parameters.host_bus, "ISA", 3)) {
		disk.bus_type = LEGACY_BUS;
 
		disk.bus.legacy.base_address = parameters.interface.legacy.base_address;
		dprintf("legacy base address %x\n", disk.bus.legacy.base_address);
	} else {
		dprintf("unknown host bus \"%s\"\n", parameters.host_bus);
		return B_BAD_DATA;
	}
 
	// parse interface
 
	if (!strncmp(parameters.interface_type, "ATA", 3)) {
		disk.device_type = ATA_DEVICE;
		disk.device.ata.master = !parameters.device.ata.slave;
		dprintf("ATA device, %s\n", disk.device.ata.master ? "master" : "slave");
	} else if (!strncmp(parameters.interface_type, "ATAPI", 3)) {
		disk.device_type = ATAPI_DEVICE;
		disk.device.atapi.master = !parameters.device.ata.slave;
		disk.device.atapi.logical_unit = parameters.device.atapi.logical_unit;
	} else if (!strncmp(parameters.interface_type, "SCSI", 3)) {
		disk.device_type = SCSI_DEVICE;
		disk.device.scsi.logical_unit = parameters.device.scsi.logical_unit;
	} else if (!strncmp(parameters.interface_type, "USB", 3)) {
		disk.device_type = USB_DEVICE;
		disk.device.usb.tbd = parameters.device.usb.tbd;
	} else if (!strncmp(parameters.interface_type, "1394", 3)) {
		disk.device_type = FIREWIRE_DEVICE;
		disk.device.firewire.guid = parameters.device.firewire.guid;
	} else if (!strncmp(parameters.interface_type, "FIBRE", 3)) {
		disk.device_type = FIBRE_DEVICE;
		disk.device.fibre.wwd = parameters.device.fibre.wwd;
	} else {
		dprintf("unknown interface type \"%s\"\n", parameters.interface_type);
		return B_BAD_DATA;
	}
 
	return B_OK;
}
 
 
/** EDD 2.0 drive table information */
 
static status_t
fill_disk_identifier_v2(disk_identifier &disk, const drive_parameters &parameters)
{
	if (parameters.device_table.segment == 0xffff
		&& parameters.device_table.offset == 0xffff)
		return B_BAD_TYPE;
 
	device_table *table = (device_table *)LINEAR_ADDRESS(parameters.device_table.segment,
		parameters.device_table.offset);
 
	disk.bus_type = LEGACY_BUS;
	disk.bus.legacy.base_address = table->base_address;
 
	disk.device_type = ATA_DEVICE;
	disk.device.ata.master = !table->is_slave;
 
	return B_OK;
}
 
 
static off_t
get_next_check_sum_offset(int32 index, off_t maxSize)
{
	// The boot block often contains the disk superblock, and should be
	// unique enough for most cases
	if (index < 2)
		return index * 512;
 
	// Try some data in the first part of the drive
	if (index < 4)
		return (maxSize >> 10) + index * 2048;
 
	// Some random value might do
	return ((system_time() + index) % (maxSize >> 9)) * 512;
}
 
 
/**	Computes a check sum for the specified block.
 *	The check sum is the sum of all data in that block interpreted as an
 *	array of uint32 values.
 *	Note, this must use the same method as the one used in kernel/fs/vfs_boot.cpp.
 */
 
static uint32
compute_check_sum(BIOSDrive *drive, off_t offset)
{
	char buffer[512];
	ssize_t bytesRead = drive->ReadAt(NULL, offset, buffer, sizeof(buffer));
	if (bytesRead < B_OK)
		return 0;
 
	if (bytesRead < (ssize_t)sizeof(buffer))
		memset(buffer + bytesRead, 0, sizeof(buffer) - bytesRead);
 
	uint32 *array = (uint32 *)buffer;
	uint32 sum = 0;
 
	for (uint32 i = 0; i < (bytesRead + sizeof(uint32) - 1) / sizeof(uint32); i++) {
		sum += array[i];
	}
 
	return sum;
}
 
/**	Checks if the specified drive is usable for reading.
 */
 
static bool
is_drive_readable(BIOSDrive *drive)
{
	char buffer;
	return drive->ReadAt(NULL, 0, &buffer, sizeof(buffer)) > 0;
}
 
 
static void
find_unique_check_sums(NodeList *devices)
{
	NodeIterator iterator = devices->GetIterator();
	Node *device;
	int32 index = 0;
	off_t minSize = 0;
	const int32 kMaxTries = 200;
 
	while (index < kMaxTries) {
		bool clash = false;
 
		iterator.Rewind();
 
		while ((device = iterator.Next()) != NULL) {
			BIOSDrive *drive = (BIOSDrive *)device;
#if 0
			// there is no RTTI in the boot loader...
			BIOSDrive *drive = dynamic_cast<BIOSDrive *>(device);
			if (drive == NULL)
				continue;
#endif
 
			// TODO: currently, we assume that the BIOS provided us with unique
			//	disk identifiers... hopefully this is a good idea
			if (drive->Identifier().device_type != UNKNOWN_DEVICE)
				continue;
 
			if (minSize == 0 || drive->Size() < minSize)
				minSize = drive->Size();
 
			// check for clashes
 
			NodeIterator compareIterator = devices->GetIterator();
			while ((device = compareIterator.Next()) != NULL) {
				BIOSDrive *compareDrive = (BIOSDrive *)device;
 
				if (compareDrive == drive
					|| compareDrive->Identifier().device_type != UNKNOWN_DEVICE)
					continue;
 
// TODO: Until we can actually get and compare *all* fields of the disk
// identifier in the kernel, we cannot compare the whole structure (we also
// should be more careful zeroing the structure before we fill it).
#if 0
				if (!memcmp(&drive->Identifier(), &compareDrive->Identifier(),
						sizeof(disk_identifier))) {
					clash = true;
					break;
				}
#else
				const disk_identifier& ourId = drive->Identifier();
				const disk_identifier& otherId = compareDrive->Identifier();
				if (memcmp(&ourId.device.unknown.check_sums,
						&otherId.device.unknown.check_sums,
						sizeof(ourId.device.unknown.check_sums)) == 0) {
					clash = true;
				}
#endif
			}
 
			if (clash)
				break;
		}
 
		if (!clash) {
			// our work here is done.
			return;
		}
 
		// add a new block to the check sums
 
		off_t offset = get_next_check_sum_offset(index, minSize);
		int32 i = index % NUM_DISK_CHECK_SUMS;
		iterator.Rewind();
 
		while ((device = iterator.Next()) != NULL) {
			BIOSDrive *drive = (BIOSDrive *)device;
 
			disk_identifier& disk = drive->Identifier();
			disk.device.unknown.check_sums[i].offset = offset;
			disk.device.unknown.check_sums[i].sum = compute_check_sum(drive, offset);
 
			TRACE(("disk %x, offset %Ld, sum %lu\n", drive->DriveID(), offset,
				disk.device.unknown.check_sums[i].sum));
		}
 
		index++;
	}
 
	// If we get here, we couldn't find a way to differentiate all disks from each other.
	// It's very likely that one disk is an exact copy of the other, so there is nothing
	// we could do, anyway.
 
	dprintf("Could not make BIOS drives unique! Might boot from the wrong disk...\n");
}
 
 
static status_t
add_block_devices(NodeList *devicesList, bool identifierMissing)
{
	if (sBlockDevicesAdded)
		return B_OK;
 
	uint8 driveCount;
	if (get_number_of_drives(&driveCount) != B_OK)
		return B_ERROR;
 
	dprintf("number of drives: %d\n", driveCount);
 
	for (int32 i = 0; i < driveCount; i++) {
		uint8 driveID = i + 0x80;
		if (driveID == gBootDriveID)
			continue;
 
		BIOSDrive *drive = new(nothrow) BIOSDrive(driveID);
		if (drive->InitCheck() != B_OK) {
			dprintf("could not add drive %u\n", driveID);
			delete drive;
			continue;
		}
 
		// Only add usable drives
		if (is_drive_readable(drive))
			devicesList->Add(drive);
		else {
			dprintf("could not read from drive %" B_PRIu8 ", not adding\n", driveID);
			delete drive;
			continue;
		}
 
		if (drive->FillIdentifier() != B_OK)
			identifierMissing = true;
	}
 
	if (identifierMissing) {
		// we cannot distinguish between all drives by identifier, we need
		// compute checksums for them
		find_unique_check_sums(devicesList);
	}
 
	sBlockDevicesAdded = true;
	return B_OK;
}
 
 
//	#pragma mark -
 
 
BIOSDrive::BIOSDrive(uint8 driveID)
	:
	fDriveID(driveID),
	fSize(0)
{
	TRACE(("drive ID %u\n", driveID));
 
	if (driveID < 0x80 || !are_extensions_available(driveID)
		|| get_ext_drive_parameters(driveID, &fParameters) != B_OK) {
		// old style CHS support
 
		if (get_drive_parameters(driveID, &fParameters) != B_OK) {
			dprintf("getting drive parameters for: %u failed!\n", fDriveID);
			return;
		}
 
		TRACE(("  cylinders: %lu, heads: %lu, sectors: %lu, bytes_per_sector: %u\n",
			fParameters.cylinders, fParameters.heads, fParameters.sectors_per_track,
			fParameters.bytes_per_sector));
		TRACE(("  total sectors: %Ld\n", fParameters.sectors));
 
		fBlockSize = 512;
		fSize = fParameters.sectors * fBlockSize;
		fLBA = false;
		fHasParameters = false;
	} else {
		TRACE(("size: %x\n", fParameters.parameters_size));
		TRACE(("drive_path_signature: %x\n", fParameters.device_path_signature));
		TRACE(("host bus: \"%s\", interface: \"%s\"\n", fParameters.host_bus,
			fParameters.interface_type));
		TRACE(("cylinders: %lu, heads: %lu, sectors: %lu, bytes_per_sector: %u\n",
			fParameters.cylinders, fParameters.heads, fParameters.sectors_per_track,
			fParameters.bytes_per_sector));
		TRACE(("total sectors: %Ld\n", fParameters.sectors));
 
		fBlockSize = fParameters.bytes_per_sector;
		fSize = fParameters.sectors * fBlockSize;
		fLBA = true;
		fHasParameters = true;
	}
}
 
 
BIOSDrive::~BIOSDrive()
{
}
 
 
status_t
BIOSDrive::InitCheck() const
{
	return fSize > 0 ? B_OK : B_ERROR;
}
 
 
ssize_t
BIOSDrive::ReadAt(void *cookie, off_t pos, void *buffer, size_t bufferSize)
{
	uint32 offset = pos % fBlockSize;
	pos /= fBlockSize;
 
	uint32 blocksLeft = (bufferSize + offset + fBlockSize - 1) / fBlockSize;
	int32 totalBytesRead = 0;
 
	//TRACE(("BIOS reads %lu bytes from %Ld (offset = %lu), drive %u\n",
	//	blocksLeft * fBlockSize, pos * fBlockSize, offset, fDriveID));
 
	uint32 scratchSize = 24 * 1024 / fBlockSize;
		// maximum value allowed by Phoenix BIOS is 0x7f
 
	while (blocksLeft > 0) {
		uint32 blocksRead = blocksLeft;
		if (blocksRead > scratchSize)
			blocksRead = scratchSize;
 
		if (fLBA) {
			struct disk_address_packet *packet = (disk_address_packet *)kDataSegmentScratch;
			memset(packet, 0, sizeof(disk_address_packet));
 
			packet->size = sizeof(disk_address_packet);
			packet->number_of_blocks = blocksRead;
			packet->buffer = kExtraSegmentScratch;
			packet->lba = pos;
 
			struct bios_regs regs;
			regs.eax = BIOS_EXT_READ;
			regs.edx = fDriveID;
			regs.esi = (addr_t)packet - kDataSegmentBase;
			call_bios(0x13, &regs);
 
			if (regs.flags & CARRY_FLAG)
				goto chs_read;
		} else {
	chs_read:
			// Old style CHS read routine
 
			// We can only read up to 64 kB this way, but since scratchSize
			// is actually lower than this value, we don't have to take care
			// of this here.
 
			uint32 sector = pos % fParameters.sectors_per_track + 1;
				// sectors start countint at 1 (unlike head and cylinder)
			uint32 head = pos / fParameters.sectors_per_track;
			uint32 cylinder = head / fParameters.heads;
			head %= fParameters.heads;
 
			if (cylinder >= fParameters.cylinders) {
				TRACE(("cylinder value %lu bigger than available %lu\n",
					cylinder, fParameters.cylinders));
				return B_BAD_VALUE;
			}
 
			// try to read from the device more than once, just to make sure it'll work
			struct bios_regs regs;
			int32 tries = 3;
			bool readWorked = false;
 
			while (tries-- > 0) {
				regs.eax = BIOS_READ | blocksRead;
				regs.edx = fDriveID | (head << 8);
				regs.ecx = sector | ((cylinder >> 2) & 0xc0) | ((cylinder & 0xff) << 8);
				regs.es = 0;
				regs.ebx = kExtraSegmentScratch;
				call_bios(0x13, &regs);
 
				if ((regs.flags & CARRY_FLAG) == 0) {
					readWorked = true;
					break;
				}
 
				TRACE(("read failed\n"));
 
				if (tries < 2) {
					// reset disk system
					TRACE(("reset disk system\n"));
					regs.eax = BIOS_RESET_DISK_SYSTEM;
					regs.edx = fDriveID;
					call_bios(0x13, &regs);
				}
 
				// wait a bit between the retries (1/20 sec)
				spin(50000);
			}
 
			if (!readWorked) {
				dprintf("reading %ld bytes from drive %u failed at %Ld\n",
					blocksRead, fDriveID, pos);
				return B_ERROR;
			}
		}
 
		uint32 bytesRead = fBlockSize * blocksRead - offset;
		// copy no more than bufferSize bytes
		if (bytesRead > bufferSize)
			bytesRead = bufferSize;
 
		memcpy(buffer, (void *)(kExtraSegmentScratch + offset), bytesRead);
		pos += blocksRead;
		offset = 0;
		blocksLeft -= blocksRead;
		bufferSize -= bytesRead;
		buffer = (void *)((addr_t)buffer + bytesRead);
		totalBytesRead += bytesRead;
	}
 
	return totalBytesRead;
}
 
 
ssize_t
BIOSDrive::WriteAt(void* cookie, off_t pos, const void* buffer,
	size_t bufferSize)
{
	// we support only LBA addressing
	if (!fLBA) {
		dprintf("BIOSDrive::WriteAt(): CHS addressing not supported\n");
		return B_UNSUPPORTED;
	}
 
	// we support only block-aligned writes
	if (pos % fBlockSize != 0 || bufferSize % fBlockSize != 0) {
		dprintf("BIOSDrive::WriteAt(pos: %" B_PRIdOFF ", size: %" B_PRIuSIZE
			"): Block-unaligned write not supported.\n", pos, bufferSize);
		return B_UNSUPPORTED;
	}
 
	pos /= fBlockSize;
 
	uint32 blocksLeft = bufferSize / fBlockSize;
	int32 totalBytesWritten = 0;
 
	uint32 scratchSize = 24 * 1024 / fBlockSize;
		// maximum value allowed by Phoenix BIOS is 0x7f
 
	while (blocksLeft > 0) {
		uint32 blocksToWrite = blocksLeft;
		if (blocksToWrite > scratchSize)
			blocksToWrite = scratchSize;
 
		uint32 bytesToWrite = blocksToWrite * fBlockSize;
 
		memcpy((void*)kExtraSegmentScratch, buffer, bytesToWrite);
 
		struct disk_address_packet* packet
			= (disk_address_packet*)kDataSegmentScratch;
		memset(packet, 0, sizeof(disk_address_packet));
 
		packet->size = sizeof(disk_address_packet);
		packet->number_of_blocks = blocksToWrite;
		packet->buffer = kExtraSegmentScratch;
		packet->lba = pos;
 
		struct bios_regs regs;
		regs.eax = BIOS_EXT_WRITE;	// al = 0x00 -- no write verify
		regs.edx = fDriveID;
		regs.esi = (addr_t)packet - kDataSegmentBase;
		call_bios(0x13, &regs);
 
		if (regs.flags & CARRY_FLAG)
			return B_ERROR;
 
		pos += blocksToWrite;
		blocksLeft -= blocksToWrite;
		bufferSize -= bytesToWrite;
		buffer = (void*)((addr_t)buffer + bytesToWrite);
		totalBytesWritten += bytesToWrite;
	}
 
	return totalBytesWritten;
}
 
 
off_t
BIOSDrive::Size() const
{
	return fSize;
}
 
 
status_t
BIOSDrive::FillIdentifier()
{
	if (HasParameters()) {
		// try all drive_parameters versions, beginning from the most informative
 
#if 0
		if (fill_disk_identifier_v3(fIdentifier, fParameters) == B_OK)
			return B_OK;
 
		if (fill_disk_identifier_v2(fIdentifier, fParameters) == B_OK)
			return B_OK;
#else
		// TODO: the above version is the correct one - it's currently
		//		disabled, as the kernel boot code only supports the
		//		UNKNOWN_BUS/UNKNOWN_DEVICE way to find the correct boot
		//		device.
		if (fill_disk_identifier_v3(fIdentifier, fParameters) != B_OK)
			fill_disk_identifier_v2(fIdentifier, fParameters);
 
#endif
 
		// no interesting information, we have to fall back to the default
		// unknown interface/device type identifier
	}
 
	fIdentifier.bus_type = UNKNOWN_BUS;
	fIdentifier.device_type = UNKNOWN_DEVICE;
	fIdentifier.device.unknown.size = Size();
 
	for (int32 i = 0; i < NUM_DISK_CHECK_SUMS; i++) {
		fIdentifier.device.unknown.check_sums[i].offset = -1;
		fIdentifier.device.unknown.check_sums[i].sum = 0;
	}
 
	return B_ERROR;
}
 
 
//	#pragma mark -
 
 
status_t
platform_add_boot_device(struct stage2_args *args, NodeList *devicesList)
{
	TRACE(("boot drive ID: %x\n", gBootDriveID));
 
	BIOSDrive *drive = new(nothrow) BIOSDrive(gBootDriveID);
	if (drive->InitCheck() != B_OK) {
		dprintf("no boot drive!\n");
		delete drive;
		return B_ERROR;
	}
 
	devicesList->Add(drive);
 
	if (drive->FillIdentifier() != B_OK) {
		// We need to add all block devices to give the kernel the possibility
		// to find the right boot volume
		add_block_devices(devicesList, true);
	}
 
	TRACE(("boot drive size: %Ld bytes\n", drive->Size()));
	gBootVolume.SetBool(BOOT_VOLUME_BOOTED_FROM_IMAGE, gBootedFromImage);
 
	return B_OK;
}
 
 
status_t
platform_get_boot_partitions(struct stage2_args *args, Node *bootDevice,
	NodeList *list, NodeList *bootList)
{
	BIOSDrive *drive = static_cast<BIOSDrive *>(bootDevice);
	off_t offset = (off_t)gBootPartitionOffset * drive->BlockSize();
 
	dprintf("boot partition offset: %Ld\n", offset);
 
	NodeIterator iterator = list->GetIterator();
	boot::Partition *partition = NULL;
	while ((partition = (boot::Partition *)iterator.Next()) != NULL) {
		TRACE(("partition offset = %Ld, size = %Ld\n", partition->offset, partition->size));
		// search for the partition that contains the partition
		// offset as reported by the BFS boot block
		if (offset >= partition->offset
			&& offset < partition->offset + partition->size) {
			bootList->Insert(partition);
			return B_OK;
		}
	}
 
	return B_ENTRY_NOT_FOUND;
}
 
 
status_t
platform_add_block_devices(stage2_args *args, NodeList *devicesList)
{
	return add_block_devices(devicesList, false);
}
 
 
status_t
platform_register_boot_device(Node *device)
{
	BIOSDrive *drive = (BIOSDrive *)device;
 
	check_cd_boot(drive);
 
	gBootVolume.SetInt64("boot drive number", drive->DriveID());
	gBootVolume.SetData(BOOT_VOLUME_DISK_IDENTIFIER, B_RAW_TYPE,
		&drive->Identifier(), sizeof(disk_identifier));
 
	return B_OK;
}
 
 
void
platform_cleanup_devices()
{
}

V730 Not all members of a class are initialized inside the constructor. Consider inspecting: fIdentifier.

V576 Potentially incorrect format string is passed to the 'dprintf' function. Prefix 'L' is not applicable to conversion specifier 'd'.

V576 Potentially incorrect format string is passed to the 'dprintf' function. Prefix 'L' is not applicable to conversion specifier 'd'.