Page 1 of 1

[SOLVED]Problems reading/writing data to drives via PIO

Posted: Fri Oct 15, 2010 3:44 pm
by Almamu
Writing my HDD r/w driver i have arrived to this code, but, when i try to write an "H" to the sector 0 or 1 and read it back i get no data in the buffer. What im doing wrong?

Code: Select all

void HDD_read_sector(long long addr, unsigned char drive, unsigned char* buffer){
	//Send two NULL(0x00)bytes to port 0x1F1
	outb(0x1F1, 0x00);
	outb(0x1F2, 0x00);
	outb(0x1F3, (unsigned char)(addr >> 24));
	outb(0x1F4, (unsigned char)(addr >> 32));
	outb(0x1F5, (unsigned char)(addr >> 40));
	outb(0x1F1, 0x00);
	outb(0x1F2, 0x01);
	outb(0x1F3, (unsigned char)addr);
	outb(0x1F4, (unsigned char)(addr >> 8));
	outb(0x1F5, (unsigned char)(addr >> 16));
	outb(0x1F6, 0x40 | (drive << 4));
	outb(0x1F7, 0x24);
	//Wait until the drive is ready
	unsigned short tmpword;
	int idx = 0;
	while(!(inb(0x1F7) & 0x08)){k_xy_printf("R", 24, 0, 0x17);}
	for(idx = 0; idx < 256; idx++){
		tmpword = inw(0x1F0);
		buffer[idx * 2] = (unsigned char)tmpword;
		buffer[idx * 2 + 1] = (unsigned char)(tmpword >> 8);
	}
}
void HDD_write_sector(long long addr, unsigned char drive, unsigned char* buffer){
	//Send two NULL(0x00)bytes to port 0x1F1
	outb(0x1F1, 0x00);
	outb(0x1F2, 0x00);
	outb(0x1F3, (unsigned char)(addr >> 24));
	outb(0x1F4, (unsigned char)(addr >> 32));
	outb(0x1F5, (unsigned char)(addr >> 40));
	outb(0x1F1, 0x00);
	outb(0x1F2, 0x01);
	outb(0x1F3, (unsigned char)addr);
	outb(0x1F4, (unsigned char)(addr >> 8));
	outb(0x1F5, (unsigned char)(addr >> 16));
	outb(0x1F6, 0x40 | (drive << 4));
	outb(0x1F7, 0x24);
	//Wait until the drive is ready
	unsigned short tmpword;
	int idx = 0;
	while(!(inb(0x1F7) & 0x08)){k_xy_printf("W", 24, 0, 0x17);}
	for (idx = 0; idx < 256; idx++){
		tmpword = buffer[8 + idx * 2] | (buffer[8 + idx * 2 + 1] << 8);
		outw(0x1F0, tmpword);
	}
}
I'm not sure at all that my function is correctly written.
P.D: Im reading this.

Re: Problems reading/writing data to drives via PIO

Posted: Fri Oct 15, 2010 4:59 pm
by ~
I think that tutorial is very unreliable and isn't telling you what is going on. The sequence for reading should be this according to ATA7. The writing procedure is very similar, the only thing that changes is the command and that you will write to the data port instead of reading it.

As an example read the LBA Sector 0. This sample pseudocode uses a little bit of assembly instead of inportb() or outportb() but should be easy enough to understand:

Code: Select all

//First, set the Device and set to LBA addressing
//(most of the time in most modern hard disks):
///
 $DX=1F6h;
 $AL=01000000b;  //Bits:
                 //   0-3 -- LBA 24-27 (0 this time)
                 //     4 -- DEV bit -- Master (0) or Slave (1)
                 //     5 -- obsolete
                 //     6 -- LBA (activate it for LBA addressing)
 asm out dx,al   ;//     7 -- obsolete



//Write the sector count (just one this time):
///
 $DX=1F2h;
 $AL=1;    //1 to 255, 0 means 256
 asm out dx,al

//Write the LBA Low, LBA Mid and LBA High
//registers (0 this time):
///
 $DX=1F3h;  //LBA Low
 $AL=0;
 asm out dx,al

 $DX=1F4h;  //LBA Mid
 $AL=0;
 asm out dx,al

 $DX=1F5h;  //LBA High
 $AL=0;
 asm out dx,al


//Write the READ SECTOR(S) command
//command 20h:
///
 $DX=1F7h;
 $AL=20h;
 asm out dx,al


///ATTENTION:
///ATTENTION:
///ATTENTION: You must read the Status or Alternate Status
              register to make sure that the DRQ bit is set
              (e.g, bit 3 at 1F7h or at 3F6h if it's the
              Primary status or alternate status registers,
              respectively), and apply your own time delaying
              control routines here.


//Read the 512-byte sector as 256 words
//(ALWAYS read/write data one word -i.e., 2 bytes- at a time):
///
 $EDI=mybuffer; //destination buffer
 $DX=1F0h; //the Data Port
 $ECX=256; //Words count == 512/2 == 512/sizeof(word) == 256
 while($ECX!=0)
 {
   asm insw   ;Read one word into address pointed to by ES:(E)DI
   $ECX--;  //Discount one more word
 }


