1/*
2 * Copyright 2003-2006, Axel D��rfler, axeld@pinc-software.de.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include "bios.h"
8
9#include <KernelExport.h>
10#include <boot/platform.h>
11#include <boot/partitions.h>
12#include <boot/stdio.h>
13#include <boot/stage2.h>
14
15#include <string.h>
16
17//#define TRACE_DEVICES
18#ifdef TRACE_DEVICES
19#	define TRACE(x) dprintf x
20#else
21#	define TRACE(x) ;
22#endif
23
24
25// exported from shell.S
26extern uint8 gBootedFromImage;
27extern uint8 gBootDriveID;
28extern uint32 gBootPartitionOffset;
29
30// int 0x13 definitions
31#define BIOS_RESET_DISK_SYSTEM			0x0000
32#define BIOS_READ						0x0200
33#define BIOS_GET_DRIVE_PARAMETERS		0x0800
34#define BIOS_IS_EXT_PRESENT				0x4100
35#define BIOS_EXT_READ					0x4200
36#define BIOS_EXT_WRITE					0x4300
37#define BIOS_GET_EXT_DRIVE_PARAMETERS	0x4800
38#define BIOS_BOOT_CD_GET_STATUS			0x4b01
39
40struct real_addr {
41	uint16	offset;
42	uint16	segment;
43};
44
45struct disk_address_packet {
46	uint8		size;
47	uint8		reserved;
48	uint16		number_of_blocks;
49	uint32		buffer;
50	uint64		lba;
51} _PACKED;
52
53static const uint16 kParametersSizeVersion1 = 0x1a;
54static const uint16 kParametersSizeVersion2 = 0x1e;
55static const uint16 kParametersSizeVersion3 = 0x42;
56
57static const uint16 kDevicePathSignature = 0xbedd;
58
59struct drive_parameters {
60	uint16		parameters_size;
61	uint16		flags;
62	uint32		cylinders;
63	uint32		heads;
64	uint32		sectors_per_track;
65	uint64		sectors;
66	uint16		bytes_per_sector;
67	/* edd 2.0 */
68	real_addr	device_table;
69	/* edd 3.0 */
70	uint16		device_path_signature;
71	uint8		device_path_size;
72	uint8		reserved1[3];
73	char		host_bus[4];
74	char		interface_type[8];
75	union {
76		struct {
77			uint16	base_address;
78		} legacy;
79		struct {
80			uint8	bus;
81			uint8	slot;
82			uint8	function;
83		} pci;
84		uint8		reserved[8];
85	} interface;
86	union {
87		struct {
88			uint8	slave;
89		} ata;
90		struct {
91			uint8	slave;
92			uint8	logical_unit;
93		} atapi;
94		struct {
95			uint8	logical_unit;
96		} scsi;
97		struct {
98			uint8	tbd;
99		} usb;
100		struct {
101			uint64	guid;
102		} firewire;
103		struct {
104			uint64	wwd;
105		} fibre;
106	} device;
107	uint8		reserved2;
108	uint8		checksum;
109} _PACKED;
110
111struct device_table {
112	uint16	base_address;
113	uint16	control_port_address;
114	uint8	_reserved1 : 4;
115	uint8	is_slave : 1;
116	uint8	_reserved2 : 1;
117	uint8	lba_enabled : 1;
118} _PACKED;
119
120struct specification_packet {
121	uint8	size;
122	uint8	media_type;
123	uint8	drive_number;
124	uint8	controller_index;
125	uint32	start_emulation;
126	uint16	device_specification;
127	uint8	_more_[9];
128} _PACKED;
129
130class BIOSDrive : public Node {
131	public:
132		BIOSDrive(uint8 driveID);
133		virtual ~BIOSDrive();
134
135		status_t InitCheck() const;
136
137		virtual ssize_t ReadAt(void *cookie, off_t pos, void *buffer, size_t bufferSize);
138		virtual ssize_t WriteAt(void *cookie, off_t pos, const void *buffer, size_t bufferSize);
139
140		virtual off_t Size() const;
141
142		uint32 BlockSize() const { return fBlockSize; }
143
144		status_t FillIdentifier();
145
146		bool HasParameters() const { return fHasParameters; }
147		const drive_parameters &Parameters() const { return fParameters; }
148
149		disk_identifier &Identifier() { return fIdentifier; }
150		uint8 DriveID() const { return fDriveID; }
151
152	protected:
153		uint8	fDriveID;
154		bool	fLBA;
155		uint64	fSize;
156		uint32	fBlockSize;
157		bool	fHasParameters;
158		drive_parameters fParameters;
159		disk_identifier fIdentifier;
160};
161
162
163static bool sBlockDevicesAdded = false;
164
165
166static void
167check_cd_boot(BIOSDrive *drive)
168{
169	gBootVolume.SetInt32(BOOT_METHOD, BOOT_METHOD_HARD_DISK);
170
171	if (drive->DriveID() != 0)
172		return;
173
174	struct bios_regs regs;
175	regs.eax = BIOS_BOOT_CD_GET_STATUS;
176	regs.edx = 0;
177	regs.esi = kDataSegmentScratch;
178	call_bios(0x13, &regs);
179
180	if ((regs.flags & CARRY_FLAG) != 0)
181		return;
182
183	// we obviously were booted from CD!
184
185	specification_packet *packet = (specification_packet *)kDataSegmentScratch;
186	if (packet->media_type != 0)
187		gBootVolume.SetInt32(BOOT_METHOD, BOOT_METHOD_CD);
188
189#if 0
190	dprintf("got CD boot spec:\n");
191	dprintf("  size: %#x\n", packet->size);
192	dprintf("  media type: %u\n", packet->media_type);
193	dprintf("  drive_number: %u\n", packet->drive_number);
194	dprintf("  controller index: %u\n", packet->controller_index);
195	dprintf("  start emulation: %lu\n", packet->start_emulation);
196	dprintf("  device_specification: %u\n", packet->device_specification);
197#endif
198}
199
200
201static bool
202are_extensions_available(uint8 drive)
203{
204	struct bios_regs regs;
205	regs.eax = BIOS_IS_EXT_PRESENT;
206	regs.ebx = 0x55aa;
207	regs.edx = drive;
208	call_bios(0x13, &regs);
209
210	TRACE(("checking extensions: carry: %u; ebx: 0x%08lx; ecx: 0x%08lx\n",
211		regs.flags & CARRY_FLAG, regs.ebx, regs.ecx));
212	return (regs.flags & CARRY_FLAG) == 0 && regs.ebx == 0xaa55
213		&& (regs.ecx & 0x01 /* supports device access using packet */) != 0;
214}
215
216
217static status_t
218get_ext_drive_parameters(uint8 drive, drive_parameters *targetParameters)
219{
220	drive_parameters *parameter = (drive_parameters *)kDataSegmentScratch;
221
222	memset(parameter, 0, sizeof(drive_parameters));
223	parameter->parameters_size = sizeof(drive_parameters);
224
225	struct bios_regs regs;
226	regs.eax = BIOS_GET_EXT_DRIVE_PARAMETERS;
227	regs.edx = drive;
228	regs.esi = (addr_t)parameter - kDataSegmentBase;
229	call_bios(0x13, &regs);
230
231	// filter out faulty BIOS return codes
232	if ((regs.flags & CARRY_FLAG) != 0
233		|| parameter->sectors == 0)
234		return B_ERROR;
235
236	memcpy(targetParameters, parameter, sizeof(drive_parameters));
237	return B_OK;
238}
239
240
241static status_t
242get_drive_parameters(uint8 drive, drive_parameters *parameters)
243{
244	struct bios_regs regs;
245	regs.eax = BIOS_GET_DRIVE_PARAMETERS;
246	regs.edx = drive;
247	regs.es = 0;
248	regs.edi = 0;	// guard against faulty BIOS, see Ralf Brown's interrupt list
249	call_bios(0x13, &regs);
250
251	if ((regs.flags & CARRY_FLAG) != 0 || (regs.ecx & 0x3f) == 0)
252		return B_ERROR;
253
254	// fill drive_parameters structure with useful values
255	parameters->parameters_size = kParametersSizeVersion1;
256	parameters->flags = 0;
257	parameters->cylinders = (((regs.ecx & 0xc0) << 2) | ((regs.ecx >> 8) & 0xff)) + 1;
258	parameters->heads = ((regs.edx >> 8) & 0xff) + 1;
259		// heads and cylinders start counting from 0
260	parameters->sectors_per_track = regs.ecx & 0x3f;
261	parameters->sectors = parameters->cylinders * parameters->heads
262		* parameters->sectors_per_track;
263	parameters->bytes_per_sector = 512;
264
265	return B_OK;
266}
267
268
269static status_t
270get_number_of_drives(uint8 *_count)
271{
272	struct bios_regs regs;
273	regs.eax = BIOS_GET_DRIVE_PARAMETERS;
274	regs.edx = 0x80;
275	regs.es = 0;
276	regs.edi = 0;
277	call_bios(0x13, &regs);
278
279	if (regs.flags & CARRY_FLAG)
280		return B_ERROR;
281
282	*_count = regs.edx & 0xff;
283	return B_OK;
284}
285
286
287/** parse EDD 3.0 drive path information */
288
289static status_t
290fill_disk_identifier_v3(disk_identifier &disk, const drive_parameters &parameters)
291{
292	if (parameters.parameters_size < kParametersSizeVersion3
293		|| parameters.device_path_signature != kDevicePathSignature)
294		return B_BAD_TYPE;
295
296	// parse host bus
297
298	if (!strncmp(parameters.host_bus, "PCI", 3)) {
299		disk.bus_type = PCI_BUS;
300
301		disk.bus.pci.bus = parameters.interface.pci.bus;
302		disk.bus.pci.slot = parameters.interface.pci.slot;
303		disk.bus.pci.function = parameters.interface.pci.function;
304	} else if (!strncmp(parameters.host_bus, "ISA", 3)) {
305		disk.bus_type = LEGACY_BUS;
306
307		disk.bus.legacy.base_address = parameters.interface.legacy.base_address;
308		dprintf("legacy base address %x\n", disk.bus.legacy.base_address);
309	} else {
310		dprintf("unknown host bus \"%s\"\n", parameters.host_bus);
311		return B_BAD_DATA;
312	}
313
314	// parse interface
315
316	if (!strncmp(parameters.interface_type, "ATA", 3)) {
317		disk.device_type = ATA_DEVICE;
318		disk.device.ata.master = !parameters.device.ata.slave;
319		dprintf("ATA device, %s\n", disk.device.ata.master ? "master" : "slave");
320	} else if (!strncmp(parameters.interface_type, "ATAPI", 3)) {
321		disk.device_type = ATAPI_DEVICE;
322		disk.device.atapi.master = !parameters.device.ata.slave;
323		disk.device.atapi.logical_unit = parameters.device.atapi.logical_unit;
324	} else if (!strncmp(parameters.interface_type, "SCSI", 3)) {
325		disk.device_type = SCSI_DEVICE;
326		disk.device.scsi.logical_unit = parameters.device.scsi.logical_unit;
327	} else if (!strncmp(parameters.interface_type, "USB", 3)) {
328		disk.device_type = USB_DEVICE;
329		disk.device.usb.tbd = parameters.device.usb.tbd;
330	} else if (!strncmp(parameters.interface_type, "1394", 3)) {
331		disk.device_type = FIREWIRE_DEVICE;
332		disk.device.firewire.guid = parameters.device.firewire.guid;
333	} else if (!strncmp(parameters.interface_type, "FIBRE", 3)) {
334		disk.device_type = FIBRE_DEVICE;
335		disk.device.fibre.wwd = parameters.device.fibre.wwd;
336	} else {
337		dprintf("unknown interface type \"%s\"\n", parameters.interface_type);
338		return B_BAD_DATA;
339	}
340
341	return B_OK;
342}
343
344
345/** EDD 2.0 drive table information */
346
347static status_t
348fill_disk_identifier_v2(disk_identifier &disk, const drive_parameters &parameters)
349{
350	if (parameters.device_table.segment == 0xffff
351		&& parameters.device_table.offset == 0xffff)
352		return B_BAD_TYPE;
353
354	device_table *table = (device_table *)LINEAR_ADDRESS(parameters.device_table.segment,
355		parameters.device_table.offset);
356
357	disk.bus_type = LEGACY_BUS;
358	disk.bus.legacy.base_address = table->base_address;
359
360	disk.device_type = ATA_DEVICE;
361	disk.device.ata.master = !table->is_slave;
362
363	return B_OK;
364}
365
366
367static off_t
368get_next_check_sum_offset(int32 index, off_t maxSize)
369{
370	// The boot block often contains the disk superblock, and should be
371	// unique enough for most cases
372	if (index < 2)
373		return index * 512;
374
375	// Try some data in the first part of the drive
376	if (index < 4)
377		return (maxSize >> 10) + index * 2048;
378
379	// Some random value might do
380	return ((system_time() + index) % (maxSize >> 9)) * 512;
381}
382
383
384/**	Computes a check sum for the specified block.
385 *	The check sum is the sum of all data in that block interpreted as an
386 *	array of uint32 values.
387 *	Note, this must use the same method as the one used in kernel/fs/vfs_boot.cpp.
388 */
389
390static uint32
391compute_check_sum(BIOSDrive *drive, off_t offset)
392{
393	char buffer[512];
394	ssize_t bytesRead = drive->ReadAt(NULL, offset, buffer, sizeof(buffer));
395	if (bytesRead < B_OK)
396		return 0;
397
398	if (bytesRead < (ssize_t)sizeof(buffer))
399		memset(buffer + bytesRead, 0, sizeof(buffer) - bytesRead);
400
401	uint32 *array = (uint32 *)buffer;
402	uint32 sum = 0;
403
404	for (uint32 i = 0; i < (bytesRead + sizeof(uint32) - 1) / sizeof(uint32); i++) {
405		sum += array[i];
406	}
407
408	return sum;
409}
410
411/**	Checks if the specified drive is usable for reading.
412 */
413
414static bool
415is_drive_readable(BIOSDrive *drive)
416{
417	char buffer;
418	return drive->ReadAt(NULL, 0, &buffer, sizeof(buffer)) > 0;
419}
420
421
422static void
423find_unique_check_sums(NodeList *devices)
424{
425	NodeIterator iterator = devices->GetIterator();
426	Node *device;
427	int32 index = 0;
428	off_t minSize = 0;
429	const int32 kMaxTries = 200;
430
431	while (index < kMaxTries) {
432		bool clash = false;
433
434		iterator.Rewind();
435
436		while ((device = iterator.Next()) != NULL) {
437			BIOSDrive *drive = (BIOSDrive *)device;
438#if 0
439			// there is no RTTI in the boot loader...
440			BIOSDrive *drive = dynamic_cast<BIOSDrive *>(device);
441			if (drive == NULL)
442				continue;
443#endif
444
445			// TODO: currently, we assume that the BIOS provided us with unique
446			//	disk identifiers... hopefully this is a good idea
447			if (drive->Identifier().device_type != UNKNOWN_DEVICE)
448				continue;
449
450			if (minSize == 0 || drive->Size() < minSize)
451				minSize = drive->Size();
452
453			// check for clashes
454
455			NodeIterator compareIterator = devices->GetIterator();
456			while ((device = compareIterator.Next()) != NULL) {
457				BIOSDrive *compareDrive = (BIOSDrive *)device;
458
459				if (compareDrive == drive
460					|| compareDrive->Identifier().device_type != UNKNOWN_DEVICE)
461					continue;
462
463// TODO: Until we can actually get and compare *all* fields of the disk
464// identifier in the kernel, we cannot compare the whole structure (we also
465// should be more careful zeroing the structure before we fill it).
466#if 0
467				if (!memcmp(&drive->Identifier(), &compareDrive->Identifier(),
468						sizeof(disk_identifier))) {
469					clash = true;
470					break;
471				}
472#else
473				const disk_identifier& ourId = drive->Identifier();
474				const disk_identifier& otherId = compareDrive->Identifier();
475				if (memcmp(&ourId.device.unknown.check_sums,
476						&otherId.device.unknown.check_sums,
477						sizeof(ourId.device.unknown.check_sums)) == 0) {
478					clash = true;
479				}
480#endif
481			}
482
483			if (clash)
484				break;
485		}
486
487		if (!clash) {
488			// our work here is done.
489			return;
490		}
491
492		// add a new block to the check sums
493
494		off_t offset = get_next_check_sum_offset(index, minSize);
495		int32 i = index % NUM_DISK_CHECK_SUMS;
496		iterator.Rewind();
497
498		while ((device = iterator.Next()) != NULL) {
499			BIOSDrive *drive = (BIOSDrive *)device;
500
501			disk_identifier& disk = drive->Identifier();
502			disk.device.unknown.check_sums[i].offset = offset;
503			disk.device.unknown.check_sums[i].sum = compute_check_sum(drive, offset);
504
505			TRACE(("disk %x, offset %lld, sum %lu\n", drive->DriveID(), offset,
506				disk.device.unknown.check_sums[i].sum));
507		}
508
509		index++;
510	}
511
512	// If we get here, we couldn't find a way to differentiate all disks from each other.
513	// It's very likely that one disk is an exact copy of the other, so there is nothing
514	// we could do, anyway.
515
516	dprintf("Could not make BIOS drives unique! Might boot from the wrong disk...\n");
517}
518
519
520static status_t
521add_block_devices(NodeList *devicesList, bool identifierMissing)
522{
523	if (sBlockDevicesAdded)
524		return B_OK;
525
526	uint8 driveCount;
527	if (get_number_of_drives(&driveCount) != B_OK)
528		return B_ERROR;
529
530	dprintf("number of drives: %d\n", driveCount);
531
532	for (int32 i = 0; i < driveCount; i++) {
533		uint8 driveID = i + 0x80;
534		if (driveID == gBootDriveID)
535			continue;
536
537		BIOSDrive *drive = new(nothrow) BIOSDrive(driveID);
538		if (drive->InitCheck() != B_OK) {
539			dprintf("could not add drive %u\n", driveID);
540			delete drive;
541			continue;
542		}
543
544		// Only add usable drives
545		if (is_drive_readable(drive))
546			devicesList->Add(drive);
547		else {
548			dprintf("could not read from drive %" B_PRIu8 ", not adding\n", driveID);
549			delete drive;
550			continue;
551		}
552
553		if (drive->FillIdentifier() != B_OK)
554			identifierMissing = true;
555	}
556
557	if (identifierMissing) {
558		// we cannot distinguish between all drives by identifier, we need
559		// compute checksums for them
560		find_unique_check_sums(devicesList);
561	}
562
563	sBlockDevicesAdded = true;
564	return B_OK;
565}
566
567
568//	#pragma mark -
569
570
571BIOSDrive::BIOSDrive(uint8 driveID)
572	:
573	fDriveID(driveID),
574	fSize(0)
575{
576	TRACE(("drive ID %u\n", driveID));
577
578	if (driveID < 0x80 || !are_extensions_available(driveID)
579		|| get_ext_drive_parameters(driveID, &fParameters) != B_OK) {
580		// old style CHS support
581
582		if (get_drive_parameters(driveID, &fParameters) != B_OK) {
583			dprintf("getting drive parameters for: %u failed!\n", fDriveID);
584			return;
585		}
586
587		TRACE(("  cylinders: %lu, heads: %lu, sectors: %lu, bytes_per_sector: %u\n",
588			fParameters.cylinders, fParameters.heads, fParameters.sectors_per_track,
589			fParameters.bytes_per_sector));
590		TRACE(("  total sectors: %lld\n", fParameters.sectors));
591
592		fBlockSize = 512;
593		fSize = fParameters.sectors * fBlockSize;
594		fLBA = false;
595		fHasParameters = false;
596	} else {
597		TRACE(("size: %x\n", fParameters.parameters_size));
598		TRACE(("drive_path_signature: %x\n", fParameters.device_path_signature));
599		TRACE(("host bus: \"%s\", interface: \"%s\"\n", fParameters.host_bus,
600			fParameters.interface_type));
601		TRACE(("cylinders: %lu, heads: %lu, sectors: %lu, bytes_per_sector: %u\n",
602			fParameters.cylinders, fParameters.heads, fParameters.sectors_per_track,
603			fParameters.bytes_per_sector));
604		TRACE(("total sectors: %lld\n", fParameters.sectors));
605
606		fBlockSize = fParameters.bytes_per_sector;
607		fSize = fParameters.sectors * fBlockSize;
608		fLBA = true;
609		fHasParameters = true;
610	}
611}
612
613
614BIOSDrive::~BIOSDrive()
615{
616}
617
618
619status_t
620BIOSDrive::InitCheck() const
621{
622	return fSize > 0 ? B_OK : B_ERROR;
623}
624
625
626ssize_t
627BIOSDrive::ReadAt(void *cookie, off_t pos, void *buffer, size_t bufferSize)
628{
629	uint32 offset = pos % fBlockSize;
630	pos /= fBlockSize;
631
632	uint32 blocksLeft = (bufferSize + offset + fBlockSize - 1) / fBlockSize;
633	int32 totalBytesRead = 0;
634
635	//TRACE(("BIOS reads %lu bytes from %lld (offset = %lu), drive %u\n",
636	//	blocksLeft * fBlockSize, pos * fBlockSize, offset, fDriveID));
637
638	uint32 scratchSize = 24 * 1024 / fBlockSize;
639		// maximum value allowed by Phoenix BIOS is 0x7f
640
641	while (blocksLeft > 0) {
642		uint32 blocksRead = blocksLeft;
643		if (blocksRead > scratchSize)
644			blocksRead = scratchSize;
645
646		if (fLBA) {
647			struct disk_address_packet *packet = (disk_address_packet *)kDataSegmentScratch;
648			memset(packet, 0, sizeof(disk_address_packet));
649
650			packet->size = sizeof(disk_address_packet);
651			packet->number_of_blocks = blocksRead;
652			packet->buffer = kExtraSegmentScratch;
653			packet->lba = pos;
654
655			struct bios_regs regs;
656			regs.eax = BIOS_EXT_READ;
657			regs.edx = fDriveID;
658			regs.esi = (addr_t)packet - kDataSegmentBase;
659			call_bios(0x13, &regs);
660
661			if (regs.flags & CARRY_FLAG)
662				goto chs_read;
663		} else {
664	chs_read:
665			// Old style CHS read routine
666
667			// We can only read up to 64 kB this way, but since scratchSize
668			// is actually lower than this value, we don't have to take care
669			// of this here.
670
671			uint32 sector = pos % fParameters.sectors_per_track + 1;
672				// sectors start countint at 1 (unlike head and cylinder)
673			uint32 head = pos / fParameters.sectors_per_track;
674			uint32 cylinder = head / fParameters.heads;
675			head %= fParameters.heads;
676
677			if (cylinder >= fParameters.cylinders) {
678				TRACE(("cylinder value %lu bigger than available %lu\n",
679					cylinder, fParameters.cylinders));
680				return B_BAD_VALUE;
681			}
682
683			// try to read from the device more than once, just to make sure it'll work
684			struct bios_regs regs;
685			int32 tries = 3;
686			bool readWorked = false;
687
688			while (tries-- > 0) {
689				regs.eax = BIOS_READ | blocksRead;
690				regs.edx = fDriveID | (head << 8);
691				regs.ecx = sector | ((cylinder >> 2) & 0xc0) | ((cylinder & 0xff) << 8);
692				regs.es = 0;
693				regs.ebx = kExtraSegmentScratch;
694				call_bios(0x13, &regs);
695
696				if ((regs.flags & CARRY_FLAG) == 0) {
697					readWorked = true;
698					break;
699				}
700
701				TRACE(("read failed\n"));
702
703				if (tries < 2) {
704					// reset disk system
705					TRACE(("reset disk system\n"));
706					regs.eax = BIOS_RESET_DISK_SYSTEM;
707					regs.edx = fDriveID;
708					call_bios(0x13, &regs);
709				}
710
711				// wait a bit between the retries (1/20 sec)
712				spin(50000);
713			}
714
715			if (!readWorked) {
716				dprintf("reading %ld bytes from drive %u failed at %lld\n",
717					blocksRead, fDriveID, pos);
718				return B_ERROR;
719			}
720		}
721
722		uint32 bytesRead = fBlockSize * blocksRead - offset;
723		// copy no more than bufferSize bytes
724		if (bytesRead > bufferSize)
725			bytesRead = bufferSize;
726
727		memcpy(buffer, (void *)(kExtraSegmentScratch + offset), bytesRead);
728		pos += blocksRead;
729		offset = 0;
730		blocksLeft -= blocksRead;
731		bufferSize -= bytesRead;
732		buffer = (void *)((addr_t)buffer + bytesRead);
733		totalBytesRead += bytesRead;
734	}
735
736	return totalBytesRead;
737}
738
739
740ssize_t
741BIOSDrive::WriteAt(void* cookie, off_t pos, const void* buffer,
742	size_t bufferSize)
743{
744	// we support only LBA addressing
745	if (!fLBA) {
746		dprintf("BIOSDrive::WriteAt(): CHS addressing not supported\n");
747		return B_UNSUPPORTED;
748	}
749
750	// we support only block-aligned writes
751	if (pos % fBlockSize != 0 || bufferSize % fBlockSize != 0) {
752		dprintf("BIOSDrive::WriteAt(pos: %" B_PRIdOFF ", size: %" B_PRIuSIZE
753			"): Block-unaligned write not supported.\n", pos, bufferSize);
754		return B_UNSUPPORTED;
755	}
756
757	pos /= fBlockSize;
758
759	uint32 blocksLeft = bufferSize / fBlockSize;
760	int32 totalBytesWritten = 0;
761
762	uint32 scratchSize = 24 * 1024 / fBlockSize;
763		// maximum value allowed by Phoenix BIOS is 0x7f
764
765	while (blocksLeft > 0) {
766		uint32 blocksToWrite = blocksLeft;
767		if (blocksToWrite > scratchSize)
768			blocksToWrite = scratchSize;
769
770		uint32 bytesToWrite = blocksToWrite * fBlockSize;
771
772		memcpy((void*)kExtraSegmentScratch, buffer, bytesToWrite);
773
774		struct disk_address_packet* packet
775			= (disk_address_packet*)kDataSegmentScratch;
776		memset(packet, 0, sizeof(disk_address_packet));
777
778		packet->size = sizeof(disk_address_packet);
779		packet->number_of_blocks = blocksToWrite;
780		packet->buffer = kExtraSegmentScratch;
781		packet->lba = pos;
782
783		struct bios_regs regs;
784		regs.eax = BIOS_EXT_WRITE;	// al = 0x00 -- no write verify
785		regs.edx = fDriveID;
786		regs.esi = (addr_t)packet - kDataSegmentBase;
787		call_bios(0x13, &regs);
788
789		if (regs.flags & CARRY_FLAG)
790			return B_ERROR;
791
792		pos += blocksToWrite;
793		blocksLeft -= blocksToWrite;
794		bufferSize -= bytesToWrite;
795		buffer = (void*)((addr_t)buffer + bytesToWrite);
796		totalBytesWritten += bytesToWrite;
797	}
798
799	return totalBytesWritten;
800}
801
802
803off_t
804BIOSDrive::Size() const
805{
806	return fSize;
807}
808
809
810status_t
811BIOSDrive::FillIdentifier()
812{
813	if (HasParameters()) {
814		// try all drive_parameters versions, beginning from the most informative
815
816#if 0
817		if (fill_disk_identifier_v3(fIdentifier, fParameters) == B_OK)
818			return B_OK;
819
820		if (fill_disk_identifier_v2(fIdentifier, fParameters) == B_OK)
821			return B_OK;
822#else
823		// TODO: the above version is the correct one - it's currently
824		//		disabled, as the kernel boot code only supports the
825		//		UNKNOWN_BUS/UNKNOWN_DEVICE way to find the correct boot
826		//		device.
827		if (fill_disk_identifier_v3(fIdentifier, fParameters) != B_OK)
828			fill_disk_identifier_v2(fIdentifier, fParameters);
829
830#endif
831
832		// no interesting information, we have to fall back to the default
833		// unknown interface/device type identifier
834	}
835
836	fIdentifier.bus_type = UNKNOWN_BUS;
837	fIdentifier.device_type = UNKNOWN_DEVICE;
838	fIdentifier.device.unknown.size = Size();
839
840	for (int32 i = 0; i < NUM_DISK_CHECK_SUMS; i++) {
841		fIdentifier.device.unknown.check_sums[i].offset = -1;
842		fIdentifier.device.unknown.check_sums[i].sum = 0;
843	}
844
845	return B_ERROR;
846}
847
848
849//	#pragma mark -
850
851
852status_t
853platform_add_boot_device(struct stage2_args *args, NodeList *devicesList)
854{
855	TRACE(("boot drive ID: %x\n", gBootDriveID));
856
857	BIOSDrive *drive = new(nothrow) BIOSDrive(gBootDriveID);
858	if (drive->InitCheck() != B_OK) {
859		dprintf("no boot drive!\n");
860		delete drive;
861		return B_ERROR;
862	}
863
864	devicesList->Add(drive);
865
866	if (drive->FillIdentifier() != B_OK) {
867		// We need to add all block devices to give the kernel the possibility
868		// to find the right boot volume
869		add_block_devices(devicesList, true);
870	}
871
872	TRACE(("boot drive size: %lld bytes\n", drive->Size()));
873	gBootVolume.SetBool(BOOT_VOLUME_BOOTED_FROM_IMAGE, gBootedFromImage);
874
875	return B_OK;
876}
877
878
879status_t
880platform_get_boot_partitions(struct stage2_args *args, Node *bootDevice,
881	NodeList *list, NodeList *bootList)
882{
883	BIOSDrive *drive = static_cast<BIOSDrive *>(bootDevice);
884	off_t offset = (off_t)gBootPartitionOffset * drive->BlockSize();
885
886	dprintf("boot partition offset: %lld\n", offset);
887
888	NodeIterator iterator = list->GetIterator();
889	boot::Partition *partition = NULL;
890	while ((partition = (boot::Partition *)iterator.Next()) != NULL) {
891		TRACE(("partition offset = %lld, size = %lld\n", partition->offset, partition->size));
892		// search for the partition that contains the partition
893		// offset as reported by the BFS boot block
894		if (offset >= partition->offset
895			&& offset < partition->offset + partition->size) {
896			bootList->Insert(partition);
897			return B_OK;
898		}
899	}
900
901	return B_ENTRY_NOT_FOUND;
902}
903
904
905status_t
906platform_add_block_devices(stage2_args *args, NodeList *devicesList)
907{
908	return add_block_devices(devicesList, false);
909}
910
911
912status_t
913platform_register_boot_device(Node *device)
914{
915	BIOSDrive *drive = (BIOSDrive *)device;
916
917	check_cd_boot(drive);
918
919	gBootVolume.SetInt64("boot drive number", drive->DriveID());
920	gBootVolume.SetData(BOOT_VOLUME_DISK_IDENTIFIER, B_RAW_TYPE,
921		&drive->Identifier(), sizeof(disk_identifier));
922
923	return B_OK;
924}
925
926
927void
928platform_cleanup_devices()
929{
930}
931