I recently got interested in developing my own operating system for fun. While creating the OS and the drivers, I encountered several issues while making the ATA driver. One of the major issues is that if I try to request ATAPIO to read an address (in CHS) and try to get the results from the data port (0x1F0), it returns the same number each time I poll the port. Any suggestions welcome!
Link to repo (code looks better)
ata.hpp
#ifndef DRIVERS_ATA_HPP
#define DRIVERS_ATA_HPP
#include <stdint.h>
#include <stddef.h>
namespace os {
namespace drivers {
namespace commands {
constexpr uint16_t identity = 0xEC;
constexpr uint8_t read_sec = 0x20;
constexpr uint8_t write_sec = 0x30;
}
namespace ports {
constexpr uint8_t io_base = (uint8_t) 0x1F0;
constexpr uint16_t data = io_base + (uint16_t) 0;
constexpr uint16_t err = io_base + (uint16_t) 1;
constexpr uint16_t sec_count = io_base + (uint16_t) 2;
constexpr uint16_t sec_num = io_base + (uint16_t) 3;
constexpr uint16_t cylin_low = io_base + (uint16_t) 4;
constexpr uint16_t cylin_high = io_base + (uint16_t) 4;
constexpr uint8_t drive_head_select = io_base + (uint8_t) 6; // 8 bit output
constexpr uint8_t command = io_base + (uint8_t) 7; // 8 bit output
constexpr uint8_t status = io_base + (uint8_t) 7; // 8 bit output
}
namespace drive_type {
enum {
master_drive = 0xA0,
slave_drive = 0xB0
};
}
// Got info from https://wiki.osdev.org/PCI_IDE_Controller#Read.2FWrite_From_ATA_Drive
namespace drive_bit {
enum {
master_bit = 0,
slave_bit = 1
};
}
// for ata::write() function
struct CHS {
uint16_t cylinder;
uint16_t head;
uint16_t sector;
};
struct data_packet {
data_packet(uint16_t *dat, size_t siz) : data(dat), size(siz) {}
uint16_t *data;
size_t size;
operator bool() { return data && size; }
};
class ata {
public:
ata(unsigned poll_lim = 1000);
~ata();
operator bool() { return ata_init_success; }
data_packet read(int drive_bit, CHS addr, uint16_t num_sectors);
bool write(int drive_bit, CHS addr, data_packet dat);
private:
void identity_cmd();
bool ata_init_success;
unsigned polling_limit;
};
}
}
#endif /* DRIVERS_ATA_HPP */
ata.cpp
#include <drivers/ata.hpp>
#include <drivers/instr.hpp>
#include <lib/zmem.hpp>
#include <lib/zio.hpp>
#include <lib/zassert.hpp>
namespace os {
namespace drivers {
ata::ata(unsigned poll_lim) : ata_init_success(false), polling_limit(poll_lim) {
identity_cmd();
}
ata::~ata() {
}
// TODO: fix memory leak resulting from data_packet... maybe add a destructor?
data_packet ata::read(int drive_bit, CHS addr, uint16_t num_sectors) {
uint16_t num_read = num_sectors * 256;
data_packet packet = { new uint16_t[num_read](), num_read };
instr::outw(ports::drive_head_select, drive_type::master_drive | (drive_bit << 4) | addr.head);
instr::outw(ports::sec_count, num_read * sizeof(uint16_t));
instr::outw(ports::sec_num, addr.sector);
instr::outw(ports::cylin_low, addr.cylinder & 0xFF); // zero out 0000 0000 1111 1111
instr::outw(ports::cylin_high, addr.cylinder & 0xFF00);
// Changed from outb to outw
instr::outw(ports::command, commands::read_sec);
for (size_t i = 0; i < packet.size; i++)
packet.data[i] = instr::inw(ports::data);
return packet;
}
bool ata::write(int drive_bit, CHS addr, data_packet dat) {
if (!dat) return false;
instr::outw(ports::sec_count, dat.size / 512);
instr::outw(ports::sec_num, addr.sector);
instr::outw(ports::cylin_low, addr.cylinder & 0xFF); // zero out 0000 0000 1111 1111
instr::outw(ports::cylin_high, addr.cylinder & 0xFF00);
instr::outw(ports::drive_head_select, drive_type::master_drive | (drive_bit << 4) | addr.head);
// changed from outb to outw
instr::outw(ports::command, commands::write_sec);
for (size_t i = 0; i < dat.size; i++)
instr::outw(ports::data, dat.data[i]);
return true;
}
void ata::identity_cmd() {
instr::outw(ports::drive_head_select, drive_type::master_drive);
// Zero out the IO ports
for (uint16_t port = 0x1F2; port <= 0x1F5; port++)
instr::outw(port, NULL);
instr::outw(ports::command, commands::identity);
// Polling for the status
unsigned polling_index = 0;
uint16_t status;
while (polling_index++ < polling_limit) {
status = instr::inw(ports::status);
if ((status & 128) == 0) break;
}
if (status == 0) {
ata_init_success = false;
return;
}
// Read 256 16 bit values from the data (0x1F0) port
uint16_t identity_data[256]{};
for (uint16_t i = 0; i < 256; i++)
identity_data[i] = instr::inw(ports::data);
#ifdef DEBUG
zl::cout << "Ata Driver Testing" << zl::endl;
zl::cout << "Polling status is " << status << zl::endl;
for (int i = 0; i < 256; i++)
zl::cout << "[" << i << "]: " << identity_data[i] << ",";
zl::cout << zl::endl;
zl::cout << "End of Ata Driver Testing" << zl::endl;
#endif
}
}
}
kernel_entry.cpp
extern "C" void main() {
config_os();
os::drivers::ata driv;
char hello_world[20] = "hello world!";
//driv.write(os::drivers::drive_bit::master_bit, {0, 0, 50}, {hello_world, sizeof(hello_world)});
auto res = driv.read(os::drivers::drive_bit::master_bit, {0, 0, 50}, 1);
zl::cout << "Data from ata driver: " << res.data[0] << zl::endl;
}
As it turns out, all the ATA commands need to be in 8 bit (or 1 byte) input/output. This means that all the inw() and outw() function calls should be converted into inb() and outb() function calls instead. When I made the switch, the problems disappeared!