Page 1 of 2

PIO hard disk access

Posted: Sun Jul 08, 2007 12:34 pm
by salil_bhagurkar
I am writing code for accessing hard disk by pio:

Code: Select all

To read a sector using LBA28:
1.	Send a NULL byte to port 0x1F1: outb(0x1F1, 0x00);
2.	Send a sector count to port 0x1F2: outb(0x1F2, 0x01);
3.	Send the low 8 bits of the block address to port 0x1F3: outb(0x1F3, (unsigned char)addr);
4.	Send the next 8 bits of the block address to port 0x1F4: outb(0x1F4, (unsigned char)(addr >> 8);
5.	Send the next 8 bits of the block address to port 0x1F5: outb(0x1F5, (unsigned char)(addr >> 16);
6.	Send the drive indicator, some magic bits, and highest 4 bits of the block address to port 0x1F6: outb(0x1F6, 0xE0 | (drive << 4) | ((addr >> 24) & 0x0F));
7.	Send the command (0x20) to port 0x1F7: outb(0x1F7, 0x20);

//Wait for ready:
while (!(inb(0x1F7) & 0x08)) {}
And then read/write your data from/to port 0x1F0:
// for read:
for (idx = 0; idx < 256; idx++)
{
tmpword = inw(0x1F0);
buffer[idx * 2] = (unsigned char)tmpword;
buffer[idx * 2 + 1] = (unsigned char)(tmpword >> 8);
}
This is the code that i used (its from osdever.net)
I could successfully read the hard disk image in Bochs (Primary master)
But on my PC the code hangs on the ready polling code.

I have detected a primary master on my PC (ATA).
So is there any initialization i need to do?
Please help me...

Posted: Sun Jul 08, 2007 2:09 pm
by XCHG
I have read that tutorial (if you can call it that) and that is a perfect example of crap because it does not follow the working flow of IDE transfers at all, having T13 documentations as your source. To be able to read a sector from an IDE device, you need to do these (ATA-4 compliant, PIO):

1) Read the status register of the primary or the secondary IDE controller.
2) The BSY and DRQ bits must be zero if the controller is ready.
3) Set the DEV bit to 0 for Drive0 and to 1 for Drive1 on the selected IDE controller using the Device/Head register and wait for approximately 400 nanoseconds using some NOP perhaps.
4) Read the status register again.
5) The BSY and DRQ bits must be 0 again for you to know that the IDE controller and the selected IDE drive are ready.
6) Write the LBA28 address to the designated IDE registers.
7) Set the Sector count using the Sector Count register.
8) Issue the Read Sector(s) command.
9) Read the Error register. If the ABRT bit is set then the Read Sector(s) command is not supported for that IDE drive. If the ABRT bit is not set, continue to the next step.
10) If you want to receive interrupts after reading each sector, clear the nIEN bit in the Device Control register. If you do not clear this bit then interrupts will not be generated after the reading of each sector which might cause an infinite loop if you are waiting for them. The Primary IDE Controller will generate IRQ14 and the secondary IDE controller generates IRQ 15.
11) Read the Alternate Status Register (you may even ignore the value that is read)
12) Read the Status register for the selected IDE Controller.
13) Whenever a sector of data is ready to be read from the Data Register, the BSY bit in the status register will be set to 0 and DRQ to 1 so you might want to wait until those bits are set to the mentioned values before attempting to read from the drive.
14) Read one sector from the IDE Controller 16-bits at a time using the IN or the INSW instructions.
15) See if you have to read one more sector. If yes, repeat from step 11 again.
16) If you don't need to read any more sectors, read the Alternate Status Register and ignore the byte that you read.
17) Read the status register. When the status register is read, the IDE Controller will negate the INTRQ and you will not have pending IRQs waiting to be detected. This is a MUST to read the status register when you are done reading from IDE ports.

I have coded a complete IDE ATA1-ATA4 compliant library in NASM so let me know if you need them. They are fully commented and partially documented (for now).

Posted: Mon Jul 09, 2007 11:19 am
by salil_bhagurkar
Thats very nice of you... Could you please also give me urls of the documentation on ATA (T13 etc...)... Thank you..

Posted: Mon Jul 09, 2007 3:23 pm
by exkor
http://www.osdev.org/phpBB2/viewtopic.php?t=14204
http://www.osdev.org/phpBB2/viewtopic.php?t=14033
http://www.osdever.net/cottontail/#ATA

check list:
ATA Host Adapter (dated January 2003)
ATA/ATAPI-7
IDE Busmastering
IDE PCI Controller

