Viewing: ata_pio.c
📄 ata_pio.c (Read Only) ⬅ To go back
#include "ata_pio.h"
#include "ata_dma.h"

#include "errno.h"
#include "io.h"
#include "arch/x86/idt.h"
#include "console.h"
#include "utils.h"

/* ATA register offsets */
#define ATA_REG_DATA       0x00
#define ATA_REG_ERROR      0x01
#define ATA_REG_SECCOUNT0  0x02
#define ATA_REG_LBA0       0x03
#define ATA_REG_LBA1       0x04
#define ATA_REG_LBA2       0x05
#define ATA_REG_HDDEVSEL   0x06
#define ATA_REG_COMMAND    0x07
#define ATA_REG_STATUS     0x07

#define ATA_CMD_READ_SECTORS  0x20
#define ATA_CMD_WRITE_SECTORS 0x30
#define ATA_CMD_CACHE_FLUSH   0xE7
#define ATA_CMD_IDENTIFY      0xEC

#define ATA_SR_BSY  0x80
#define ATA_SR_DRDY 0x40
#define ATA_SR_DF   0x20
#define ATA_SR_DSC  0x10
#define ATA_SR_DRQ  0x08
#define ATA_SR_ERR  0x01

/* Channel I/O port bases */
static const uint16_t ch_io_base[ATA_NUM_CHANNELS]   = { 0x1F0, 0x170 };
static const uint16_t ch_ctrl_base[ATA_NUM_CHANNELS]  = { 0x3F6, 0x376 };
static const uint8_t  ch_irq_vec[ATA_NUM_CHANNELS]    = { 46, 47 };

/* Drive presence flags */
static int drive_present[ATA_MAX_DRIVES];
static int ata_pio_inited = 0;

static const char* drive_names[ATA_MAX_DRIVES] = { "hda", "hdb", "hdc", "hdd" };

/* ---- Low-level helpers ---- */

static inline void io_wait_400ns(uint16_t ctrl) {
    (void)inb(ctrl);
    (void)inb(ctrl);
    (void)inb(ctrl);
    (void)inb(ctrl);
}

static int ata_wait_not_busy(uint16_t io) {
    for (int i = 0; i < 1000000; i++) {
        uint8_t st = inb((uint16_t)(io + ATA_REG_STATUS));
        if ((st & ATA_SR_BSY) == 0) return 0;
    }
    return -EIO;
}

static int ata_wait_drq(uint16_t io) {
    for (int i = 0; i < 1000000; i++) {
        uint8_t st = inb((uint16_t)(io + ATA_REG_STATUS));
        if (st & ATA_SR_ERR) return -EIO;
        if (st & ATA_SR_DF)  return -EIO;
        if ((st & ATA_SR_BSY) == 0 && (st & ATA_SR_DRQ)) return 0;
    }
    return -EIO;
}

/* ---- IRQ handlers (deassert INTRQ by reading status) ---- */

static void ata_irq14_handler(struct registers* regs) {
    (void)regs;
    (void)inb((uint16_t)(0x1F0 + ATA_REG_STATUS));
}

static void ata_irq15_handler(struct registers* regs) {
    (void)regs;
    (void)inb((uint16_t)(0x170 + ATA_REG_STATUS));
}

/* ---- Drive probing ---- */

static int ata_probe_drive(int channel, int slave) {
    uint16_t io   = ch_io_base[channel];
    uint16_t ctrl = ch_ctrl_base[channel];

    /* Select drive */
    uint8_t sel = slave ? 0xB0 : 0xA0;
    outb((uint16_t)(io + ATA_REG_HDDEVSEL), sel);
    io_wait_400ns(ctrl);

    if (ata_wait_not_busy(io) < 0) return 0;

    /* Zero registers */
    outb((uint16_t)(io + ATA_REG_SECCOUNT0), 0);
    outb((uint16_t)(io + ATA_REG_LBA0), 0);
    outb((uint16_t)(io + ATA_REG_LBA1), 0);
    outb((uint16_t)(io + ATA_REG_LBA2), 0);

    /* IDENTIFY */
    outb((uint16_t)(io + ATA_REG_COMMAND), ATA_CMD_IDENTIFY);

    uint8_t st = inb((uint16_t)(io + ATA_REG_STATUS));
    if (st == 0) return 0; /* No drive */

    /* Wait for BSY to clear */
    for (int i = 0; i < 1000000; i++) {
        st = inb((uint16_t)(io + ATA_REG_STATUS));
        if (!(st & ATA_SR_BSY)) break;
    }
    if (st & ATA_SR_BSY) return 0;

    /* Non-zero LBA1/LBA2 means ATAPI — skip */
    uint8_t lba1 = inb((uint16_t)(io + ATA_REG_LBA1));
    uint8_t lba2 = inb((uint16_t)(io + ATA_REG_LBA2));
    if (lba1 != 0 || lba2 != 0) return 0;

    /* Wait for DRQ */
    if (ata_wait_drq(io) < 0) return 0;

    /* Read and discard 256 words of identify data */
    for (int i = 0; i < 256; i++) {
        (void)inw((uint16_t)(io + ATA_REG_DATA));
    }

    return 1;
}

