Reading data from ATA disk not working

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
Chindusky
Posts: 2
Joined: Fri Jan 26, 2024 8:09 am

Reading data from ATA disk not working

Post by Chindusky »

Hello,
I've been trying to read data from ATA disk in PIO mode. Everything goes seemingly right, but when i check buffer with read data, only every second byte is read (ie. bytes 88 16 EC 7C read from my boot sector turn to 88 00 EC 00). I've tried to compare it with other drivers, but nothing really seems off with mine.

My code:

kernel.c

Code: Select all

#include "kernel.h"

void kernel_main(){
    tty_set_color(GET_COLOR(COLOR_WHITE, COLOR_BLACK));
    tty_fill_screen();
    error_t error = ata_init(0);
    if(error == SUCCESS) tty_print("Success", 0);
    else {tty_print("Error ", 0); tty_print_number(error, 6);}

    error = ata_load_sectors(0, 1, (word*) 0x5000);
    if(error == SUCCESS) tty_print("Success", 80);
    else {tty_print("Error ", 80); tty_print_number(error, 86);}

    /* Print out first 4 bytes as integer */
    tty_print_number(*(int*) 0x5000, 160);
}


ata.c

Code: Select all


#include "ata.h"

static int disk_number = -1;

#define ATA_ERROR_CHECK() {error_t error = ata_error_check(); \
                            if(error < 0) return error;}

#define ATA_WAIT_READY() {error_t error = ata_wait_for_ready(); \
                            if(error < 0) return error;}

error_t ata_wait_for_ready();
error_t ata_handle_error();
error_t ata_error_check();

error_t ata_init(int disk){
    if(disk < 0 || disk > 1) return ATA_INVALID_DISK_NUMBER_ERR;
    ports_IO_writeB(ATA_IO_REG_DRIVE_HEAD, disk == 0 ? 0xA0 : 0xB0);
    ports_IO_writeB(ATA_IO_REG_SECTOR_COUNT, 0);
    ports_IO_writeB(ATA_IO_REG_LBA_LOW, 0);
    ports_IO_writeB(ATA_IO_REG_LBA_MID, 0);
    ports_IO_writeB(ATA_IO_REG_LBA_HIGH, 0);

    /* 0xEC: IDENTIFY */
    ports_IO_writeB(ATA_IO_REG_CMD_STATUS, 0xEC);
    
    byte status = ports_IO_readB(ATA_IO_REG_CMD_STATUS);
    
    /* IDENTIFY command returning 0 means disk is not present */
    if(status == 0) return ATA_DISK_NOT_PRESENT_ERR;

    /* Wait for BUSY flag to clear */
    while(status >> 7){
        status = ports_IO_readB(ATA_IO_REG_CMD_STATUS);
    }

    /* If ports 0x1F4 or 0x1F5 are non-zero device is non-ATA */
    if(ports_IO_readB(ATA_IO_REG_LBA_MID) != 0) return ATA_DISK_NOT_ATA_ERR;
    if(ports_IO_readB(ATA_IO_REG_LBA_HIGH) != 0) return ATA_DISK_NOT_ATA_ERR;

    ATA_WAIT_READY()

    /* Read data about disk specification */
    uint16_t* disk_spec = (uint16_t*) 0x1500;
    for(int i = 0; i < 256; i++){
        disk_spec[i] = ports_IO_readW(ATA_IO_REG_DATA);
    }

    ATA_ERROR_CHECK()

    disk_number = disk;
    return SUCCESS;
}

