OSDev.org

The Place to Start for Operating System Developers
It is currently Thu Mar 28, 2024 7:33 am

All times are UTC - 6 hours




Post new topic Reply to topic  [ 8 posts ] 
Author Message
 Post subject: AHCI/ATAPI Read/Write commands in DMA mode
PostPosted: Sun Sep 04, 2022 12:03 pm 
Offline

Joined: Mon Aug 15, 2022 12:30 pm
Posts: 18
Hello everyone.

I'm facing some trouble trying to perform reads from a cdrom device (qemu emulated).

As i have read, it is possible to tell the fis to operate in ATAPI mode as explained in:

https://wiki.osdev.org/AHCI#AHCI_.26_ATAPI

The steps i'm following (starting from a working read ata function) are:

1. Changing the ATA DMA READ command (0x25) to ATA PACKET (0xA0).
2. Setting the ATAPI command (READ (0xA8)) in the command table.
3. Setting the ATAPI mode bit on the command header.
4. Changing the FIS type from REG_H2D (0x27) to PIO_SETUP(0x5f).

My code so far is:

Code:
uint8_t read_atapi_port(uint8_t port_no, uint64_t sector, uint32_t sector_count) {
    struct ahci_port* port = &ahci_ports[port_no];

    uint32_t sector_low = (uint32_t)sector;
    uint32_t sector_high = (uint32_t)(sector >> 32);

    port->hba_port->interrupt_status = (uint32_t)-1;
    int spin = 0;
    int slot = (int)find_cmd_slot(port);
    if (slot == -1) {
        printf("No free command slots\n");
        return 0;
    }

    struct hba_command_header* command_header = (struct hba_command_header*)(uint64_t)(port->hba_port->command_list_base);
    command_header += slot;
    command_header->command_fis_length = sizeof(struct hba_command_fis) / sizeof(uint32_t);
    command_header->write = 0;
    command_header->atapi = 1;
    command_header->prdt_length = (uint16_t)((sector_count - 1) >> 4) + 1;

    struct hba_command_table* command_table = (struct hba_command_table*)(uint64_t)(command_header->command_table_base_address);
    memset(command_table, 0, sizeof(struct hba_command_table) + (command_header->prdt_length - 1) * sizeof(struct hba_prdt_entry));

    command_table->atapi_command[0] = ATAPI_READ_CMD;
    command_table->atapi_command[1] = 0;
    command_table->atapi_command[2] = (uint8_t)(sector >> 0x18);
    command_table->atapi_command[3] = (uint8_t)(sector >> 0x10);
    command_table->atapi_command[4] = (uint8_t)(sector >> 0x08);
    command_table->atapi_command[5] = (uint8_t)(sector >> 0x00);

    void* buffer = port->buffer;
    int i;
    for (i = 0; i < command_header->prdt_length - 1; i++) {
        command_table->prdt_entry[i].data_base_address = (uint32_t)(uint64_t)buffer;
        command_table->prdt_entry[i].data_base_address_upper = (uint32_t)((uint64_t)buffer >> 32);
        command_table->prdt_entry[i].byte_count = 8 * 1024 - 1;
        command_table->prdt_entry[i].interrupt_on_completion = 1;
        buffer = (void*)((uint64_t*)buffer+0x1000);
        sector_count -= 16;
    }

    command_table->prdt_entry[i].data_base_address = (uint32_t)(uint64_t)buffer;
    command_table->prdt_entry[i].data_base_address_upper = (uint32_t)((uint64_t)buffer >> 32);
    command_table->prdt_entry[i].byte_count = (sector_count << 9) - 1;
    command_table->prdt_entry[i].interrupt_on_completion = 1;

    struct hba_command_fis* command_fis = (struct hba_command_fis*)command_table->command_fis;

    command_fis->fis_type = FIS_TYPE_PIO_SETUP;
    command_fis->command_control = 1;
    command_fis->command = ATA_CMD_PACKET;

    command_fis->lba0 = (uint8_t)sector_low;
    command_fis->lba1 = (uint8_t)(sector_low >> 8);
    command_fis->lba2 = (uint8_t)(sector_low >> 16);

    command_fis->device_register = 1 << 6;

    command_fis->lba3 = (uint8_t)(sector_low >> 24);
    command_fis->lba4 = (uint8_t)(sector_high);
    command_fis->lba5 = (uint8_t)(sector_high >> 8);

    command_fis->count_low = sector_count & 0xFF;
    command_fis->count_high = (sector_count >> 8);

    while (port->hba_port->task_file_data & (ATA_DEV_BUSY | ATA_DEV_DRQ) && spin < 1000000) {
        spin++;
    };
    if (spin == 1000000) {
        printf("Port is hung\n");
        return 0;
    }

    port->hba_port->command_issue = (1 << slot);

    while(1) {
        if ((port->hba_port->command_issue & (1<<slot)) == 0) break;
        if (port->hba_port->interrupt_status & HBA_PxIS_TFES) {
            return 0;
        }
    }

    if (port->hba_port->interrupt_status & HBA_PxIS_TFES) {
        return 0;
    }

    return 1;
}


