Page 1 of 1

Why do ahci_read and ahci_write of sector 1 data not matching?

Posted: Tue Apr 01, 2025 3:10 am
by gamingjam60
I am detecting SATA drive by PCI scan and setting
ahci_controller_t sata_disk;
where

Code: Select all

struct ahci_controller {
    uint8_t bus;
    uint8_t device;
    uint8_t function;
    uint64_t abar;
    bool initialized;
};
typedef struct ahci_controller ahci_controller_t;
and wrote following ahci.c code

Code: Select all

#define HBA_GHC_AHCI_ENABLE (1 << 31)   // 0x80000000 
#define HBA_PORT_CMD_ST     (1 << 0)    // 0x1
#define HBA_PORT_CMD_FRE    (1 << 4)    // 0x10
#define HBA_PORT_CMD_CR     (1 << 15)   // 0x8000

#define ATA_CMD_READ_DMA_EX 0x25
#define ATA_CMD_WRITE_DMA_EX 0x35


ahci_controller_t sata_disk;


void ahci_init() {
    hba_mem *hba = (hba_mem*)(sata_disk.abar);
    
    // Enable AHCI in GHC
    hba->ghc |= HBA_GHC_AHCI_ENABLE;
    while (!(hba->ghc & HBA_GHC_AHCI_ENABLE)); // Wait for enable

    // Find active port
    uint32_t pi = hba->pi;
    for (int i = 0; i < 32; i++) {
        if (pi & (1 << i)) {
            struct hba_port *port = &hba->ports[i];
            uint32_t ssts = port->ssts;
            if ((ssts & 0xF) == 3) { // Device detected
                ahci_port_init(port);
                sata_disk.initialized = true;
                return;
            }
        }
    }
    printf("AHCI: No active port found\n");
}


void ahci_port_init(struct hba_port *port) {
    // Stop command engine
    port->cmd &= ~HBA_PORT_CMD_ST;
    while (port->cmd & HBA_PORT_CMD_CR);

    // Allocate command list and FIS
    // Assuming aligned allocation functions exist
    void *cmd_list = (void *) kmalloc_a(1024, 1); // 32 entries * 32 bytes
    void *fis = (void *) kmalloc_a(256, 1);

    // Set command list and FIS base
    port->clb = (uint32_t)(uintptr_t) vir_to_phys((uint64_t)cmd_list);
    port->clbu = (uint32_t)((uintptr_t) vir_to_phys((uint64_t)cmd_list) >> 32);
    port->fb = (uint32_t)(uintptr_t) vir_to_phys((uint64_t)fis);
    port->fbu = (uint32_t)((uintptr_t) vir_to_phys((uint64_t)fis) >> 32);

    // Enable FIS and start engine
    port->cmd |= HBA_PORT_CMD_FRE;
    port->cmd |= HBA_PORT_CMD_ST;
}



int ahci_read(uint64_t lba, uint32_t count, void *buffer) {
    if (!sata_disk.initialized) return -1;
    hba_mem *hba = (hba_mem*)sata_disk.abar;
    struct hba_port *port = &hba->ports[0]; // Assuming port 0

    // Prepare command header
    volatile hba_cmd_header *cmdheader = (volatile hba_cmd_header *)(uintptr_t)port->clb;;     // Access command list
    cmdheader->cfl = sizeof(h2d_fis)/4;     // Size in DWORDS
    cmdheader->w = 0;                       // Read
    cmdheader->prdtl = 1;                   // 1 PRDT entry

    // Setup command table
    hba_cmd_table *cmdtbl = (hba_cmd_table *) kheap_alloc(sizeof(hba_cmd_table));
    memset(cmdtbl, 0, sizeof(hba_cmd_table));
    h2d_fis *fis = (h2d_fis*) &cmdtbl->cfis;
    fis->fis_type = 0x27;                   // H2D FIS
    fis->pm_port = 0x80;                    // Command
    fis->command = ATA_CMD_READ_DMA_EX;
    fis->lba0 = lba;
    fis->lba1 = lba >> 8;
    fis->lba2 = lba >> 16;
    fis->device = 0x40;                     // LBA mode
    fis->lba3 = lba >> 24;
    fis->lba4 = lba >> 32;
    fis->lba5 = lba >> 40;
    fis->countl = count & 0xFF;
    fis->counth = (count >> 8) & 0xFF;

    // Setup PRDT entry using hba_prdt_entry structure
    cmdtbl->prdt[0].dba = (uint32_t)(uintptr_t)vir_to_phys((uint64_t)buffer);
    cmdtbl->prdt[0].dbau = (uint32_t)(((uintptr_t)vir_to_phys((uint64_t)buffer)) >> 32);

    cmdtbl->prdt[0].dbc = (count * 512) - 1; // 512 bytes per sector, minus one
    cmdtbl->prdt[0].rsvd = 0;
    cmdtbl->prdt[0].i = 1;                   // Interrupt on completion

    // Issue command
    port->ci = 1; // Use command slot 0
    while (port->ci & 1); // Wait for completion

    kheap_free((void *)cmdtbl,  sizeof(hba_cmd_table));
    return 0;
}


