diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 64642f8..948eb11 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -20,6 +20,7 @@ add_executable(kernel vfs.c initrd_fs.c devicefs.c + ide.c env.c keyboard.c interrupts.S diff --git a/src/ide.c b/src/ide.c new file mode 100644 index 0000000..15a929a --- /dev/null +++ b/src/ide.c @@ -0,0 +1,390 @@ +/** + * @file ide.c + * @brief IDE/ATA disk driver implementation. + * + * Probes the primary and secondary IDE channels for ATA hard drives and + * ATAPI CD/DVD drives using PIO-mode IDENTIFY commands. Detected devices + * are registered with the devicefs subsystem as block devices: + * - ATA drives → "hdd" class (hdd1, hdd2, ...) + * - ATAPI drives → "cd" class (cd1, cd2, ...) + * + * Supports PIO-mode sector reads and writes using 28-bit LBA addressing, + * which covers drives up to 128 GiB. + */ + +#include "ide.h" +#include "port_io.h" +#include "devicefs.h" +#include "driver.h" +#include + +/* Debug print helpers defined in kernel.c */ +extern void offset_print(const char *str); +extern void print_hex(uint32_t val); + +/** All detected IDE devices. */ +static ide_device_t ide_devices[IDE_MAX_DEVICES]; + +/* ================================================================ + * Low-level IDE I/O helpers + * ================================================================ */ + +/** + * Wait for the BSY flag to clear on the given channel. + * Returns the final status byte, or 0xFF on timeout. + * + * @param io_base Channel I/O base port. + * @return Status byte. + */ +static uint8_t ide_wait(uint16_t io_base) { + uint8_t status; + int timeout = 100000; + do { + status = inb(io_base + IDE_REG_STATUS); + if (--timeout == 0) return 0xFF; + } while (status & IDE_STATUS_BSY); + return status; +} + +/** + * Wait for BSY to clear and DRQ to set (data ready). + * Returns 0 on success, -1 on timeout or error. + * + * @param io_base Channel I/O base port. + * @return 0 on success, -1 on error/timeout. + */ +static int ide_wait_drq(uint16_t io_base) { + uint8_t status; + int timeout = 100000; + do { + status = inb(io_base + IDE_REG_STATUS); + if (status & (IDE_STATUS_ERR | IDE_STATUS_DF)) return -1; + if (--timeout == 0) return -1; + } while ((status & (IDE_STATUS_BSY | IDE_STATUS_DRQ)) != IDE_STATUS_DRQ); + return 0; +} + +/** + * Read 256 16-bit words (512 bytes) from the data register. + * + * @param io_base Channel I/O base port. + * @param buf Destination buffer (must be at least 512 bytes). + */ +static void ide_read_buffer(uint16_t io_base, uint16_t *buf) { + for (int i = 0; i < 256; i++) { + buf[i] = inw(io_base + IDE_REG_DATA); + } +} + +/** + * Write 256 16-bit words (512 bytes) to the data register. + * + * @param io_base Channel I/O base port. + * @param buf Source buffer (must be at least 512 bytes). + */ +static void ide_write_buffer(uint16_t io_base, const uint16_t *buf) { + for (int i = 0; i < 256; i++) { + outw(io_base + IDE_REG_DATA, buf[i]); + } +} + +/** + * Perform a software reset on an IDE channel. + * + * @param ctrl_base Channel control port. + */ +static void ide_soft_reset(uint16_t ctrl_base) { + outb(ctrl_base, 0x04); /* Set SRST bit */ + /* Wait ~5 µs (several I/O reads) */ + for (int i = 0; i < 4; i++) inb(ctrl_base); + outb(ctrl_base, 0x00); /* Clear SRST bit */ + /* Wait for BSY to clear */ + for (int i = 0; i < 4; i++) inb(ctrl_base); +} + +/** + * Select a drive on a channel (master=0, slave=1). + * + * @param io_base Channel I/O base port. + * @param drive 0 for master, 1 for slave. + */ +static void ide_select_drive(uint16_t io_base, uint8_t drive) { + outb(io_base + IDE_REG_DRIVE_HEAD, 0xA0 | (drive << 4)); + /* Wait ~400 ns by reading status 4 times */ + for (int i = 0; i < 4; i++) inb(io_base + IDE_REG_STATUS); +} + +/* ================================================================ + * IDENTIFY command + * ================================================================ */ + +/** + * Send IDENTIFY (or IDENTIFY PACKET) to a drive and read the result. + * + * @param dev Pointer to the device descriptor to fill. + * @return 0 on success, -1 if no device or error. + */ +static int ide_identify(ide_device_t *dev) { + uint16_t io = dev->io_base; + + /* Select the drive */ + ide_select_drive(io, dev->drive); + + /* Clear sector count and LBA registers */ + outb(io + IDE_REG_SECCOUNT, 0); + outb(io + IDE_REG_LBA_LO, 0); + outb(io + IDE_REG_LBA_MID, 0); + outb(io + IDE_REG_LBA_HI, 0); + + /* Send IDENTIFY command */ + outb(io + IDE_REG_COMMAND, IDE_CMD_IDENTIFY); + + /* Read status — if 0, no device */ + uint8_t status = inb(io + IDE_REG_STATUS); + if (status == 0) return -1; + + /* Wait for BSY to clear */ + status = ide_wait(io); + if (status == 0xFF) return -1; + + /* Check if this is an ATAPI device (LBA_MID/HI will be non-zero) */ + uint8_t lba_mid = inb(io + IDE_REG_LBA_MID); + uint8_t lba_hi = inb(io + IDE_REG_LBA_HI); + + if (lba_mid == 0x14 && lba_hi == 0xEB) { + /* ATAPI device — re-identify with IDENTIFY PACKET DEVICE */ + dev->type = IDE_TYPE_ATAPI; + outb(io + IDE_REG_COMMAND, IDE_CMD_IDENTIFY_PKT); + status = ide_wait(io); + if (status == 0xFF) return -1; + } else if (lba_mid == 0 && lba_hi == 0) { + dev->type = IDE_TYPE_ATA; + } else { + /* Unknown device type */ + return -1; + } + + /* Wait for DRQ */ + if (ide_wait_drq(io) != 0) return -1; + + /* Read 256 words of identification data */ + uint16_t identify_buf[256]; + ide_read_buffer(io, identify_buf); + + /* Parse model string (words 27-46, each word is big-endian) */ + for (int i = 0; i < 20; i++) { + dev->model[i * 2] = (char)(identify_buf[27 + i] >> 8); + dev->model[i * 2 + 1] = (char)(identify_buf[27 + i] & 0xFF); + } + dev->model[40] = '\0'; + /* Trim trailing spaces */ + for (int i = 39; i >= 0; i--) { + if (dev->model[i] == ' ') dev->model[i] = '\0'; + else break; + } + + /* Parse sector count (28-bit LBA: words 60-61) */ + if (dev->type == IDE_TYPE_ATA) { + dev->sector_count = (uint32_t)identify_buf[60] | + ((uint32_t)identify_buf[61] << 16); + dev->sector_size = 512; + } else { + /* ATAPI: sector count from READ CAPACITY, default to 0 */ + dev->sector_count = 0; + dev->sector_size = 2048; /* Standard CD sector size */ + } + + dev->present = 1; + return 0; +} + +/* ================================================================ + * Block device operations (for devicefs) + * ================================================================ */ + +/** + * Read sectors from an ATA drive using PIO. + */ +static int ide_block_read(void *dev_data, uint32_t lba, + uint32_t count, void *buf) { + ide_device_t *dev = (ide_device_t *)dev_data; + if (!dev || dev->type != IDE_TYPE_ATA) return -1; + + uint16_t io = dev->io_base; + uint8_t *dest = (uint8_t *)buf; + + for (uint32_t s = 0; s < count; s++) { + uint32_t cur_lba = lba + s; + + /* Select drive with LBA mode and top 4 LBA bits */ + outb(io + IDE_REG_DRIVE_HEAD, + 0xE0 | (dev->drive << 4) | ((cur_lba >> 24) & 0x0F)); + + /* Set sector count = 1 and LBA */ + outb(io + IDE_REG_SECCOUNT, 1); + outb(io + IDE_REG_LBA_LO, cur_lba & 0xFF); + outb(io + IDE_REG_LBA_MID, (cur_lba >> 8) & 0xFF); + outb(io + IDE_REG_LBA_HI, (cur_lba >> 16) & 0xFF); + + /* Send READ SECTORS command */ + outb(io + IDE_REG_COMMAND, IDE_CMD_READ_PIO); + + /* Wait for data */ + if (ide_wait_drq(io) != 0) return -1; + + /* Read 256 words (512 bytes) */ + ide_read_buffer(io, (uint16_t *)(dest + s * 512)); + } + + return 0; +} + +/** + * Write sectors to an ATA drive using PIO. + */ +static int ide_block_write(void *dev_data, uint32_t lba, + uint32_t count, const void *buf) { + ide_device_t *dev = (ide_device_t *)dev_data; + if (!dev || dev->type != IDE_TYPE_ATA) return -1; + + uint16_t io = dev->io_base; + const uint8_t *src = (const uint8_t *)buf; + + for (uint32_t s = 0; s < count; s++) { + uint32_t cur_lba = lba + s; + + outb(io + IDE_REG_DRIVE_HEAD, + 0xE0 | (dev->drive << 4) | ((cur_lba >> 24) & 0x0F)); + + outb(io + IDE_REG_SECCOUNT, 1); + outb(io + IDE_REG_LBA_LO, cur_lba & 0xFF); + outb(io + IDE_REG_LBA_MID, (cur_lba >> 8) & 0xFF); + outb(io + IDE_REG_LBA_HI, (cur_lba >> 16) & 0xFF); + + outb(io + IDE_REG_COMMAND, IDE_CMD_WRITE_PIO); + + if (ide_wait_drq(io) != 0) return -1; + + ide_write_buffer(io, (const uint16_t *)(src + s * 512)); + + /* Flush cache: wait for BSY to clear after write */ + ide_wait(io); + } + + return 0; +} + +/** + * Return sector size for a device. + */ +static uint32_t ide_block_sector_size(void *dev_data) { + ide_device_t *dev = (ide_device_t *)dev_data; + return dev ? dev->sector_size : 512; +} + +/** + * Return total sector count for a device. + */ +static uint32_t ide_block_sector_count(void *dev_data) { + ide_device_t *dev = (ide_device_t *)dev_data; + return dev ? dev->sector_count : 0; +} + +/** Block operations for ATA/ATAPI devices. */ +static devicefs_block_ops_t ide_block_ops = { + .read_sectors = ide_block_read, + .write_sectors = ide_block_write, + .sector_size = ide_block_sector_size, + .sector_count = ide_block_sector_count, +}; + +/* ================================================================ + * Driver probe and init + * ================================================================ */ + +/** + * Probe: always return OK since IDE ports are standard. + */ +static driver_probe_result_t ide_probe(void) { + return DRIVER_PROBE_OK; +} + +/** + * Initialize the IDE driver: scan channels and register devices. + */ +static int ide_driver_init(void) { + memset(ide_devices, 0, sizeof(ide_devices)); + + /* Channel definitions: primary (0x1F0, 0x3F6), secondary (0x170, 0x376) */ + static const uint16_t io_bases[2] = { IDE_PRIMARY_IO, IDE_SECONDARY_IO }; + static const uint16_t ctrl_bases[2] = { IDE_PRIMARY_CTRL, IDE_SECONDARY_CTRL }; + + int found = 0; + + for (int ch = 0; ch < 2; ch++) { + /* Software reset the channel */ + ide_soft_reset(ctrl_bases[ch]); + + for (int drv = 0; drv < 2; drv++) { + int idx = ch * 2 + drv; + ide_devices[idx].channel = (uint8_t)ch; + ide_devices[idx].drive = (uint8_t)drv; + ide_devices[idx].io_base = io_bases[ch]; + ide_devices[idx].ctrl_base = ctrl_bases[ch]; + ide_devices[idx].present = 0; + + if (ide_identify(&ide_devices[idx]) == 0) { + found++; + + const char *class_name = (ide_devices[idx].type == IDE_TYPE_ATA) + ? "hdd" : "cd"; + const char *type_str = (ide_devices[idx].type == IDE_TYPE_ATA) + ? "ATA" : "ATAPI"; + + offset_print(" IDE: "); + offset_print(type_str); + offset_print(" device on "); + offset_print(ch == 0 ? "primary" : "secondary"); + offset_print(drv == 0 ? " master" : " slave"); + offset_print(": "); + offset_print(ide_devices[idx].model); + offset_print(" ("); + print_hex(ide_devices[idx].sector_count); + offset_print(" sectors)\n"); + + /* Register with devicefs */ + devicefs_register_block(class_name, &ide_block_ops, + &ide_devices[idx]); + } + } + } + + if (found == 0) { + offset_print(" IDE: no devices found\n"); + } else { + offset_print(" IDE: "); + print_hex((uint32_t)found); + offset_print(" device(s) found\n"); + } + + return 0; +} + +int ide_init(void) { + return ide_driver_init(); +} + +ide_device_t *ide_get_device(int index) { + if (index < 0 || index >= IDE_MAX_DEVICES) return NULL; + if (!ide_devices[index].present) return NULL; + return &ide_devices[index]; +} + +/** IDE driver descriptor. */ +static const driver_t ide_driver = { + .name = "ide", + .probe = ide_probe, + .init = ide_driver_init, +}; + +REGISTER_DRIVER(ide_driver); diff --git a/src/ide.h b/src/ide.h new file mode 100644 index 0000000..ec62f8f --- /dev/null +++ b/src/ide.h @@ -0,0 +1,88 @@ +/** + * @file ide.h + * @brief IDE/ATA disk driver. + * + * Enumerates IDE devices on the primary and secondary channels, identifies + * ATA hard drives and ATAPI CD-ROMs, and registers them with the devicefs + * subsystem as block devices (hddN / cdN). + */ + +#ifndef IDE_H +#define IDE_H + +#include + +/** Maximum number of IDE devices (2 channels × 2 drives). */ +#define IDE_MAX_DEVICES 4 + +/** IDE channel I/O port bases. */ +#define IDE_PRIMARY_IO 0x1F0 +#define IDE_PRIMARY_CTRL 0x3F6 +#define IDE_SECONDARY_IO 0x170 +#define IDE_SECONDARY_CTRL 0x376 + +/** IDE register offsets from I/O base. */ +#define IDE_REG_DATA 0x00 +#define IDE_REG_ERROR 0x01 +#define IDE_REG_FEATURES 0x01 +#define IDE_REG_SECCOUNT 0x02 +#define IDE_REG_LBA_LO 0x03 +#define IDE_REG_LBA_MID 0x04 +#define IDE_REG_LBA_HI 0x05 +#define IDE_REG_DRIVE_HEAD 0x06 +#define IDE_REG_STATUS 0x07 +#define IDE_REG_COMMAND 0x07 + +/** IDE status register bits. */ +#define IDE_STATUS_ERR 0x01 /**< Error occurred. */ +#define IDE_STATUS_DRQ 0x08 /**< Data request ready. */ +#define IDE_STATUS_SRV 0x10 /**< Overlapped mode service request. */ +#define IDE_STATUS_DF 0x20 /**< Drive fault. */ +#define IDE_STATUS_DRDY 0x40 /**< Drive ready. */ +#define IDE_STATUS_BSY 0x80 /**< Drive busy. */ + +/** IDE commands. */ +#define IDE_CMD_IDENTIFY 0xEC /**< ATA IDENTIFY DEVICE. */ +#define IDE_CMD_IDENTIFY_PKT 0xA1 /**< ATAPI IDENTIFY PACKET DEVICE. */ +#define IDE_CMD_READ_PIO 0x20 /**< Read sectors (PIO, 28-bit LBA). */ +#define IDE_CMD_WRITE_PIO 0x30 /**< Write sectors (PIO, 28-bit LBA). */ + +/** IDE device types. */ +#define IDE_TYPE_NONE 0 /**< No device present. */ +#define IDE_TYPE_ATA 1 /**< ATA hard disk. */ +#define IDE_TYPE_ATAPI 2 /**< ATAPI CD/DVD drive. */ + +/** + * IDE device descriptor. + */ +typedef struct ide_device { + uint8_t present; /**< 1 if device is present. */ + uint8_t type; /**< IDE_TYPE_ATA or IDE_TYPE_ATAPI. */ + uint8_t channel; /**< 0 = primary, 1 = secondary. */ + uint8_t drive; /**< 0 = master, 1 = slave. */ + uint16_t io_base; /**< I/O base port for this channel. */ + uint16_t ctrl_base; /**< Control port for this channel. */ + uint32_t sector_count; /**< Total sectors (28-bit LBA max). */ + uint32_t sector_size; /**< Sector size in bytes (usually 512). */ + char model[41]; /**< Model string from IDENTIFY. */ +} ide_device_t; + +/** + * Initialize the IDE driver. + * + * Scans primary and secondary channels for ATA/ATAPI devices, + * reads their IDENTIFY data, and registers them with devicefs. + * + * @return Number of devices found. + */ +int ide_init(void); + +/** + * Get an IDE device by index (0–3). + * + * @param index Device index. + * @return Pointer to the device descriptor, or NULL if invalid/not present. + */ +ide_device_t *ide_get_device(int index); + +#endif /* IDE_H */ diff --git a/src/port_io.h b/src/port_io.h index b353f9e..6136c48 100644 --- a/src/port_io.h +++ b/src/port_io.h @@ -15,6 +15,18 @@ static inline uint8_t inb(uint16_t port) return ret; } +static inline void outw(uint16_t port, uint16_t val) +{ + asm volatile ( "outw %w0, %w1" : : "a"(val), "Nd"(port) ); +} + +static inline uint16_t inw(uint16_t port) +{ + uint16_t ret; + asm volatile ( "inw %w1, %w0" : "=a"(ret) : "Nd"(port) ); + return ret; +} + static inline void io_wait(void) { /* Port 0x80 is used for 'checkpoints' during POST. */