Page 1 of 1

Question regarding LBA28 and 48 IO

Posted: Sun Feb 03, 2013 11:23 pm
by greyOne
First post, and it's a question, huzzah. I've been here a while though.
In any case, I've written a PIO driver for reading from a hard drive using LBA28 and 48.
The result was code that was seemingly functional at first, but I've now found it to be a major problem.

Basically here's the issue I've found:
Regardless of where I write and read from, (LBA address wise) I get the same result;
I tried writing 5 different strings to consecutive addresses, but when I tried reading them back out,
I ended with the same string times.
(The result string that was that which was written to the last address.)

I also tried changing the address values to ones grossly far away from each other (0, 512, 1024, etc.)
And ended up with the same result.
I made sure that I was clearing my buffers correctly between operations by the way.

Below is my code:

Code: Select all

// Read operations
#define DRIVE_READ			0x20
#define DRIVE_READ_EXT		0x24

// Write operations
#define DRIVE_WRITE			0x30
#define DRIVE_WRITE_EXT		0x34

// Master/lave masks
#define MASTER				0xE0
#define MASTER_EXT			0x40

// Register masks
#define REG_DATA			0
#define REG_FEATURES			1
#define REG_SECT_COUNT		2
#define REG_SECT_NUM		3
#define REG_CYL_LOW			4
#define REG_CYL_HIGH			5
#define REG_DRIVE			6
#define REG_COMMAND			7

u8int read_lba28(u16int drive, u32int lba_address, u8int *buffer) {
	u16int i = 0, tmp = 0;
													// Master/Slave and high nibble
	outportb(drive | REG_DRIVE, 	 MASTER | (drive << 4) | ((lba_address >> 24) & 0x0F));
	outportb(drive | REG_FEATURES,	 0x00);					// Null byte
	outportb(drive | REG_SECT_COUNT, 0x00);					// Sector count
	outportb(drive | REG_SECT_NUM,	(u8int) lba_address);			// Low 8 bits
	outportb(drive | REG_CYL_LOW,	(u8int) (lba_address >> 8));		// Next 8 bits
	outportb(drive | REG_CYL_HIGH,	(u8int) (lba_address >> 16));		// Next 8 bits
	outportb(drive | REG_COMMAND, 	DRIVE_READ);				// Read command
	timer_wait(1);										// Tiny delay

	while(!(inportb(drive | REG_COMMAND) & 0x08));				// Wait for drive to be ready
	for(i = 0; i < 256; i++) {								// Read data into buffer
		tmp = inportw(drive);								// Read a word
		*buffer++ = (u8int)tmp;							// Low byte
		*buffer++ = (u8int)(tmp >> 8);						// High byte
	}
	return 1;											// Return on success
}

u8int write_lba28(u16int drive, u32int lba_address, u8int *buffer) {
	u16int i = 0, tmp = 0;
													// Master/Slave and high nibble
	outportb(drive | REG_DRIVE, 	 MASTER | (drive << 4) | ((lba_address >> 24) & 0x0F));
	outportb(drive | REG_FEATURES,	 0x00);					// Null byte
	outportb(drive | REG_SECT_COUNT, 0x00);					// Sector count
	outportb(drive | REG_SECT_NUM,	(u8int) lba_address);			// Low 8 bits
	outportb(drive | REG_CYL_LOW,	(u8int) (lba_address >> 8));		// Next 8 bits
	outportb(drive | REG_CYL_HIGH,	(u8int) (lba_address >> 16));		// Next 8 bits
	outportb(drive | REG_COMMAND, 	DRIVE_WRITE);				// Write command
	timer_wait(1);										// Tiny delay

	while(!(inportb(drive | REG_COMMAND) & 0x08));				// Wait for drive to be ready
	for(i = 0; i < 256; i++) {								// Write data from buffer
		tmp = *buffer++;									// Store low byte
		tmp |= (*buffer++ << 8);							// OR with high byte
		outportw(drive, tmp);								// Write word to drive
	}
	outportb(drive | REG_COMMAND, 0xE7);						// Flush the cache
	return 1;											// Return on success
}