/* ---- Public API ---- */

uint32_t ata_pio_sector_size(void) {
    return 512;
}

int ata_pio_init(void) {
    if (ata_pio_inited) return 0;

    /* Register IRQ handlers for both channels */
    register_interrupt_handler(ch_irq_vec[0], ata_irq14_handler);
    register_interrupt_handler(ch_irq_vec[1], ata_irq15_handler);

    int found = 0;

    for (int ch = 0; ch < ATA_NUM_CHANNELS; ch++) {
        /* Floating bus check — 0xFF means no controller */
        uint8_t st = inb((uint16_t)(ch_io_base[ch] + ATA_REG_STATUS));
        if (st == 0xFF) {
            drive_present[ch * 2]     = 0;
            drive_present[ch * 2 + 1] = 0;
            continue;
        }

        for (int sl = 0; sl < 2; sl++) {
            int id = ch * 2 + sl;
            drive_present[id] = ata_probe_drive(ch, sl);
            if (drive_present[id]) found++;
        }

        /* Try DMA for this channel */
        if (ata_dma_init(ch) == 0) {
            kprintf("[ATA] Channel %d: DMA mode.\n", ch);
        } else {
            kprintf("[ATA] Channel %d: PIO mode.\n", ch);
        }
    }

    /* Log detected drives */
    for (int i = 0; i < ATA_MAX_DRIVES; i++) {
        if (drive_present[i]) {
            kprintf("[ATA] /dev/%s detected.\n", drive_names[i]);
        }
    }

    ata_pio_inited = 1;
    return found > 0 ? 0 : -ENODEV;
}

int ata_pio_drive_present(int drive) {
    if (drive < 0 || drive >= ATA_MAX_DRIVES) return 0;
    return drive_present[drive];
}

int ata_pio_read28(int drive, uint32_t lba, uint8_t* buf512) {
    if (!buf512) return -EFAULT;
    if (drive < 0 || drive >= ATA_MAX_DRIVES) return -EINVAL;
    if (!drive_present[drive]) return -ENODEV;
    if (lba & 0xF0000000U) return -EINVAL;

    int ch = drive / 2;
    int sl = drive & 1;

    /* Use DMA if available for this channel */
    if (ata_dma_available(ch)) {
        return ata_dma_read28(ch, sl, lba, buf512);
    }

    uint16_t io   = ch_io_base[ch];
    uint16_t ctrl = ch_ctrl_base[ch];

    if (ata_wait_not_busy(io) < 0) return -EIO;

    uint8_t sel = sl ? 0xF0 : 0xE0;
    outb((uint16_t)(io + ATA_REG_HDDEVSEL), (uint8_t)(sel | ((lba >> 24) & 0x0F)));
    io_wait_400ns(ctrl);

    outb((uint16_t)(io + ATA_REG_SECCOUNT0), 1);
    outb((uint16_t)(io + ATA_REG_LBA0), (uint8_t)(lba & 0xFF));
    outb((uint16_t)(io + ATA_REG_LBA1), (uint8_t)((lba >> 8) & 0xFF));
    outb((uint16_t)(io + ATA_REG_LBA2), (uint8_t)((lba >> 16) & 0xFF));
    outb((uint16_t)(io + ATA_REG_COMMAND), ATA_CMD_READ_SECTORS);

    if (ata_wait_drq(io) < 0) return -EIO;

    uint16_t* w = (uint16_t*)buf512;
    for (int i = 0; i < 256; i++) {
        w[i] = inw((uint16_t)(io + ATA_REG_DATA));
    }

    io_wait_400ns(ctrl);
    return 0;
}

