Page 1 of 1

Why does the following SATAPI inquiry function is showing "Task File Error"?

Posted: Fri Sep 26, 2025 6:48 am
by gamingjam60
My long vison is to read the sector from AHCI SATAPI CD/DVD Disk. I get HBA_PORT *port for corresponding SATAPI Disk from a AHCI Controller by using PCI Scan. I know SATAPI will manage ATAPI PACKET instead ATA Commands which is using in SATA Disk. I have written following function to manage SATAPI Device:

Code: Select all

void AtpiPortRebase(HBA_MEM_T *abar, int port_no)
{
    HBA_PORT_T *port = &abar->ports[port_no];
    stopCMD(port);

    // Allocate a physically contiguous region for this port:
    // Make it large enough for CLB (1K), FB (1K), CTBA area (8K), plus margin.
    const size_t ALLOC_SIZE = 64 * 1024; // 64 KiB to be safe
    void *base_virt = (void *) kheap_alloc(ALLOC_SIZE, ALLOCATE_DATA);
    if (!base_virt) {
        printf(" portRebase: kmalloc failed\n");
        return;
    }

    // Convert to physical base (what HBA will use)
    uintptr_t base_phys = vir_to_phys((uintptr_t)base_virt);
    if (base_phys == 0) {
        printf(" portRebase: vir_to_phys returned 0\n");
        return;
    }

    // Layout within the allocated block (choose simple contiguous layout)
    // CLB: offset 0
    uintptr_t clb_phys = base_phys + 0x0;
    void *clb_virt = (void *)((uintptr_t)base_virt + 0x0); // virtual pointer for CPU
    memset(clb_virt, 0, 0x400); // 1 KiB

    // FB: offset 4K (use 4 KiB aligned area)
    uintptr_t fb_phys = base_phys + 0x1000;
    void *fb_virt = (void *)((uintptr_t)base_virt + 0x1000);
    memset(fb_virt, 0, 0x100); // 256 bytes (FIS receive area), zeroed

    // Command tables area: offset 8K (we'll allocate 8 KiB per port for command tables)
    uintptr_t ctba_base_phys = base_phys + 0x2000;
    void *ctba_base_virt = (void *)((uintptr_t)base_virt + 0x2000);
    // Zero the whole CTBA area (32 * 256 = 8192)
    memset(ctba_base_virt, 0, 0x2000);

    // Program registers (hardware uses physical addresses)
    port->clb  = (uint32_t)(clb_phys & 0xFFFFFFFF);
    port->clbu = (uint32_t)(clb_phys >> 32);

    port->fb   = (uint32_t)(fb_phys & 0xFFFFFFFF);
    port->fbu  = (uint32_t)(fb_phys >> 32);

    // Now initialize each command header to point into the CTBA area
    HBA_CMD_HEADER_T* cmd_header = (HBA_CMD_HEADER_T*) clb_virt; // CPU-side pointer into CLB area
    for (int i = 0; i < 32; ++i) {
        // Each command table sized 256 bytes (0x100) at sequential offsets
        uintptr_t phys_ctba = ctba_base_phys + (i * 0x100);
        cmd_header[i].ctba  = (uint32_t)(phys_ctba & 0xFFFFFFFF);
        cmd_header[i].ctbau = (uint32_t)(phys_ctba >> 32);
        cmd_header[i].prdtl = 8; // example default
        // Clear command table memory (use virtual)
        void* virt_ctba = (void*)((uintptr_t)ctba_base_virt + (i * 0x100));
        memset(virt_ctba, 0, 0x100);
    }

    port->cmd |= HBA_PxCMD_ATAPI;  // Set bit 24 for ATAPI devices

    // Wait a bit for ATAPI mode to take effect
    for (volatile int i = 0; i < 1000; i++);

    // Start command engine after setting CLB/FB/CTBA
    startCMD(port);

    // printf("[AHCI] portRebase: done for port %d (phys base %x)\n", port_no, (unsigned long)base_phys);
}

Code: Select all

static bool runAtapiCommand(HBA_PORT_T *port, uint8_t *cdb, size_t cdb_len, uintptr_t buf_phys, uint32_t buf_size, bool write)
{
    port->is = (uint32_t)-1; // clear pending interrupts

    int slot = findCMDSlot(port, 32);
    if (slot == -1) return false;

    // CLB → command header base
    uintptr_t clb_phys = ((uint64_t)port->clb) | ((uint64_t)port->clbu << 32);
    HBA_CMD_HEADER_T *cmd_header_base = (HBA_CMD_HEADER_T*)(uintptr_t)phys_to_vir(clb_phys);
    HBA_CMD_HEADER_T *cmd_header = &cmd_header_base[slot];

    // set up header
    cmd_header->cfl   = sizeof(FIS_REG_H2D_T) / 4;
    cmd_header->w     = write ? 1 : 0;
    cmd_header->a     = 1; // ATAPI
    cmd_header->prdtl = (buf_size > 0) ? 1 : 0; // single PRDT

    // Command table
    uint64_t ctba_phys = ((uint64_t)cmd_header->ctbau << 32) | cmd_header->ctba;
    HBA_CMD_TBL_T *cmd_tbl = (HBA_CMD_TBL_T*)(uintptr_t)phys_to_vir(ctba_phys);
    memset(cmd_tbl, 0, sizeof(HBA_CMD_TBL_T) + (cmd_header->prdtl-1)*sizeof(HBA_PRDT_ENTRY_T));

    // Fill PRDT if data buffer
    if (buf_size > 0) {
        cmd_tbl->prdt_entry[0].dba  = (uint32_t)(buf_phys & 0xFFFFFFFF);
        cmd_tbl->prdt_entry[0].dbau = (uint32_t)(buf_phys >> 32);
        cmd_tbl->prdt_entry[0].dbc  = buf_size - 1;
        cmd_tbl->prdt_entry[0].i    = 1;
    }

    // Fill CFIS
    FIS_REG_H2D_T *cfis = (FIS_REG_H2D_T*)&cmd_tbl->cfis;
    memset(cfis, 0, sizeof(FIS_REG_H2D_T));
    cfis->fis_type = FIS_TYPE_REG_H2D;
    cfis->c = 1;
    cfis->command = ATA_CMD_PACKET;
    cfis->device = 0;

    // Copy CDB into acmd
    memset(cmd_tbl->acmd, 0, 16);
    memcpy(cmd_tbl->acmd, cdb, (cdb_len > 16 ? 16 : cdb_len));

    // Wait until port not busy
    int spin = 0;
    while ((port->tfd & (ATA_DEV_BUSY | ATA_DEV_DRQ)) && spin < 1000000) spin++;
    if (spin == 1000000) {
        printf(" [SATAPI] Port hung\n");
        return false;
    }

    // Issue command
    port->ci = 1 << slot;

    // Wait for completion
    while (true) {
        if (!(port->ci & (1 << slot))) break;
        if (port->is & HBA_PxIS_TFES) {
            printf(" [SATAPI] Task File Error\n");
            return false;
        }
    }

    if (port->is & HBA_PxIS_TFES) {
        printf(" [SATAPI] TFES after completion\n");
        return false;
    }

    return true;
}
Now I have written following inquiry function

