diff options
author | Ian McFarlane <i.mcfarlane2002@gmail.com> | 2025-04-20 14:19:33 -0400 |
---|---|---|
committer | Freya Murphy <freya@freyacat.org> | 2025-04-20 23:44:38 -0400 |
commit | 74e6646b5c4d4363ec0f339d33e3adb6659737e8 (patch) | |
tree | ec92bbe1689334379e7c90470ee6bf9c8dc69f39 /kernel/drivers/ata.c | |
parent | adding code from osdev and reading (diff) | |
download | comus-74e6646b5c4d4363ec0f339d33e3adb6659737e8.tar.gz comus-74e6646b5c4d4363ec0f339d33e3adb6659737e8.tar.bz2 comus-74e6646b5c4d4363ec0f339d33e3adb6659737e8.zip |
added initialization but it is untested
Diffstat (limited to 'kernel/drivers/ata.c')
-rw-r--r-- | kernel/drivers/ata.c | 461 |
1 files changed, 455 insertions, 6 deletions
diff --git a/kernel/drivers/ata.c b/kernel/drivers/ata.c index 9117d85..5371f60 100644 --- a/kernel/drivers/ata.c +++ b/kernel/drivers/ata.c @@ -19,8 +19,8 @@ #define IDE_PROG_IF_SECONDARY_CHANNEL_CAN_SWITCH_TO_AND_FROM_PCI_NATIVE_FLAG 0x4 #define IDE_PROG_IF_DMA_SUPPORT_FLAG 0x7 -// ATA statuses, read off of the command/status port // clang-format off +// ATA statuses, read off of the command/status port #define ATA_SR_BUSY 0x80 #define ATA_SR_DRIVEREADY 0x40 #define ATA_SR_DRIVEWRITEFAULT 0x20 @@ -29,10 +29,8 @@ #define ATA_SR_CORRECTEDATA 0x04 #define ATA_SR_INDEX 0x02 #define ATA_SR_ERROR 0x01 -// clang-format on -// ATA errors, ready off of the features/error port -// clang-format off +// ATA errors, read off of the features/error port #define ATA_ER_BADBLOCK 0x80 #define ATA_ER_UNCORRECTABLE 0x40 #define ATA_ER_MEDIACHANGED 0x20 @@ -41,10 +39,8 @@ #define ATA_ER_COMMANDABORTED 0x04 #define ATA_ER_TRACK0NOTFOUND 0x02 #define ATA_ER_NOADDRESSMARK 0x01 -// clang-format on // ATA commands -// clang-format off #define ATA_CMD_READ_PIO 0x20 #define ATA_CMD_READ_PIO_EXT 0x24 #define ATA_CMD_READ_DMA 0xC8 @@ -58,12 +54,465 @@ #define ATA_CMD_PACKET 0xA0 #define ATA_CMD_IDENTIFY_PACKET 0xA1 #define ATA_CMD_IDENTIFY 0xEC + +// ATAPI commands +#define ATAPI_CMD_READ 0xA8 +#define ATAPI_CMD_EJECT 0x1B + +// values in "identification space," part of the disk with info about the device +#define ATA_IDENT_DEVICETYPE 0 +#define ATA_IDENT_CYLINDERS 2 +#define ATA_IDENT_HEADS 6 +#define ATA_IDENT_SECTORS 12 +#define ATA_IDENT_SERIAL 20 +#define ATA_IDENT_MODEL 54 +#define ATA_IDENT_FEATURES 98 +#define ATA_IDENT_FIELDVALID 106 +#define ATA_IDENT_MAX_LBA 120 +#define ATA_IDENT_COMMANDSETS 164 +#define ATA_IDENT_MAX_LBA_EXT 200 + +// interface types for use when selecting drives +#define IDE_ATA 0x00 +#define IDE_ATAPI 0x01 +#define ATA_MASTER 0x00 +#define ATA_SLAVE 0x01 + +// the parts of the "task file," offsets from channels BAR0 and/or BAR2. +// right hand side comments are for primary channel values +#define ATA_REG_DATA 0x00 // rw +#define ATA_REG_ERROR 0x01 // r +#define ATA_REG_FEATURES 0x01 // w +#define ATA_REG_SECCOUNT0 0x02 // rw +#define ATA_REG_LBA0 0x03 // rw +#define ATA_REG_LBA1 0x04 // rw +#define ATA_REG_LBA2 0x05 // rw +#define ATA_REG_HDDEVSEL 0x06 // rw, used to select drive in channel +#define ATA_REG_COMMAND 0x07 // w +#define ATA_REG_STATUS 0x07 // r +#define ATA_REG_SECCOUNT1 0x08 +#define ATA_REG_LBA3 0x09 +#define ATA_REG_LBA4 0x0A +#define ATA_REG_LBA5 0x0B +#define ATA_REG_CONTROL 0x0C // w, can also be written as BAR1 + 2 +#define ATA_REG_ALTSTATUS 0x0C // r, also BAR1 + 2 +#define ATA_REG_DEVADDRESS 0x0D // BAR1 + 3 ???? osdev doesn't know what this is + +// Channels: +#define ATA_PRIMARY 0x00 +#define ATA_SECONDARY 0x01 + +// Directions: +#define ATA_READ 0x00 +#define ATA_WRITE 0x01 // clang-format on #include <comus/drivers/ata.h> #include <comus/drivers/pci.h> +#include <comus/asm.h> #include <lib.h> +static struct ide_channel { + uint16_t io_base; + uint16_t control_base; + uint16_t bus_master_ide_base; + uint8_t no_interrupt; +} channels[2]; + +volatile static uint8_t ide_irq_invoked = 0; +static uint8_t atapi_packet[12] = { 0xA8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + +struct ide_device { + uint8_t is_reserved; // 0 (Empty) or 1 (This Drive really exists). + uint8_t channel_idx; // 0 (Primary Channel) or 1 (Secondary Channel). + uint8_t drive_idx; // 0 (Master Drive) or 1 (Slave Drive). + uint16_t type; // 0: ATA, 1:ATAPI. + uint16_t drive_signature; + uint16_t features; + uint32_t supported_command_sets; + uint32_t size_in_sectors; + char model_str[41]; // NOTE: originally uint8_t according to osdev +} ide_devices[4]; + +static struct ide_device *device(const uint8_t idx) +{ + assert(idx < (sizeof(ide_devices) / sizeof(struct ide_device)), + "out of bounds"); + return ide_devices + idx; +} + +static struct ide_channel *channel(const uint8_t idx) +{ + assert(idx < (sizeof(channels) / sizeof(struct ide_channel)), + "out of bounds"); + return channels + idx; +} + +static struct ide_channel *device_channel(uint8_t device_idx) +{ + return channel(device(device_idx)->channel_idx); +} + +static void ide_channel_write(struct ide_channel *channel, const uint8_t reg, + uint8_t data) +{ + const bool disable_interrupts = reg > 0x07 && reg < 0x0C; + if (disable_interrupts) { + ide_channel_write(channel, ATA_REG_CONTROL, + 0x80 | channel->no_interrupt); + } + + if (reg < 0x08) { + outb(channel->io_base + reg - 0x00, data); + } else if (reg < 0x0C) { + outb(channel->io_base + reg - 0x06, data); + } else if (reg < 0x0E) { + outb(channel->control_base + reg - 0x0A, data); + } else if (reg < 0x16) { + outb(channel->bus_master_ide_base + reg - 0x0E, data); + } + + if (disable_interrupts) { + ide_channel_write(channel, ATA_REG_CONTROL, channel->no_interrupt); + } +} + +static uint8_t ide_channel_read(struct ide_channel *channel, const uint8_t reg) +{ + uint8_t result; + + const bool disable_interrupts = reg > 0x07 && reg < 0x0C; + + if (disable_interrupts) { + ide_channel_write(channel, ATA_REG_CONTROL, + 0x80 | channel->no_interrupt); + } + + if (reg < 0x08) { + result = inb(channel->io_base + reg - 0x00); + } else if (reg < 0x0C) { + result = inb(channel->io_base + reg - 0x06); + } else if (reg < 0x0E) { + result = inb(channel->control_base + reg - 0x0A); + } else if (reg < 0x16) { + result = inb(channel->bus_master_ide_base + reg - 0x0E); + } else { + assert(false, "invalid ide channel register %u", reg); + } + + if (disable_interrupts) { + ide_channel_write(channel, ATA_REG_CONTROL, channel->no_interrupt); + } + + return result; +} + +// TODO: fix stack-trashing in ide_read_id_space_buffer() so we can call +// functions there and dont have to do this garbage. but i dont understand why +// esp and es need to be overwritten in the first place so I'm afraid i'll break +// something - Ian +#define __insl_nocall(reg, buffer_uint32_ptr, quads) \ + do { \ + for (uint32_t index = 0; index < quads; ++index) { \ + uint32_t out; \ + __inl_nocall(reg, out); \ + buffer_uint32_ptr[index] = out; \ + } \ + } while (0) + +void ide_read_id_space_buffer(struct ide_channel *channel, uint8_t reg, + uint32_t *out_buffer, + size_t out_buffer_size_bytes) +{ + const bool disable_interrupts = reg > 0x07 && reg < 0x0C; + + const size_t quads = out_buffer_size_bytes / 4; + assert(sizeof(uint32_t) == 4, "what."); + assert(out_buffer_size_bytes % 4 == 0, + "id space buffer size not divisible by 4"); + + if (disable_interrupts) { + ide_channel_write(channel, ATA_REG_CONTROL, + 0x80 | channel->no_interrupt); + } + + /* WARNING: This code contains a serious bug. The inline assembly trashes ES and + * ESP for all of the code the compiler generates between the inline + * assembly blocks. Do not call functions here. + */ + __asm__ volatile("pushw %es; movw %ds, %ax; movw %ax, %es"); + // NOTE: can be fixed with this code? + // __asm__ volatile("pushw %es; pushw %ax; movw %ds, %ax; movw %ax, %es; popw %ax;"); + + if (reg < 0x08) { + __insl_nocall(channel->io_base + reg - 0x00, out_buffer, quads); + } else if (reg < 0x0C) { + __insl_nocall(channel->io_base + reg - 0x06, out_buffer, quads); + } else if (reg < 0x0E) { + __insl_nocall(channel->control_base + reg - 0x0A, out_buffer, quads); + } else if (reg < 0x16) { + __insl_nocall(channel->bus_master_ide_base + reg - 0x0E, out_buffer, + quads); + } + + __asm__ volatile("popw %es;"); + + if (disable_interrupts) { + ide_channel_write(channel, ATA_REG_CONTROL, channel->no_interrupt); + } +} + +enum ide_channel_poll_error { + POLL_ERROR_OK, + POLL_ERROR_DRIVE_REQUEST_NOT_READY, + POLL_ERROR_DEVICE_FAULT, + POLL_ERROR_STATUS_REGISTER_ERROR, + POLL_ERROR_WRITE_PROTECTED, +}; + +enum ide_channel_poll_error ide_channel_poll(struct ide_channel *channel, + bool advanced_check) +{ + // (I) Delay 400 nanosecond for BSY to be set: + for (int i = 0; i < 4; i++) { + ide_channel_read( + channel, + ATA_REG_ALTSTATUS); // Reading the Alternate Status port wastes 100ns; loop four times. + } + + while (ide_channel_read(channel, ATA_REG_STATUS) & ATA_SR_BUSY) + ; // Wait for BSY to be zero. + + if (advanced_check) { + uint8_t state = ide_channel_read(channel, ATA_REG_STATUS); + + if (state & ATA_SR_ERROR) { + return POLL_ERROR_STATUS_REGISTER_ERROR; + } + + if (state & ATA_SR_DRIVEWRITEFAULT) { + return POLL_ERROR_DEVICE_FAULT; + } + + // BSY = 0; DF = 0; ERR = 0 so we should check for DRQ now. + if ((state & ATA_SR_DRIVEREQUESTREADY) == 0) { + return POLL_ERROR_DRIVE_REQUEST_NOT_READY; + } + } + + return POLL_ERROR_OK; +} + +uint8_t ide_print_poll_error(const uint32_t device_idx, + const enum ide_channel_poll_error err) +{ + uint8_t out_err = err; + + if (err == POLL_ERROR_OK) { + return out_err; + } + + ERROR("IDE ERROR:"); + if (err == POLL_ERROR_DEVICE_FAULT) { + ERROR("\tDevice fault"); + out_err = 19; + } + + else if (err == POLL_ERROR_STATUS_REGISTER_ERROR) { + uint8_t status_reg = + ide_channel_read(device_channel(device_idx), ATA_REG_ERROR); + + if (status_reg & ATA_ER_NOADDRESSMARK) { + ERROR("\tNo address mark found"); + out_err = 7; + } + + if (status_reg & ATA_ER_TRACK0NOTFOUND) { + ERROR("\tNo media or media error"); + out_err = 3; + } + + if (status_reg & ATA_ER_COMMANDABORTED) { + ERROR("\tDrive aborted command"); + out_err = 20; + } + + if (status_reg & ATA_ER_MEDIACHANGEREQUEST) { + ERROR("\tNo media or media error"); + out_err = 3; + } + + if (status_reg & ATA_ER_IDMARKNOTFOUND) { + ERROR("\tID mark not found, unable to read ID space"); + out_err = 21; + } + + if (status_reg & ATA_ER_MEDIACHANGED) { + ERROR("\tNo media or media error"); + out_err = 3; + } + + if (status_reg & ATA_ER_UNCORRECTABLE) { + ERROR("\tUncorrectable data error"); + out_err = 22; + } + if (status_reg & ATA_ER_BADBLOCK) { + ERROR("\tBad sectors"); + out_err = 13; + } + } else if (err == POLL_ERROR_DRIVE_REQUEST_NOT_READY) { + ERROR("\tRead nothing, drive request not ready"); + out_err = 23; + } else if (err == POLL_ERROR_WRITE_PROTECTED) { + ERROR("\tWrite-protected"); + out_err = 8; + } + + static const char *channel_displaynames[] = { "Primary", "Secondary" }; + static const char *device_displaynames[] = { "Master", "Slave" }; + + struct ide_device *dev = device(device_idx); + const char *channel_displayname = channel_displaynames[dev->channel_idx]; + const char *device_displayname = device_displaynames[dev->drive_idx]; + + ERROR("\t[%s %s] %s", channel_displayname, device_displayname, + dev->model_str); + + return err; +} + +void ide_initialize(uint32_t BAR0, uint32_t BAR1, uint32_t BAR2, uint32_t BAR3, + uint32_t BAR4) +{ + // 1- Detect I/O Ports which interface IDE Controller: + channels[ATA_PRIMARY] = (struct ide_channel){ + .io_base = (BAR0 & 0xFFFFFFFC) + 0x1F0 * (!BAR0), + .control_base = (BAR1 & 0xFFFFFFFC) + 0x3F6 * (!BAR1), + .bus_master_ide_base = (BAR4 & 0xFFFFFFFC) + 0, + }; + channels[ATA_SECONDARY] = (struct ide_channel){ + .io_base = (BAR2 & 0xFFFFFFFC) + 0x170 * (!BAR2), + .control_base = (BAR3 & 0xFFFFFFFC) + 0x376 * (!BAR3), + .bus_master_ide_base = (BAR4 & 0xFFFFFFFC) + 8, + }; + + // 2- Disable IRQs: + ide_channel_write(channel(ATA_PRIMARY), ATA_REG_CONTROL, 2); + ide_channel_write(channel(ATA_SECONDARY), ATA_REG_CONTROL, 2); + + // 3- Detect ATA-ATAPI Devices: + uint32_t device_count = 0; + for (uint8_t channel_idx = 0; channel_idx < 2; channel_idx++) + // drive idx is like device_count but it starts at 0 per channel + // and increments regardless of whether a device is present + for (uint8_t drive_idx = 0; drive_idx < 2; drive_idx++) { + // select our uninitialized device from preallocated buffer + struct ide_device *dev = device(device_count); + + // select our initialized channel + struct ide_channel *chan = channel(channel_idx); + + // default drive to not existing + dev->is_reserved = 0; + + // select drive: + ide_channel_write(chan, ATA_REG_HDDEVSEL, 0xA0 | (drive_idx << 4)); + // This function should be implemented in your OS. which waits for 1 ms. + // it is based on System Timer Device Driver. + sleep(1); // Wait 1ms for drive select to work. + + // (II) Send ATA Identify Command: + ide_channel_write(chan, ATA_REG_COMMAND, ATA_CMD_IDENTIFY); + sleep(1); + + // (III) Polling: + if (ide_channel_read(chan, ATA_REG_STATUS) == 0) { + continue; // If status == 0, no device. + } + + bool ata_err = false; + while (1) { + uint8_t status; + status = ide_channel_read(chan, ATA_REG_STATUS); + + // if error, device is not ATA + if ((status & ATA_SR_ERROR)) { + ata_err = true; + break; + } + + // break when not busy and we our request is done + if (!(status & ATA_SR_BUSY) && + (status & ATA_SR_DRIVEREQUESTREADY)) { + break; + } + } + + // probe for ATAPI devices, if needed + uint8_t type = IDE_ATA; + if (ata_err) { + uint8_t cl = ide_channel_read(chan, ATA_REG_LBA1); + uint8_t ch = ide_channel_read(chan, ATA_REG_LBA2); + + if (cl == 0x14 && ch == 0xEB) { + type = IDE_ATAPI; + } else if (cl == 0x69 && ch == 0x96) { + type = IDE_ATAPI; + } else { + // unknown type (may not be a device). + continue; + } + + ide_channel_write(chan, ATA_REG_COMMAND, + ATA_CMD_IDENTIFY_PACKET); + sleep(1); + } + + static uint8_t id_space_buf[2048] = { 0 }; + ide_read_id_space_buffer(chan, ATA_REG_DATA, + (uint32_t *)id_space_buf, 512); + + // read device info from the id space buffer into our struct: + *dev = (struct ide_device){ + .is_reserved = 1, + .type = type, + .channel_idx = channel_idx, + .drive_idx = drive_idx, + .drive_signature = + *((uint16_t *)(id_space_buf + ATA_IDENT_DEVICETYPE)), + .features = *((uint16_t *)(id_space_buf + ATA_IDENT_FEATURES)), + .supported_command_sets = + *((uint32_t *)(id_space_buf + ATA_IDENT_COMMANDSETS)) + }; + + // get size (depends on address mode): + if (dev->supported_command_sets & (1 << 26)) { + // device uses 48-bit addressing: + dev->size_in_sectors = + *((uint32_t *)(id_space_buf + ATA_IDENT_MAX_LBA_EXT)); + } else { + // device uses CHS or 28-bit dddressing: + dev->size_in_sectors = + *((uint32_t *)(id_space_buf + ATA_IDENT_MAX_LBA)); + } + + // string indicates model of device (Western Digital HDD, SONY DVD-RW...) + for (uint8_t i = 0; i < 40; i += 2) { + dev->model_str[i] = id_space_buf[ATA_IDENT_MODEL + i + 1]; + dev->model_str[i + 1] = id_space_buf[ATA_IDENT_MODEL + i]; + } + dev->model_str[40] = 0; // null-terminate string + + device_count++; + } +} + +void ata_init(void) +{ + // numbers for supporting only parallel ATA + ide_initialize(0x1F0, 0x3F6, 0x170, 0x376, 0x000); +} + bool ata_find_primary_drive(struct pci_device *out) { if (!pci_findby_class(out, CLASS_MASS_STORAGE_CONTROLLER, |