// Copyright (c) 2023-2026 Chris (boreddevnl) // This software is released under the GNU General Public License v3.0. See LICENSE file for details. // This header needs to maintain in any file it is present in, as per the GPL license terms. #include "ahci.h" #include "pci.h" #include "disk.h" #include "memory_manager.h" #include "paging.h" #include "io.h" #include #include "../sys/spinlock.h" extern void serial_write(const char *str); extern void serial_write_num(uint64_t num); extern void serial_write_hex(uint32_t val); // ============================================================================ // AHCI Driver State // ============================================================================ static HBA_MEM *abar = NULL; // MMIO-mapped AHCI Base Address static bool ahci_initialized = false; static int active_port_count = 0; #define MAX_AHCI_PORTS 32 typedef struct { bool active; int port_num; HBA_PORT *port; HBA_CMD_HEADER *cmd_list; // 1KB, 1KB aligned void *fis_base; // 256B, 256B aligned HBA_CMD_TBL *cmd_tbl; // Command table for slot 0 spinlock_t lock; // Port-level lock for thread-safety } ahci_port_state_t; static ahci_port_state_t ports[MAX_AHCI_PORTS]; // ============================================================================ // String Helpers // ============================================================================ static void ahci_strcpy(char *d, const char *s) { while ((*d++ = *s++)); } // Kernel virtual to physical address conversion extern uint64_t v2p(uint64_t vaddr); extern uint64_t p2v(uint64_t paddr); static int ahci_disk_sync(Disk *disk); static int ahci_find_free_slot(HBA_PORT *port); static void ahci_stop_cmd(HBA_PORT *port) { // Clear ST (Start) port->cmd &= ~HBA_PORT_CMD_ST; // Clear FRE (FIS Receive Enable) port->cmd &= ~HBA_PORT_CMD_FRE; // Wait until FR and CR clear int timeout = 500000; while (timeout-- > 0) { if (port->cmd & HBA_PORT_CMD_FR) continue; if (port->cmd & HBA_PORT_CMD_CR) continue; break; } } static void ahci_start_cmd(HBA_PORT *port) { // Wait until CR clears while (port->cmd & HBA_PORT_CMD_CR); // Set FRE and ST port->cmd |= HBA_PORT_CMD_FRE; port->cmd |= HBA_PORT_CMD_ST; } static int ahci_check_port_type(HBA_PORT *port) { uint32_t ssts = port->ssts; uint8_t ipm = (ssts >> 8) & 0x0F; uint8_t det = ssts & 0x0F; if (det != 3) return -1; // No device detected if (ipm != 1) return -1; // Not in active state switch (port->sig) { case SATA_SIG_ATA: return 0; // SATA drive case SATA_SIG_ATAPI: return 1; // SATAPI drive case SATA_SIG_SEMB: return 2; // SEMB case SATA_SIG_PM: return 3; // Port multiplier default: return -1; } } static void ahci_port_rebase(ahci_port_state_t *ps) { HBA_PORT *port = ps->port; ahci_stop_cmd(port); // Allocate command list (1KB, 1024-byte aligned) ps->cmd_list = (HBA_CMD_HEADER*)kmalloc_aligned(1024, 1024); if (!ps->cmd_list) return; mem_memset(ps->cmd_list, 0, 1024); uint64_t clb_phys = v2p((uint64_t)ps->cmd_list); port->clb = (uint32_t)(clb_phys & 0xFFFFFFFF); port->clbu = (uint32_t)(clb_phys >> 32); // Allocate FIS receive area (256 bytes, 256-byte aligned) ps->fis_base = kmalloc_aligned(256, 256); if (!ps->fis_base) return; mem_memset(ps->fis_base, 0, 256); uint64_t fb_phys = v2p((uint64_t)ps->fis_base); port->fb = (uint32_t)(fb_phys & 0xFFFFFFFF); port->fbu = (uint32_t)(fb_phys >> 32); int cmd_tbl_size = sizeof(HBA_CMD_TBL) + 32 * sizeof(HBA_PRDT_ENTRY); ps->cmd_tbl = (HBA_CMD_TBL*)kmalloc_aligned(cmd_tbl_size, 256); if (!ps->cmd_tbl) return; mem_memset(ps->cmd_tbl, 0, cmd_tbl_size); // Set command header 0 to point to our command table uint64_t ctba_phys = v2p((uint64_t)ps->cmd_tbl); ps->cmd_list[0].ctba = (uint32_t)(ctba_phys & 0xFFFFFFFF); ps->cmd_list[0].ctbau = (uint32_t)(ctba_phys >> 32); ps->cmd_list[0].prdtl = 1; // 1 PRDT entry default // Clear error and interrupt status port->serr = 0xFFFFFFFF; port->is = 0xFFFFFFFF; ahci_start_cmd(port); } static int ahci_find_free_slot(HBA_PORT *port) { uint32_t slots = (port->sact | port->ci); for (int i = 0; i < 32; i++) { if (!(slots & (1 << i))) return i; } return -1; } static int ahci_identify(int port_num, uint32_t *sectors, char *model) { ahci_port_state_t *ps = &ports[port_num]; HBA_PORT *port = ps->port; uint64_t rflags = spinlock_acquire_irqsave(&ps->lock); port->is = 0xFFFFFFFF; int slot = ahci_find_free_slot(port); if (slot < 0) { spinlock_release_irqrestore(&ps->lock, rflags); return -1; } HBA_CMD_HEADER *cmd_hdr = &ps->cmd_list[slot]; cmd_hdr->cfl = sizeof(FIS_REG_H2D) / sizeof(uint32_t); cmd_hdr->w = 0; cmd_hdr->prdtl = 1; HBA_CMD_TBL *cmd_tbl = ps->cmd_tbl; mem_memset(cmd_tbl, 0, sizeof(HBA_CMD_TBL) + 32 * sizeof(HBA_PRDT_ENTRY)); uint16_t *buf = (uint16_t*)kmalloc_aligned(512, 512); uint64_t phys = v2p((uint64_t)buf); cmd_tbl->prdt[0].dba = (uint32_t)(phys & 0xFFFFFFFF); cmd_tbl->prdt[0].dbau = (uint32_t)(phys >> 32); cmd_tbl->prdt[0].dbc = 511; // 512 bytes cmd_tbl->prdt[0].i = 1; FIS_REG_H2D *fis = (FIS_REG_H2D*)(&cmd_tbl->cfis); fis->fis_type = FIS_TYPE_REG_H2D; fis->c = 1; // Command fis->command = 0xEC; // IDENTIFY DEVICE // Wait for port to be idle int timeout = 1000000; while ((port->tfd & (ATA_SR_BSY | ATA_SR_DRQ)) && --timeout > 0); if (timeout <= 0) { kfree(buf); spinlock_release_irqrestore(&ps->lock, rflags); return -1; } port->ci = (1 << slot); while (1) { if ((port->ci & (1 << slot)) == 0) break; if (port->is & HBA_PORT_IS_TFES) { kfree(buf); spinlock_release_irqrestore(&ps->lock, rflags); return -1; } } // Extract sectors (28-bit LBA for now, or 48-bit if supported) uint32_t s28 = *((uint32_t*)&buf[60]); uint64_t s48 = *((uint64_t*)&buf[100]); if (s48 > 0) *sectors = (uint32_t)s48; else *sectors = s28; // Extract model name (Words 27-46, 40 bytes, big-endian shorts) for (int i = 0; i < 20; i++) { model[i*2] = (char)(buf[27+i] >> 8); model[i*2+1] = (char)(buf[27+i] & 0xFF); } model[40] = 0; // Swap bytes in model string (ATA strings are byte-swapped) for (int i = 0; i < 40; i += 2) { char tmp = model[i]; model[i] = model[i+1]; model[i+1] = tmp; } kfree(buf); spinlock_release_irqrestore(&ps->lock, rflags); return 0; } int ahci_read_sectors(int port_num, uint64_t lba, uint32_t count, uint8_t *buffer) { if (!ahci_initialized || port_num < 0 || port_num >= MAX_AHCI_PORTS) return -1; ahci_port_state_t *ps = &ports[port_num]; if (!ps->active) return -1; uint64_t rflags = spinlock_acquire_irqsave(&ps->lock); HBA_PORT *port = ps->port; // Clear any pending interrupts/errors port->is = 0xFFFFFFFF; int slot = ahci_find_free_slot(port); if (slot < 0) return -1; HBA_CMD_HEADER *cmd_hdr = &ps->cmd_list[slot]; cmd_hdr->cfl = sizeof(FIS_REG_H2D) / sizeof(uint32_t); cmd_hdr->w = 0; // Read cmd_hdr->prdtl = 1; HBA_CMD_TBL *cmd_tbl = ps->cmd_tbl; mem_memset(cmd_tbl, 0, sizeof(HBA_CMD_TBL) + 32 * sizeof(HBA_PRDT_ENTRY)); extern uint64_t paging_get_pml4_phys(void); extern uint64_t paging_virt2phys(uint64_t pml4_phys, uint64_t virtual_addr); uint64_t pml4 = paging_get_pml4_phys(); uint64_t buf_addr = (uint64_t)buffer; uint32_t remaining = count * 512; int prd_idx = 0; while (remaining > 0 && prd_idx < 32) { uint64_t phys = paging_virt2phys(pml4, buf_addr); if (!phys) { spinlock_release_irqrestore(&ps->lock, rflags); return -1; } uint32_t offset = buf_addr & 0xFFF; uint32_t can_do = 4096 - offset; if (can_do > remaining) can_do = remaining; cmd_tbl->prdt[prd_idx].dba = (uint32_t)(phys & 0xFFFFFFFF); cmd_tbl->prdt[prd_idx].dbau = (uint32_t)(phys >> 32); cmd_tbl->prdt[prd_idx].dbc = can_do - 1; // 0-based cmd_tbl->prdt[prd_idx].i = 0; buf_addr += can_do; remaining -= can_do; prd_idx++; } if (prd_idx > 0) cmd_tbl->prdt[prd_idx - 1].i = 1; // Interrupt on last cmd_hdr->prdtl = prd_idx; // Setup Command FIS FIS_REG_H2D *fis = (FIS_REG_H2D*)&cmd_tbl->cfis; fis->fis_type = FIS_TYPE_REG_H2D; fis->c = 1; // Command fis->command = ATA_CMD_READ_DMA_EX; fis->lba0 = (uint8_t)(lba); fis->lba1 = (uint8_t)(lba >> 8); fis->lba2 = (uint8_t)(lba >> 16); fis->device = 1 << 6; // LBA mode fis->lba3 = (uint8_t)(lba >> 24); fis->lba4 = (uint8_t)(lba >> 32); fis->lba5 = (uint8_t)(lba >> 40); fis->countl = (uint8_t)(count); fis->counth = (uint8_t)(count >> 8); // Issue command port->ci = (1 << slot); // Wait for completion int timeout = 1000000; while (timeout-- > 0) { if (!(port->ci & (1 << slot))) break; if (port->is & (1 << 30)) { // Task File Error serial_write("\n"); spinlock_release_irqrestore(&ps->lock, rflags); return -1; } } if (timeout <= 0) { serial_write("[AHCI] Read timeout on port "); serial_write_num(port_num); serial_write("\n"); spinlock_release_irqrestore(&ps->lock, rflags); return -1; } spinlock_release_irqrestore(&ps->lock, rflags); return 0; } int ahci_write_sectors(int port_num, uint64_t lba, uint32_t count, const uint8_t *buffer) { if (!ahci_initialized || port_num < 0 || port_num >= MAX_AHCI_PORTS) return -1; ahci_port_state_t *ps = &ports[port_num]; if (!ps->active) return -1; uint64_t rflags = spinlock_acquire_irqsave(&ps->lock); HBA_PORT *port = ps->port; port->is = 0xFFFFFFFF; int slot = ahci_find_free_slot(port); if (slot < 0) return -1; HBA_CMD_HEADER *cmd_hdr = &ps->cmd_list[slot]; cmd_hdr->cfl = sizeof(FIS_REG_H2D) / sizeof(uint32_t); cmd_hdr->w = 1; // Write cmd_hdr->prdtl = 1; HBA_CMD_TBL *cmd_tbl = ps->cmd_tbl; mem_memset(cmd_tbl, 0, sizeof(HBA_CMD_TBL) + 32 * sizeof(HBA_PRDT_ENTRY)); // Setup PRDT - handle buffers spanning multiple physical pages extern uint64_t paging_get_pml4_phys(void); extern uint64_t paging_virt2phys(uint64_t pml4_phys, uint64_t virtual_addr); uint64_t pml4 = paging_get_pml4_phys(); uint64_t buf_addr = (uint64_t)buffer; uint32_t remaining = count * 512; int prd_idx = 0; while (remaining > 0 && prd_idx < 32) { uint64_t phys = paging_virt2phys(pml4, buf_addr); if (!phys) { spinlock_release_irqrestore(&ps->lock, rflags); return -1; } uint32_t offset = buf_addr & 0xFFF; uint32_t can_do = 4096 - offset; if (can_do > remaining) can_do = remaining; cmd_tbl->prdt[prd_idx].dba = (uint32_t)(phys & 0xFFFFFFFF); cmd_tbl->prdt[prd_idx].dbau = (uint32_t)(phys >> 32); cmd_tbl->prdt[prd_idx].dbc = can_do - 1; // 0-based cmd_tbl->prdt[prd_idx].i = 0; buf_addr += can_do; remaining -= can_do; prd_idx++; } if (prd_idx > 0) cmd_tbl->prdt[prd_idx - 1].i = 1; // Interrupt on last cmd_hdr->prdtl = prd_idx; FIS_REG_H2D *fis = (FIS_REG_H2D*)&cmd_tbl->cfis; fis->fis_type = FIS_TYPE_REG_H2D; fis->c = 1; fis->command = ATA_CMD_WRITE_DMA_EX; fis->lba0 = (uint8_t)(lba); fis->lba1 = (uint8_t)(lba >> 8); fis->lba2 = (uint8_t)(lba >> 16); fis->device = 1 << 6; fis->lba3 = (uint8_t)(lba >> 24); fis->lba4 = (uint8_t)(lba >> 32); fis->lba5 = (uint8_t)(lba >> 40); fis->countl = (uint8_t)(count); fis->counth = (uint8_t)(count >> 8); port->ci = (1 << slot); int timeout = 1000000; while (timeout-- > 0) { if (!(port->ci & (1 << slot))) break; if (port->is & (1 << 30)) { serial_write("[AHCI] Write error on port "); serial_write_num(port_num); serial_write("\n"); spinlock_release_irqrestore(&ps->lock, rflags); return -1; } } if (timeout <= 0) { serial_write("[AHCI] Write timeout on port "); serial_write_num(port_num); serial_write("\n"); spinlock_release_irqrestore(&ps->lock, rflags); return -1; } spinlock_release_irqrestore(&ps->lock, rflags); return 0; } // ============================================================================ // AHCI Disk Integration — wrap AHCI into Disk read/write_sector // ============================================================================ typedef struct { int ahci_port; } AHCIDriverData; static int ahci_disk_read_sector(Disk *disk, uint32_t sector, uint8_t *buffer) { AHCIDriverData *data = (AHCIDriverData*)disk->driver_data; // For partitions, add offset and use parent's port if (disk->is_partition && disk->parent) { AHCIDriverData *pdata = (AHCIDriverData*)disk->parent->driver_data; return ahci_read_sectors(pdata->ahci_port, (uint64_t)sector + disk->partition_lba_offset, 1, buffer); } return ahci_read_sectors(data->ahci_port, (uint64_t)sector, 1, buffer); } static int ahci_disk_write_sector(Disk *disk, uint32_t sector, const uint8_t *buffer) { AHCIDriverData *data = (AHCIDriverData*)disk->driver_data; if (disk->is_partition && disk->parent) { AHCIDriverData *pdata = (AHCIDriverData*)disk->parent->driver_data; return ahci_write_sectors(pdata->ahci_port, (uint64_t)sector + disk->partition_lba_offset, 1, buffer); } return ahci_write_sectors(data->ahci_port, (uint64_t)sector, 1, buffer); } static int ahci_disk_read_sectors(Disk *disk, uint32_t sector, uint32_t count, uint8_t *buffer) { AHCIDriverData *data = (AHCIDriverData*)disk->driver_data; if (disk->is_partition && disk->parent) { AHCIDriverData *pdata = (AHCIDriverData*)disk->parent->driver_data; return ahci_read_sectors(pdata->ahci_port, (uint64_t)sector + disk->partition_lba_offset, count, buffer); } return ahci_read_sectors(data->ahci_port, (uint64_t)sector, count, buffer); } static int ahci_disk_write_sectors(Disk *disk, uint32_t sector, uint32_t count, const uint8_t *buffer) { AHCIDriverData *data = (AHCIDriverData*)disk->driver_data; if (disk->is_partition && disk->parent) { AHCIDriverData *pdata = (AHCIDriverData*)disk->parent->driver_data; return ahci_write_sectors(pdata->ahci_port, (uint64_t)sector + disk->partition_lba_offset, count, buffer); } return ahci_write_sectors(data->ahci_port, (uint64_t)sector, count, buffer); } // ============================================================================ // Initialization // ============================================================================ int ahci_get_port_count(void) { return active_port_count; } bool ahci_port_is_active(int port_num) { if (port_num < 0 || port_num >= MAX_AHCI_PORTS) return false; return ports[port_num].active; } void ahci_init(void) { serial_write("[AHCI] Scanning PCI for AHCI controller...\n"); // Find AHCI controller (Class 0x01, Subclass 0x06) pci_device_t pci_dev; if (!pci_find_device_by_class(PCI_CLASS_MASS_STORAGE, PCI_SUBCLASS_SATA, &pci_dev)) { serial_write("[AHCI] No AHCI controller found\n"); return; } serial_write("[AHCI] Found AHCI controller ("); serial_write("vendor=0x"); serial_write_hex(pci_dev.vendor_id); serial_write(", device=0x"); serial_write_hex(pci_dev.device_id); serial_write(")\n"); // Enable Bus Mastering and MMIO pci_enable_bus_mastering(&pci_dev); pci_enable_mmio(&pci_dev); // Read ABAR (BAR5) uint32_t abar_raw = pci_get_bar(&pci_dev, 5); uint64_t abar_phys = abar_raw & 0xFFFFF000; // Mask out lower bits if (abar_phys == 0) { serial_write("[AHCI] Invalid ABAR address\n"); return; } serial_write("[AHCI] ABAR physical address: 0x"); serial_write_hex((uint32_t)abar_phys); serial_write("\n"); uint64_t abar_virt = p2v(abar_phys); for (uint64_t offset = 0; offset < 0x2000; offset += 4096) { paging_map_page(paging_get_pml4_phys(), abar_virt + offset, abar_phys + offset, PT_PRESENT | PT_RW | PT_CACHE_DISABLE); } abar = (HBA_MEM*)abar_virt; // Enable AHCI mode abar->ghc |= (1 << 31); // AE (AHCI Enable) serial_write("[AHCI] Version: "); serial_write_num(abar->vs >> 16); serial_write("."); serial_write_num(abar->vs & 0xFFFF); serial_write("\n"); // Probe ports uint32_t pi = abar->pi; active_port_count = 0; for (int i = 0; i < 32; i++) { ports[i].active = false; HBA_PORT *port = &abar->ports[i]; ports[i].lock = SPINLOCK_INIT; int type = ahci_check_port_type(port); if (type == 0) { // SATA drive serial_write("[AHCI] Port "); serial_write_num(i); serial_write(": SATA drive detected\n"); ports[i].port_num = i; ports[i].port = port; ahci_port_rebase(&ports[i]); ports[i].active = true; active_port_count++; ahci_initialized = true; // Register as a block device Disk *disk = (Disk*)kmalloc(sizeof(Disk)); if (disk) { AHCIDriverData *drv = (AHCIDriverData*)kmalloc(sizeof(AHCIDriverData)); drv->ahci_port = i; disk->devname[0] = 0; // Auto-assign disk->type = DISK_TYPE_SATA; uint32_t sectors = 0; char model[64]; if (ahci_identify(i, §ors, model) == 0) { ahci_strcpy(disk->label, model); disk->total_sectors = sectors; } else { ahci_strcpy(disk->label, "SATA Drive"); disk->total_sectors = 0; } disk->read_sector = ahci_disk_read_sector; disk->write_sector = ahci_disk_write_sector; disk->read_sectors = ahci_disk_read_sectors; disk->write_sectors = ahci_disk_write_sectors; disk->sync = ahci_disk_sync; disk->driver_data = drv; disk->partition_lba_offset = 0; disk->parent = NULL; disk->is_partition = false; disk->is_fat32 = false; disk_register(disk); extern void serial_write(const char *str); extern int disk_rescan(Disk *disk); serial_write("[AHCI] Probing partitions on /dev/"); serial_write(disk->devname); serial_write("...\n"); disk_rescan(disk); } } else if (type == 1) { serial_write("[AHCI] Port "); serial_write_num(i); serial_write(": SATAPI drive (ignored)\n"); } } if (active_port_count > 0) { ahci_initialized = true; serial_write("[AHCI] Initialization complete: "); serial_write_num(active_port_count); serial_write(" SATA port(s) active\n"); } else { serial_write("[AHCI] No active SATA ports found\n"); } } int ahci_flush_cache(int port_num) { HBA_PORT *port = &abar->ports[port_num]; ahci_port_state_t *ps = &ports[port_num]; if (port_num < 0 || port_num >= 32 || !ps->active) return -1; uint64_t rflags = spinlock_acquire_irqsave(&ps->lock); port->is = 0xFFFFFFFF; // Clear interrupts int slot = ahci_find_free_slot(port); if (slot == -1) { spinlock_release_irqrestore(&ps->lock, rflags); return -1; } HBA_CMD_HEADER *cmd_header = (HBA_CMD_HEADER*)p2v(port->clb); cmd_header += slot; cmd_header->cfl = sizeof(FIS_REG_H2D) / 4; cmd_header->w = 0; cmd_header->prdtl = 0; HBA_CMD_TBL *cmd_tbl = (HBA_CMD_TBL*)p2v(cmd_header->ctba); for (int i = 0; i < 256; i++) ((uint8_t*)cmd_tbl)[i] = 0; FIS_REG_H2D *fis = (FIS_REG_H2D*)(&cmd_tbl->cfis); fis->fis_type = FIS_TYPE_REG_H2D; fis->c = 1; fis->command = 0xEA; // FLUSH CACHE EXT // Wait for port to be ready int timeout = 1000000; while ((port->tfd & (ATA_SR_BSY | ATA_SR_DRQ)) && --timeout > 0); if (timeout == 0) { spinlock_release_irqrestore(&ps->lock, rflags); return -1; } port->ci = (1 << slot); while (1) { if ((port->ci & (1 << slot)) == 0) break; if (port->is & HBA_PORT_IS_TFES) { spinlock_release_irqrestore(&ps->lock, rflags); return -1; } } spinlock_release_irqrestore(&ps->lock, rflags); return 0; } static int ahci_disk_sync(Disk *disk) { AHCIDriverData *drv = (AHCIDriverData*)disk->driver_data; if (!drv) return -1; return ahci_flush_cache(drv->ahci_port); }