OSDev.org

The Place to Start for Operating System Developers
It is currently Thu Mar 28, 2024 10:26 am

All times are UTC - 6 hours




Post new topic Reply to topic  [ 24 posts ]  Go to page 1, 2  Next
Author Message
 Post subject: ATA read returning 0xFF...
PostPosted: Mon Jan 06, 2020 4:24 am 
Offline

Joined: Tue Nov 05, 2019 2:09 am
Posts: 4
Hello OSDev!
I'm trying to write a simple ATA driver, to read and write to an HDD (I'm running on the QEMU emulator) in PIO mode, with 28 bit LBA addressing.
However, when I try to write information to the disk, and then read it, the result is all garbage (0xFF bytes).

I'm using C++ as my language of choice.

My ata initialization routine is basically just doing the identify command. The information returned by the command accurately describes the HDD i'm trying to emulate (the sector count matches up). In the identify command, I use the select command to select the Master device.

Here are the macro functions and enum classes I use in my code:
Code:
    constexpr uint32_t PRIMARY_IO_BASE =        0x1F0
    constexpr uint32_t PRIMARY_COMMAND_BASE = 0x3F7


    enum class IoRegister : uint32_t {
        // name     // offset from
        Data            = 0,
        Error           = 1,
        Features        = 1,
        SectorCount     = 2,

        // different names for same ports
        SectorNumber    = 3, LBAlow  = 3,
        CylinderLow     = 4, LBAmid  = 4,
        CylinderHigh    = 5, LBAhigh = 5,
       
        Head            = 6,
        Status          = 7,
        Command         = 7
    };

enum class Command : uint8_t {
        Identify = 0xEC,
        ReadBuffer = 0xE4,
        WriteBuffer = 0xE8
    };

inline uint32_t IO_REG_OFFSET(IoRegister reg, Bus bus)
{
    return static_cast<int>(reg) + IO_BASE(bus);
}

inline void WAIT_NO_BUSY()
{
    StatusRegister status;
    do {
        status.value = READ_BYTE(IoRegister::Status);
    } while (status.flags.Bsy);
}

inline void WAIT_UNTIL_READY()
{
    StatusRegister status;
    do {
        status.value = READ_BYTE(IoRegister::Status);
    } while (status.flags.Rdy == 0);
}

inline void COMMAND(Command cmd)
{
    WAIT_NO_BUSY();

    asm volatile("cli;");

    WAIT_UNTIL_READY();

    uint32_t port = (selected_controller == Bus::Primary ? PRIMARY_IO_BASE : SECONDARY_IO_BASE) + ((int)IoRegister::Command);
    uint8_t val = static_cast<uint8_t>(cmd);
    outb(port, val);

    asm volatile("sti;");
}

inline uint8_t READ_BYTE(IoRegister reg)
{
    uint16_t port = IO_REG_OFFSET(reg, selected_controller);
   
    return inb(port);
}

inline uint32_t IO_BASE(Bus bus)
{
    return (bus == Bus::Primary ? PRIMARY_IO_BASE : SECONDARY_IO_BASE);   
}

inline uint32_t IO_REG_OFFSET(IoRegister reg, Bus bus)
{
    return static_cast<int>(reg) + IO_BASE(bus);
}


in my kernel_main, i call write, and the read.
Here is the actual read\write code:
(There are hard coded values, such as the LBA and the 'A' bytes im trying to write - this is just for testing)
Code:
void ata::read()
{
    SET_SECTOR_COUNT(1);
    SET_LBA(100);

    COMMAND(Command::ReadBuffer);

    WAIT_NO_BUSY();

    WAIT_UNTIL_READY();

    char buf[512] = { 0 };
    for (int i = 0; i < 512; i++)
    {
        buf[i] = READ_BYTE(IoRegister::Data);
    }

    printf("data read: %s\n", buf);
}

void ata::write(char* buf, size_t len)
{
    SET_SECTOR_COUNT(1);
    SET_LBA(100);

    COMMAND(Command::WriteBuffer);

    WAIT_NO_BUSY();

    WAIT_UNTIL_READY();

    for (int i = 0; i < 512; i++)
    {
        outb(IO_REG_OFFSET(IoRegister::Data, selected_controller), 'A');
    }

    DELAY_400_NS();
}