Code: Select all

bool satapi_inquiry(HBA_PORT_T *port) {
    if(!port) return false;
    uint8_t cdb[12] = {0};
    cdb[0] = ATAPI_CMD_INQUIRY;
    cdb[4] = 36; // allocation length

    void *buf = kheap_alloc(36, ALLOCATE_DATA);
    if (!buf) return false;

    uintptr_t buf_phys = vir_to_phys((uintptr_t)buf);
    if (!runAtapiCommand(port, cdb, 12, buf_phys, 36, false)) {
        printf(" Inquiry failed\n");
        return false;
    }

    uint8_t *resp = (uint8_t*)buf;
    printf(" Vendor: %.8s, Product: %.16s, Revision: %.4s\n",
           &resp[8], &resp[16], &resp[32]);
    return true;
}
When I am calling `satapi_inquiry(satapi_disks[0]);` is showing

Code: Select all

[SATAPI] Task File Error
 Inquiry failed
 
How could I solve this issue?

Re: Why does the following SATAPI inquiry function is showing "Task File Error"?

Posted: Fri Sep 26, 2025 6:00 pm
by Octocontrabass
What debugging have you tried so far?

Which virtual machine are you using? QEMU's trace logger is excellent for debugging this kind of thing.

Re: Why does the following SATAPI inquiry function is showing "Task File Error"?

Posted: Sun Sep 28, 2025 9:18 am
by gamingjam60
Octocontrabass wrote: Fri Sep 26, 2025 6:00 pm What debugging have you tried so far?

Which virtual machine are you using? QEMU's trace logger is excellent for debugging this kind of thing.
I am using QEmu Virtual Machine. Thankyou for mentioning QEMU's trace logger. SATA disk is working with previous type code but not for satapi.

Re: Why does the following SATAPI inquiry function is showing "Task File Error"?

Posted: Sun Sep 28, 2025 2:51 pm
by Octocontrabass
Since you already have SATA disks working, have you tried an IDENTIFY PACKET DEVICE command?

When you enable QEMU's trace logger, do any of the trace events report errors?

Re: Why does the following SATAPI inquiry function is showing "Task File Error"?

Posted: Tue Oct 07, 2025 12:14 pm
by gamingjam60
Octocontrabass wrote: Sun Sep 28, 2025 2:51 pm Since you already have SATA disks working, have you tried an IDENTIFY PACKET DEVICE command?

When you enable QEMU's trace logger, do any of the trace events report errors?
When I am trying to run above code in VirtualBox it is working fine. I would try to use QEmu event trace first I need some documentation about that as I never used that.

Code: Select all

qemu-system-x86_64 \
	-machine q35 \
	-m 4096 \
	-smp cores=4,threads=1,sockets=1,maxcpus=4 \
	-boot d \
	-drive id=sata_disk1,file=$(DISK_1),if=none,format=raw \
	-drive id=sata_disk2,file=$(DISK_2),if=none,format=raw \
	-device ahci,id=ahci \
	-device ide-hd,drive=sata_disk1,bus=ahci.0 \
	-device ide-hd,drive=sata_disk2,bus=ahci.1 \
	-device ide-cd,drive=cdrom,bus=ahci.2 \
	-drive id=cdrom,media=cdrom,file=$(BUILD_DIR)/$(OS_NAME)-$(OS_VERSION)-image.iso,if=none \
	-serial stdio \
	-vga std \
	-rtc base=utc,clock=host \
	-netdev user,id=n1 -device e1000,netdev=n1 \
	-d guest_errors,int,cpu_reset \
	-D $(DEBUG_DIR)/qemu.log


Do the above QEmu Run command has something problematic??

Re: Why does the following SATAPI inquiry function is showing "Task File Error"?

Posted: Tue Oct 07, 2025 12:30 pm
by Octocontrabass
gamingjam60 wrote: Tue Oct 07, 2025 12:14 pmI would try to use QEmu event trace first I need some documentation about that as I never used that.
Here is the QEMU documentation. Here is a list of potentially useful trace events.
gamingjam60 wrote: Tue Oct 07, 2025 12:14 pmDo the above QEmu Run command has something problematic??
I don't see anything wrong.