Add IDE/ATA disk driver with devicefs integration

Implement PIO-mode IDE driver that scans primary and secondary channels
for ATA hard drives and ATAPI CD/DVD drives using IDENTIFY commands.

Features:
- Scans 4 possible devices (2 channels x 2 drives each)
- ATA IDENTIFY DEVICE for hard drives
- ATAPI IDENTIFY PACKET DEVICE for CD/DVD drives
- PIO-mode 28-bit LBA sector read/write for ATA drives
- Model string extraction and sector count parsing
- Registers as kernel driver via REGISTER_DRIVER macro
- Registers devices with devicefs: ATA → hdd class, ATAPI → cd class
- Added inw/outw to port_io.h for 16-bit I/O

Tested: QEMU detects hdd1 (QEMU HARDDISK) and cd1 (QEMU DVD-ROM).
This commit is contained in:
AI
2026-02-23 13:57:00 +00:00
parent c12d49dea0
commit c07ec030a7
4 changed files with 491 additions and 0 deletions

View File

@@ -20,6 +20,7 @@ add_executable(kernel
vfs.c
initrd_fs.c
devicefs.c
ide.c
env.c
keyboard.c
interrupts.S

390
src/ide.c Normal file
View File

@@ -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 <string.h>
/* 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);

88
src/ide.h Normal file
View File

@@ -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 <stdint.h>
/** 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 (03).
*
* @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 */

View File

@@ -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. */