c++filesystemsosdevhard-driveata

virtual hard disk - ata pio & saving state of hard drive, write operations in the same place


I'm writing an operating system. I'm developing my code in linux mint and running it on a qemu virtual machine. I'm currently developing a file system and for that purpose I first need to initialize communication with the hard disk (reading and writing to sectors) so I chose the ata pio interface and began to implement it. I am facing a very weird problem which has two parts:

  1. For example, if I write to sector 165 and then read from sector 140 the outputted data is what I wrote to sector 165. In other words, I coulnd't actually distinguish between sectors
  2. I can't save data and changes to my virtual hard disk file and each time I want to read from the virtual hard drive, no matter in which sector I always get the same values.

Here is my code

#port.cpp defines a class that represnts port communication
Port8Bit::Port8Bit(uint16_t portnumber)
: Port(portnumber)
{

}

Port8Bit::~Port8Bit()
{

}

void Port8Bit::Write(uint8_t data)
{
    // Using inline assembly to write and read from the specific port
    __asm__ volatile("outb %0, %1" : : "a" (data), "Nd" (portnumber));
}

uint8_t Port8Bit::Read()
{
    uint8_t result;
    __asm__ volatile("inb %1, %0" : "=a" (result) : "Nd" (portnumber));
    return result;
}


Port8BitSlow::Port8BitSlow(uint16_t portnumber)
: Port8Bit(portnumber)
{

}

Port8BitSlow::~Port8BitSlow()
{

}

void Port8BitSlow::Write(uint8_t data)
{
        __asm__ volatile(
        "outb %0, %1\n\t"
        "jmp 1f\n\t"
        "1:\n\t"
        :
        : "a" (data), "Nd" (portnumber)
    );
    //__asm__ volatile("outb %0, %1\njmp if\n1: jmp if\n1:" : : "a" (data), "Nd" (portnumber));
}



Port16Bit::Port16Bit(uint16_t portnumber)
: Port(portnumber)
{

}

Port16Bit::~Port16Bit()
{

}

void Port16Bit::Write(uint16_t data)
{
    __asm__ volatile("outw %0, %1" : : "a" (data), "Nd" (portnumber));
}

uint16_t Port16Bit::Read()
{
    uint16_t result;
    __asm__ volatile("inw %1, %0" : "=a" (result) : "Nd" (portnumber));
    return result;
}


Port32Bit::Port32Bit(uint16_t portnumber)
: Port(portnumber)
{

}

Port32Bit::~Port32Bit()
{

}

void Port32Bit::Write(uint32_t data)
{
    __asm__ volatile("outl %0, %1" : : "a" (data), "Nd" (portnumber));
}

uint32_t Port32Bit::Read()
{
    uint32_t result;
    __asm__ volatile("inl %1, %0" : "=a" (result) : "Nd" (portnumber));
    return result;
}

#disk.h
#pragma once
#include <types.h>
#include <port/port.h>

void printf(uint8_t* ltr, int flag);   
class AdvancedTechnologyAttachment
{
protected:
    bool master;
    Port16Bit dataPort;
    Port8Bit errorPort;
    Port8Bit sectorCountPort;
    Port8Bit lbaLowPort;
    Port8Bit lbaMidPort;
    Port8Bit lbaHiPort;
    Port8Bit devicePort;
    Port8Bit commandPort;
    Port8Bit controlPort;
public:
    
    AdvancedTechnologyAttachment(bool master, uint16_t portBase);
    ~AdvancedTechnologyAttachment();
    
    void Identify();
    void Read28(uint32_t sectorNum, int count = 512);
    void Write28(uint32_t sectorNum, uint8_t* data, uint32_t count);

void Flush();
    
    
};


# disk.cpp file - implementing disk.h
#include <fat16/disk.h>


AdvancedTechnologyAttachment::AdvancedTechnologyAttachment(bool master, uint16_t portBase)
:   dataPort(portBase),
    errorPort(portBase + 0x1),
    sectorCountPort(portBase + 0x2),
    lbaLowPort(portBase + 0x3),
    lbaMidPort(portBase + 0x4),
    lbaHiPort(portBase + 0x5),
    devicePort(portBase + 0x6),
    commandPort(portBase + 0x7),
    controlPort(portBase + 0x206)
{
    this->master = master;
}