//It is done here! Check whether the data buffer is correct
///
As you can see, the first parameter to be written is always the Device Register at 1F6h, etc, and the last parameter to be written is the actual command at 1F7h, etc. Then you must read all of the data you requested even if there was an error. Otherwise, the drive could stall, waiting for you to read all of the requested data, be it correct or corrupted.

You must also test for the DRQ bit if you read more than one sector, to give the drive proper response time.

Re: Problems reading/writing data to drives via PIO

Posted: Sat Oct 16, 2010 3:17 am
by Almamu
I dont understand some parts. For example:

Code: Select all

$DX=1F6h;
$AL=01000000b;  //Bits:
                 //   0-3 -- LBA 24-27 (0 this time)
                 //     4 -- DEV bit -- Master (0) or Slave (1)
                 //     5 -- obsolete
                 //     6 -- LBA (activate it for LBA addressing)
asm out dx,al   ;//     7 -- obsolete
How can i send these bits to the port in C(using outb)?

Re: Problems reading/writing data to drives via PIO

Posted: Sat Oct 16, 2010 4:34 am
by Gaidheal
Construct a byte that fits the pattern (in this case it's listed) and send it to the port - "outb (0x1f7, 0x20)" (100 0000b is 20h is 32d). You know about bitmasks and so on, right?

Re: Problems reading/writing data to drives via PIO

Posted: Sat Oct 16, 2010 8:05 am
by ~
Almamu wrote:I dont understand some parts. For example:

Code: Select all

$DX=1F6h;
$AL=01000000b;  //Bits:
                 //   0-3 -- LBA 24-27 (0 this time)
                 //     4 -- DEV bit -- Master (0) or Slave (1)
                 //     5 -- obsolete
                 //     6 -- LBA (activate it for LBA addressing)
asm out dx,al   ;//     7 -- obsolete
How can i send these bits to the port in C(using outb)?

$DX is the I/O port and $AL is the value. To convert between binary and hexadecimal and decimal, you can use the Windows calculator in Scientific mode, and also the scientifc calculator in Gnome/KDE.

Paste the binary value (in this case 01000000) in binary mode and translate it selecting Hex or Dec. Then the C code in this case would be:

Code: Select all

outb(0x1F6, 0x40);

Re: Problems reading/writing data to drives via PIO

Posted: Sat Oct 16, 2010 3:20 pm
by Gaidheal
Oops! Quite right, 40h it is.

Re: Problems reading/writing data to drives via PIO

Posted: Sat Oct 16, 2010 6:04 pm
by Almamu
~ wrote:
Almamu wrote:I dont understand some parts. For example:

Code: Select all

$DX=1F6h;
$AL=01000000b;  //Bits:
                 //   0-3 -- LBA 24-27 (0 this time)
                 //     4 -- DEV bit -- Master (0) or Slave (1)
                 //     5 -- obsolete
                 //     6 -- LBA (activate it for LBA addressing)
asm out dx,al   ;//     7 -- obsolete
How can i send these bits to the port in C(using outb)?

$DX is the I/O port and $AL is the value. To convert between binary and hexadecimal and decimal, you can use the Windows calculator in Scientific mode, and also the scientifc calculator in Gnome/KDE.

Paste the binary value (in this case 01000000) in binary mode and translate it selecting Hex or Dec. Then the C code in this case would be:

Code: Select all

outb(0x1F6, 0x40);
Oh, ok, so i only need to convert from binary to hex, not much more. Thanks, i will try to do all in this way...

Re: Problems reading/writing data to drives via PIO

Posted: Sun Oct 17, 2010 4:18 am
by Almamu
I have this code now:

Code: Select all

void HDD_read_sector(unsigned char* buffer){
	outb(0x1F6, 0x40);//Drive set to LBA addressing
	outb(0x1F2, 1);//Sector count
	outb(0x1F3, 0);//LBA Low
	outb(0x1F4, 0);//LBA Mid
	outb(0x1F5, 0);//LBA Hig
	outb(0x1F7, 0x20);//Read Sector(s) command
	printf("Status: %d\n", inb(0x1F7));
	while(inb(0x1F7) != 0x58){
		HDD_draw_info();
		k_xy_printf("B", 24, 0, 0x17);
		printf("RStatus: %d\n", inb(0x1F7));
	}
	int c = 256;
	printf("Reading bytes...\n");
	while(c != 0){
		HDD_draw_info();
		k_xy_printf("R", 24, 0, 0x17);
		buffer[c] = inw(0x1F0);
		c--;
	}
	return;
}
void HDD_write_sector(unsigned char* buffer){
	outb(0x1F6, 0x40);
	outb(0x1F2, 1);
	outb(0x1F3, 0);
	outb(0x1F4, 0);
	outb(0x1F5, 0);
	outb(0x1F7, 0x30);
	printf("Status: %d\n", inb(0x1F7));
	while(inb(0x1F7) != 0x48){
		HDD_draw_info();
		k_xy_printf("B", 24, 0, 0x17);
		printf("WStatus: %d\n", inb(0x1F7));
	}
	printf("Writing bytes\n");
	int c = 256;
	while(c != 0){
		HDD_draw_info();
		k_xy_printf("W", 24, 0, 0x17);
		outw(0x1F0, buffer[c]);
		c--;
	}
	return;
}
I always get 58h(reading 0x1F7) from the read(if i put a while and read it all the time) code and 48h(reading 0x1F7) from the write code. But, when i read the same sector i don't get the "H" im trying to write...

Re: Problems reading/writing data to drives via PIO

Posted: Sun Dec 12, 2010 7:46 am
by Almamu
Solved(at least it works at the moment). There is how the functions looks now(the master drive is selected in kernel initialization):

Code: Select all

#define HDD_PRIMARY_CONTROLLER 0x1F0
#define HDD_SECONDARY_CONTROLLER 0x170

#define HDD_PRIMARY_ALTERNATE_STATUS 0x3F6
#define HDD_SECONDARY_ALTERNATE_STATUS 0x376

#define HDD_DATA_PORT		0x000
#define HDD_ERROR_PORT		0x001
#define HDD_SECTORCOUNT_PORT	0x002
#define HDD_SECTORNUMBER_PORT	0x003
#define HDD_CYLINDERLOW_PORT	0x004
#define HDD_CYLINDERHIGH_PORT	0x005
#define HDD_DRIVE_PORT		0x006
#define HDD_COMMAND_PORT	0x007

#define HDD_COMMAND_FLUSH	0xE7
#define HDD_SINGLEREAD_COMMAND	0x20
#define HDD_SINGLEWRITE_COMMAND 0x30

#define HDD_ADDRESSING_MODE	0x40

void HDD_read_sector(unsigned char* buffer, u8int sectorcount, u32int LBA){
	outb(HDD_PRIMARY_CONTROLLER + HDD_DRIVE_PORT, HDD_ADDRESSING_MODE);//Modo LBA addressing
	outb(HDD_PRIMARY_CONTROLLER + HDD_ERROR_PORT, 0x00);
	outb(HDD_PRIMARY_CONTROLLER + HDD_SECTORCOUNT_PORT, sectorcount);//Sector count
	outb(HDD_PRIMARY_CONTROLLER + HDD_SECTORNUMBER_PORT, (u8int)LBA);//Sector to read
	outb(HDD_PRIMARY_CONTROLLER + HDD_CYLINDERLOW_PORT, (u8int)(LBA >> 8));//Cylinder low
	outb(HDD_PRIMARY_CONTROLLER + HDD_CYLINDERHIGH_PORT, (u8int)(LBA >> 16));//Cylinder high
	outb(HDD_PRIMARY_CONTROLLER + HDD_COMMAND_PORT, HDD_SINGLEREAD_COMMAND);//Read Sector(s) command
	inb(HDD_PRIMARY_ALTERNATE_STATUS);
	inb(HDD_PRIMARY_ALTERNATE_STATUS);
	inb(HDD_PRIMARY_ALTERNATE_STATUS);
	inb(HDD_PRIMARY_ALTERNATE_STATUS);
	int c = 0;
	printf("Reading bytes...\n");
	while(c != 256){
		buffer[c] = inw(HDD_PRIMARY_CONTROLLER);
		c++;
	}
	return;
}
void HDD_write_sector(unsigned char* buffer, u8int sectorcount, u32int LBA){
	outb(HDD_PRIMARY_CONTROLLER + HDD_DRIVE_PORT, HDD_ADDRESSING_MODE);//LBA addressing
	outb(HDD_PRIMARY_CONTROLLER + HDD_ERROR_PORT, 0x00);
	outb(HDD_PRIMARY_CONTROLLER + HDD_SECTORCOUNT_PORT, sectorcount);//Sector count
	outb(HDD_PRIMARY_CONTROLLER + HDD_SECTORNUMBER_PORT, (u8int)LBA);//Sector to read
	outb(HDD_PRIMARY_CONTROLLER + HDD_CYLINDERLOW_PORT, (u8int)(LBA >> 8));//Cylinder low
	outb(HDD_PRIMARY_CONTROLLER + HDD_CYLINDERHIGH_PORT, (u8int)(LBA >> 16));//Cylinder high
	outb(HDD_PRIMARY_CONTROLLER + HDD_COMMAND_PORT, HDD_SINGLEWRITE_COMMAND);//Write Sector(s) command
	inb(HDD_PRIMARY_ALTERNATE_STATUS);
	inb(HDD_PRIMARY_ALTERNATE_STATUS);
	inb(HDD_PRIMARY_ALTERNATE_STATUS);
	inb(HDD_PRIMARY_ALTERNATE_STATUS);
	printf("Writing bytes\n");
	int c = 0;
	while(c != 256){
		outw(HDD_PRIMARY_CONTROLLER, buffer[c]);
		c++;
		delay(2);
		outb(HDD_PRIMARY_CONTROLLER + HDD_COMMAND_PORT, HDD_COMMAND_FLUSH);
	}
	delay(400);
	return;
}
Im not sure if its correctly written, any errors please tell me.

P.D: Sorry for triple posting