u8int read_lba48(u16int drive, u64int lba_address, u8int *buffer, u32int sector_count) {
	u16int i = 0, tmp = 0;
	
	outportb(drive | REG_DRIVE, MASTER_EXT | (drive << 4));			// Master/Slave byte	
	outportb(drive | REG_SECT_COUNT, (u8int)(sector_count >> 8));		// Sector count high byte	
	outportb(drive | REG_SECT_NUM, 	(u8int)(lba_address >> 24));		// High byte 1 LBA address
	outportb(drive | REG_CYL_LOW, 	(u8int)(lba_address >> 32));		// High byte 2 LBA address
	outportb(drive | REG_CYL_HIGH, 	(u8int)(lba_address >> 40));		// High byte 3 LBA address
	outportb(drive | REG_SECT_COUNT, (u8int)(sector_count));			// Sector count low byte
	outportb(drive | REG_SECT_NUM, 	(u8int)(lba_address));			// Low byte 1 LBA address
	outportb(drive | REG_CYL_LOW, 	(u8int)(lba_address >> 8));		// Low byte 2 LBA address
	outportb(drive | REG_CYL_HIGH, 	(u8int)(lba_address >> 16));		// Low byte 3 LBA address
	outportb(drive | REG_COMMAND, 	DRIVE_READ_EXT);			// Read operation

	while(!(inportb(drive | REG_COMMAND) & 0x08));				// Wait for drive to be ready
	for(i = 0; i < 256; i++) {								// Read data into buffer
		tmp = inportw(drive);								// Read a word
		*buffer++ = (u8int)tmp;							// Low byte
		*buffer++ = (u8int)(tmp >> 8);						// High byte
	}
	return 1;											// Return on success
}

u8int write_lba48(u16int drive, u64int lba_address, u8int *buffer, u32int sector_count) {
	u16int i = 0, tmp = 0;
	
	outportb(drive | REG_DRIVE, MASTER_EXT | (drive << 4));			// Master/Slave byte	
	outportb(drive | REG_SECT_COUNT, (u8int)(sector_count >> 8));		// Sector count high byte	
	outportb(drive | REG_SECT_NUM, 	(u8int)(lba_address >> 24));		// High byte 1 LBA address
	outportb(drive | REG_CYL_LOW, 	(u8int)(lba_address >> 32));		// High byte 2 LBA address
	outportb(drive | REG_CYL_HIGH, 	(u8int)(lba_address >> 40));		// High byte 3 LBA address
	outportb(drive | REG_SECT_COUNT, (u8int)(sector_count));			// Sector count low byte
	outportb(drive | REG_SECT_NUM, 	(u8int)(lba_address));			// Low byte 1 LBA address
	outportb(drive | REG_CYL_LOW, 	(u8int)(lba_address >> 8));		// Low byte 2 LBA address
	outportb(drive | REG_CYL_HIGH, 	(u8int)(lba_address >> 16));		// Low byte 3 LBA address
	outportb(drive | REG_COMMAND, DRIVE_WRITE_EXT);				// Write operation

	while(!(inportb(drive | 7) & 0x08));							// Wait for drive to be ready
	for(i = 0; i < 256; i++) {								// Write data from buffer
		tmp = *buffer++;									// Store low byte
		tmp |= (*buffer++ << 8);							// OR with high byte
		outportw(drive, tmp);								// Write word to drive
	}
	outportb(drive | REG_COMMAND, 0xE7);						// Flush the cache
	return 1;											// Return on success
}
(Despite my best efforts, my comments are not correctly aligned in the post.)

And now for my 2 main questions;
1. I've spent a good deal of time trying to find out what purpose sector count serves,
The wiki didn't yield much insight into what it does, and from my observations a value of 1
Will make all subsequent read/write attempts hang whereas any other value will not,
Regardless how many operation you make.
I've tried to look it up in Wikipedia as well... Not much help either sadly.

2. Why do my read/write operations not go through correctly?
The fact writing a string "Hello" to address 4 and then reading from address 512 will retrieve that string
Suggests to me that all my reads and write are going to sector 0 on the drive.
That and the fact that the virtual drive file is still 1MB in size.

Remark: I'm working under Virtual Box using a dynamically sized vdi drive image.
I'm also working in 32 bits, so u64int is typedef for unsigned long long.

Thanks in advance to anyone who takes the time to make heads or tails of this!

Re: Question regarding LBA28 and 48 IO

Posted: Mon Feb 04, 2013 9:29 am
by greyOne
It seems I have found the answer to both my questions myself.

If anyone encounters the issue in the future,
The problem was in this line,

Code: Select all

    outportb(drive | REG_DRIVE,     MASTER | (drive << 4) | ((lba_address >> 24) & 0x0F));
Partially due to this:

Code: Select all

(drive << 4)
Basically, the bit shift left wasn't giving the right result when I traced it.
So a simple work around was in order.

The solution generally looked like this:

Code: Select all

#define MASTER        0x40
#define SLAVE          0x10

. . .

u8int drive_mask = MASTER;
if(slave) {
    drive_mask |= SLAVE;
}

. . .

outportb(drive | REG_DRIVE,	(u8int)(drive_mask | (lba_address & 0x0F000000) >> 24));
Cheers.

Re: Question regarding LBA28 and 48 IO

Posted: Mon Feb 04, 2013 7:40 pm
by bewing
What sectorcount does is to allow you (the driver) to read and write multiple contiguous sectors of the disk with one command. You can always just do one sector per command if you like -- but it saves you a dozen or two IN and OUT operations between sectors if you use the sectorcount. Doing multiple sectors works on any drive that anyone here has ever tested.