From 59e122f8ba745178f1796406a49bf90d4fcaad8d Mon Sep 17 00:00:00 2001 From: Jesse Date: Sun, 28 Jul 2019 23:44:01 +1000 Subject: Kernel: Expand PATA driver to support multiple hard drives (#365) The previous implementation of the PIIX3/4 PATA/IDE channel driver only supported a single drive, as the object model was wrong (the channel inherits the IRQ, not the disk drive itself). This fixes it by 'attaching' two `PATADiskDevices` to a `PATAChannel`, which makes more sense. The reading/writing code is presented as is, which violates the spec outlined by Seagate in the linked datasheet. That spec is rather old, so it might not be 100% up to date, though may cause issues on real hardware, so until we can actually test it, this will suffice. --- Kernel/Devices/IDEDiskDevice.cpp | 528 -------------------------------------- Kernel/Devices/IDEDiskDevice.h | 75 ------ Kernel/Devices/PATAChannel.cpp | 507 ++++++++++++++++++++++++++++++++++++ Kernel/Devices/PATAChannel.h | 72 ++++++ Kernel/Devices/PATADiskDevice.cpp | 164 ++++++++++++ Kernel/Devices/PATADiskDevice.h | 62 +++++ Kernel/Makefile | 3 +- Kernel/init.cpp | 10 +- 8 files changed, 812 insertions(+), 609 deletions(-) delete mode 100644 Kernel/Devices/IDEDiskDevice.cpp delete mode 100644 Kernel/Devices/IDEDiskDevice.h create mode 100644 Kernel/Devices/PATAChannel.cpp create mode 100644 Kernel/Devices/PATAChannel.h create mode 100644 Kernel/Devices/PATADiskDevice.cpp create mode 100644 Kernel/Devices/PATADiskDevice.h diff --git a/Kernel/Devices/IDEDiskDevice.cpp b/Kernel/Devices/IDEDiskDevice.cpp deleted file mode 100644 index f9ce5daa72..0000000000 --- a/Kernel/Devices/IDEDiskDevice.cpp +++ /dev/null @@ -1,528 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -//#define DISK_DEBUG - -#define IRQ_FIXED_DISK 14 - -#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_CORR 0x04 -#define ATA_SR_IDX 0x02 -#define ATA_SR_ERR 0x01 - -#define ATA_ER_BBK 0x80 -#define ATA_ER_UNC 0x40 -#define ATA_ER_MC 0x20 -#define ATA_ER_IDNF 0x10 -#define ATA_ER_MCR 0x08 -#define ATA_ER_ABRT 0x04 -#define ATA_ER_TK0NF 0x02 -#define ATA_ER_AMNF 0x01 - -#define ATA_CMD_READ_PIO 0x20 -#define ATA_CMD_READ_PIO_EXT 0x24 -#define ATA_CMD_READ_DMA 0xC8 -#define ATA_CMD_READ_DMA_EXT 0x25 -#define ATA_CMD_WRITE_PIO 0x30 -#define ATA_CMD_WRITE_PIO_EXT 0x34 -#define ATA_CMD_WRITE_DMA 0xCA -#define ATA_CMD_WRITE_DMA_EXT 0x35 -#define ATA_CMD_CACHE_FLUSH 0xE7 -#define ATA_CMD_CACHE_FLUSH_EXT 0xEA -#define ATA_CMD_PACKET 0xA0 -#define ATA_CMD_IDENTIFY_PACKET 0xA1 -#define ATA_CMD_IDENTIFY 0xEC - -#define ATAPI_CMD_READ 0xA8 -#define ATAPI_CMD_EJECT 0x1B - -#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_CAPABILITIES 98 -#define ATA_IDENT_FIELDVALID 106 -#define ATA_IDENT_MAX_LBA 120 -#define ATA_IDENT_COMMANDSETS 164 -#define ATA_IDENT_MAX_LBA_EXT 200 - -#define IDE_ATA 0x00 -#define IDE_ATAPI 0x01 - -#define ATA_REG_DATA 0x00 -#define ATA_REG_ERROR 0x01 -#define ATA_REG_FEATURES 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_REG_SECCOUNT1 0x08 -#define ATA_REG_LBA3 0x09 -#define ATA_REG_LBA4 0x0A -#define ATA_REG_LBA5 0x0B -#define ATA_REG_CONTROL 0x0C -#define ATA_REG_ALTSTATUS 0x0C -#define ATA_REG_DEVADDRESS 0x0D - -NonnullRefPtr IDEDiskDevice::create(DriveType type) -{ - return adopt(*new IDEDiskDevice(type)); -} - -IDEDiskDevice::IDEDiskDevice(DriveType type) - : IRQHandler(IRQ_FIXED_DISK) - , m_io_base(0x1f0) - , m_drive_type(type) -{ - m_dma_enabled.resource() = true; - ProcFS::the().add_sys_bool("ide_dma", m_dma_enabled); - initialize(); -} - -IDEDiskDevice::~IDEDiskDevice() -{ -} - -const char* IDEDiskDevice::class_name() const -{ - return "IDEDiskDevice"; -} - -unsigned IDEDiskDevice::block_size() const -{ - return 512; -} - -bool IDEDiskDevice::read_blocks(unsigned index, u16 count, u8* out) -{ - if (m_bus_master_base && m_dma_enabled.resource()) - return read_sectors_with_dma(index, count, out); - return read_sectors(index, count, out); -} - -bool IDEDiskDevice::read_block(unsigned index, u8* out) const -{ - return const_cast(this)->read_blocks(index, 1, out); -} - -bool IDEDiskDevice::write_blocks(unsigned index, u16 count, const u8* data) -{ - if (m_bus_master_base && m_dma_enabled.resource()) - return write_sectors_with_dma(index, count, data); - for (unsigned i = 0; i < count; ++i) { - if (!write_sectors(index + i, 1, data + i * 512)) - return false; - } - return true; -} - -bool IDEDiskDevice::write_block(unsigned index, const u8* data) -{ - return write_blocks(index, 1, data); -} - -static void print_ide_status(u8 status) -{ - kprintf("DRQ=%u BSY=%u DRDY=%u DSC=%u DF=%u CORR=%u IDX=%u ERR=%u\n", - (status & ATA_SR_DRQ) != 0, - (status & ATA_SR_BSY) != 0, - (status & ATA_SR_DRDY) != 0, - (status & ATA_SR_DSC) != 0, - (status & ATA_SR_DF) != 0, - (status & ATA_SR_CORR) != 0, - (status & ATA_SR_IDX) != 0, - (status & ATA_SR_ERR) != 0); -} - -bool IDEDiskDevice::wait_for_irq() -{ -#ifdef DISK_DEBUG - kprintf("disk: waiting for interrupt...\n"); -#endif - // FIXME: Add timeout. - while (!m_interrupted) { - // FIXME: Put this process into a Blocked state instead, it's stupid to wake up just to check a flag. - Scheduler::yield(); - } -#ifdef DISK_DEBUG - kprintf("disk: got interrupt!\n"); -#endif - return true; -} - -void IDEDiskDevice::handle_irq() -{ - u8 status = IO::in8(m_io_base + ATA_REG_STATUS); - if (status & ATA_SR_ERR) { - print_ide_status(status); - m_device_error = IO::in8(m_io_base + ATA_REG_ERROR); - kprintf("IDEDiskDevice: Error %b!\n", m_device_error); - } else { - m_device_error = 0; - } -#ifdef DISK_DEBUG - kprintf("disk:interrupt: DRQ=%u BSY=%u DRDY=%u\n", (status & ATA_SR_DRQ) != 0, (status & ATA_SR_BSY) != 0, (status & ATA_SR_DRDY) != 0); -#endif - m_interrupted = true; -} - -void IDEDiskDevice::initialize() -{ - static const PCI::ID piix3_ide_id = { 0x8086, 0x7010 }; - static const PCI::ID piix4_ide_id = { 0x8086, 0x7111 }; - PCI::enumerate_all([this](const PCI::Address& address, PCI::ID id) { - if (id == piix3_ide_id || id == piix4_ide_id) { - m_pci_address = address; - kprintf("PIIX%u IDE device found!\n", id == piix3_ide_id ? 3 : 4); - } - }); - -#ifdef DISK_DEBUG - u8 status = IO::in8(m_io_base + ATA_REG_STATUS); - kprintf("initial status: "); - print_ide_status(status); - - if (is_slave()) - kprintf("This IDE device is the SECONDARY device on the channel!\n"); -#endif - - m_interrupted = false; - - while (IO::in8(m_io_base + ATA_REG_STATUS) & ATA_SR_BSY) - ; - - enable_irq(); - - u8 devsel = 0xA0; - if (is_slave()) - devsel |= 0x10; - - IO::out8(0x1F6, devsel); - IO::out8(0x3F6, devsel); - IO::out8(m_io_base + ATA_REG_COMMAND, ATA_CMD_IDENTIFY); - - enable_irq(); - wait_for_irq(); - - ByteBuffer wbuf = ByteBuffer::create_uninitialized(512); - ByteBuffer bbuf = ByteBuffer::create_uninitialized(512); - u8* b = bbuf.pointer(); - u16* w = (u16*)wbuf.pointer(); - const u16* wbufbase = (u16*)wbuf.pointer(); - - for (u32 i = 0; i < 256; ++i) { - u16 data = IO::in16(m_io_base + ATA_REG_DATA); - *(w++) = data; - *(b++) = MSB(data); - *(b++) = LSB(data); - } - - // "Unpad" the device name string. - for (u32 i = 93; i > 54 && bbuf[i] == ' '; --i) - bbuf[i] = 0; - - m_cylinders = wbufbase[1]; - m_heads = wbufbase[3]; - m_sectors_per_track = wbufbase[6]; - - kprintf( - "IDEDiskDevice: Master=\"%s\", C/H/Spt=%u/%u/%u\n", - bbuf.pointer() + 54, - m_cylinders, - m_heads, - m_sectors_per_track); - - // Let's try to set up DMA transfers. - if (!m_pci_address.is_null()) { - m_prdt.end_of_table = 0x8000; - PCI::enable_bus_mastering(m_pci_address); - m_bus_master_base = PCI::get_BAR4(m_pci_address) & 0xfffc; - m_dma_buffer_page = MM.allocate_supervisor_physical_page(); - dbgprintf("PIIX Bus master IDE: I/O @ %x\n", m_bus_master_base); - } -} - -static void wait_400ns(u16 io_base) -{ - for (int i = 0; i < 4; ++i) - IO::in8(io_base + ATA_REG_ALTSTATUS); -} - -bool IDEDiskDevice::read_sectors_with_dma(u32 lba, u16 count, u8* outbuf) -{ - LOCKER(m_lock); -#ifdef DISK_DEBUG - dbgprintf("%s(%u): IDEDiskDevice::read_sectors_with_dma (%u x%u) -> %p\n", - current->process().name().characters(), - current->pid(), lba, count, outbuf); -#endif - - disable_irq(); - - m_prdt.offset = m_dma_buffer_page->paddr(); - m_prdt.size = 512 * count; - - ASSERT(m_prdt.size <= PAGE_SIZE); - - // Stop bus master - IO::out8(m_bus_master_base, 0); - - // Write the PRDT location - IO::out32(m_bus_master_base + 4, (u32)&m_prdt); - - // Turn on "Interrupt" and "Error" flag. The error flag should be cleared by hardware. - IO::out8(m_bus_master_base + 2, IO::in8(m_bus_master_base + 2) | 0x6); - - // Set transfer direction - IO::out8(m_bus_master_base, 0x8); - - m_interrupted = false; - enable_irq(); - - while (IO::in8(m_io_base + ATA_REG_STATUS) & ATA_SR_BSY) - ; - - u8 devsel = 0xe0; - if (is_slave()) - devsel |= 0x10; - - IO::out8(m_io_base + ATA_REG_CONTROL, 0); - IO::out8(m_io_base + ATA_REG_HDDEVSEL, devsel | (is_slave() << 4)); - wait_400ns(m_io_base); - - IO::out8(m_io_base + ATA_REG_FEATURES, 0); - - IO::out8(m_io_base + ATA_REG_SECCOUNT0, 0); - IO::out8(m_io_base + ATA_REG_LBA0, 0); - IO::out8(m_io_base + ATA_REG_LBA1, 0); - IO::out8(m_io_base + ATA_REG_LBA2, 0); - - IO::out8(m_io_base + ATA_REG_SECCOUNT0, count); - IO::out8(m_io_base + ATA_REG_LBA0, (lba & 0x000000ff) >> 0); - IO::out8(m_io_base + ATA_REG_LBA1, (lba & 0x0000ff00) >> 8); - IO::out8(m_io_base + ATA_REG_LBA2, (lba & 0x00ff0000) >> 16); - - for (;;) { - auto status = IO::in8(m_io_base + ATA_REG_STATUS); - if (!(status & ATA_SR_BSY) && (status & ATA_SR_DRDY)) - break; - } - - IO::out8(m_io_base + ATA_REG_COMMAND, ATA_CMD_READ_DMA_EXT); - wait_400ns(m_io_base); - - // Start bus master - IO::out8(m_bus_master_base, 0x9); - - wait_for_irq(); - disable_irq(); - - if (m_device_error) - return false; - - memcpy(outbuf, m_dma_buffer_page->paddr().as_ptr(), 512 * count); - - // I read somewhere that this may trigger a cache flush so let's do it. - IO::out8(m_bus_master_base + 2, IO::in8(m_bus_master_base + 2) | 0x6); - return true; -} - -bool IDEDiskDevice::read_sectors(u32 start_sector, u16 count, u8* outbuf) -{ - ASSERT(count <= 256); - LOCKER(m_lock); -#ifdef DISK_DEBUG - dbgprintf("%s: Disk::read_sectors request (%u sector(s) @ %u)\n", - current->process().name().characters(), - count, - start_sector); -#endif - disable_irq(); - - while (IO::in8(m_io_base + ATA_REG_STATUS) & ATA_SR_BSY) - ; - -#ifdef DISK_DEBUG - kprintf("IDEDiskDevice: Reading %u sector(s) @ LBA %u\n", count, start_sector); -#endif - - u8 devsel = 0xe0; - if (is_slave()) - devsel |= 0x10; - - IO::out8(m_io_base + ATA_REG_SECCOUNT0, count == 256 ? 0 : LSB(count)); - IO::out8(m_io_base + ATA_REG_LBA0, start_sector & 0xff); - IO::out8(m_io_base + ATA_REG_LBA1, (start_sector >> 8) & 0xff); - IO::out8(m_io_base + ATA_REG_LBA2, (start_sector >> 16) & 0xff); - IO::out8(m_io_base + ATA_REG_HDDEVSEL, devsel | ((start_sector >> 24) & 0xf)); - - IO::out8(0x3F6, 0x08); - while (!(IO::in8(m_io_base + ATA_REG_STATUS) & ATA_SR_DRDY)) - ; - - IO::out8(m_io_base + ATA_REG_COMMAND, ATA_CMD_READ_PIO); - m_interrupted = false; - enable_irq(); - wait_for_irq(); - - if (m_device_error) - return false; - - u8 status = IO::in8(m_io_base + ATA_REG_STATUS); - ASSERT(status & ATA_SR_DRQ); -#ifdef DISK_DEBUG - kprintf("Retrieving %u bytes (status=%b), outbuf=%p...\n", count * 512, status, outbuf); -#endif - - IO::repeated_in16(m_io_base + ATA_REG_DATA, outbuf, count * 256); - return true; -} - -bool IDEDiskDevice::write_sectors_with_dma(u32 lba, u16 count, const u8* inbuf) -{ - LOCKER(m_lock); -#ifdef DISK_DEBUG - dbgprintf("%s(%u): IDEDiskDevice::write_sectors_with_dma (%u x%u) <- %p\n", - current->process().name().characters(), - current->pid(), lba, count, inbuf); -#endif - - disable_irq(); - - m_prdt.offset = m_dma_buffer_page->paddr(); - m_prdt.size = 512 * count; - - memcpy(m_dma_buffer_page->paddr().as_ptr(), inbuf, 512 * count); - - ASSERT(m_prdt.size <= PAGE_SIZE); - - // Stop bus master - IO::out8(m_bus_master_base, 0); - - // Write the PRDT location - IO::out32(m_bus_master_base + 4, (u32)&m_prdt); - - // Turn on "Interrupt" and "Error" flag. The error flag should be cleared by hardware. - IO::out8(m_bus_master_base + 2, IO::in8(m_bus_master_base + 2) | 0x6); - - m_interrupted = false; - enable_irq(); - - while (IO::in8(m_io_base + ATA_REG_STATUS) & ATA_SR_BSY) - ; - - u8 devsel = 0xe0; - if (is_slave()) - devsel |= 0x10; - - IO::out8(m_io_base + ATA_REG_CONTROL, 0); - IO::out8(m_io_base + ATA_REG_HDDEVSEL, devsel | (is_slave() << 4)); - wait_400ns(m_io_base); - - IO::out8(m_io_base + ATA_REG_FEATURES, 0); - - IO::out8(m_io_base + ATA_REG_SECCOUNT0, 0); - IO::out8(m_io_base + ATA_REG_LBA0, 0); - IO::out8(m_io_base + ATA_REG_LBA1, 0); - IO::out8(m_io_base + ATA_REG_LBA2, 0); - - IO::out8(m_io_base + ATA_REG_SECCOUNT0, count); - IO::out8(m_io_base + ATA_REG_LBA0, (lba & 0x000000ff) >> 0); - IO::out8(m_io_base + ATA_REG_LBA1, (lba & 0x0000ff00) >> 8); - IO::out8(m_io_base + ATA_REG_LBA2, (lba & 0x00ff0000) >> 16); - - for (;;) { - auto status = IO::in8(m_io_base + ATA_REG_STATUS); - if (!(status & ATA_SR_BSY) && (status & ATA_SR_DRDY)) - break; - } - - IO::out8(m_io_base + ATA_REG_COMMAND, ATA_CMD_WRITE_DMA_EXT); - wait_400ns(m_io_base); - - // Start bus master - IO::out8(m_bus_master_base, 0x1); - - wait_for_irq(); - disable_irq(); - - if (m_device_error) - return false; - - // I read somewhere that this may trigger a cache flush so let's do it. - IO::out8(m_bus_master_base + 2, IO::in8(m_bus_master_base + 2) | 0x6); - return true; -} - -bool IDEDiskDevice::write_sectors(u32 start_sector, u16 count, const u8* data) -{ - ASSERT(count <= 256); - LOCKER(m_lock); -#ifdef DISK_DEBUG - dbgprintf("%s(%u): IDEDiskDevice::write_sectors request (%u sector(s) @ %u)\n", - current->process().name().characters(), - current->pid(), - count, - start_sector); -#endif - disable_irq(); - - while (IO::in8(m_io_base + ATA_REG_STATUS) & ATA_SR_BSY) - ; - - //dbgprintf("IDEDiskDevice: Writing %u sector(s) @ LBA %u\n", count, start_sector); - - u8 devsel = 0xe0; - if (is_slave()) - devsel |= 0x10; - - IO::out8(m_io_base + ATA_REG_SECCOUNT0, count == 256 ? 0 : LSB(count)); - IO::out8(m_io_base + ATA_REG_LBA0, start_sector & 0xff); - IO::out8(m_io_base + ATA_REG_LBA1, (start_sector >> 8) & 0xff); - IO::out8(m_io_base + ATA_REG_LBA2, (start_sector >> 16) & 0xff); - IO::out8(m_io_base + ATA_REG_HDDEVSEL, devsel | ((start_sector >> 24) & 0xf)); - - IO::out8(0x3F6, 0x08); - - IO::out8(m_io_base + ATA_REG_COMMAND, ATA_CMD_WRITE_PIO); - - while (!(IO::in8(m_io_base + ATA_REG_STATUS) & ATA_SR_DRQ)) - ; - - u8 status = IO::in8(m_io_base + ATA_REG_STATUS); - ASSERT(status & ATA_SR_DRQ); - IO::repeated_out16(m_io_base + ATA_REG_DATA, data, count * 256); - - m_interrupted = false; - enable_irq(); - wait_for_irq(); - - disable_irq(); - IO::out8(m_io_base + ATA_REG_COMMAND, ATA_CMD_CACHE_FLUSH); - while (IO::in8(m_io_base + ATA_REG_STATUS) & ATA_SR_BSY) - ; - m_interrupted = false; - enable_irq(); - wait_for_irq(); - - return !m_device_error; -} - -bool IDEDiskDevice::is_slave() const -{ - return m_drive_type == DriveType::SLAVE; -} diff --git a/Kernel/Devices/IDEDiskDevice.h b/Kernel/Devices/IDEDiskDevice.h deleted file mode 100644 index 5c55cee5bc..0000000000 --- a/Kernel/Devices/IDEDiskDevice.h +++ /dev/null @@ -1,75 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -struct PhysicalRegionDescriptor { - PhysicalAddress offset; - u16 size { 0 }; - u16 end_of_table { 0 }; -}; - -class IDEDiskDevice final : public IRQHandler - , public DiskDevice { - AK_MAKE_ETERNAL -public: - - // Type of drive this IDEDiskDevice is on the ATA channel. - // - // Each PATA channel can contain only two devices, which (I think) are - // jumper selectable on the drive itself by shorting two pins. - enum class DriveType : u8 { - MASTER, - SLAVE - }; - -public: - static NonnullRefPtr create(DriveType type); - virtual ~IDEDiskDevice() override; - - // ^DiskDevice - virtual unsigned block_size() const override; - virtual bool read_block(unsigned index, u8*) const override; - virtual bool write_block(unsigned index, const u8*) override; - virtual bool read_blocks(unsigned index, u16 count, u8*) override; - virtual bool write_blocks(unsigned index, u16 count, const u8*) override; - -protected: - explicit IDEDiskDevice(DriveType); - -private: - // ^IRQHandler - virtual void handle_irq() override; - - // ^DiskDevice - virtual const char* class_name() const override; - - void initialize(); - bool wait_for_irq(); - bool read_sectors_with_dma(u32 lba, u16 count, u8*); - bool write_sectors_with_dma(u32 lba, u16 count, const u8*); - bool read_sectors(u32 lba, u16 count, u8* buffer); - bool write_sectors(u32 lba, u16 count, const u8* data); - - bool is_slave() const; - - Lock m_lock { "IDEDiskDevice" }; - u16 m_cylinders { 0 }; - u16 m_heads { 0 }; - u16 m_sectors_per_track { 0 }; - u16 m_io_base { 0 }; - volatile bool m_interrupted { false }; - volatile u8 m_device_error { 0 }; - - DriveType m_drive_type { DriveType::MASTER }; - PCI::Address m_pci_address; - PhysicalRegionDescriptor m_prdt; - RefPtr m_dma_buffer_page; - u16 m_bus_master_base { 0 }; - Lockable m_dma_enabled; -}; diff --git a/Kernel/Devices/PATAChannel.cpp b/Kernel/Devices/PATAChannel.cpp new file mode 100644 index 0000000000..8847505636 --- /dev/null +++ b/Kernel/Devices/PATAChannel.cpp @@ -0,0 +1,507 @@ +#include "PATADiskDevice.h" +#include +#include +#include +#include +#include +#include + +#define PATA_PRIMARY_IRQ 14 +#define PATA_SECONDARY_IRQ 15 + +//#define PATA_DEBUG + +#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_CORR 0x04 +#define ATA_SR_IDX 0x02 +#define ATA_SR_ERR 0x01 + +#define ATA_ER_BBK 0x80 +#define ATA_ER_UNC 0x40 +#define ATA_ER_MC 0x20 +#define ATA_ER_IDNF 0x10 +#define ATA_ER_MCR 0x08 +#define ATA_ER_ABRT 0x04 +#define ATA_ER_TK0NF 0x02 +#define ATA_ER_AMNF 0x01 + +#define ATA_CMD_READ_PIO 0x20 +#define ATA_CMD_READ_PIO_EXT 0x24 +#define ATA_CMD_READ_DMA 0xC8 +#define ATA_CMD_READ_DMA_EXT 0x25 +#define ATA_CMD_WRITE_PIO 0x30 +#define ATA_CMD_WRITE_PIO_EXT 0x34 +#define ATA_CMD_WRITE_DMA 0xCA +#define ATA_CMD_WRITE_DMA_EXT 0x35 +#define ATA_CMD_CACHE_FLUSH 0xE7 +#define ATA_CMD_CACHE_FLUSH_EXT 0xEA +#define ATA_CMD_PACKET 0xA0 +#define ATA_CMD_IDENTIFY_PACKET 0xA1 +#define ATA_CMD_IDENTIFY 0xEC + +#define ATAPI_CMD_READ 0xA8 +#define ATAPI_CMD_EJECT 0x1B + +#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_CAPABILITIES 98 +#define ATA_IDENT_FIELDVALID 106 +#define ATA_IDENT_MAX_LBA 120 +#define ATA_IDENT_COMMANDSETS 164 +#define ATA_IDENT_MAX_LBA_EXT 200 + +#define IDE_ATA 0x00 +#define IDE_ATAPI 0x01 + +#define ATA_REG_DATA 0x00 +#define ATA_REG_ERROR 0x01 +#define ATA_REG_FEATURES 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_REG_SECCOUNT1 0x08 +#define ATA_REG_LBA3 0x09 +#define ATA_REG_LBA4 0x0A +#define ATA_REG_LBA5 0x0B +#define ATA_REG_CONTROL 0x0C +#define ATA_REG_ALTSTATUS 0x0C +#define ATA_REG_DEVADDRESS 0x0D + +static Lock& s_lock() +{ + static Lock* lock; + if (!lock) + lock = new Lock; + + return *lock; +}; + +OwnPtr PATAChannel::create(ChannelType type) +{ + return make(type); +} + +PATAChannel::PATAChannel(ChannelType type) + : IRQHandler((type == ChannelType::Primary ? PATA_PRIMARY_IRQ : PATA_SECONDARY_IRQ)) + , m_channel_number((type == ChannelType::Primary ? 0 : 1)) + , m_io_base((type == ChannelType::Primary ? 0x1F0 : 0x170)) +{ + m_dma_enabled.resource() = true; + ProcFS::the().add_sys_bool("ide_dma", m_dma_enabled); + + initialize(); + detect_disks(); +} + +PATAChannel::~PATAChannel() +{ +} + +void PATAChannel::initialize() +{ + static const PCI::ID piix3_ide_id = { 0x8086, 0x7010 }; + static const PCI::ID piix4_ide_id = { 0x8086, 0x7111 }; + PCI::enumerate_all([this](const PCI::Address& address, PCI::ID id) { + if (id == piix3_ide_id || id == piix4_ide_id) { + m_pci_address = address; + kprintf("PIIX%u PATA Controller found!\n", id == piix3_ide_id ? 3 : 4); + } + }); + + // Let's try to set up DMA transfers. + if (!m_pci_address.is_null()) { + m_prdt.end_of_table = 0x8000; + PCI::enable_bus_mastering(m_pci_address); + m_bus_master_base = PCI::get_BAR4(m_pci_address) & 0xfffc; + m_dma_buffer_page = MM.allocate_supervisor_physical_page(); + dbgprintf("PIIX Bus master IDE: I/O @ %x\n", m_bus_master_base); + } +} + +static void print_ide_status(u8 status) +{ + kprintf("DRQ=%u BSY=%u DRDY=%u DSC=%u DF=%u CORR=%u IDX=%u ERR=%u\n", + (status & ATA_SR_DRQ) != 0, + (status & ATA_SR_BSY) != 0, + (status & ATA_SR_DRDY) != 0, + (status & ATA_SR_DSC) != 0, + (status & ATA_SR_DF) != 0, + (status & ATA_SR_CORR) != 0, + (status & ATA_SR_IDX) != 0, + (status & ATA_SR_ERR) != 0); +} + +bool PATAChannel::wait_for_irq() +{ +#ifdef PATA_DEBUG + kprintf("PATA: waiting for IRQ %d...\n", irq_number()); +#endif + while (!m_interrupted) { + // FIXME: Put this process into a Blocked state instead, it's stupid to wake up just to check a flag. + Scheduler::yield(); + } +#ifdef PATA_DEBUG + kprintf("PATA: received IRQ %d!\n", irq_number()); +#endif + + return true; +} + +void PATAChannel::handle_irq() +{ + u8 status = IO::in8(m_io_base + ATA_REG_STATUS); + if (status & ATA_SR_ERR) { + print_ide_status(status); + m_device_error = IO::in8(m_io_base + ATA_REG_ERROR); + kprintf("PATADiskDevice: Error %b!\n", m_device_error); + } else { + m_device_error = 0; + } +#ifdef PATA_DEBUG + kprintf("disk:interrupt: DRQ=%u BSY=%u DRDY=%u\n", (status & ATA_SR_DRQ) != 0, (status & ATA_SR_BSY) != 0, (status & ATA_SR_DRDY) != 0); +#endif + m_interrupted = true; +} + +static void wait_400ns(u16 io_base) +{ + for (int i = 0; i < 4; ++i) + IO::in8(io_base + ATA_REG_ALTSTATUS); +} + +void PATAChannel::detect_disks() +{ + // There are only two possible disks connected to a channel + for (auto i = 0; i < 2; i++) { + enable_irq(); + + IO::out8(m_io_base + ATA_REG_HDDEVSEL, 0xA0 | (i << 4)); // First, we need to select the drive itself + + // Apparently these need to be 0 before sending IDENTIFY?! + IO::out8(m_io_base + ATA_REG_SECCOUNT0, 0x00); + IO::out8(m_io_base + ATA_REG_LBA0, 0x00); + IO::out8(m_io_base + ATA_REG_LBA1, 0x00); + IO::out8(m_io_base + ATA_REG_LBA2, 0x00); + + IO::out8(m_io_base + ATA_REG_COMMAND, ATA_CMD_IDENTIFY); // Send the ATA_IDENTIFY command + + // Wait for the BSY flag to be reset + while (IO::in8(m_io_base + ATA_REG_STATUS) & ATA_SR_BSY) + ; + + if (IO::in8(m_io_base + ATA_REG_STATUS) == 0x00) { +#ifdef PATA_DEBUG + kprintf("PATA: No %s disk detected!\n", (i == 0 ? "master" : "slave")); +#endif + continue; + } + + ByteBuffer wbuf = ByteBuffer::create_uninitialized(512); + ByteBuffer bbuf = ByteBuffer::create_uninitialized(512); + u8* b = bbuf.pointer(); + u16* w = (u16*)wbuf.pointer(); + const u16* wbufbase = (u16*)wbuf.pointer(); + + for (u32 i = 0; i < 256; ++i) { + u16 data = IO::in16(m_io_base + ATA_REG_DATA); + *(w++) = data; + *(b++) = MSB(data); + *(b++) = LSB(data); + } + + // "Unpad" the device name string. + for (u32 i = 93; i > 54 && bbuf[i] == ' '; --i) + bbuf[i] = 0; + + u8 cyls = wbufbase[1]; + u8 heads = wbufbase[3]; + u8 spt = wbufbase[6]; + + kprintf( + "PATADiskDevice: Name=\"%s\", C/H/Spt=%u/%u/%u\n", + bbuf.pointer() + 54, + cyls, + heads, + spt); + + if (i == 0) { + m_master = PATADiskDevice::create(*this, PATADiskDevice::DriveType::Master); + m_master->set_drive_geometry(cyls, heads, spt); + } else { + m_slave = PATADiskDevice::create(*this, PATADiskDevice::DriveType::Slave); + m_slave->set_drive_geometry(cyls, heads, spt); + } + } +} + +bool PATAChannel::ata_read_sectors_with_dma(u32 lba, u16 count, u8* outbuf, bool slave_request) +{ + LOCKER(s_lock()); +#ifdef PATA_DEBUG + dbgprintf("%s(%u): PATADiskDevice::read_sectors_with_dma (%u x%u) -> %p\n", + current->process().name().characters(), + current->pid(), lba, count, outbuf); +#endif + + disable_irq(); + + m_prdt.offset = m_dma_buffer_page->paddr(); + m_prdt.size = 512 * count; + + ASSERT(m_prdt.size <= PAGE_SIZE); + + // Stop bus master + IO::out8(m_bus_master_base, 0); + + // Write the PRDT location + IO::out32(m_bus_master_base + 4, (u32)&m_prdt); + + // Turn on "Interrupt" and "Error" flag. The error flag should be cleared by hardware. + IO::out8(m_bus_master_base + 2, IO::in8(m_bus_master_base + 2) | 0x6); + + // Set transfer direction + IO::out8(m_bus_master_base, 0x8); + + m_interrupted = false; + enable_irq(); + + while (IO::in8(m_io_base + ATA_REG_STATUS) & ATA_SR_BSY) + ; + + u8 devsel = 0xe0; + if (slave_request) + devsel |= 0x10; + + IO::out8(m_io_base + ATA_REG_CONTROL, 0); + IO::out8(m_io_base + ATA_REG_HDDEVSEL, devsel | (static_cast(slave_request) << 4)); + wait_400ns(m_io_base); + + IO::out8(m_io_base + ATA_REG_FEATURES, 0); + + IO::out8(m_io_base + ATA_REG_SECCOUNT0, 0); + IO::out8(m_io_base + ATA_REG_LBA0, 0); + IO::out8(m_io_base + ATA_REG_LBA1, 0); + IO::out8(m_io_base + ATA_REG_LBA2, 0); + + IO::out8(m_io_base + ATA_REG_SECCOUNT0, count); + IO::out8(m_io_base + ATA_REG_LBA0, (lba & 0x000000ff) >> 0); + IO::out8(m_io_base + ATA_REG_LBA1, (lba & 0x0000ff00) >> 8); + IO::out8(m_io_base + ATA_REG_LBA2, (lba & 0x00ff0000) >> 16); + + for (;;) { + auto status = IO::in8(m_io_base + ATA_REG_STATUS); + if (!(status & ATA_SR_BSY) && (status & ATA_SR_DRDY)) + break; + } + + IO::out8(m_io_base + ATA_REG_COMMAND, ATA_CMD_READ_DMA_EXT); + wait_400ns(m_io_base); + + // Start bus master + IO::out8(m_bus_master_base, 0x9); + + wait_for_irq(); + disable_irq(); + + if (m_device_error) + return false; + + memcpy(outbuf, m_dma_buffer_page->paddr().as_ptr(), 512 * count); + + // I read somewhere that this may trigger a cache flush so let's do it. + IO::out8(m_bus_master_base + 2, IO::in8(m_bus_master_base + 2) | 0x6); + return true; +} + +bool PATAChannel::ata_write_sectors_with_dma(u32 lba, u16 count, const u8* inbuf, bool slave_request) +{ + LOCKER(s_lock()); +#ifdef PATA_DEBUG + dbgprintf("%s(%u): PATADiskDevice::write_sectors_with_dma (%u x%u) <- %p\n", + current->process().name().characters(), + current->pid(), lba, count, inbuf); +#endif + + disable_irq(); + + m_prdt.offset = m_dma_buffer_page->paddr(); + m_prdt.size = 512 * count; + + memcpy(m_dma_buffer_page->paddr().as_ptr(), inbuf, 512 * count); + + ASSERT(m_prdt.size <= PAGE_SIZE); + + // Stop bus master + IO::out8(m_bus_master_base, 0); + + // Write the PRDT location + IO::out32(m_bus_master_base + 4, (u32)&m_prdt); + + // Turn on "Interrupt" and "Error" flag. The error flag should be cleared by hardware. + IO::out8(m_bus_master_base + 2, IO::in8(m_bus_master_base + 2) | 0x6); + + m_interrupted = false; + enable_irq(); + + while (IO::in8(m_io_base + ATA_REG_STATUS) & ATA_SR_BSY) + ; + + u8 devsel = 0xe0; + if (slave_request) + devsel |= 0x10; + + IO::out8(m_io_base + ATA_REG_CONTROL, 0); + IO::out8(m_io_base + ATA_REG_HDDEVSEL, devsel | (static_cast(slave_request) << 4)); + wait_400ns(m_io_base); + + IO::out8(m_io_base + ATA_REG_FEATURES, 0); + + IO::out8(m_io_base + ATA_REG_SECCOUNT0, 0); + IO::out8(m_io_base + ATA_REG_LBA0, 0); + IO::out8(m_io_base + ATA_REG_LBA1, 0); + IO::out8(m_io_base + ATA_REG_LBA2, 0); + + IO::out8(m_io_base + ATA_REG_SECCOUNT0, count); + IO::out8(m_io_base + ATA_REG_LBA0, (lba & 0x000000ff) >> 0); + IO::out8(m_io_base + ATA_REG_LBA1, (lba & 0x0000ff00) >> 8); + IO::out8(m_io_base + ATA_REG_LBA2, (lba & 0x00ff0000) >> 16); + + for (;;) { + auto status = IO::in8(m_io_base + ATA_REG_STATUS); + if (!(status & ATA_SR_BSY) && (status & ATA_SR_DRDY)) + break; + } + + IO::out8(m_io_base + ATA_REG_COMMAND, ATA_CMD_WRITE_DMA_EXT); + wait_400ns(m_io_base); + + // Start bus master + IO::out8(m_bus_master_base, 0x1); + + wait_for_irq(); + disable_irq(); + + if (m_device_error) + return false; + + // I read somewhere that this may trigger a cache flush so let's do it. + IO::out8(m_bus_master_base + 2, IO::in8(m_bus_master_base + 2) | 0x6); + return true; +} + +bool PATAChannel::ata_read_sectors(u32 start_sector, u16 count, u8* outbuf, bool slave_request) +{ + ASSERT(count <= 256); + LOCKER(s_lock()); +#ifdef PATA_DEBUG + dbgprintf("%s: Disk::read_sectors request (%u sector(s) @ %u)\n", + current->process().name().characters(), + count, + start_sector); +#endif + disable_irq(); + + while (IO::in8(m_io_base + ATA_REG_STATUS) & ATA_SR_BSY) + ; + +#ifdef PATA_DEBUG + kprintf("PATADiskDevice: Reading %u sector(s) @ LBA %u\n", count, start_sector); +#endif + + u8 devsel = 0xe0; + if (slave_request) + devsel |= 0x10; + + IO::out8(m_io_base + ATA_REG_SECCOUNT0, count == 256 ? 0 : LSB(count)); + IO::out8(m_io_base + ATA_REG_LBA0, start_sector & 0xff); + IO::out8(m_io_base + ATA_REG_LBA1, (start_sector >> 8) & 0xff); + IO::out8(m_io_base + ATA_REG_LBA2, (start_sector >> 16) & 0xff); + IO::out8(m_io_base + ATA_REG_HDDEVSEL, devsel | ((start_sector >> 24) & 0xf)); + + IO::out8(0x3F6, 0x08); + while (!(IO::in8(m_io_base + ATA_REG_STATUS) & ATA_SR_DRDY)) + ; + + IO::out8(m_io_base + ATA_REG_COMMAND, ATA_CMD_READ_PIO); + m_interrupted = false; + enable_irq(); + wait_for_irq(); + + if (m_device_error) + return false; + + u8 status = IO::in8(m_io_base + ATA_REG_STATUS); + ASSERT(status & ATA_SR_DRQ); +#ifdef PATA_DEBUG + kprintf("Retrieving %u bytes (status=%b), outbuf=%p...\n", count * 512, status, outbuf); +#endif + + IO::repeated_in16(m_io_base + ATA_REG_DATA, outbuf, count * 256); + return true; +} + +bool PATAChannel::ata_write_sectors(u32 start_sector, u16 count, const u8* inbuf, bool slave_request) +{ + ASSERT(count <= 256); + LOCKER(s_lock()); +#ifdef PATA_DEBUG + dbgprintf("%s(%u): PATADiskDevice::write_sectors request (%u sector(s) @ %u)\n", + current->process().name().characters(), + current->pid(), + count, + start_sector); +#endif + disable_irq(); + + while (IO::in8(m_io_base + ATA_REG_STATUS) & ATA_SR_BSY) + ; + + //dbgprintf("PATADiskDevice: Writing %u sector(s) @ LBA %u\n", count, start_sector); + + u8 devsel = 0xe0; + if (slave_request) + devsel |= 0x10; + + IO::out8(m_io_base + ATA_REG_SECCOUNT0, count == 256 ? 0 : LSB(count)); + IO::out8(m_io_base + ATA_REG_LBA0, start_sector & 0xff); + IO::out8(m_io_base + ATA_REG_LBA1, (start_sector >> 8) & 0xff); + IO::out8(m_io_base + ATA_REG_LBA2, (start_sector >> 16) & 0xff); + IO::out8(m_io_base + ATA_REG_HDDEVSEL, devsel | ((start_sector >> 24) & 0xf)); + + IO::out8(0x3F6, 0x08); + + IO::out8(m_io_base + ATA_REG_COMMAND, ATA_CMD_WRITE_PIO); + + while (!(IO::in8(m_io_base + ATA_REG_STATUS) & ATA_SR_DRQ)) + ; + + u8 status = IO::in8(m_io_base + ATA_REG_STATUS); + ASSERT(status & ATA_SR_DRQ); + IO::repeated_out16(m_io_base + ATA_REG_DATA, inbuf, count * 256); + + m_interrupted = false; + enable_irq(); + wait_for_irq(); + + disable_irq(); + IO::out8(m_io_base + ATA_REG_COMMAND, ATA_CMD_CACHE_FLUSH); + while (IO::in8(m_io_base + ATA_REG_STATUS) & ATA_SR_BSY) + ; + m_interrupted = false; + enable_irq(); + wait_for_irq(); + + return !m_device_error; +} diff --git a/Kernel/Devices/PATAChannel.h b/Kernel/Devices/PATAChannel.h new file mode 100644 index 0000000000..8bf28adf1a --- /dev/null +++ b/Kernel/Devices/PATAChannel.h @@ -0,0 +1,72 @@ +// +// Parallel ATA (PATA) controller driver +// +// This driver describes a logical PATA Channel. Each channel can connect up to 2 +// IDE Hard Disk Drives. The drives themselves can be either the master drive (hd0) +// or the slave drive (hd1). +// +// More information about the ATA spec for PATA can be found here: +// ftp://ftp.seagate.com/acrobat/reference/111-1c.pdf +// +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +struct PhysicalRegionDescriptor { + PhysicalAddress offset; + u16 size { 0 }; + u16 end_of_table { 0 }; +}; + +class PATADiskDevice; +class PATAChannel final : public IRQHandler { + friend class PATADiskDevice; + AK_MAKE_ETERNAL +public: + enum class ChannelType : u8 { + Primary, + Secondary + }; + +public: + static OwnPtr create(ChannelType type); + explicit PATAChannel(ChannelType); + virtual ~PATAChannel() override; + + RefPtr master_device() { return m_master; }; + RefPtr slave_device() { return m_slave; }; + +private: + //^ IRQHandler + virtual void handle_irq() override; + + void initialize(); + void detect_disks(); + + bool wait_for_irq(); + bool ata_read_sectors_with_dma(u32, u16, u8*, bool); + bool ata_write_sectors_with_dma(u32, u16, const u8*, bool); + bool ata_read_sectors(u32, u16, u8*, bool); + bool ata_write_sectors(u32, u16, const u8*, bool); + + // Data members + u8 m_channel_number { 0 }; // Channel number. 0 = master, 1 = slave + u16 m_io_base { 0x1F0 }; + volatile u8 m_device_error { 0 }; + volatile bool m_interrupted { false }; + + PCI::Address m_pci_address; + PhysicalRegionDescriptor m_prdt; + RefPtr m_dma_buffer_page; + u16 m_bus_master_base { 0 }; + Lockable m_dma_enabled; + + RefPtr m_master; + RefPtr m_slave; +}; diff --git a/Kernel/Devices/PATADiskDevice.cpp b/Kernel/Devices/PATADiskDevice.cpp new file mode 100644 index 0000000000..69b50e08c2 --- /dev/null +++ b/Kernel/Devices/PATADiskDevice.cpp @@ -0,0 +1,164 @@ +#include "PATADiskDevice.h" +#include +#include +#include +#include +#include +#include + +//#define DISK_DEBUG + +#define IRQ_FIXED_DISK 14 + +#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_CORR 0x04 +#define ATA_SR_IDX 0x02 +#define ATA_SR_ERR 0x01 + +#define ATA_ER_BBK 0x80 +#define ATA_ER_UNC 0x40 +#define ATA_ER_MC 0x20 +#define ATA_ER_IDNF 0x10 +#define ATA_ER_MCR 0x08 +#define ATA_ER_ABRT 0x04 +#define ATA_ER_TK0NF 0x02 +#define ATA_ER_AMNF 0x01 + +#define ATA_CMD_READ_PIO 0x20 +#define ATA_CMD_READ_PIO_EXT 0x24 +#define ATA_CMD_READ_DMA 0xC8 +#define ATA_CMD_READ_DMA_EXT 0x25 +#define ATA_CMD_WRITE_PIO 0x30 +#define ATA_CMD_WRITE_PIO_EXT 0x34 +#define ATA_CMD_WRITE_DMA 0xCA +#define ATA_CMD_WRITE_DMA_EXT 0x35 +#define ATA_CMD_CACHE_FLUSH 0xE7 +#define ATA_CMD_CACHE_FLUSH_EXT 0xEA +#define ATA_CMD_PACKET 0xA0 +#define ATA_CMD_IDENTIFY_PACKET 0xA1 +#define ATA_CMD_IDENTIFY 0xEC + +#define ATAPI_CMD_READ 0xA8 +#define ATAPI_CMD_EJECT 0x1B + +#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_CAPABILITIES 98 +#define ATA_IDENT_FIELDVALID 106 +#define ATA_IDENT_MAX_LBA 120 +#define ATA_IDENT_COMMANDSETS 164 +#define ATA_IDENT_MAX_LBA_EXT 200 + +#define IDE_ATA 0x00 +#define IDE_ATAPI 0x01 + +#define ATA_REG_DATA 0x00 +#define ATA_REG_ERROR 0x01 +#define ATA_REG_FEATURES 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_REG_SECCOUNT1 0x08 +#define ATA_REG_LBA3 0x09 +#define ATA_REG_LBA4 0x0A +#define ATA_REG_LBA5 0x0B +#define ATA_REG_CONTROL 0x0C +#define ATA_REG_ALTSTATUS 0x0C +#define ATA_REG_DEVADDRESS 0x0D + +NonnullRefPtr PATADiskDevice::create(PATAChannel& channel, DriveType type) +{ + return adopt(*new PATADiskDevice(channel, type)); +} + +PATADiskDevice::PATADiskDevice(PATAChannel& channel, DriveType type) + : m_drive_type(type) + , m_channel(channel) +{ +} + +PATADiskDevice::~PATADiskDevice() +{ +} + +const char* PATADiskDevice::class_name() const +{ + return "PATADiskDevice"; +} + +unsigned PATADiskDevice::block_size() const +{ + return 512; +} + +bool PATADiskDevice::read_blocks(unsigned index, u16 count, u8* out) +{ + if (m_channel.m_bus_master_base && m_channel.m_dma_enabled.resource()) + return read_sectors_with_dma(index, count, out); + return read_sectors(index, count, out); +} + +bool PATADiskDevice::read_block(unsigned index, u8* out) const +{ + return const_cast(this)->read_blocks(index, 1, out); +} + +bool PATADiskDevice::write_blocks(unsigned index, u16 count, const u8* data) +{ + if (m_channel.m_bus_master_base && m_channel.m_dma_enabled.resource()) + return write_sectors_with_dma(index, count, data); + for (unsigned i = 0; i < count; ++i) { + if (!write_sectors(index + i, 1, data + i * 512)) + return false; + } + return true; +} + +bool PATADiskDevice::write_block(unsigned index, const u8* data) +{ + return write_blocks(index, 1, data); +} + +void PATADiskDevice::set_drive_geometry(u16 cyls, u16 heads, u16 spt) +{ + m_cylinders = cyls; + m_heads = heads; + m_sectors_per_track = spt; +} + +bool PATADiskDevice::read_sectors_with_dma(u32 lba, u16 count, u8* outbuf) +{ + return m_channel.ata_read_sectors_with_dma(lba, count, outbuf, is_slave()); +} + +bool PATADiskDevice::read_sectors(u32 start_sector, u16 count, u8* outbuf) +{ + return m_channel.ata_read_sectors(start_sector, count, outbuf, is_slave()); +} + +bool PATADiskDevice::write_sectors_with_dma(u32 lba, u16 count, const u8* inbuf) +{ + return m_channel.ata_write_sectors_with_dma(lba, count, inbuf, is_slave()); +} + +bool PATADiskDevice::write_sectors(u32 start_sector, u16 count, const u8* inbuf) +{ + return m_channel.ata_write_sectors(start_sector, count, inbuf, is_slave()); +} + +bool PATADiskDevice::is_slave() const +{ + return m_drive_type == DriveType::Slave; +} diff --git a/Kernel/Devices/PATADiskDevice.h b/Kernel/Devices/PATADiskDevice.h new file mode 100644 index 0000000000..3d3c4928d4 --- /dev/null +++ b/Kernel/Devices/PATADiskDevice.h @@ -0,0 +1,62 @@ +// +// A Disk Device Connected to a PATA Channel +// +// +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +class PATADiskDevice final : public DiskDevice { + AK_MAKE_ETERNAL +public: + // Type of drive this IDEDiskDevice is on the ATA channel. + // + // Each PATA channel can contain only two devices, which (I think) are + // jumper selectable on the drive itself by shorting two pins. + enum class DriveType : u8 { + Master, + Slave + }; + +public: + static NonnullRefPtr create(PATAChannel&, DriveType); + virtual ~PATADiskDevice() override; + + // ^DiskDevice + virtual unsigned block_size() const override; + virtual bool read_block(unsigned index, u8*) const override; + virtual bool write_block(unsigned index, const u8*) override; + virtual bool read_blocks(unsigned index, u16 count, u8*) override; + virtual bool write_blocks(unsigned index, u16 count, const u8*) override; + + void set_drive_geometry(u16, u16, u16); + +protected: + explicit PATADiskDevice(PATAChannel&, DriveType); + +private: + // ^DiskDevice + virtual const char* class_name() const override; + + bool wait_for_irq(); + bool read_sectors_with_dma(u32 lba, u16 count, u8*); + bool write_sectors_with_dma(u32 lba, u16 count, const u8*); + bool read_sectors(u32 lba, u16 count, u8* buffer); + bool write_sectors(u32 lba, u16 count, const u8* data); + bool is_slave() const; + + Lock m_lock { "IDEDiskDevice" }; + u16 m_cylinders { 0 }; + u16 m_heads { 0 }; + u16 m_sectors_per_track { 0 }; + DriveType m_drive_type { DriveType::Master }; + + PATAChannel& m_channel; +}; diff --git a/Kernel/Makefile b/Kernel/Makefile index c302147db7..65325a1ad0 100644 --- a/Kernel/Makefile +++ b/Kernel/Makefile @@ -13,7 +13,8 @@ KERNEL_OBJS = \ CMOS.o \ Arch/i386/PIC.o \ Syscall.o \ - Devices/IDEDiskDevice.o \ + Devices/PATAChannel.o \ + Devices/PATADiskDevice.o \ Devices/FloppyDiskDevice.o \ VM/MemoryManager.o \ VM/Region.o \ diff --git a/Kernel/init.cpp b/Kernel/init.cpp index 1b7c243311..2b22bfe29f 100644 --- a/Kernel/init.cpp +++ b/Kernel/init.cpp @@ -1,3 +1,4 @@ +#include "Devices/PATADiskDevice.h" #include "KSyms.h" #include "Process.h" #include "RTC.h" @@ -13,13 +14,13 @@ #include #include #include -#include #include #include #include +#include #include -#include #include +#include #include #include #include @@ -68,9 +69,8 @@ VFS* vfs; hang(); } - auto dev_hd0 = IDEDiskDevice::create(IDEDiskDevice::DriveType::MASTER); - - NonnullRefPtr root_dev = dev_hd0; + auto pata0 = PATAChannel::create(PATAChannel::ChannelType::Primary); + NonnullRefPtr root_dev = *pata0->master_device(); root = root.substring(strlen("/dev/hda"), root.length() - strlen("/dev/hda")); -- cgit v1.2.3