Also, the outb and inb functions (if they are relevant):
Code:
inline uint8_t inb(short port) {
   uint8_t ret;
   asm volatile("inb %1, %0" : "=a" (ret) : "dN" (port));
   return ret;
}

inline uint16_t inw(short port) {
   uint16_t ret;
   asm volatile("inw %1, %0" : "=a" (ret) : "dN" (port));
   return ret;
}


I know this is alot of code, hopefuly it is not a problem and is enough to spot any problem I have with the code.
Please tell me if there is more information you need to identify the problem.
Thank you in advance to all :)


Top
 Profile  
 
 Post subject: Re: ATA read returning 0xFF...
PostPosted: Mon Jan 06, 2020 5:07 pm 
Offline
Member
Member

Joined: Mon Mar 25, 2013 7:01 pm
Posts: 5099
Code:
        buf[i] = READ_BYTE(IoRegister::Data);
        outb(IO_REG_OFFSET(IoRegister::Data, selected_controller), 'A');

I don't think 8-bit accesses will work on a 16-bit port.


Top
 Profile  
 
 Post subject: Re: ATA read returning 0xFF...
PostPosted: Mon Jan 06, 2020 6:37 pm 
Offline
Member
Member

Joined: Sun Jun 23, 2019 5:36 pm
Posts: 618
Location: North Dakota, United States
Sorry for hijacking the topic...
Could someone write all the types for each port? That's one thing lacking from the ATA PIO mode article, and I'd rather not confuse my out* instructions and break things. I'm trying to deal with a page fault on my kernel and can't figure out the problem. My ATA code is like this:
Code:
// ATA ports (primary)
const DATA: u16 = 0x1F0;
const ERROR: u16 = 0x1F1;
const FEATURES: u16 = 0x1F1;
const SECTOR_COUNT: u16 = 0x1F2;
const LBAL: u16 = 0x1F3;
const LBAM: u16 = 0x1F4;
const LBAH: u16 = 0x1F5;
const DRIVESEL: u16 = 0x1F6;
const STATUS: u16 = 0x1F7;
const COMMAND: u16 = 0x1F7;
const ALTSTATUS: u16 = 0x3F6;
const DEVCTL: u16 = 0x3F6;
const DRIVE_ADDR: u16 = 0x3F7;
// ATA ports (secondary)
const DATA2: u16 = 0x170;
const ERROR2: u16 = 0x171;
const FEATURES2: u16 = 0x171;
const SECTOR_COUNT2: u16 = 0x172;
const LBAL2: u16 = 0x173;
const LBAM2: u16 = 0x174;
const LBAH2: u16 = 0x175;
const DRIVESEL2: u16 = 0x176;
const STATUS2: u16 = 0x177;
const COMMAND2: u16 = 0x177;
const ALTSTATUS2: u16 = 0x376;
const DEVCTL2: u16 = 0x376;
const DRIVE_ADDR2: u16 = 0x377;
// Error register bits
const AMNF: usize = 0;
const TKZNF: usize = 1;
const ABRT: usize = 2;
const MCR: usize = 3;
const IDNF: usize = 4;
const MC: usize = 5;
const UNC: usize = 6;
const BBK: usize = 7;
// Drive/head register bits (no ranges)
const DRV: usize = 4;
const LBA: usize = 6;
// Status register bits
const ERR: usize = 0;
const DRQ: usize = 3;
const SRV: usize = 4;
const DF: usize = 5;
const RDY: usize = 6;
const BSY: usize = 7;
// DEVCTL bits
const NEIN: usize = 1;
const SRST: usize = 2;
const HOB: usize = 7;
// Drive address register bits
const DS0: usize = 0;
const DS1: usize = 1;
const HS0: usize = 2;
const HS1: usize = 3;
const HS2: usize = 4;
const HS3: usize = 5;
const WTG: usize = 6;