Serial ATA Advanced Host Controller Interface (AHCI)
http://www.intel.com/technology/serialata/ahci.htm

Posted: Thu Aug 09, 2007 10:25 am
by xlq
I've followed your instructions, and the controller doesn't report an error, but after issuing the 'read sectors' command and waiting for the BSY bit to clear, the DRQ bit is not set.

If I wait for the DRQ bit, then the ERR bit is also set.

Also, I'm not sure how to wait 250ns. Maybe this is causing it to fail?

(Btw, testing in Bochs 2.3)

Posted: Thu Aug 09, 2007 10:52 am
by exkor
xlq wrote: Also, I'm not sure how to wait 250ns. Maybe this is causing it to fail?
You can reprogram timer so it triggers fixed # each second.
Have "delay" varaible declared.
Assign number to to delay variable in HDD driver.
And wait for timer to decrement it.

not sure where you can find reprogramming code, this is what I got

Code: Select all

  mov   al,0x34  
  out   0x43,al 
  mov   al,0x9b 
  out   0x40,al
  mov   al,0x2e 
  out   0x40,al             

Posted: Thu Aug 09, 2007 11:33 am
by xlq
Now I'm reading "Gegegegegegegegege" from the data port.

This is definitely not what I have on my hard disk.

Posted: Thu Aug 09, 2007 5:56 pm
by Pavia
xlq wrote: Also, I'm not sure how to wait 250ns. Maybe this is causing it to fail?
BIOS use macros NEWIODELAY
Macros NEWIODELAY is equal instruction OUT 0EBh, AL
Port 0EBh - not used and so computer wait 250ns

Posted: Fri Aug 10, 2007 3:46 am
by xlq
OK, thanks. Still can't get any good results though :(

I think I'm getting a series of ASCII 219s out of it.

Posted: Fri Aug 10, 2007 6:03 am
by xlq
VICTORY IS MINE!!!

My inb() and inw() were completely screwed up!

Anyway, thanks for the advice on port 0xEB

Posted: Fri Aug 10, 2007 8:22 am
by lukem95
Congratulations :)

Maybe you could post the code so people could have a look at how it was done?

Posted: Fri Aug 10, 2007 8:41 am
by XCHG
I will put the complete code of my ATA-1 Driver here once I am done writing documentation for it. However, for now, maybe reading my [ATA4IDEReadFromPortsLBA28] function will help someone understand this better:

Code: Select all

