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:
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
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: 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
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).