Unable to read from status port in ATAPIO mode

67 views Asked by At

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;
}
1

There are 1 answers

0
moonasteroid On BEST ANSWER

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!