/* Load sectors from disk 
* @param LBA_adress 28-bit adress (sector number)
* @param sector_count number of sectors to read (up to 256)
* @param buffer pointer to buffer (should be at least sector_count * 512 bytes)
* @return error code
*/
error_t ata_load_sectors(uint32_t LBA_adress, byte sector_count, word* buffer){
    if(disk_number < 0) return ATA_NOT_INITIALIZED_ERR;
    if(!ATA_VERIFY_ADRESS(LBA_adress)) return ATA_INVALID_ADRESS_ERR;

    /* Select drive 0 + LBA adressing */
    byte port_write_data = (LBA_adress & 0xF000000 >> 24) | 0b11100000;
    ports_IO_writeB(ATA_IO_REG_DRIVE_HEAD, port_write_data);

    ports_IO_writeB(ATA_IO_REG_SECTOR_COUNT, sector_count);
    ports_IO_writeB(ATA_IO_REG_LBA_LOW, LBA_adress & 0xFF);
    ports_IO_writeB(ATA_IO_REG_LBA_MID, (LBA_adress & 0xFF00) >> 8);
    ports_IO_writeB(ATA_IO_REG_LBA_HIGH, (LBA_adress & 0xFF0000) >> 16);

    /* 0x20: read sector(s) */
    ports_IO_writeB(ATA_IO_REG_CMD_STATUS, 0x20);

    ATA_WAIT_READY()

    int transfer_length = 256*sector_count; 
    for(int i = 0; i < transfer_length; i++){
        word temp = ports_IO_readW(ATA_IO_REG_DATA);
        buffer[i] = temp;
    }

    ATA_ERROR_CHECK()

    return SUCCESS;
}

error_t ata_wait_for_ready(){

    // TODO: replace polling with IRQ
    int repeats = 0;
    while (true){
        byte status = ports_IO_readB(ATA_IO_REG_CMD_STATUS);
        if((status & 0b1000) >> 3) return SUCCESS;
        if(repeats++ < 4) continue;
        if(status & 0b1) return ata_handle_error();
        if(status & 0b00100000 >> 5) return ATA_DRIVE_FAULT_ERR;
    }
}

error_t ata_handle_error(){
    byte status = ports_IO_readB(ATA_IO_REG_ERROR);
    switch(status){
        case 0x01: return ATA_ADDR_MARK_NOT_FOUND_ERR;
        case 0x02: return ATA_ADDR_TRK_ZERO_NOT_FOUND_ERR;
        case 0x04: return ATA_ABORTED_ERR;
        case 0x08: return ATA_MEDIA_CHANGE_REQUEST_ERR;
        case 0x10: return ATA_ID_NOT_FOUND_ERR;
        case 0x20: return ATA_MEDIA_CHANGED_ERR;
        case 0x40: return ATA_UNCORRECTABLE_DATA_ERROR_ERR;
        case 0x80: return ATA_BAD_BLOCK_DETECTED_ERR;
    }
    return ATA_GENERIC_ERR;
}

error_t ata_error_check(){
    byte status = ports_IO_readB(ATA_IO_REG_CMD_STATUS);
    if(status & 0b1) return ata_handle_error();
    return SUCCESS;
}
ports.c

Code: Select all

#include "ports.h"

byte ports_IO_readB (uint16_t port) {
    byte result;
    asm volatile ("in %%dx, %%al" : "=a" (result) : "d" (port));
    return result;
}

void ports_IO_writeB (uint16_t port, byte data) {
    asm volatile ("out %%al, %%dx" : : "a" (data), "d" (port));
}

word ports_IO_readW (uint16_t port) {
    byte result;
    asm volatile ("in %%dx, %%ax" : "=a" (result) : "d" (port));
    return result;
}

void ports_IO_writeW (uint16_t port, word data) {
    asm volatile ("out %%ax, %%dx" : : "a" (data), "d" (port));
}
Thanks for your help :)
uri
Posts: 8
Joined: Tue Feb 22, 2011 12:53 pm

Re: Reading data from ATA disk not working

Post by uri »

Looks like a copy-paste error in ports_IO_readW: "byte result" should be "word result".
Chindusky
Posts: 2
Joined: Fri Jan 26, 2024 8:09 am

Re: Reading data from ATA disk not working

Post by Chindusky »

Yes, that was it, thank you so much
Post Reply