int ata_pio_write28(int drive, uint32_t lba, const uint8_t* buf512) {
    if (!buf512) return -EFAULT;
    if (drive < 0 || drive >= ATA_MAX_DRIVES) return -EINVAL;
    if (!drive_present[drive]) return -ENODEV;
    if (lba & 0xF0000000U) return -EINVAL;

    int ch = drive / 2;
    int sl = drive & 1;

    if (ata_dma_available(ch)) {
        return ata_dma_write28(ch, sl, lba, buf512);
    }

    uint16_t io   = ch_io_base[ch];
    uint16_t ctrl = ch_ctrl_base[ch];

    if (ata_wait_not_busy(io) < 0) return -EIO;

    uint8_t sel = sl ? 0xF0 : 0xE0;
    outb((uint16_t)(io + ATA_REG_HDDEVSEL), (uint8_t)(sel | ((lba >> 24) & 0x0F)));
    io_wait_400ns(ctrl);

    outb((uint16_t)(io + ATA_REG_SECCOUNT0), 1);
    outb((uint16_t)(io + ATA_REG_LBA0), (uint8_t)(lba & 0xFF));
    outb((uint16_t)(io + ATA_REG_LBA1), (uint8_t)((lba >> 8) & 0xFF));
    outb((uint16_t)(io + ATA_REG_LBA2), (uint8_t)((lba >> 16) & 0xFF));
    outb((uint16_t)(io + ATA_REG_COMMAND), ATA_CMD_WRITE_SECTORS);

    if (ata_wait_drq(io) < 0) return -EIO;

    const uint16_t* w = (const uint16_t*)buf512;
    for (int i = 0; i < 256; i++) {
        outw((uint16_t)(io + ATA_REG_DATA), w[i]);
    }

    outb((uint16_t)(io + ATA_REG_COMMAND), ATA_CMD_CACHE_FLUSH);
    (void)ata_wait_not_busy(io);
    io_wait_400ns(ctrl);
    return 0;
}

/* --- Register detected ATA drives in devfs --- */

#include "devfs.h"

static fs_node_t ata_dev_nodes[ATA_MAX_DRIVES];

static uint32_t ata_dev_read(fs_node_t* node, uint32_t offset, uint32_t size, uint8_t* buffer) {
    int drive = (int)node->inode;
    if (!buffer || size == 0) return 0;

    uint32_t lba = offset / 512;
    uint32_t off_in_sect = offset % 512;
    uint32_t total = 0;
    uint8_t tmp[512];

    while (total < size) {
        if (ata_pio_read28(drive, lba, tmp) < 0) break;
        uint32_t avail = 512 - off_in_sect;
        uint32_t want = size - total;
        if (want > avail) want = avail;
        memcpy(buffer + total, tmp + off_in_sect, want);
        total += want;
        lba++;
        off_in_sect = 0;
    }
    return total;
}

static uint32_t ata_dev_write(fs_node_t* node, uint32_t offset, uint32_t size, const uint8_t* buffer) {
    int drive = (int)node->inode;
    if (!buffer || size == 0) return 0;

    uint32_t lba = offset / 512;
    uint32_t off_in_sect = offset % 512;
    uint32_t total = 0;
    uint8_t tmp[512];

    while (total < size) {
        uint32_t want = size - total;
        if (off_in_sect != 0 || want < 512) {
            /* Read-modify-write for partial sector */
            if (ata_pio_read28(drive, lba, tmp) < 0) break;
            uint32_t avail = 512 - off_in_sect;
            if (want > avail) want = avail;
            memcpy(tmp + off_in_sect, buffer + total, want);
        } else {
            want = 512;
            memcpy(tmp, buffer + total, 512);
        }
        if (ata_pio_write28(drive, lba, tmp) < 0) break;
        total += want;
        lba++;
        off_in_sect = 0;
    }
    return total;
}

static const struct file_operations ata_dev_fops = {
    .read  = ata_dev_read,
    .write = ata_dev_write,
};

void ata_register_devfs(void) {
    for (int i = 0; i < ATA_MAX_DRIVES; i++) {
        if (!drive_present[i]) continue;
        memset(&ata_dev_nodes[i], 0, sizeof(fs_node_t));
        strcpy(ata_dev_nodes[i].name, drive_names[i]);
        ata_dev_nodes[i].flags = FS_BLOCKDEVICE;
        ata_dev_nodes[i].inode = (uint32_t)i;
        ata_dev_nodes[i].f_ops = &ata_dev_fops;
        devfs_register_device(&ata_dev_nodes[i]);
    }
}

int ata_name_to_drive(const char* name) {
    if (!name) return -1;
    for (int i = 0; i < ATA_MAX_DRIVES; i++) {
        if (strcmp(name, drive_names[i]) == 0) return i;
    }
    return -1;
}

const char* ata_drive_to_name(int drive) {
    if (drive < 0 || drive >= ATA_MAX_DRIVES) return 0;
    return drive_names[drive];
}