Page 1 of 1

ahci sata writes only flush to disk when virtualbox shutdown

Posted: Wed Sep 28, 2016 2:23 am
by poby
I have written an ahci sata driver that seems to work ok. I can read and write to any sector on the disk. No problem. Except that is, the writes only hit the disk when I "shutdown the machine" in virtualbox. If I write a sector to disk, and then read back the same sector, the data isn't there. Only after I restart does it appear.

I've tried wading through the SATA and AHCI specs but it's like swimming in molasses. There is probably a simple command to send that flushes the data but I haven't been able to find it. I'm really hoping someone can point me in the right direction.

Code: Select all

; RAX = LBA sector,  BX = Sector count,  RDX = Buffer to write sectors
sata_write:
        mov rcx, rbx
        shl rcx, 9                                              ; Multiply by 512 to get expected number of bytes
        call sata_tables_setup
        mov [CMD_TAB.CMD_FIS.command_byte], ATA_CMD_WRITE_DMA_EXT
        call send_SATA_command

        xor rax, rax                                           ; I added this section but still no joy :(
        xor rbx, rbx
        xor rcx, rcx
        call sata_tables_setup
        mov [CMD_TAB.CMD_FIS.command_byte], ATA_CMD_CACHE_FLUSH_EXT
        call send_SATA_command
        ret 

Code: Select all

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Set common table values ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; IN:   RAX = LBA sector,  BX = Sector count, ECX = number of bytes to transfer, RDX = Buffer to write data

sata_tables_setup:
        push rax
        push rcx
        CLEAR_BLOCK CMD_LST, CMD_LST.size shr 3
        CLEAR_BLOCK CMD_TAB, CMD_TAB.size shr 3
        CLEAR_BLOCK RX_FIS, RX_FIS.size shr 3
        pop rcx
        pop rax

        mov [CMD_LST.command_information], 0x0005               ; set up the command list
        mov [CMD_LST.prdt_length], 1
        mov [CMD_LST.command_table], CMD_TAB

        mov [CMD_TAB.CMD_FIS.fis_type], FIS_TYPE_REG_H2D        ; now set up the command FIS
        mov [CMD_TAB.CMD_FIS.command], 0x80                     ; we're sending a command (set .c bit 7)

        mov qword[CMD_TAB.CMD_FIS.lba0], rax                    ; setup lba fields, 1st sector is 1
        shl dword[CMD_TAB.CMD_FIS.device], 8
        mov [CMD_TAB.CMD_FIS.device], 1 shl 6                   ; LBA MODE!

        test rcx, rcx
        jz @f

        mov [CMD_TAB.CMD_FIS.count], bx                         ; 1 to 65535 sectors

        mov eax, ecx
        sub eax, 1                                              ; bit 0 must be 1.  actual bytes = byte_count + 1

        mov [CMD_TAB.PRDT.memory], rdx                          ; now set up the PRDT
        mov [CMD_TAB.PRDT.byte_count], eax                      ; bytes to transfer
@@:     ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Ready, set and send, error if failure ;;;;;;;;;;;;;;;;;;;;;;;;;;
; IN : ECX = expected number of bytes to transfer
; OUT: RDI,*
send_SATA_command:
        push rdi

        mov rdi, [DRIVE_TABLE]
.wait_for_not_bsy:
        pause
        test dword[ABAR_PORTS_rdi.TASK_FILE], 0x80      ; first ensure the device is not busy
        jnz .wait_for_not_bsy

        and [ABAR_PORTS_rdi.COMMAND], 0xFFFFFFFE
        mov [ABAR_PORTS_rdi.COMMAND_ISSUE], 0

.wait_for_idle:
        pause
        test [ABAR_PORTS_rdi.COMMAND], 1                ; first ensure the device is not busy
        jnz .wait_for_idle
        cmp [ABAR_PORTS_rdi.COMMAND_ISSUE], 0
        jnz .wait_for_idle

        mov [ABAR_PORTS_rdi.COMMAND_LIST], CMD_LST      ; send CMD_LST to the device
        btc [ABAR_PORTS_rdi.COMMAND], 4                 ; FRE
        mov [ABAR_PORTS_rdi.FIS], RX_FIS                ; send the RX_FIS to the device
        bts [ABAR_PORTS_rdi.COMMAND], 4                 ; FRE
        bts [ABAR_PORTS_rdi.COMMAND], 0                 ; ST

        mov [ABAR_PORTS_rdi.COMMAND_ISSUE], 1
@@:
        pause
        test  [ABAR_PORTS_rdi.COMMAND_ISSUE], 1
        jnz @b
        test [ABAR_PORTS_rdi.TASK_FILE], 0x80
        jnz @b
        test dword[ABAR_PORTS_rdi.IRQ_STATUS], 0x40000000        ; task file error
        jnz .error
        test [ABAR_PORTS_rdi.TASK_FILE], 0x01               ; error
        jnz .error
        test [ABAR_PORTS_rdi.TASK_FILE], 0x20               ; drive fault
        jnz .error

        ;and [ABAR_PORTS_rdi.COMMAND], 0xFFFFFFEE
        btc [ABAR_PORTS_rdi.COMMAND], 0                 ; ST
        btc [ABAR_PORTS_rdi.COMMAND], 4                 ; FRE

        cmp [CMD_LST.prdt_successful_count], ecx        ; ensure the controller transferred the correct # of bytes
        jne .error

        pop rdi
        ret
.error:
        print_ch ' '
        mov rsi, .sata_error
        call os_print_string
        hlt
        ret

.sata_error        db "SATA ERROR!", 0

Re: ahci sata writes only flush to disk when virtualbox shut

Posted: Wed Sep 28, 2016 2:46 am
by Kevin
Not going to read more than 10 lines of assembly code, but you need the FLUSH CACHE command that is described in the ATA Command Set (ACS) specification.

Re: ahci sata writes only flush to disk when virtualbox shut

Posted: Wed Sep 28, 2016 2:53 am
by poby
Kevin wrote:Not going to read more than 10 lines of assembly code, but you need the FLUSH CACHE command that is described in the ATA Command Set (ACS) specification.
Yeah, how do I send the flush_cache command? The following code I hoped would do it but it doesn't so I'm missing something

Code: Select all

        call sata_tables_setup
        mov [CMD_TAB.CMD_FIS.command_byte], ATA_CMD_CACHE_FLUSH_EXT
        call send_SATA_command
I'm assuming PRDT fields should be zero as I'm not transferring any data. I haven't seen much info anywhere about cache_flushing using FIS (as opposed to IO ports)

Re: ahci sata writes only flush to disk when virtualbox shut

Posted: Wed Sep 28, 2016 6:08 am
by BrightLight
Sending commands using the FIS is much the same as sending commands using the I/O ports. Each I/O port has an entry in the FIS. So for the flush command, construct the FIS with all values zero except the command byte, which is 0xE7 (if I recall correctly, someone could correct me here.)