int ahci_write(uint64_t lba, uint32_t count, void *buffer) {
    if (!sata_disk.initialized) return -1;
    hba_mem *hba = (hba_mem*)sata_disk.abar;
    hba_port_t *port = &hba->ports[0]; // Using port 0

    // Initialize command header pointer from the port's command list base.
    volatile hba_cmd_header *cmdheader = (volatile hba_cmd_header *)(uintptr_t)port->clb;
    
    // Prepare command header for slot 0
    cmdheader[0].cfl = sizeof(h2d_fis) / 4;  // FIS length in DWORDS
    cmdheader[0].w = 1;                      // Write operation
    cmdheader[0].prdtl = 1;                  // One PRDT entry

    // Allocate and clear a command table for this slot
    hba_cmd_table *cmdtbl = (hba_cmd_table *) kmalloc_a(sizeof(hba_cmd_table), 1);
    memset(cmdtbl, 0, sizeof(hba_cmd_table));

    // Setup Host-to-Device FIS in the command table
    h2d_fis *fis = (h2d_fis *)&cmdtbl->cfis;
    fis->fis_type = 0x27;                  // H2D FIS
    fis->pm_port = 0x80;                   // Command (bit 7 set)
    fis->command = ATA_CMD_WRITE_DMA_EX;
    fis->lba0 = lba;
    fis->lba1 = lba >> 8;
    fis->lba2 = lba >> 16;
    fis->device = 0x40;                    // LBA mode
    fis->lba3 = lba >> 24;
    fis->lba4 = lba >> 32;
    fis->lba5 = lba >> 40;
    fis->countl = count & 0xFF;
    fis->counth = (count >> 8) & 0xFF;

    // Setup PRDT entry using hba_prdt_entry structure
    cmdtbl->prdt[0].dba = (uint32_t)(uint64_t)vir_to_phys((uint64_t)buffer);
    cmdtbl->prdt[0].dbau = (uint32_t)(vir_to_phys((uint64_t)buffer) >> 32);
    cmdtbl->prdt[0].dbc = (count * 512) - 1; // (Transfer size in bytes) - 1
    cmdtbl->prdt[0].rsvd = 0;
    cmdtbl->prdt[0].i = 1; // Interrupt on completion

    // Link the command table to the command header (slot 0)
    uint64_t phys_cmdtbl = vir_to_phys((uint64_t)cmdtbl);
    cmdheader[0].ctba = (uint32_t)phys_cmdtbl;
    cmdheader[0].ctbau = (uint32_t)(phys_cmdtbl >> 32);

    // Issue command by setting the command issue bit for slot 0
    port->ci = 1; // Using command slot 0
    // Wait for command to complete (consider adding a timeout)
    while (port->ci & 1);

    return 0;
}
To testing this by

Code: Select all

if (sata_disk.abar != 0) {

        char *buffer = (char *) kmalloc_a(512, 1);
        memset(buffer, 0, 512); // Clearing buffer

        if (ahci_read(0, 1, (void *)buffer) == 0) { 
            printf("Disk Read Successful!\n");
        
            // Check MBR signature (last 2 bytes of sector)
            if (buffer[510] == 0x55 && buffer[511] == 0xAA) {
                printf("Valid MBR Signature found!\n");
            } else {
                printf("No MBR found. Disk may be empty or unformatted.\n");
            }
        } else {
            printf("AHCI Read Failed!\n");
        }

        char *write_buffer = (char *) kmalloc_a(512, 1);
        memset(write_buffer, 'A', 512);  // Fill buffer with 'A'

        if (ahci_write(1, 1, write_buffer) == 0) {
            printf("AHCI Write Successful at LBA 1!\n");
        } else {
            printf("AHCI Write Failed!\n");
        }

        char *read_buffer = (char *) kmalloc_a(512, 1);
        memset(read_buffer, 0, 512);

        if (ahci_read(1, 1, read_buffer) == 0) {
            if (memcmp(write_buffer, read_buffer, 512) == 0) {
                printf("Write Verification Successful! Data matches. %x\n", *write_buffer);
            } else {
                printf("Write Verification Failed! Data does not match.\n");
            }
        } else {
            printf("Failed to read back written data.\n");
        }
        printf("Write_buffer[%d]: %x\n", 1,  write_buffer[1]);
        printf("Read_buffer[%d]: %x\n", 1, read_buffer[1]);
    }
The Output :

Code: Select all

Disk Read Successful!
No MBR found. Disk may be empty or unformatted.
AHCI Write Successful at LBA 1!
Write Verification Failed! Data does not match.
Write_buffer[1]: 0x41
Read_buffer[1]: 0x0
How to resolve this issue?

pci.c
pci.h

ahci.h
ahci.h

Re: Why do ahci_read and ahci_write of sector 1 data not matching?

Posted: Tue Apr 01, 2025 10:15 am
by Octocontrabass
gamingjam60 wrote: Tue Apr 01, 2025 3:10 am

Code: Select all

    port->clb = (uint32_t)(uintptr_t) vir_to_phys((uint64_t)cmd_list);
    port->clbu = (uint32_t)((uintptr_t) vir_to_phys((uint64_t)cmd_list) >> 32);

Code: Select all

    volatile hba_cmd_header *cmdheader = (volatile hba_cmd_header *)(uintptr_t)port->clb;;     // Access command list
This might be a problem.

What kind of debugging have you done so far? Have you tried using QEMU's trace logger to see if the emulated AHCI controller is behaving the way you expect?