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

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
User avatar
gamingjam60
Member
Member
Posts: 39
Joined: Sat Aug 24, 2024 10:06 pm
Libera.chat IRC: gamingjam60
Location: India
GitHub: https://github.com/baponkar
Contact:

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

Post 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
Octocontrabass
Member
Member
Posts: 5754
Joined: Mon Mar 25, 2013 7:01 pm

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

Post 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?
Post Reply