Top
 Profile  
 
 Post subject: Re: AHCI/ATAPI Read/Write commands in DMA mode
PostPosted: Sun Sep 04, 2022 6:33 pm 
Offline
Member
Member

Joined: Mon Mar 25, 2013 7:01 pm
Posts: 5099
tretornesp wrote:
4. Changing the FIS type from REG_H2D (0x27) to PIO_SETUP(0x5f).

That's not right. You need a Register H2D FIS to tell the drive you're sending the PACKET command. The HBA will automatically handle the PIO Setup FIS.

You also need to change the values you write into the task file registers in your Register H2D FIS, since the PACKET command assigns different meanings to those registers.

The ATA Features register (which you're not setting) is the ATAPI Features register. In the ATAPI Features register, you may need to use bits 0 and 2. Bit 0 controls whether the data transfer is PIO or DMA, and some drives may require you to set bit 2 when performing device-to-host DMA. Use IDENTIFY PACKET DEVICE to check your drive's DMA capabilities.

The ATA LBA1 and LBA2 registers are the ATAPI Byte Count Low and Byte Count High registers. When using DMA transfers, these registers are ignored. When using PIO transfers, these registers indicate the number of bytes to transfer per DRQ block. AHCI does not support more than 0x2000 bytes per DRQ block, and may not support multiple DRQ blocks per command, so you should use DMA transfers if your drive supports them.

The rest of the task file registers are either unused or don't exist.


Top
 Profile  
 
 Post subject: Re: AHCI/ATAPI Read/Write commands in DMA mode
PostPosted: Wed Sep 07, 2022 2:43 pm 
Offline

Joined: Mon Aug 15, 2022 12:30 pm
Posts: 18
After a few days trying to implement your suggestions I can't seem to get it right. I'm an absolute newbie on device driver development.

If you don't mind I would really appreciate if you could confirm if the following structure values apply all your suggestions or if i'm missing something:

Code:
command_fis->fis_type = FIS_TYPE_REG_H2D; //0x27
command_fis->feature_low |= 1;
command_fis->feature_low |= (1 << 2); //Setting feature bits 0 and 2
command_fis->command_control = 1;
command_fis->command = ATA_CMD_PACKET; //0xA0
command_fis->device_register = 1 << 6;
command_fis->count_low = sector_count & 0xFF;
command_fis->count_high = (sector_count >> 8);


sector_count is an uint32 with the number of sectors to read
sector_high is an uint32 with the highest 32 bits of the sector number.
sector_low is an uint32 with the lowest 32 bits of the sector number.

The rest of the values are set to 0.

also my atapi command:

Code:
command_table->atapi_command[0] = 0xA8;
command_table->atapi_command[1] = 0x00;
command_table->atapi_command[2] = (uint8_t)(sector_high >> 8);
command_table->atapi_command[3] = (uint8_t)sector_high;
command_table->atapi_command[4] = (uint8_t)(sector_low >> 24);
command_table->atapi_command[5] = (uint8_t)(sector_low >> 16);
command_table->atapi_command[6] = (uint8_t)(sector_low >> 8);
command_table->atapi_command[7] = (uint8_t)sector_low;
command_table->atapi_command[8] = 0x00;
command_table->atapi_command[9] = 0x00;
command_table->atapi_command[10] = 0x00;
command_table->atapi_command[11] = 0x00;
command_table->atapi_command[12] = 0x00;
command_table->atapi_command[13] = 0x00;
command_table->atapi_command[14] = 0x00;
command_table->atapi_command[15] = 0x00;


if this is correct, i'm guessing DMA may be unavailable... I guess it would be a good idea to spend the time writing support for the IDENTIFY PACKET DEVICE command.

Related structure (FIS_REG_H2D)
Code:
struct hba_command_fis {
    uint8_t fis_type;

    uint8_t pm_port : 4;
    uint8_t reserved0 : 3;
    uint8_t command_control : 1;

    uint8_t command;
    uint8_t feature_low;

    uint8_t lba0;
    uint8_t lba1;
    uint8_t lba2;
    uint8_t device_register;

    uint8_t lba3;
    uint8_t lba4;
    uint8_t lba5;
    uint8_t feature_high;

    uint8_t count_low;
    uint8_t count_high;
    uint8_t icc;
    uint8_t control;

    uint8_t reserved1[4];
} __attribute__ ((packed));


Thank you so much for your time and knowledge!


Top
 Profile  
 
 Post subject: Re: AHCI/ATAPI Read/Write commands in DMA mode
PostPosted: Wed Sep 07, 2022 3:10 pm 
Offline
Member
Member

Joined: Mon Mar 25, 2013 7:01 pm
Posts: 5099
tretornesp wrote:
If you don't mind I would really appreciate if you could confirm if the following structure values apply all your suggestions or if i'm missing something:

All of the ATA task file registers/bits I didn't list in my previous post are unused and should be zero.

Code:
command_fis->feature_low |= 1;
command_fis->feature_low |= (1 << 2); //Setting feature bits 0 and 2

Unused bits of the features register should be zero. Drives that don't use bit 2 may reject commands with bit 2 set, but QEMU's emulated drive probably doesn't care.

Code:
command_fis->device_register = 1 << 6;

ATAPI devices are supposed to ignore this bit, but I'd set the whole register to 0 just in case.

Code:
command_fis->count_low = sector_count & 0xFF;
command_fis->count_high = (sector_count >> 8);

This register is unused in ATAPI and should be zero.

Code:
command_table->atapi_command[2] = (uint8_t)(sector_high >> 8);
command_table->atapi_command[3] = (uint8_t)sector_high;
command_table->atapi_command[4] = (uint8_t)(sector_low >> 24);
command_table->atapi_command[5] = (uint8_t)(sector_low >> 16);

READ(12) uses 32-bit LBA. You should use only sector_low to fill these four bytes.

Code:
command_table->atapi_command[6] = (uint8_t)(sector_low >> 8);
command_table->atapi_command[7] = (uint8_t)sector_low;
command_table->atapi_command[8] = 0x00;
command_table->atapi_command[9] = 0x00;

You should use sector_count to fill these four bytes.

tretornesp wrote:
I guess it would be a good idea to spend the time writing support for the IDENTIFY PACKET DEVICE command.

You should be able to get QEMU to work without it.


Top
 Profile  
 
 Post subject: Re: AHCI/ATAPI Read/Write commands in DMA mode
PostPosted: Sat Sep 10, 2022 2:43 am 
Offline

Joined: Mon Aug 15, 2022 12:30 pm
Posts: 18
Quick update: After a lot of tinkering i'm now able to read the first n sectors of a CD with the following code:

Code:
uint8_t read_atapi_port(uint8_t port_no, uint64_t sector, uint32_t sector_count) {
    printf("Atapi read issued (port %d, sector %d, sector_count %d)\n", port_no, sector, sector_count);

    struct ahci_port* port = &ahci_ports[port_no];
    void* buffer = port->buffer;

    port->hba_port->interrupt_status = (uint32_t)-1;
    int spin = 0;
    int slot = (int)find_cmd_slot(port);
    if (slot == -1) {
        printf("No free command slots\n");
        return 0;
    }

    struct hba_command_header* command_header = (struct hba_command_header*)(uint64_t)(port->hba_port->command_list_base);
    command_header += slot;

    command_header->command_fis_length = sizeof(struct hba_command_fis) / sizeof(uint32_t);
    command_header->write = 0;
    command_header->atapi = 1;
    command_header->prdt_length = 1;
   
    struct hba_command_table* command_table = (struct hba_command_table*)(uint64_t)(command_header->command_table_base_address);
    memset(command_table, 0, sizeof(struct hba_command_table) + (command_header->prdt_length - 1) * sizeof(struct hba_prdt_entry));

    command_table->prdt_entry[0].data_base_address = (uint32_t)(uint64_t)buffer;
    command_table->prdt_entry[0].data_base_address_upper = (uint32_t)((uint64_t)buffer >> 32);
    command_table->prdt_entry[0].byte_count = (sector_count << 9) - 1;
    command_table->prdt_entry[0].interrupt_on_completion = 1;

    struct hba_command_fis* command_fis = (struct hba_command_fis*)command_table->command_fis;
    memset(command_fis, 0, sizeof(struct hba_command_fis));

    command_fis->fis_type = FIS_TYPE_REG_H2D;
    command_fis->command_control = 1;
    command_fis->feature_low = 5;
    command_fis->command = ATA_CMD_PACKET;

    command_table->atapi_command[0] = ATAPI_READ_CMD;
    command_table->atapi_command[1] = 0;
    command_table->atapi_command[2] = (uint8_t)((sector >> 24)& 0xff);
    command_table->atapi_command[3] = (uint8_t)((sector >> 16)& 0xff);
    command_table->atapi_command[4] = (uint8_t)((sector >> 8)& 0xff);
    command_table->atapi_command[5] = (uint8_t)((sector >> 0)& 0xff);
    command_table->atapi_command[6] = 0;
    command_table->atapi_command[7] = 0;
    command_table->atapi_command[8] = 0;
    command_table->atapi_command[9] = (uint8_t)(sector_count & 0xff);
    command_table->atapi_command[10] = 0;
    command_table->atapi_command[11] = 0;
    command_table->atapi_command[12] = 0;
    command_table->atapi_command[13] = 0;
    command_table->atapi_command[14] = 0;
    command_table->atapi_command[15] = 0;

    while (port->hba_port->task_file_data & (ATA_DEV_BUSY | ATA_DEV_DRQ) && spin < 1000000) {
        spin++;
    };
    if (spin == 1000000) {
        printf("Port is hung\n");
        return 0;
    }

    port->hba_port->command_issue = (1 << slot);

    while(1) {
        if ((port->hba_port->command_issue & (1<<slot)) == 0) break;
        if (port->hba_port->interrupt_status & HBA_PxIS_TFES) {
            return 0;
        }
    }

    if (port->hba_port->interrupt_status & HBA_PxIS_TFES) {
        return 0;
    }

    return 1;
}


Sadly when i specify an starting LBA different than 0. ex:

Code:
command_table->atapi_command[5] = 1;


The program returns a buffer full of zeros.

If anybody has an idea of what may be happening here, i'll appreciate the help. I will update this with fully working code once i find out what is going on.

Thank you so much and have a nice day!


Top
 Profile  
 
 Post subject: Re: AHCI/ATAPI Read/Write commands in DMA mode
PostPosted: Sat Sep 10, 2022 2:19 pm 
Offline
Member
Member

Joined: Mon Mar 25, 2013 7:01 pm
Posts: 5099
tretornesp wrote:
Sadly when i specify an starting LBA different than 0. ex:

Code:
command_table->atapi_command[5] = 1;


The program returns a buffer full of zeros.

That sounds normal. Most optical discs don't have any data in LBA 0 through 15. Did you try LBA 16?


Top
 Profile  
 
 Post subject: Re: AHCI/ATAPI Read/Write commands in DMA mode
PostPosted: Sat Sep 10, 2022 5:46 pm 
Offline

Joined: Mon Aug 15, 2022 12:30 pm
Posts: 18
LOL, you are right, it works. Thank you!


Top
 Profile  
 
 Post subject: Re: AHCI/ATAPI Read/Write commands in DMA mode
PostPosted: Wed Sep 21, 2022 10:14 am 
Offline

Joined: Tue Jan 05, 2016 9:10 am
Posts: 12
A small suggestion, the byte_count for CDROM should be a multiple of 2048 as compared to 512 of HDDs.

So the part
Code:
command_table->prdt_entry[0].byte_count = (sector_count << 9) - 1;

becomes
Code:
#define CDROM_SECTOR_SIZE 2048
command_table->prdt_entry[0].byte_count = (sector_count * CDROM_SECTOR_SIZE) - 1;


I was facing this problem when running on VirtualBox 6.1 (Ubuntu 22.04 HostOS)

Found the solution in this thread viewtopic.php?f=1&t=33511 (User zity's 1st reply)


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 8 posts ] 

All times are UTC - 6 hours


Who is online

Users browsing this forum: Google [Bot], SemrushBot [Bot] and 65 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group