|
| 1 | +/* |
| 2 | +* ata.cpp |
| 3 | +* As part of the Avery project |
| 4 | +* Created by Max Van den Eynde in 2026 |
| 5 | +* -------------------------------------- |
| 6 | +* Description: ATA implementation |
| 7 | +* Copyright (c) 2026 Max Van den Eynde |
| 8 | +*/ |
| 9 | + |
| 10 | +#include "drivers/ata.h" |
| 11 | + |
| 12 | +#include "types.h" |
| 13 | +#include "io/io.h" |
| 14 | +#include "kernel/debug.h" |
| 15 | + |
| 16 | +namespace { |
| 17 | + constexpr u8 ATA_REG_DATA = 0x00; |
| 18 | + constexpr u8 ATA_REG_SECCOUNT0 = 0x02; |
| 19 | + constexpr u8 ATA_REG_LBA0 = 0x03; |
| 20 | + constexpr u8 ATA_REG_LBA1 = 0x04; |
| 21 | + constexpr u8 ATA_REG_LBA2 = 0x05; |
| 22 | + constexpr u8 ATA_REG_HDDEVSEL = 0x06; |
| 23 | + constexpr u8 ATA_REG_COMMAND = 0x07; |
| 24 | + constexpr u8 ATA_REG_STATUS = 0x07; |
| 25 | + |
| 26 | + constexpr u8 ATA_SR_BSY = 0x80; |
| 27 | + constexpr u8 ATA_SR_DF = 0x20; |
| 28 | + constexpr u8 ATA_SR_DRQ = 0x08; |
| 29 | + constexpr u8 ATA_SR_ERR = 0x01; |
| 30 | + |
| 31 | + constexpr u8 ATA_CMD_READ_PIO = 0x20; |
| 32 | + constexpr u8 ATA_CMD_WRITE_PIO = 0x30; |
| 33 | + constexpr u8 ATA_CMD_CACHE_FLUSH = 0xE7; |
| 34 | + constexpr u8 ATA_CMD_IDENTIFY = 0xEC; |
| 35 | + |
| 36 | + bool waitBSY(u16 ioBase) { |
| 37 | + for (usize i = 0; i < 100000; i++) { |
| 38 | + if (!(io::inb(ioBase + ATA_REG_STATUS) & ATA_SR_BSY)) return true; |
| 39 | + } |
| 40 | + return false; |
| 41 | + } |
| 42 | + |
| 43 | + bool waitDRQ(u16 ioBase) { |
| 44 | + for (usize i = 0; i < 100000; i++) { |
| 45 | + u8 status = io::inb(ioBase + ATA_REG_STATUS); |
| 46 | + |
| 47 | + if (status & (ATA_SR_ERR | ATA_SR_DF)) { |
| 48 | + return false; |
| 49 | + } |
| 50 | + |
| 51 | + if (!(status & ATA_SR_BSY) && (status & ATA_SR_DRQ)) return true; |
| 52 | + } |
| 53 | + return false; |
| 54 | + } |
| 55 | + |
| 56 | + void selectDrive(u16 ioBase, u8 drive, u64 lba) { |
| 57 | + io::outb(ioBase + ATA_REG_HDDEVSEL, |
| 58 | + static_cast<u8>(0xE0 | static_cast<u8>((drive & 1) << 4) | ((lba >> 24) & 0x0F))); |
| 59 | + io::wait(); |
| 60 | + } |
| 61 | + |
| 62 | + bool identifyDrive(u16 ioBase, u8 drive, u16* identify) { |
| 63 | + selectDrive(ioBase, drive, 0); |
| 64 | + |
| 65 | + io::outb(ioBase + ATA_REG_SECCOUNT0, 0); |
| 66 | + io::outb(ioBase + ATA_REG_LBA0, 0); |
| 67 | + io::outb(ioBase + ATA_REG_LBA1, 0); |
| 68 | + io::outb(ioBase + ATA_REG_LBA2, 0); |
| 69 | + io::outb(ioBase + ATA_REG_COMMAND, ATA_CMD_IDENTIFY); |
| 70 | + |
| 71 | + io::wait(); |
| 72 | + |
| 73 | + if (io::inb(ioBase + ATA_REG_STATUS) == 0) { |
| 74 | + return false; |
| 75 | + } |
| 76 | + |
| 77 | + if (!waitDRQ(ioBase)) { |
| 78 | + return false; |
| 79 | + } |
| 80 | + |
| 81 | + for (usize i = 0; i < 256; i++) { |
| 82 | + identify[i] = io::inw(ioBase + ATA_REG_DATA); |
| 83 | + } |
| 84 | + |
| 85 | + return true; |
| 86 | + } |
| 87 | + |
| 88 | + u64 sectorCountFromIdentify(const u16* id) { |
| 89 | + return static_cast<u64>(id[60]) | (static_cast<u64>(id[61]) << 16); |
| 90 | + } |
| 91 | +} |
| 92 | + |
| 93 | + |
| 94 | +ATADiskDevice::ATADiskDevice([[maybe_unused]] PCIDevice* controller, u16 ioBase, [[maybe_unused]] u16 ctrlBase, |
| 95 | + u8 drive, u64 sectors) : |
| 96 | + BlockDevice("Ata Disk", sectors, 512), |
| 97 | + ioBase(ioBase), |
| 98 | + drive(drive) { |
| 99 | +} |
| 100 | + |
| 101 | +bool ATADiskDevice::readBlocks(u64 lba, u32 count, void* buffer) { |
| 102 | + return access(false, lba, count, buffer); |
| 103 | +} |
| 104 | + |
| 105 | +bool ATADiskDevice::writeBlocks(u64 lba, u32 count, const void* buffer) { |
| 106 | + return access(true, lba, count, const_cast<void*>(buffer)); |
| 107 | +} |
| 108 | + |
| 109 | +bool ATADiskDevice::access(bool write, u64 lba, u32 count, void* buffer) { |
| 110 | + if (count == 0) { |
| 111 | + return true; |
| 112 | + } |
| 113 | + |
| 114 | + if (lba + count > blockCount()) { |
| 115 | + debug::error("Tried to access region ", lba, " + ", count, " which resulted in an overflow."); |
| 116 | + return false; |
| 117 | + } |
| 118 | + |
| 119 | + if (lba > 0x0FFFFFFF) { |
| 120 | + debug::error("For now LBA28 is not supported"); |
| 121 | + return false; |
| 122 | + } |
| 123 | + |
| 124 | + auto* words = reinterpret_cast<u16*>(buffer); |
| 125 | + |
| 126 | + for (u32 sector = 0; sector < count; sector++) { |
| 127 | + u64 currentLBA = lba + sector; |
| 128 | + |
| 129 | + if (!waitBSY(ioBase)) { |
| 130 | + return false; |
| 131 | + } |
| 132 | + |
| 133 | + selectDrive(ioBase, drive, currentLBA); |
| 134 | + |
| 135 | + io::outb(ioBase + ATA_REG_SECCOUNT0, 1); |
| 136 | + io::outb(ioBase + ATA_REG_LBA0, currentLBA & 0xFF); |
| 137 | + io::outb(ioBase + ATA_REG_LBA1, (currentLBA >> 8) & 0xFF); |
| 138 | + io::outb(ioBase + ATA_REG_LBA2, (currentLBA >> 16) & 0xFF); |
| 139 | + |
| 140 | + io::outb(ioBase + ATA_REG_COMMAND, write ? ATA_CMD_WRITE_PIO : ATA_CMD_READ_PIO); |
| 141 | + |
| 142 | + if (write) { |
| 143 | + for (usize i = 0; i < 256; i++) { |
| 144 | + io::outw(ioBase + ATA_REG_DATA, words[sector * 256 + i]); |
| 145 | + } |
| 146 | + |
| 147 | + io::outb(ioBase + ATA_REG_COMMAND, ATA_CMD_CACHE_FLUSH); |
| 148 | + |
| 149 | + if (!waitBSY(ioBase)) { |
| 150 | + return false; |
| 151 | + } |
| 152 | + } |
| 153 | + else { |
| 154 | + for (usize i = 0; i < 256; i++) { |
| 155 | + words[sector * 256 + i] = io::inw(ioBase + ATA_REG_DATA); |
| 156 | + } |
| 157 | + } |
| 158 | + } |
| 159 | + |
| 160 | + return true; |
| 161 | +} |
| 162 | + |
| 163 | +bool ATADriver::probe(Device& device) { |
| 164 | + if (device.type() != DeviceType::PCI) { |
| 165 | + return false; |
| 166 | + } |
| 167 | + |
| 168 | + auto& pciDevice = static_cast<PCIDevice&>(device); |
| 169 | + |
| 170 | + return pciDevice.isClass(0x01, 0x01); |
| 171 | +} |
| 172 | + |
| 173 | +bool ATADriver::start(Device& device) { |
| 174 | + debug::log("Starting ATA Driver"); |
| 175 | + |
| 176 | + auto& pciDevice = static_cast<PCIDevice&>(device); |
| 177 | + |
| 178 | + pciDevice.enableIOSpace(); |
| 179 | + pciDevice.enableBusMastering(); |
| 180 | + |
| 181 | + struct Channel { |
| 182 | + u16 ioBase; |
| 183 | + u16 ctrlBase; |
| 184 | + }; |
| 185 | + |
| 186 | + Channel channels[] = { |
| 187 | + {0x1F0, 0x3F6}, |
| 188 | + {0x170, 0x376} |
| 189 | + }; |
| 190 | + |
| 191 | + bool foundAny = false; |
| 192 | + |
| 193 | + for (auto& channel : channels) { |
| 194 | + for (u8 drive = 0; drive < 2; drive++) { |
| 195 | + u16 identify[256]{}; |
| 196 | + |
| 197 | + if (!identifyDrive(channel.ioBase, drive, identify)) continue; |
| 198 | + |
| 199 | + u64 sectors = sectorCountFromIdentify(identify); |
| 200 | + |
| 201 | + if (sectors == 0) { |
| 202 | + debug::warn("Found a drive with no sectors."); |
| 203 | + } |
| 204 | + |
| 205 | + auto* disk = new ATADiskDevice( |
| 206 | + &pciDevice, |
| 207 | + channel.ioBase, |
| 208 | + channel.ctrlBase, |
| 209 | + drive, |
| 210 | + sectors |
| 211 | + ); |
| 212 | + |
| 213 | + disk->parent = &pciDevice; |
| 214 | + disk->driver = this; |
| 215 | + |
| 216 | + DeviceManager::registerDevice(disk); |
| 217 | + foundAny = true; |
| 218 | + } |
| 219 | + } |
| 220 | + |
| 221 | + if (!foundAny) { |
| 222 | + return false; |
| 223 | + } |
| 224 | + |
| 225 | + device.driver = this; |
| 226 | + setState(DriverState::Active); |
| 227 | + return true; |
| 228 | +} |
| 229 | + |
| 230 | +bool ATADriver::stop(Device& device) { |
| 231 | + if (device.driver == this) { |
| 232 | + device.driver = nullptr; |
| 233 | + } |
| 234 | + |
| 235 | + setState(DriverState::Stopping); |
| 236 | + return true; |
| 237 | +} |
| 238 | + |
| 239 | +void ata::registerDriver() { |
| 240 | + driver = new ATADriver(); |
| 241 | + DriverManager::registerDriver(driver); |
| 242 | +} |
0 commit comments