AdvancedTechnologyAttachment::~AdvancedTechnologyAttachment()
{
}
            
void AdvancedTechnologyAttachment::Identify()
{
    devicePort.Write(master ? 0xA0 : 0xB0);
    controlPort.Write(0);
    
    devicePort.Write(0xA0);
    uint8_t status = commandPort.Read();
    if(status == 0xFF)
        return;
    
    
    devicePort.Write(master ? 0xA0 : 0xB0);
    sectorCountPort.Write(0);
    lbaLowPort.Write(0);
    lbaMidPort.Write(0);
    lbaHiPort.Write(0);
    commandPort.Write(0xEC); // identify command
    
    
    status = commandPort.Read();
    if(status == 0x00)
        return;
    
    while(((status & 0x80) == 0x80)
       && ((status & 0x01) != 0x01))
        status = commandPort.Read();
        
    if(status & 0x01)
    {
        return;
    }
    
    for(int i = 0; i < 256; i++)
    {
        uint16_t data = dataPort.Read();
        char *text = "  \0";
        text[0] = (data >> 8) & 0xFF;
        text[1] = data & 0xFF;
    }
}

void AdvancedTechnologyAttachment::Read28(uint32_t sectorNum, int count)
{
    if(sectorNum > 0x0FFFFFFF)
        return;
    
    devicePort.Write( (master ? 0xE0 : 0xF0) | ((sectorNum & 0x0F000000) >> 24) );
    errorPort.Write(0);
    sectorCountPort.Write(1);
    lbaLowPort.Write(  sectorNum & 0x000000FF );
    lbaMidPort.Write( (sectorNum & 0x0000FF00) >> 8);
    lbaLowPort.Write( (sectorNum & 0x00FF0000) >> 16 );
    commandPort.Write(0x20);
    
    uint8_t status = commandPort.Read();
    while(((status & 0x80) == 0x80)
       && ((status & 0x01) != 0x01))
        status = commandPort.Read();
        
    if(status & 0x01)
    {
        return;
    }
    
    
    
    for(int i = 0; i < count; i += 2)
    {
        uint16_t wdata = dataPort.Read();
        
        char *text = "  \0";
        text[0] = wdata & 0xFF;
        
        if(i+1 < count)
            text[1] = (wdata >> 8) & 0xFF;
        else
            text[1] = '\0';
        
         printf((uint8_t*)text,0);
    }    
    
    for(int i = count + (count%2); i < 512; i += 2)
        dataPort.Read();
}

void AdvancedTechnologyAttachment::Write28(uint32_t sectorNum, uint8_t* data, uint32_t count)
{
# count - number of bytes in the sector to write to
    if(sectorNum > 0x0FFFFFFF)
        return;
    if(count > 512)
        return;
    
    
    devicePort.Write( (master ? 0xE0 : 0xF0) | ((sectorNum & 0x0F000000) >> 24) );
    errorPort.Write(0);
    sectorCountPort.Write(1);
    lbaLowPort.Write(  sectorNum & 0x000000FF );
    lbaMidPort.Write( (sectorNum & 0x0000FF00) >> 8);
    lbaLowPort.Write( (sectorNum & 0x00FF0000) >> 16 );
    commandPort.Write(0x30);
    
    

    for(int i = 0; i < count; i += 2)
    {
        uint16_t wdata = data[i];
        if(i+1 < count)
            wdata |= ((uint16_t)data[i+1]) << 8;
        dataPort.Write(wdata);
        
        char *text = "  \0";
        text[0] = (wdata >> 8) & 0xFF;
        text[1] = wdata & 0xFF;
    }
    
    for(int i = count + (count%2); i < 512; i += 2)
        dataPort.Write(0x0000);

}
# send flushing command after write for no caching and actual saving
void AdvancedTechnologyAttachment::Flush()
{

    devicePort.Write( master ? 0xE0 : 0xF0 );
    commandPort.Write(0xE7);

    uint8_t status = commandPort.Read();
    if(status == 0x00)
        return;
    
    while(((status & 0x80) == 0x80)
       && ((status & 0x01) != 0x01))
        status = commandPort.Read();
        
    if(status & 0x01)
    {
        return;
    }
}
# in kernel.cpp file - this is how the disk.cpp functions are called

    AdvancedTechnologyAttachment ata0m(true, 0x1F0);
    ata0m.Identify();
    uint8_t write_test[512];
    for (int i = 0; i < 512; i++) 
    {
        write_test[i] = 'r';
    }
    ata0m.Write28(165, write_test, 512);
    ata0m.Flush();

    ata0m.Read28(165, 512);
    printf((uint8_t*)"here \n",0);