; ——————————————————————————————————————————————————
  __ATA4IDEReadFromPortsLBA28:
    ; DWORD __ATA4IDEReadFromPortsLBA28 (DWORD Controller, DWORD Drive, DWORD LBA28,
    ;                                    DWORD SectorCount, void* Buffer, DWORD MaxBufferLen);


    ; Returns the number of sectors that were read from the drive

    PUSH    EBX
    PUSH    ECX
    PUSH    EDX
    PUSH    ESI
    PUSH    EDI
    PUSHFD
    PUSH    EBP
    MOV     EBP , ESP


    ; [EBP + 0x20] = Controller
    ; [EBP + 0x24] = Drive
    ; [EBP + 0x28] = LBA28
    ; [EBP + 0x2C] = SectorCount
    ; [EBP + 0x30] = Buffer
    ; [EBP + 0x34] = MaxBufferLen

    .CheckSectorCount1:
      ; Sector Count must not be zero
      MOV     ESI , DWORD PTR [EBP + 0x2C]
      TEST    ESI , ESI
      JNZ     .CheckSectorCount2
      JMP     .Failure


    .CheckSectorCount2:
      ; ESI = [SectorCount]
      ; Sector count must not be bigger than 255
      TEST    ESI , 0xFFFFFF00
      JZ      .CheckBufferLength1
      JMP     .Failure


    .CheckBufferLength1:
      ; Buffer Length must not be zero
      ; ESI = [SectorCount]
      MOV     ECX , DWORD PTR [EBP + 0x34]
      TEST    ECX , ECX
      JNZ     .CheckBufferLength2
      JMP     .Failure

    .CheckBufferLength2:
      ; Buffer length must be equal to or greater than SectorCount * 512
      ; ECX = [MaxBufferLen]
      ; ESI = [SectorCount]
      SHL     ESI , 0x00000009 ; ESI = SectorCount * 512
      CMP     ECX , ESI
      JAE     .CheckLBA28
      JMP     .Failure


    .CheckLBA28:
      SHR     ESI , 0x00000009
      ; ESI = [SectorCount]
      ; ECX = [MaxBufferLen]
      ; LBA28 must not have its leftmost nibble set
      MOV     EBX , DWORD PTR [EBP + 0x28]
      TEST    EBX , 0xF0000000
      JZ      .CheckController
      JMP     .Failure


    .CheckController:
      ; ESI = [SectorCount]
      ; ECX = [MaxBufferLen]
      ; EBX = [LBA28]
      MOV     EDX , DWORD PTR [EBP + 0x20] ; Controller
      TEST    EDX , ~(IDE_PRIMARY_CONTROLLER | IDE_SECONDARY_CONTROLLER)
      JZ      .CheckDrive
      JMP     .Failure


    .CheckDrive:
      ; ESI = [SectorCount]
      ; ECX = [MaxBufferLen]
      ; EBX = [LBA28]
      ; EDX = [Controller]
      MOV     EDI , DWORD PTR [EBP + 0x24] ; Drive
      TEST    EDI , ~(IDE_DRIVE0 | IDE_DRIVE1)
      JZ      .GetPorts
      JMP     .Failure

    .GetPorts:
      ; ESI = [SectorCount]
      ; ECX = [MaxBufferLen]
      ; EBX = [LBA28]
      ; EDX = [Controller]
      ; EDI = [Drive]
      MOV     EBX , PIDEC_PORT_BASE
      TEST    EDX , EDX
      JZ      .PrimaryControllerIsSelected
      MOV     EBX , SIDEC_PORT_BASE
      .PrimaryControllerIsSelected:



    ; Before we go on, the BSY and DRQ bits in the status register
    ; must have been set to zero
    MOV     ECX , IDE_TYPICAL_TIMEOUT
    MOV     EDX , EBX
    ADD     EDX , IDE_PORT_OFFSET_STATUS
    XOR     EAX , EAX
    .CheckBSYAndDRQ1:
      NOP
      IN      AL , DX
      AND     EAX , (IDE_STATUSREG_DRQ | IDE_STATUSREG_BSY)
      JZ      .CheckDriveExistence
      DEC     ECX
      JNZ     .CheckBSYAndDRQ1
      JMP     .Failure





    .CheckDriveExistence:
      ; ESI = [SectorCount]
      ; EBX = The base port address of the selected controller
      ; EDX = Status register's port address
      ; EDI = [Drive]
      MOV     EDX , EBX
      ADD     EDX , IDE_PORT_OFFSET_DRIVEHEAD
      MOV     EAX , EDI
      AND     EAX , (IDE_DRIVE0 | IDE_DRIVE1)
      SHL     EAX , 0x00000004
      OUT     DX , AL
      TIMES   0x04 NOP
      MOV     EDX , EBX
      ADD     EDX , IDE_PORT_OFFSET_STATUS
      XOR     EAX , EAX
      IN      AL , DX
      TEST    EAX , IDE_STATUSREG_DRDY
      JNZ     .ClearnIEN
      JMP     .Failure



    ; The nIEN bit must be cleared in the Device Control Register for
    ; interrupts to be generated by the device
    .ClearnIEN:
      XOR     EAX , EAX
      MOV     EDX , IDEC_PORT_DEVICECONTROL
      OUT     DX , AL
      TIMES   0x04 NOP



    .SetSectorcount:
      MOV     EAX , ESI
      MOV     EDX , EBX
      ADD     EDX , IDE_PORT_OFFSET_SECTORCOUNT
      OUT     DX , AL
      TIMES   0x04 NOP


    .SetLBA0TO7:
      ; ESI = [SectorCount]
      ; EBX = The base port address of the selected controller
      ; EDI = [Drive]
      MOV     EAX , DWORD PTR [EBP + 0x28] ; LBA28
      MOV     EDX , EBX
      ADD     EDX , IDE_PORT_OFFSET_LBABITS0TO7
      OUT     DX , AL
      TIMES   0x04 NOP


    .SetLBA8TO15:
      ; ESI = [SectorCount]
      ; EBX = The base port address of the selected controller
      ; EDI = [Drive]
      ; EAX = LBA28
      SHR     EAX , 0x00000008
      MOV     EDX , EBX
      ADD     EDX , IDE_PORT_OFFSET_LBABITS8TO15
      OUT     DX , AL
      TIMES   0x04 NOP


    .SetLBA16TO23:
      ; ESI = [SectorCount]
      ; EBX = The base port address of the selected controller
      ; EDI = [Drive]
      ; EAX = LBA28 >> 8
      SHR     EAX , 0x00000008
      MOV     EDX , EBX
      ADD     EDX , IDE_PORT_OFFSET_LBABITS16TO23
      OUT     DX , AL
      TIMES   0x04 NOP


    .SetLBA24TO27:
      ; ESI = [SectorCount]
      ; EBX = The base port address of the selected controller
      ; EDI = [Drive]
      ; EAX = LBA28 >> 16
      MOV     EDX , EBX
      ADD     EDX , IDE_PORT_OFFSET_LBABITS24TO27
      SHR     EAX , 0x00000008
      AND     EDI , (IDE_DRIVE0 | IDE_DRIVE1)
      SHL     EDI , 0x00000004
      OR      EDI , IDE_DRIVEHEADREG_L
      OR      EAX , EDI
      OUT     DX , AL
      TIMES   0x04 NOP


    .SendReadWithRetryCommand:
      ; ESI = [SectorCount]
      ; EBX = The base port address of the selected controller
      MOV     EDX , EBX
      ADD     EDX , IDE_PORT_OFFSET_COMMAND
      MOV     EAX , MIDE_CMD_READSECTORSWR
      OUT     DX , AL
      TIMES   0x04 NOP



    .PrepareToReadData:
      MOV     EDI , DWORD PTR [EBP + 0x30] ; Buffer
      ; EDI = [Buffer]
      ; ESI = [SectorCount]



    .StartReadingData:


      ; When the device is ready to transfer the data,
      ; it will clear BSY and set DRQ
      MOV     EDX , EBX
      ADD     EDX , IDE_PORT_OFFSET_STATUS
      MOV     ECX , IDE_TYPICAL_TIMEOUT
      XOR     EAX , EAX
      .WaitForBSYAndDRQ:
        NOP
        IN      AL , DX
        AND     EAX , (IDE_STATUSREG_BSY | IDE_STATUSREG_DRQ)
        XOR     EAX , IDE_STATUSREG_DRQ
        JZ      .PrepareToSenseInterrupt
        DEC     ECX
        JNZ     .WaitForBSYAndDRQ
        JMP     .Failure



      .PrepareToSenseInterrupt:
        MOV     EAX , IRQ_PULSE_IRQ14
        CMP     EBX , PIDEC_PORT_BASE
        JE      .SenseInterrupt
        MOV     EAX , IRQ_PULSE_IRQ15
        .SenseInterrupt:
          INVOKE  __WaitForIRQPulse, EAX



      .CheckStatusRegister1:
        ; EDX = Status Register's port address
        IN      AL , DX


      .ReadData:
        ; EDI = [Buffer]
        CLD
        MOV     EDX , EBX
        ADD     EDX , IDE_PORT_OFFSET_DATA
        MOV     ECX , 512 / 2 ; We are reading 2 bytes at a time and one sector after the other
        REP     INSW




      DEC     ESI
      JNZ     .StartReadingData




    ; Now that the data is read, we should first read the
    ; Alternate Status Register and ignore the result
    ; EBX = The base port address of the selected controller
    .ReadAlternateStatusRegister:
      MOV     EDX , IDEC_PORT_ALTERNATESTATUS
      IN      AL , DX
      TIMES   0x04 NOP




    ; Reading the Status Register is also recommended by ATA-4 specifications
    .ReadStatusRegister:
      ; EBX = The base port address of the selected controller
      MOV     EDX , EBX
      ADD     EDX , IDE_PORT_OFFSET_STATUS
      IN      AL , DX
      TIMES   0x04 NOP


    .SetResult:
      ; [EBP + 0x2C] = SectorCount
      MOV     EAX , DWORD PTR [EBP + 0x2C]
      JMP     .EP


    .Failure:
      XOR     EAX , EAX
    .EP:
      POP     EBP
      POPFD
      POP     EDI
      POP     ESI
      POP     EDX
      POP     ECX
      POP     EBX
    RET     0x18
; ——————————————————————————————————————————————————

Posted: Fri Aug 10, 2007 8:58 am
by xlq
yeah, well my code is in a total mess now. I'll post it when I've cleaned it up.

Posted: Fri Aug 10, 2007 5:36 pm
by exkor
Hey XCHG. Some suggestion: in you CheckController & CheckDrive probability that conditional jump is successful is high(right?) so you may put conditional jump for exit and get rid of second jump.
If jump conditions are not met CPU will skip the jump oppcode completely and there will be no penalty if jump is not correctly predicted. Jump will not be even loaded to be executed.

Posted: Sat Aug 11, 2007 12:12 am
by XCHG
exkor,

Thank you for the suggestion. I wish I could use conditional jumps for branching like that but since conditional jumps are SHORT jumps and the code size when assembled exceeds the amount of opcodes a SHORT jump can jump over, it is not possible to use SHORT jumps in that code. So I have to short jump to a near label and then LONG jump to the end of the procedure.