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

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
User avatar
Almamu
Member
Member
Posts: 47
Joined: Wed Jul 07, 2010 9:41 am

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

Post 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.
Last edited by Almamu on Sun Dec 12, 2010 7:46 am, edited 1 time in total.
Image
User avatar
~
Member
Member
Posts: 1228
Joined: Tue Mar 06, 2007 11:17 am
Libera.chat IRC: ArcheFire

Re: Problems reading/writing data to drives via PIO

Post 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.
User avatar
Almamu
Member
Member
Posts: 47
Joined: Wed Jul 07, 2010 9:41 am

Re: Problems reading/writing data to drives via PIO

Post 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)?
Image
Gaidheal
Member
Member
Posts: 51
Joined: Mon Oct 04, 2010 6:23 pm

Re: Problems reading/writing data to drives via PIO

Post 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?
User avatar
~
Member
Member
Posts: 1228
Joined: Tue Mar 06, 2007 11:17 am
Libera.chat IRC: ArcheFire

Re: Problems reading/writing data to drives via PIO

Post 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);
Gaidheal
Member
Member
Posts: 51
Joined: Mon Oct 04, 2010 6:23 pm

Re: Problems reading/writing data to drives via PIO

Post by Gaidheal »

Oops! Quite right, 40h it is.
User avatar
Almamu
Member
Member
Posts: 47
Joined: Wed Jul 07, 2010 9:41 am

Re: Problems reading/writing data to drives via PIO

Post 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...
Image
User avatar
Almamu
Member
Member
Posts: 47
Joined: Wed Jul 07, 2010 9:41 am

Re: Problems reading/writing data to drives via PIO

Post 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...
Image
User avatar
Almamu
Member
Member
Posts: 47
Joined: Wed Jul 07, 2010 9:41 am

Re: Problems reading/writing data to drives via PIO

Post 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
Image
Post Reply