pub fn init() {
    // We use no PCI enumeration code here
    let mut drive_cnt = 0;
    unsafe {
        if inb(STATUS) == 0xFF {
            printkln!("ATA: bus 0 has no master");
        } else {
            drive_cnt += 1;
        }
        if inb(STATUS2) == 0xFF {
            printkln!("ATA: bus 0 has no slave");
        } else {
            drive_cnt += 1;
        }
    }
    if drive_cnt == 0 {
        printkln!("ATA: no ATA drives available; aborting initialization sequence");
        return;
    }
    printkln!("ATA: identifying drive 0");
    unsafe {
        outb(0xA0, DRIVESEL);
        outb(0, LBAL);
        outb(0, LBAM);
        outb(0, LBAH);
        outb(ATACommand::IdentifyDevice as u8, COMMAND);
        if inb(STATUS) == 0 {
            printkln!("ATA: drive 0 does not exist");
            return;
        }
        while inb(STATUS).get_bit(BSY) {
            continue;
        }
        if (inb(LBAM) > 0 || inb(LBAH) > 0) || (inb(LBAM) > 0 && inb(LBAH) > 0) {
            printkln!("ATA: drive 0 is non-ATA");
            return;
        }
        while !inb(STATUS).get_bit(DRQ) || !inb(STATUS).get_bit(ERR) {
            continue;
        }
        if !inb(STATUS).get_bit(ERR) {
            let mut data: [u16; 256] = [0; 256];
            for item in data.iter_mut() {
                *item = inw(DATA);
            }
        } else {
            // ATAPI/SATA drive?
            if inb(LBAM) == 0x14 && inb(LBAH) == 0xEB {
                printkln!("ATA: drive 0: ATAPI device found");
            } else if inb(LBAM) == 0x3C && inb(LBAH) == 0xC3 {
                printkln!("ATA: drive 0: SATA device found");
            }
        }
    }
    printkln!("ATA: identifying drive 1");
    unsafe {
        outb(0xB0, DRIVESEL);
        outb(0, LBAL);
        outb(0, LBAM);
        outb(0, LBAH);
        outb(ATACommand::IdentifyDevice as u8, COMMAND);
        if inb(STATUS) == 0 {
            printkln!("ATA: drive 1 does not exist");
            return;
        }
        while inb(STATUS).get_bit(BSY) {
            continue;
        }
        if (inb(LBAM) > 0 || inb(LBAH) > 0) || (inb(LBAM) > 0 && inb(LBAH) > 0) {
            printkln!("ATA: drive 0 is non-ATA");
            return;
        }
        while !inb(STATUS).get_bit(DRQ) || !inb(STATUS).get_bit(ERR) {
            continue;
        }
        if !inb(STATUS).get_bit(ERR) {
            let mut data: [u16; 256] = [0; 256];
            for item in data.iter_mut() {
                *item = inw(DATA);
            }
        } else {
            // ATAPI/SATA drive?
            if inb(LBAM) == 0x14 && inb(LBAH) == 0xEB {
                printkln!("ATA: drive 0: ATAPI device found");
            } else if inb(LBAM) == 0x3C && inb(LBAH) == 0xC3 {
                printkln!("ATA: drive 0: SATA device found");
            }
        }
    }
}

I know, a lot of code of my own -- sorry! :) This is all the code I have thus far (excluding the enumerations of ATA commands from ATA/ACS8). Thoughts on this? It might not be C but I hope its clear enough. According to GDB, its the outb() function itself, but that doesn't make any sense because the out* functions work everywhere else but here.


Top
 Profile  
 
 Post subject: Re: ATA read returning 0xFF...
PostPosted: Mon Jan 06, 2020 8:16 pm 
Offline
Member
Member

Joined: Mon Mar 25, 2013 7:01 pm
Posts: 5099
Ethin wrote:
Could someone write all the types for each port?

They're all 8-bit other than the data port. You can assume the data port is always 16-bit (unless you're writing diagnostic software for MFM/RLL/ESDI drives or doing something silly with CF cards).

Ethin wrote:
According to GDB, its the outb() function itself, but that doesn't make any sense because the out* functions work everywhere else but here.

Which call to the outb() function causes the fault?


Top
 Profile  
 
 Post subject: Re: ATA read returning 0xFF...
PostPosted: Mon Jan 06, 2020 11:09 pm 
Offline
Member
Member

Joined: Sun Jun 23, 2019 5:36 pm
Posts: 618
Location: North Dakota, United States
Octocontrabass wrote:
Ethin wrote:
Could someone write all the types for each port?

They're all 8-bit other than the data port. You can assume the data port is always 16-bit (unless you're writing diagnostic software for MFM/RLL/ESDI drives or doing something silly with CF cards).