as you can see, in this example I write 512 bytes to sector 165 and then read the same sector. The output on screen is also correct enter image description here

If I close the qemu machine and look at the hexdump of the file using sudo hexdump -C os-disk.qcow2 I also see that the 'r' character is present many times:

00050000 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 |rrrrrrrrrrrrrrrr| Which means the data I have written to the disk has been saved. However, if I now decide to edit my code a bit and do the following:

    AdvancedTechnologyAttachment ata0m(true, 0x1F0);
    ata0m.Identify();
    uint8_t write_test[512];
    for (int i = 0; i < 512; i++) 
    {
        write_test[i] = 'd';
    }
    ata0m.Write28(200, write_test, 512);
    ata0m.Flush();

    ata0m.Read28(165, 512);
    printf((uint8_t*)"here \n",0);

(we write different data into a different sector in the virtual hard drive, but we are still reading from the same place) when I run my code I get the following output: enter image description here and the hexdump shows this output

0050000 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 |dddddddddddddddd|

in other words, the data we wrote previously has been overwrriten although these are different sectors, which I have no idea why...

Also, this is how I create my virtual hard drive and run my code:

#disk.sh
/usr/bin/qemu-img create -f qcow2 os-disk.qcow2 512M
# This starts a GParted virtual machine that is used to define the ext2 partition to the hard drive used by the os 
/usr/bin/qemu-system-i386 -cdrom gparted-live-1.6.0-3-i686.iso -hda os-disk.qcow2 -boot d -m 512

First, I run this command to create the virtual hard drive. This is a one time use script. The GParted tool allows me to format my os-disk.qcow2 - add partitions to it and define what file systems they work with. In my case, I defined one partition that takes the entire hard drive state and uses FAT16.

after creating the virtual hard disk I use a script named run.sh. This script loads my compiled code (which I turn into an .iso image) and the virtual hard drive into a qemu virtual machine.

/usr/bin/qemu-system-i386 -cdrom objects/mykernel.iso -drive file=os-disk.qcow2,cache=none -boot d -m 512 -no-reboot -no-shutdown -d int -M smm=off -s 

the compiler I use is gcc and the compilation flags are:

GPPPARAMS = -I./include -m32 -fno-use-cxa-atexit -nostdlib -fno-builtin -fno-rtti -fno-exceptions -fno-leading-underscore -fpermissive -g -nostdinc++

Does anyone have any idea why the sectors I'm working with refer to the same addresses? In other words, why I'm overriding myself each time?

I would have expected the sectors to be effective and actually make changes in different parts of the file, but it appears all the changes (write operations) are done to the same place...

Thanks in advance! In addition, this code is from https://www.youtube.com/watch?v=uS02rOvLgak&list=PLHh55M_Kq4OApWScZyPl5HhgsTJS9MZ6M&index=20 and I modified it a little bit


Solution

  • Well, logical answer would be "you are not writing to a correct place". This behaviour suggests your code always writes and reads from the same place regardless of the LBA provided. So how does your code performs the LBA selection?

    lbaLowPort.Write(  sectorNum & 0x000000FF );
    lbaMidPort.Write( (sectorNum & 0x0000FF00) >> 8);
    lbaLowPort.Write( (sectorNum & 0x00FF0000) >> 16 );
    

    Yep, there it is, our old friend copy-paste error. Everyone met it a few times.

    Morale of the story? When you get such an error and debugging gets you nowhere (and this is a pet project with no deadlines), put your code down for a day or two then review it as if it was someone else's code. Get a yellow rubber duck and explain the code to it. Verify external effects of your code if you can (in this particular example, you should've used raw HDD image - that way you could've verified not just the fact of writing but also the location of said write).