Ethin wrote:
According to GDB, its the outb() function itself, but that doesn't make any sense because the out* functions work everywhere else but here.

Which call to the outb() function causes the fault?

From what I can tell, the outb of the ATA identify device command.


Top
 Profile  
 
 Post subject: Re: ATA read returning 0xFF...
PostPosted: Tue Jan 07, 2020 7:20 am 
Offline
Member
Member

Joined: Mon Mar 25, 2013 7:01 pm
Posts: 5099
It sounds to me like your IRQ14 handler is set up wrong and page faulting. It doesn't look like your code is designed to use IRQs anyway, so
maybe your code to initialize the interrupt controller isn't working properly, or maybe you meant to set nIEN before trying to send commands to the drive.


Top
 Profile  
 
 Post subject: Re: ATA read returning 0xFF...
PostPosted: Tue Jan 07, 2020 10:09 am 
Offline
Member
Member

Joined: Sun Jun 23, 2019 5:36 pm
Posts: 618
Location: North Dakota, United States
Yeah, that was the problem. Didn't help that I never set sector_count either.
I'm now suffering a very strange problem. Is it normal for the total addressable sectors for LBA 48-bit addressing to be 987 no matter what you through in there? The wiki article says:
[quote]
uint16_t 100 through 103 taken as a uint64_t contain the total number of 48 bit addressable sectors on the drive. (Probably also proof that LBA48 is supported.)
[/code]
I take this to mean that array indices 100-103 is the addressable sectors in LBA 48-bit mode. As suchI've done this with my code:
Code:
if data[83].get_bit(10) {
    printkln!("ATA: drive 0: LBA 48 supported");
    let sectors = ((data[103] as u64) << 48) + ((data[102] as u64) << 32) + ((data[101] as u64) << 16) + data[100] as u64;
    printkln!("ATA: drive 0: max sectors = {}", sectors);
} else {
    printkln!("ATA: drive 0: LBA 28 is supported");
    let sectors = ((data[60] as u32) << 16) + data[61] as u32;
    printkln!("ATA: drive 0: max sectors = {}", sectors);
}

The 987 answer doesn't seem right to me. I've tried this both on a 20 MB disk and a 50 MB disk... I'd think the sectors would be in the millions.


Top
 Profile  
 
 Post subject: Re: ATA read returning 0xFF...
PostPosted: Tue Jan 07, 2020 11:12 am 
Offline
Member
Member

Joined: Mon Mar 25, 2013 7:01 pm
Posts: 5099
Ethin wrote:
Is it normal for the total addressable sectors for LBA 48-bit addressing to be 987 no matter what you through in there?

No, it is not normal for a hard disk to be smaller than a floppy disk. Have you checked your data buffer with a debugger to make sure it actually contains something that looks like a response to the IDENTIFY DEVICE command? Does your printkln function work correctly with u64 data?

Ethin wrote:
I've tried this both on a 20 MB disk and a 50 MB disk... I'd think the sectors would be in the millions.

That's a bit of an overestimate. Assuming 512-byte sectors, a 50MB disk has around one hundred thousand sectors.


Top
 Profile  
 
 Post subject: Re: ATA read returning 0xFF...
PostPosted: Tue Jan 07, 2020 11:59 am 
Offline
Member
Member

Joined: Sun Jun 23, 2019 5:36 pm
Posts: 618
Location: North Dakota, United States
There is valid data in the data buffer. I am able to determine, for example, that LBA 48-bit sector addressing is supported. printkln!() does work with all integer types (well, anything that has the format trait, which includes all integer and floating point types). GDB reveals my data buffer to have [987, 0, 0, 0] at words 100-103. Words 60-61 are [987, 0].
The standard I'm using is INCITS 452-2009 (reaffirmed as INCITS 452-2009[R2014]) – Information Technology – AT Attachment 8 - ATA/ATAPI Command Set (ATA8-ACS). That standard states (for word 60 and 61):
Quote:
7.16.7.22 Words 60-61: Total number of user addressable logical sectors for 28-bit commands
This field contains a value that is one greater than the maximum user addressable LBA. The maximum value that shall be placed in this field is 0FFF_FFFFh. If this field contains 0FFF_FFFFh and the device has user addressable LBAs greater than or equal to 0FFF_FFFFh then words 100-103 contain the total number of user addressable LBAs (see 4.11.4).

And this for words 100-103:
Quote:
7.16.7.51 Words 100-103: Total Number of User Addressable Sectors for the 48-bit Address feature set
Words 100-103 contain a value that is one greater than the maximum LBA in user accessible space when the 48-bit Addressing feature set is supported. The maximum value that shall be placed in this field is 0000_FFFF_FFFF_FFFFh. Support of these words is mandatory if the 48-bit Address feature set is supported.

This standard has been the one I have been refering back to for my implementation thus far.
GDB indicates that word 83 is 7400h.


Top
 Profile  
 
 Post subject: Re: ATA read returning 0xFF...
PostPosted: Tue Jan 07, 2020 12:33 pm 
Offline
Member
Member

Joined: Mon Mar 25, 2013 7:01 pm
Posts: 5099
It sounds an awful lot like your virtual hard disk has only 987 sectors, despite your attempts to attach a larger disk. How did you create the disks? How are you attaching them?


Top
 Profile  
 
 Post subject: Re: ATA read returning 0xFF...
PostPosted: Tue Jan 07, 2020 3:51 pm 
Offline
Member
Member

Joined: Sun Jun 23, 2019 5:36 pm
Posts: 618
Location: North Dakota, United States
I'm not sure how this one disk was created, though it does work with QEMU, leading me to think it uses qemu-img. This blank disk, the 50 MB one, was created with qemu-img in the QCOW2 format. I'm mounting it with this set of arguments: -drive format=raw,id=disk,file=disk.img,if=none -device ahci,id=ahci -device ide-hd,drive=disk,bus=ahci.0.


Top
 Profile  
 
 Post subject: Re: ATA read returning 0xFF...
PostPosted: Tue Jan 07, 2020 4:02 pm 
Offline
Member
Member

Joined: Mon Mar 25, 2013 7:01 pm
Posts: 5099
You created a qcow2 disk image, but you're telling Qemu that it's a raw disk image. That's why it doesn't work. Remove "format=raw" from the command line to let Qemu automatically detect the format.


Top
 Profile  
 
 Post subject: Re: ATA read returning 0xFF...
PostPosted: Tue Jan 07, 2020 4:17 pm 
Offline
Member
Member

Joined: Sun Jun 23, 2019 5:36 pm
Posts: 618
Location: North Dakota, United States
Octocontrabass wrote:
You created a qcow2 disk image, but you're telling Qemu that it's a raw disk image. That's why it doesn't work. Remove "format=raw" from the command line to let Qemu automatically detect the format.

Qemu likes to complain about this:
Quote:
WARNING: Image format was not specified for 'linux-0.2.img' and probing guessed.
Automatically detecting the format is dangerous for raw images, write .
Specify the 'raw' format explicitly to remove the restrictions.

Additionally it still gives me the 987 response.


Top
 Profile  
 
 Post subject: Re: ATA read returning 0xFF...
PostPosted: Tue Jan 07, 2020 5:12 pm 
Offline
Member
Member

Joined: Mon Mar 25, 2013 7:01 pm
Posts: 5099
Qemu is complaining about that disk image because that one is not in qcow2 format. You can keep specifying "format=raw" for raw disk images, or convert your raw disk images to qcow2. You can't use "format=raw" with qcow2 disk images.

Taking another look at your command line, I notice you're attaching the disk to an AHCI controller. I'm not familiar with qemu's AHCI emulation, but maybe it doesn't work very well as an IDE controller.


Top
 Profile  
 
 Post subject: Re: ATA read returning 0xFF...
PostPosted: Tue Jan 07, 2020 5:27 pm 
Offline
Member
Member

Joined: Sun Jun 23, 2019 5:36 pm
Posts: 618
Location: North Dakota, United States
I just recreated it as a QCOW2 disk. Now it says that the max sectors for word 100-103 is 988. So we're getting somewhere. I'm not sure how to emulate an ATA disk and not an AHCI device.


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 24 posts ]  Go to page 1, 2  Next

All times are UTC - 6 hours


Who is online

Users browsing this forum: Bing [Bot], Google [Bot], Majestic-12 [Bot] and 43 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group