kolibrios-gitea/kernel/branches/kolibri-ahci/blkdev/ahci.inc

1529 lines
53 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ;;
;; Copyright (C) KolibriOS team 2004-2021. All rights reserved. ;;
;; Distributed under terms of the GNU General Public License ;;
;; ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
$Revision$
PCI_REG_STATUS_COMMAND = 0x0004
PCI_REG_BAR5 = 0x0024
AHCI_DBGLVL = 0 ; debug output verbosity level. 0 - less verbose, 1 - more verbose
; different SATA device signatures
SATA_SIG_ATA = 0x00000101 ; SATA drive
SATA_SIG_ATAPI = 0xEB140101 ; SATAPI drive
SATA_SIG_SEMB = 0xC33C0101 ; Enclosure management bridge
SATA_SIG_PM = 0x96690101 ; Port multiplier
; Device type constants
AHCI_DEV_NULL = 0
AHCI_DEV_SATA = 1
AHCI_DEV_SEMB = 2
AHCI_DEV_PM = 3
AHCI_DEV_SATAPI = 4
; ATA commands
ATA_IDENTIFY = 0xEC
ATA_CMD_READ_DMA_EX = 0x25
ATA_CMD_WRITE_DMA_EX = 0x35
; ATA constants
ATA_DEV_BUSY = 0x80
ATA_DEV_DRQ = 0x08
; ATAPI commands
ATAPI_IDENTIFY = 0xA1
PRDT_MAX_ENTRIES = 16 ;65535
; bit_ prefix means that its index of bit
; format: bit_AHCI_STR_REG_BIT
bit_AHCI_HBA_CAP2_BOH = 0 ; Supports BIOS/OS Handoff
bit_AHCI_HBA_BOHC_BOS = 0 ; BIOS-Owned Semaphore (BIOS owns controller)
bit_AHCI_HBA_BOHC_OOS = 1 ; OS-Owned Semaphore (OS owns controller)
bit_AHCI_HBA_BOHC_BB = 4 ; BIOS Busy (polling bit while BIOS cleans up
bit_AHCI_HBA_GHC_AHCI_ENABLE = 31 ; Enable AHCI mode
bit_AHCI_HBA_GHC_RESET = 0 ; Reset HBA
bit_AHCI_HBA_GHC_INTERRUPT_ENABLE = 1 ; Enable interrupts from the HBA
bit_AHCI_HBA_PxCMD_ST = 0
bit_AHCI_HBA_PxCMD_FRE = 4
bit_AHCI_HBA_PxCMD_FR = 14
bit_AHCI_HBA_PxCMD_CR = 15
bit_AHCI_HBA_PxIS_TFES = 30
AHCI_HBA_PxCMD_ST = 1 shl 0
AHCI_HBA_PxCMD_FRE = 1 shl 4
AHCI_HBA_PxCMD_FR = 1 shl 14
AHCI_HBA_PxCMD_CR = 1 shl 15
bit_AHCI_H2D_FLAG_CMD = 7
AHCI_HBA_PxSSTS_DET = 0xF
AHCI_HBA_PORT_IPM_ACTIVE = 1
AHCI_HBA_PxSSTS_DET_PRESENT = 3
AHCI_MAX_PORTS = 32 ;
;HBA_MEMORY_SIZE = 0x1100
AHCI_PORT_TIMEOUT = 1000000
; Frame Information Structure Types
FIS_TYPE_REG_H2D = 0x27 ; Register FIS - host to device
FIS_TYPE_REG_D2H = 0x34 ; Register FIS - device to host
FIS_TYPE_DMA_ACT = 0x39 ; DMA activate FIS - device to host
FIS_TYPE_DMA_SETUP = 0x41 ; DMA setup FIS - bidirectional
FIS_TYPE_DATA = 0x46 ; Data FIS - bidirectional
FIS_TYPE_BIST = 0x58 ; BIST activate FIS - bidirectional
FIS_TYPE_PIO_SETUP = 0x5F ; PIO setup FIS - device to host
FIS_TYPE_DEV_BITS = 0xA1 ; Set device bits FIS - device to host
; Generic Host Control registers
struct HBA_MEM
cap dd ? ; 0x00, Host capabilities
ghc dd ? ; 0x04, Global host control
is dd ? ; 0x08, Interrupt status
pi dd ? ; 0x0C, Port implemented
version dd ? ; 0x10, Version
ccc_ctl dd ? ; 0x14, Command completion coalescing control
ccc_pts dd ? ; 0x18, Command completion coalescing ports
em_loc dd ? ; 0x1C, Enclosure management location
em_ctl dd ? ; 0x20, Enclosure management control
cap2 dd ? ; 0x24, Host capabilities extended
bohc dd ? ; 0x28, BIOS/OS handoff control and status
reserved rb (0xA0-HBA_MEM.reserved) ; 0x2C - 0x9F, Reserved
vendor rb (0x100-HBA_MEM.vendor) ; 0xA0 - 0xFF, Vendor specific
ports rb (sizeof.HBA_PORT*AHCI_MAX_PORTS) ; 0x100 - 0x10FF, Port control registers, max AHCI_MAX_PORTS
ends
; Port Control registers
struct HBA_PORT
command_list_base_l dd ? ; 0x00, command list base address, 1K-byte aligned
command_list_base_h dd ? ; 0x04, command list base address upper 32 bits, used on 64 bit systems
fis_base_l dd ? ; 0x08, FIS base address, 256-byte aligned
fis_base_h dd ? ; 0x0C, FIS base address upper 32 bits, used on 64 bit systems
interrupt_status dd ? ; 0x10
interrupt_enable dd ? ; 0x14
command dd ? ; 0x18, command and status
reserved0 dd ? ; 0x1C
task_file_data dd ? ; 0x20
signature dd ? ; 0x24
sata_status dd ? ; 0x28, SATA status (SCR0:SStatus)
sata_control dd ? ; 0x2C, SATA control (SCR2:SControl)
sata_error dd ? ; 0x30, SATA error (SCR1:SError)
sata_active dd ? ; 0x34, SATA active (SCR3:SActive)
command_issue dd ? ; 0x38
sata_notification dd ? ; 0x3C, SATA notification (SCR4:SNotification)
fis_based_switch_control dd ? ; 0x40
reserved1 rd 11 ; 0x44 - 0x6F
vendor rd 4 ; 0x70 - 0x7F, vendor specific
ends
; Command header structure, size = 32 bytes
struct HBA_CMD_HDR
flags1 db ? ; 0bPWACCCCC, P - Prefetchable, W - Write (1: H2D, 0: D2H)
; A - ATAPI, C - Command FIS length in DWORDS, 2 ~ 16
flags2 db ? ; 0bPPPPRCB(Re), P - Port multiplier port, R - Reserved,
; C - Clear busy upon R_OK, B - BIST, Re - Reset
prdtl dw ? ; Physical region descriptor table length in entries
prdbc dd ? ; Physical region descriptor byte count transferred
ctba dd ? ; Command table descriptor base address
ctbau dd ? ; Command table descriptor base address upper 32 bits
rd 4 ; Reserved
ends
; Physical region descriptor table entry, size = 16 bytes
struct HBA_PRDT_ENTRY
dba dd ? ; Data base address
dbau dd ? ; Data base address upper 32 bits
dd ? ; Reserved
flags dd ? ; 0bIR..RD..D, I (1 bit) - Interrupt on completion,
; R (9 bits) - Reserved, D (22 bits) - Byte count, 4M max
ends
struct HBA_CMD_TBL
cfis rb 64 ; 0x00, Command FIS
acmd rb 16 ; 0x40, ATAPI command, 12 or 16 bytes
rb 48 ; 0x50, Reserved
prdt_entry HBA_PRDT_ENTRY ; 0x80, Physical region descriptor table entries, 0 ~ 65535
; so, this structure is variable-length
ends
; Contains virtual mappings for port phys memory regions
struct PORT_DATA
clb dd ? ; Command list base
fb dd ? ; FIS base
ctba_arr rd 32 ; ctba_arr[0] = clb[0].ctba, ... and so on.
port dd ? ; address of correspoding HBA_PORT structure
portno dd ? ; port index, 0..31
drive_type db ? ; drive type
sector_count dq ? ; number of sectors
ctr_ptr dd ? ; pointer to controller to which port belongs
ends
; Register FIS Host to Device
struct FIS_REG_H2D
fis_type db ? ; FIS_TYPE_REG_H2D
flags db ? ; 0bCRRRPPPP, C - 1: Command, 0: Control
; R - Reserved, P - Port multiplier
command db ? ; Command register
featurel db ? ; Feature register, 7:0
lba0 db ? ; LBA low register, 7:0
lba1 db ? ; LBA mid register, 15:8
lba2 db ? ; LBA high register, 23:16
device db ? ; Device register
lba3 db ? ; LBA register, 31:24
lba4 db ? ; LBA register, 39:32
lba5 db ? ; LBA register, 47:40
featureh db ? ; Feature register, 15:8
countl db ? ; Count register, 7:0
counth db ? ; Count register, 15:8
icc db ? ; Isochronous command completion
control db ? ; Control register
rb 4 ; Reserved
ends
; Register FIS Device to Host
struct FIS_REG_D2H
fis_type db ? ; FIS_TYPE_REG_D2H
flags db ? ; 0bRIRPPPP, P - Port multiplier, R - Reserved
; I - Interrupt bit
status db ? ; Status register
error db ? ; Error register
lba0 db ? ; LBA low register, 7:0
lba1 db ? ; LBA mid register, 15:8
lba2 db ? ; LBA high register, 23:16
device db ? ; Device register
lba3 db ? ; LBA register, 31:24
lba4 db ? ; LBA register, 39:32
lba5 db ? ; LBA register, 47:40
db ? ; Reserved
countl db ? ; Count register, 7:0
counth db ? ; Count register, 15:8
rb 2 ; Reserved
rb 4 ; Reserved
ends
; Data FIS Bidirectional
struct FIS_DATA
fis_type db ? ; FIS_TYPE_DATA
flags db ? ; 0bRRRRPPPP, R - Reserved, P - Port multiplier
rb 2 ; Reserved
; DWORD 1 ~ N (?)
data rd 1 ; Payload
ends
; PIO Setup Device to Host
struct FIS_PIO_SETUP
fis_type db ? ; FIS_TYPE_PIO_SETUP
flags db ? ; 0bRIDRPPPP, P - Port multiplier, R - Reserved
; I - Interrupt bit, D - Data transfer direction, 1 - device to host
status db ? ; Status register
error db ? ; Error register
lba0 db ? ; LBA low register, 7:0
lba1 db ? ; LBA mid register, 15:8
lba2 db ? ; LBA high register, 23:16
device db ? ; Device register
lba3 db ? ; LBA register, 31:24
lba4 db ? ; LBA register, 39:32
lba5 db ? ; LBA register, 47:40
db ? ; Reserved
countl db ? ; Count register, 7:0
counth db ? ; Count register, 15:8
db ? ; Reserved
e_status db ? ; New value of status register
tc dw ? ; Transfer count
rb 2 ; Reserved
ends
; DMA Setup Device to Host
struct FIS_DMA_SETUP
fis_type db ? ; FIS_TYPE_DMA_SETUP
flags db ? ; 0bAIDRPPPP, A - Auto-activate. Specifies if DMA Activate FIS is needed,
; I - Interrupt bit, D - Data transfer direction, 1 - device to host,
; R - Reserved, P - Port multiplier
rb 2 ; Reserved
DMAbufferID dq ? ; DMA Buffer Identifier.
; Used to Identify DMA buffer in host memory.
; SATA Spec says host specific and not in Spec.
; Trying AHCI spec might work.
dd ? ; Reserved
DMAbufOffset dd ? ; Byte offset into buffer. First 2 bits must be 0
TransferCount dd ? ; Number of bytes to transfer. Bit 0 must be 0
dd ? ; Reserved
ends
; Set device bits FIS - device to host
struct FIS_DEV_BITS
fis_type db ? ; FIS_TYPE_DEV_BITS
flags db ? ; 0bNIRRPPPP, N - Notification, I - Interrupt,
; R - Reserved, P - Port multiplier
status db ? ; Status register
error db ? ; Error register
protocol dd ? ; Protocol
ends
struct HBA_FIS
dsfis FIS_DMA_SETUP ; 0x00, DMA Setup FIS
rb 4 ; padding
psfis FIS_PIO_SETUP ; 0x20, PIO Setup FIS
rb 12 ; padding
rfis FIS_REG_D2H ; 0x40, Register - Device to Host FIS
rb 4 ; padding
sdbfis FIS_DEV_BITS ; 0x58, Set Device Bit FIS
ufis rb 64 ; 0x60
rb (0x100 - 0xA0) ; 0xA0, Reserved
ends
; --------------------------------------------------
uglobal
align 4
; AHCI controller structure
struct AHCI_CTR
abar dd ? ; pointer to HBA Memory (BAR5) mapped to virtual kernelspace memory
pcidev dd ? ; pointer to corresponding PCIDEV structure
port_data_arr rb (sizeof.PORT_DATA*AHCI_MAX_PORTS)
mutex MUTEX
ends
ctr1_data AHCI_CTR
ctr2_data AHCI_CTR
ctr3_data AHCI_CTR
ctr4_data AHCI_CTR
ctr5_data AHCI_CTR
ctr6_data AHCI_CTR
ctr7_data AHCI_CTR
ctr8_data AHCI_CTR
ctr_ptr dd ?
endg
iglobal
align 4
ahci_callbacks:
dd ahci_callbacks.end - ahci_callbacks
dd 0 ; no close function
dd 0 ; no closemedia function
dd ahci_querymedia
dd ahci_read
dd ahci_write
dd 0 ; no flush function
dd 0 ; use default cache size
.end:
sata_dev_name db 'sd', 0, 0, 0
satapi_dev_name db 'scd', 0, 0, 0
sata_dev_counter dd 0 ; sata devices counter
satapi_dev_counter dd 0 ; satapi devices (optical drives) counter
ctr_counter dd 0 ; controllers counter
disk_to_add_name dd 0 ; local var for ahci_init
endg
; -----------------------------------------------------------------------
; detect ahci controller and initialize
align 4
ahci_init:
mov esi, pcidev_list
mov [ctr_ptr], ctr1_data
mov [sata_dev_counter], 0
.find_ahci_ctr:
mov esi, [esi + PCIDEV.fd]
cmp esi, pcidev_list
jz .end_find_ahci_ctr
mov eax, [esi + PCIDEV.class]
;DEBUGF 1, "K: device class = %x\n", eax
shr eax, 8 ; shift right because lowest 8 bits if ProgIf field
cmp eax, 0x0106 ; 0x01 - Mass Storage Controller class, 0x06 - Serial ATA Controller subclass
jz .ahci_ctr_found
jmp .find_ahci_ctr
.end_find_ahci_ctr:
cmp [ctr_counter], 0
ja @f
DEBUGF 1, "K: AHCI: controllers not found\n"
@@:
ret
.ahci_ctr_found:
push esi
mov ecx, [ctr_ptr]
add ecx, AHCI_CTR.mutex
call mutex_init
mov ecx, [ctr_ptr]
mov [ecx + AHCI_CTR.pcidev], esi
mov eax, [esi+PCIDEV.class]
movzx ebx, byte [esi+PCIDEV.bus]
movzx ecx, byte [esi+PCIDEV.devfn]
shr ecx, 3 ; get rid of 3 lowest bits (function code), the rest bits is device code
movzx edx, byte [esi+PCIDEV.devfn]
and edx, 00000111b ; get only 3 lowest bits (function code)
DEBUGF 1, "K: found AHCI controller, (class, subcl, progif) = %x, bus = %x, device = %x, function = %x\n", eax, ebx, ecx, edx
; get BAR5 value, it is physical address
movzx ebx, [esi + PCIDEV.bus]
movzx ebp, [esi + PCIDEV.devfn]
stdcall pci_read32, ebx, ebp, PCI_REG_BAR5
DEBUGF 1, "K: AHCI controller MMIO = %x\n", eax
mov edi, eax
; get the size of MMIO region
stdcall pci_write32, ebx, ebp, PCI_REG_BAR5, 0xFFFFFFFF
stdcall pci_read32, ebx, ebp, PCI_REG_BAR5
not eax
inc eax
DEBUGF 1, "K: AHCI: MMIO region size = 0x%x bytes\n", eax
; Map MMIO region to virtual memory
stdcall map_io_mem, edi, eax, PG_SWR + PG_NOCACHE
push ecx
mov ecx, [ctr_ptr]
mov [ecx + AHCI_CTR.abar], eax
pop ecx
DEBUGF 1, "K: AHCI controller BAR5 mapped to virtual addr %x\n", eax
; Restore the original BAR5 value
stdcall pci_write32, ebx, ebp, PCI_REG_BAR5, edi
; Enable dma bus mastering, memory space access, clear the "disable interrupts" bit
; Usually, it is already done before us
movzx ebx, [esi + PCIDEV.bus]
movzx ebp, [esi + PCIDEV.devfn]
stdcall pci_read32, ebx, ebp, PCI_REG_STATUS_COMMAND
DEBUGF 1, "K: AHCI: pci_status_command = %x\nEnabling interrupts, DMA bus mastering and memory space access\n", eax
or eax, 0x06 ; pci.command |= 0x06 (dma bus mastering + memory space access)
btr eax, 10 ; clear the "disable interrupts" bit
DEBUGF 1, "K: AHCI: pci_status_command = %x\n", eax
stdcall pci_write32, ebx, ebp, PCI_REG_STATUS_COMMAND, eax
mov esi, [ctr_ptr]
mov esi, [esi + AHCI_CTR.abar]
; ; Print some register values to debug board
; DEBUGF 1, "K: AHCI: HBA.cap = %x, HBA.ghc = %x, HBA_MEM.version = %x\n", [esi + HBA_MEM.cap], [esi + HBA_MEM.ghc], [esi + HBA_MEM.version]
;-------------------------------------------------------
; Request BIOS/OS ownership handoff, if supported. (TODO check correctness)
;mov ebx, [esi + HBA_MEM.cap2]
;DEBUGF 1, "K: AHCI: HBA_MEM.cap2 = %x\n", ebx
bt [esi + HBA_MEM.cap2], bit_AHCI_HBA_CAP2_BOH
jnc .end_handoff
DEBUGF 1, "K: AHCI: requesting AHCI ownership change...\n"
bts [esi + HBA_MEM.bohc], bit_AHCI_HBA_BOHC_OOS
.wait_not_bos:
bt [esi + HBA_MEM.bohc], bit_AHCI_HBA_BOHC_BOS
jc .wait_not_bos
mov ebx, 3
call delay_hs
; if Bios Busy is still set after 30 mS, wait 2 seconds.
bt [esi + HBA_MEM.bohc], bit_AHCI_HBA_BOHC_BB
jnc @f
mov ebx, 200
call delay_hs
@@:
DEBUGF 1, "K: AHCI: ownership change completed.\n"
.end_handoff:
;-------------------------------------------------------
; enable the AHCI and reset it
bts [esi + HBA_MEM.ghc], bit_AHCI_HBA_GHC_AHCI_ENABLE
bts [esi + HBA_MEM.ghc], bit_AHCI_HBA_GHC_RESET
; wait for reset to complete
.wait_reset:
bt [esi + HBA_MEM.ghc], bit_AHCI_HBA_GHC_RESET
jc .wait_reset
; enable the AHCI and interrupts
bts [esi + HBA_MEM.ghc], bit_AHCI_HBA_GHC_AHCI_ENABLE
bts [esi + HBA_MEM.ghc], bit_AHCI_HBA_GHC_INTERRUPT_ENABLE
mov ebx, 2
call delay_hs
DEBUGF 1, "K: AHCI: caps: %x %x, ver: %x, ghc: %x, pi: %x\n", [esi + HBA_MEM.cap], [esi + HBA_MEM.cap2], [esi + HBA_MEM.version], [esi + HBA_MEM.ghc], [esi + HBA_MEM.pi]
; TODO:
; calculate irq line
; ahciHBA->ghc |= AHCI_GHC_IE;
; IDT::RegisterInterruptHandler(irq, InterruptHandler);
; ahciHBA->is = 0xffffffff;
xor ebx, ebx
.detect_drives:
cmp ebx, AHCI_MAX_PORTS
jae .end_detect_drives
; if port with index ebx is not implemented then go to next
mov ecx, [esi + HBA_MEM.pi]
bt ecx, ebx
jnc .continue_detect_drives
mov edi, ebx
imul edi, sizeof.HBA_PORT
add edi, HBA_MEM.ports
add edi, esi
; now edi - base of HBA_MEM.ports[ebx]
DEBUGF 1, "K: AHCI: port %d, cmd = %x, ssts = %x\n", ebx, [edi + HBA_PORT.command], [edi + HBA_PORT.sata_status]
; If port is not idle force it to be idle
mov eax, [edi + HBA_PORT.command]
and eax, (AHCI_HBA_PxCMD_ST or AHCI_HBA_PxCMD_CR or AHCI_HBA_PxCMD_FRE or AHCI_HBA_PxCMD_FR)
test eax, eax
jz @f
mov eax, edi
DEBUGF 1, "ahci_stop_cmd..\n"
call ahci_stop_cmd
@@:
; TODO: what is purpose of this block of code ?
; Reset port, disable slumber and partial state
; mov [edi + HBA_PORT.sata_control], 0x301
; push ebx
; mov ebx, 5 ; wait 50 ms
; call delay_hs
; pop ebx
; mov [edi + HBA_PORT.sata_control], 0x300
; if(abar->cap & HBA_MEM_CAP_SSS)
; {
; abar->ports[i].cmd |= (HBA_PxCMD_SUD | HBA_PxCMD_POD | HBA_PxCMD_ICC);
; Sleep(10);
; }
; rewritten to:
bt [esi + HBA_MEM.cap], 27 ; check Supports Staggered Spin-up bit in capabilities
jnc @f
DEBUGF 1, "Supports Staggered Spin-up, spinning up the port..\n"
or [edi + HBA_PORT.command], (0x0002 or 0x0004 or 0x10000000)
push ebx
mov ebx, 10 ; wait 100 ms
call delay_hs
pop ebx
@@:
; Clear interrupt status and error status
mov [edi + HBA_PORT.sata_error], 0xFFFFFFFF
mov [edi + HBA_PORT.interrupt_status], 0xFFFFFFFF
; ------------------------------------------
mov ecx, [edi + HBA_PORT.sata_status]
shr ecx, 8
and ecx, 0x0F
cmp ecx, AHCI_HBA_PORT_IPM_ACTIVE
jne .continue_detect_drives
mov ecx, [edi + HBA_PORT.sata_status]
and ecx, AHCI_HBA_PxSSTS_DET
cmp ecx, AHCI_HBA_PxSSTS_DET_PRESENT
jne .continue_detect_drives
; DEBUGF 1, "K: AHCI: found drive at port %d, cmd = 0x%x, ssts = 0x%x, signature = 0x%x\n", ebx, [edi + HBA_PORT.command], [edi + HBA_PORT.sata_status], [edi + HBA_PORT.signature]
mov ecx, ebx
imul ecx, sizeof.PORT_DATA
add ecx, AHCI_CTR.port_data_arr
add ecx, [ctr_ptr]
mov eax, [ctr_ptr]
mov [ecx + PORT_DATA.ctr_ptr], eax ; to which controller the port belongs
stdcall ahci_port_rebase, edi, ebx, ecx
; DEBUGF 1, "K: AHCI: After REBASING, signature = 0x%x\n", [edi + HBA_PORT.signature]
; Determine drive type by checking port signature
.switch_sig:
cmp [edi + HBA_PORT.signature], SATA_SIG_ATA
mov eax, AHCI_DEV_SATA
jz .end_switch_sig
cmp [edi + HBA_PORT.signature], SATA_SIG_ATAPI
mov eax, AHCI_DEV_SATAPI
jz .end_switch_sig
cmp [edi + HBA_PORT.signature], SATA_SIG_SEMB
mov eax, AHCI_DEV_SEMB
jz .end_switch_sig
cmp [edi + HBA_PORT.signature], SATA_SIG_PM
mov eax, AHCI_DEV_PM
jz .end_switch_sig
DEBUGF 1, "Unknown device signature\n"
mov eax, AHCI_DEV_NULL
.end_switch_sig:
mov [ecx + PORT_DATA.drive_type], al
DEBUGF 1, "K: AHCI: found drive on port %u: TYPE = %u\n", ebx, [ecx + PORT_DATA.drive_type]
stdcall ahci_port_identify, ecx
; register drive as disk in system if it is SATA or SATAPI:
cmp [ecx + PORT_DATA.drive_type], AHCI_DEV_SATA
je .disk_add_sata
cmp [ecx + PORT_DATA.drive_type], AHCI_DEV_SATAPI
je .disk_add_satapi
jne .after_add_disk ; else skip adding disk code
.disk_add_sata:
push ecx
mov eax, [sata_dev_counter]
inc [sata_dev_counter]
xor edx, edx
mov ecx, 10
div ecx ; eax = sata_dev_counter / 10, edx = sata_dev_counter % 10
test eax, eax
jz .concat_one_digit
add al, '0'
mov byte [sata_dev_name + 2], al
add dl, '0'
mov byte [sata_dev_name + 3], dl
jmp .endif1
.concat_one_digit:
add dl, '0'
mov byte [sata_dev_name + 2], dl
.endif1:
mov [disk_to_add_name], sata_dev_name
pop ecx
jmp .disk_add
.disk_add_satapi:
push ecx
mov eax, [satapi_dev_counter]
inc [satapi_dev_counter]
xor edx, edx
mov ecx, 10
div ecx ; eax = satapi_dev_counter / 10, edx = satapi_dev_counter % 10
test eax, eax
jz .concat_one_digit2
add al, '0'
mov byte [satapi_dev_name + 3], al
add dl, '0'
mov byte [satapi_dev_name + 4], dl
jmp .endif2
.concat_one_digit2:
add dl, '0'
mov byte [satapi_dev_name + 3], dl
.endif2:
mov [disk_to_add_name], satapi_dev_name
pop ecx
.disk_add:
DEBUGF 1, "adding '%s'\n", [disk_to_add_name]
push ecx
stdcall disk_add, ahci_callbacks, [disk_to_add_name], ecx, 0
pop ecx
test eax, eax
jz .disk_add_fail
push ecx
stdcall disk_media_changed, eax, 1 ; system will scan for partitions on disk
pop ecx
jmp .after_add_disk
.disk_add_fail:
DEBUGF 1, "Failed to add disk\n"
.after_add_disk:
.continue_detect_drives:
inc ebx
jmp .detect_drives
.end_detect_drives:
pop esi
add [ctr_ptr], sizeof.AHCI_CTR
inc [ctr_counter]
cmp [ctr_counter], 8
jnz .find_ahci_ctr
DEBUGF 1, "AHCI: reached controllers number limit\n"
ret
; -------------------------------------------------
modelstr rb 42
; Identify drive on port ; TODO check
; in: pdata - address of PORT_DATA structure
proc ahci_port_identify stdcall, pdata: dword
locals
cmdslot dd ?
cmdheader dd ?
cmdtable dd ?
buf_phys dd ?
buf_virt dd ?
endl
pushad
mov esi, [pdata] ; esi - address of PORT_DATA struct of port
stdcall ahci_find_cmdslot, esi
cmp eax, -1
jne .cmdslot_found
DEBUGF 1, "No free cmdslot on port %u\n", [esi + PORT_DATA.portno]
jmp .ret
.cmdslot_found:
mov [cmdslot], eax
; DEBUGF 1, "Found free cmdslot %u on port %u\n", [cmdslot], [esi + PORT_DATA.portno]
shl eax, BSF sizeof.HBA_CMD_HDR
add eax, [esi + PORT_DATA.clb]
mov [cmdheader], eax ; address of virtual mapping of command header
mov eax, [cmdslot]
mov eax, [esi + eax*4 + PORT_DATA.ctba_arr]
mov [cmdtable], eax ; address of virtual mapping of command table of command header
stdcall _memset, eax, 0, sizeof.HBA_CMD_TBL
call alloc_page
mov [buf_phys], eax
stdcall map_io_mem, eax, 4096, PG_NOCACHE + PG_SWR ; map to virt memory so we can work with it
mov [buf_virt], eax
mov eax, [cmdtable]
mov ebx, [buf_phys]
mov dword [eax + HBA_CMD_TBL.prdt_entry + HBA_PRDT_ENTRY.dba], ebx
mov dword [eax + HBA_CMD_TBL.prdt_entry + HBA_PRDT_ENTRY.dbau], 0
and [eax + HBA_CMD_TBL.prdt_entry + HBA_PRDT_ENTRY.flags], not 0x3FFFFF ; zero out lower 22 bits, they used for byte count
or [eax + HBA_CMD_TBL.prdt_entry + HBA_PRDT_ENTRY.flags], 512 - 1 ; reason why -1 see in spec on this field
; or [eax + HBA_CMD_TBL.prdt_entry + HBA_PRDT_ENTRY.flags], 1 shl 31 ; enable interrupt on completion
mov eax, [cmdheader]
and [eax + HBA_CMD_HDR.flags1], not 0x1F ; zero out lower 5 bits, they will be used for cfl
or [eax + HBA_CMD_HDR.flags1], (sizeof.FIS_REG_H2D / 4) ; set command fis length in dwords
movzx bx, [eax + HBA_CMD_HDR.flags1]
btr bx, 6 ; flag W = 0
mov [eax + HBA_CMD_HDR.flags1], bl
movzx bx, [eax + HBA_CMD_HDR.flags2]
btr bx, 2 ; flag C = 0
mov [eax + HBA_CMD_HDR.flags2], bl
mov [eax + HBA_CMD_HDR.prdtl], 1
mov eax, [cmdtable]
mov byte [eax + HBA_CMD_TBL.cfis + FIS_REG_H2D.fis_type], FIS_TYPE_REG_H2D
movzx ebx, byte [eax + HBA_CMD_TBL.cfis + FIS_REG_H2D.flags]
bts ebx, bit_AHCI_H2D_FLAG_CMD ; Set Command bit in H2D FIS.
mov byte [eax + HBA_CMD_TBL.cfis + FIS_REG_H2D.flags], bl
; Choose identify command depending on drive type
mov byte [eax + HBA_CMD_TBL.cfis + FIS_REG_H2D.command], ATA_IDENTIFY
cmp [esi + PORT_DATA.drive_type], AHCI_DEV_SATAPI
jne @f
mov byte [eax + HBA_CMD_TBL.cfis + FIS_REG_H2D.command], ATAPI_IDENTIFY
@@:
mov byte [eax + HBA_CMD_TBL.cfis + FIS_REG_H2D.device], 0
mov edi, [esi + PORT_DATA.port] ; edi - address of HBA_PORT struct of port
; Wait on previous command to complete, before issuing new command.
stdcall ahci_port_wait, edi, AHCI_PORT_TIMEOUT
; DEBUGF 1, "eax = %x\n", eax
; TODO check eax error value
mov eax, [cmdslot]
bts [edi + HBA_PORT.command_issue], eax ; Issue the command
; Wait for command completion
stdcall ahci_port_cmd_wait, edi, eax;, AHCI_PORT_CMD_TIMEOUT
; DEBUGF 1, " eax = %x\n", eax
; TODO check eax error value
; DEBUGF 1, "sata_error register = 0x%x\n", [edi + HBA_PORT.sata_error]
mov esi, [buf_virt]
add esi, 27*2
mov edi, modelstr
mov ecx, ((46-27)+1)*2
cld
rep movsb
mov byte [edi], 0
stdcall swap_bytes_in_words, modelstr, (46-27)+1
DEBUGF 1, "IDENTIFICATION RESULT: MODEL = %s\n", modelstr
mov esi, [pdata]
cmp [esi + PORT_DATA.drive_type], AHCI_DEV_SATAPI
je .satapi_get_capacity
.sata_get_capacity:
mov esi, [buf_virt]
mov eax, [esi + 200]
mov edx, [esi + 200 + 4]
DEBUGF 1, "lba48 mode sector count = 0x%x:%x\n", edx, eax
mov ebx, [pdata]
mov dword [ebx + PORT_DATA.sector_count], eax
mov dword [ebx + PORT_DATA.sector_count + 4], edx
shrd eax, edx, 11 ; i.e *512 / 1024 / 1024, 512 - sector size
DEBUGF 1, "disk capacity = %u MiB ", eax
shrd eax, edx, 10 ; / 1024
DEBUGF 1, "= %u GiB\n", eax
jmp .end_get_capacity
.satapi_get_capacity:
mov eax, [cmdtable]
mov ebx, [buf_phys]
mov dword [eax + HBA_CMD_TBL.prdt_entry + HBA_PRDT_ENTRY.dba], ebx
mov dword [eax + HBA_CMD_TBL.prdt_entry + HBA_PRDT_ENTRY.dbau], 0
and [eax + HBA_CMD_TBL.prdt_entry + HBA_PRDT_ENTRY.flags], not 0x3FFFFF ; zero out lower 22 bits, they used for byte count
or [eax + HBA_CMD_TBL.prdt_entry + HBA_PRDT_ENTRY.flags], 2048 - 1 ; reason why -1 see in spec on this field
; or [eax + HBA_CMD_TBL.prdt_entry + HBA_PRDT_ENTRY.flags], 1 shl 31 ; enable interrupt on completion
mov byte [eax + HBA_CMD_TBL.cfis + FIS_REG_H2D.command], 0xA0 ; TODO: move to ATA_PACKET const
mov byte [eax + HBA_CMD_TBL.acmd], 0x25 ; means: cmd_table->acmd[0] = ATAPI_READ_CAPACITY >> 8; TODO use consts.
mov edi, [esi + PORT_DATA.port] ; edi - address of HBA_PORT struct of port
; Wait on previous command to complete, before issuing new command.
stdcall ahci_port_wait, edi, AHCI_PORT_TIMEOUT
; DEBUGF 1, "eax = %x\n", eax
; TODO check eax error value
mov eax, [cmdslot]
bts [edi + HBA_PORT.command_issue], eax ; Issue the command
; Wait for command completion
stdcall ahci_port_cmd_wait, edi, eax;, AHCI_PORT_CMD_TIMEOUT
; DEBUGF 1, ". sata_error register = 0x%x\n", [edi + HBA_PORT.sata_error]
mov esi, [buf_virt]
mov eax, [esi]
mov edx, [esi + 4]
DEBUGF 1, "ATAPI sector count = 0x%x:%x\n", edx, eax
mov ebx, [pdata]
mov dword [ebx + PORT_DATA.sector_count], eax
mov dword [ebx + PORT_DATA.sector_count + 4], edx
shrd eax, edx, 9 ; i.e *2048 / 1024 / 1024, 2048 - sector size
DEBUGF 1, "ATAPI disk capacity = %u MiB ", eax
shrd eax, edx, 10 ; / 1024
DEBUGF 1, "= %u GiB\n", eax
; aboba
.end_get_capacity:
.ret:
popad
ret
endp
proc ahci_querymedia stdcall, pdata, mediainfo
push ecx edx
mov eax, [mediainfo]
mov edx, [pdata]
mov [eax + DISKMEDIAINFO.Flags], 0
mov [eax + DISKMEDIAINFO.SectorSize], 512 ; TODO: use const
cmp [edx + PORT_DATA.drive_type], AHCI_DEV_SATAPI
jne @f
mov [eax + DISKMEDIAINFO.SectorSize], 2048 ; TODO: use const
@@:
mov ecx, dword[edx + PORT_DATA.sector_count]
mov dword [eax + DISKMEDIAINFO.Capacity], ecx
mov ecx, dword[edx + PORT_DATA.sector_count + 4]
mov dword [eax + DISKMEDIAINFO.Capacity + 4], ecx
pop edx ecx
xor eax, eax
ret
endp
; Read/write sectors, note: currently work for SATA, not SATAPI
; return value: 0 = success, otherwise = error
proc ahci_rw_sectors stdcall pdata: dword, vbuf: dword, startsector: qword, numsectors: dword, is_write: dword
locals
cmdslot dd ?
cmdheader dd ?
cmdtable dd ?
vbuf_orig dd ?
vbuf_len dd ?
phys_region_start dd ?
new_phys_region_start dd ?
cur_prd dd ?
cur_phys dd ?
dbc dd ?
cur_phys_page dd ?
next_phys_page dd ?
cur_antioffset dd ?
prdt_bytes_total dd ?
endl
pushad
DEBUGF AHCI_DBGLVL, " ahci_rw_sectors: buffer = 0x%x, startsector = 0x%x:%x, numsectors = %u, is_write = %u\n", [vbuf], [startsector], [startsector + 4], [numsectors], [is_write]:1
mov esi, [pdata] ; esi - address of PORT_DATA struct of port
stdcall ahci_find_cmdslot, esi
cmp eax, -1
jne .cmdslot_found
DEBUGF AHCI_DBGLVL, "No free cmdslot on port %u\n", [esi + PORT_DATA.portno]
jmp .fail
.cmdslot_found:
mov [cmdslot], eax
DEBUGF AHCI_DBGLVL, "Found free cmdslot %u on port %u\n", [cmdslot], [esi + PORT_DATA.portno]
shl eax, BSF sizeof.HBA_CMD_HDR
add eax, [esi + PORT_DATA.clb]
mov [cmdheader], eax ; address of virtual mapping of command header
mov eax, [cmdslot]
mov eax, [esi + eax*4 + PORT_DATA.ctba_arr]
mov [cmdtable], eax ; address of virtual mapping of command table of command header
mov eax, [cmdheader]
and [eax + HBA_CMD_HDR.flags1], not 0x1F ; zero out lower 5 bits, they will be used for cfl
or [eax + HBA_CMD_HDR.flags1], (sizeof.FIS_REG_H2D / 4) ; set command fis length in dwords
movzx bx, [eax + HBA_CMD_HDR.flags1]
btr bx, 6 ; flag W = 0
cmp [is_write], 1 ; if is_write then set W flag
jne @f
bts bx, 6
@@:
mov [eax + HBA_CMD_HDR.flags1], bl
movzx bx, [eax + HBA_CMD_HDR.flags2]
btr bx, 2 ; flag C = 0
mov [eax + HBA_CMD_HDR.flags2], bl
mov eax, [vbuf]
mov [vbuf_orig], eax
mov ebx, [numsectors]
shl ebx, 9 ; *= 512
mov [vbuf_len], ebx
DEBUGF AHCI_DBGLVL, "vbuf_len = %u bytes\n", ebx
mov ebx, [vbuf]
and ebx, 0xFFF
mov eax, [vbuf]
call get_pg_addr
add eax, ebx
mov [phys_region_start], eax
mov [prdt_bytes_total], 0
mov [cur_prd], 0
.fill_prdt:
cmp [vbuf_len], 0
jbe .fill_prdt_end
mov eax, [vbuf]
call get_pg_addr
mov [cur_phys_page], eax
mov eax, [vbuf]
add eax, 4096
call get_pg_addr
mov [next_phys_page], eax
mov eax, 4096
mov ebx, [vbuf]
and ebx, 0xFFF
sub eax, ebx
mov [cur_antioffset], eax
mov eax, [cur_phys_page]
add eax, ebx
mov [cur_phys], eax
.check_if1:
mov eax, [vbuf_len]
cmp eax, [cur_antioffset]
ja .check_if2
mov eax, [cur_phys]
sub eax, [phys_region_start]
add eax, [vbuf_len]
dec eax
mov [dbc], eax
mov eax, [next_phys_page]
mov [new_phys_region_start], eax
jmp .add_prd
.check_if2:
mov eax, [cur_phys]
add eax, [cur_antioffset]
cmp eax, [next_phys_page]
je .check_if3
mov eax, [cur_phys]
add eax, [cur_antioffset]
sub eax, [phys_region_start]
dec eax
mov [dbc], eax
mov eax, [next_phys_page]
mov [new_phys_region_start], eax
jmp .add_prd
.check_if3:
mov eax, [cur_phys]
add eax, [cur_antioffset]
sub eax, [phys_region_start]
cmp eax, 4*1024*1024
jb .after_ifs
mov [dbc], 4*1024*1024 - 1
mov eax, [phys_region_start]
add eax, 4*1024*1024
jmp .add_prd
.after_ifs:
jmp .step_next
.add_prd:
mov ebx, [cur_prd]
shl ebx, BSF sizeof.HBA_PRDT_ENTRY
add ebx, [cmdtable]
add ebx, HBA_CMD_TBL.prdt_entry ; now ebx - address of 'th prdt_entry
DEBUGF AHCI_DBGLVL, "Added PRDT entry: dba = 0x%x, dbc = %u\n", [phys_region_start], [dbc]
mov eax, [phys_region_start]
mov [ebx + HBA_PRDT_ENTRY.dba], eax
mov [ebx + HBA_PRDT_ENTRY.dbau], 0
and [ebx + HBA_PRDT_ENTRY.flags], not 0x3FFFFF ; zero out lower 22 bits, they used for byte count
mov eax, [dbc]
or [ebx + HBA_PRDT_ENTRY.flags], eax
inc [cur_prd]
mov eax, [dbc]
inc eax
add [prdt_bytes_total], eax
mov eax, [new_phys_region_start]
mov [phys_region_start], eax
cmp [cur_prd], PRDT_MAX_ENTRIES
jne @f
jmp .fill_prdt_end
@@:
.step_next:
mov eax, [vbuf_len]
cmp eax, [cur_antioffset]
jbe @f
mov eax, [cur_antioffset]
@@:
add [vbuf], eax
sub [vbuf_len], eax
jmp .fill_prdt
.fill_prdt_end:
mov eax, [cmdheader]
mov ebx, [cur_prd]
DEBUGF AHCI_DBGLVL, " PRDTL = %u\n", ebx
mov [eax + HBA_CMD_HDR.prdtl], bx
mov eax, [prdt_bytes_total]
DEBUGF AHCI_DBGLVL, " prdt_bytes_total = %u\n", eax
shr eax, 9 ; /= 512
mov [numsectors], eax
mov eax, [cmdtable]
mov byte [eax + HBA_CMD_TBL.cfis + FIS_REG_H2D.fis_type], FIS_TYPE_REG_H2D
movzx ebx, byte [eax + HBA_CMD_TBL.cfis + FIS_REG_H2D.flags]
bts ebx, bit_AHCI_H2D_FLAG_CMD ; Set Command bit in H2D FIS.
mov byte [eax + HBA_CMD_TBL.cfis + FIS_REG_H2D.flags], bl
mov byte [eax + HBA_CMD_TBL.cfis + FIS_REG_H2D.command], ATA_CMD_READ_DMA_EX
cmp [is_write], 1
jne @f
mov byte [eax + HBA_CMD_TBL.cfis + FIS_REG_H2D.command], ATA_CMD_WRITE_DMA_EX
@@:
mov ebx, dword [startsector]
mov byte [eax + HBA_CMD_TBL.cfis + FIS_REG_H2D.lba0], bl
shr ebx, 8
mov byte [eax + HBA_CMD_TBL.cfis + FIS_REG_H2D.lba1], bl
shr ebx, 8
mov byte [eax + HBA_CMD_TBL.cfis + FIS_REG_H2D.lba2], bl
mov byte [eax + HBA_CMD_TBL.cfis + FIS_REG_H2D.device], 1 shl 6 ; LBA mode
shr ebx, 8
mov byte [eax + HBA_CMD_TBL.cfis + FIS_REG_H2D.lba3], bl
mov ebx, dword [startsector + 4]
mov byte [eax + HBA_CMD_TBL.cfis + FIS_REG_H2D.lba4], bl
shr ebx, 8
mov byte [eax + HBA_CMD_TBL.cfis + FIS_REG_H2D.lba5], bl
mov ebx, [numsectors]
mov byte [eax + HBA_CMD_TBL.cfis + FIS_REG_H2D.countl], bl
shr ebx, 8
mov byte [eax + HBA_CMD_TBL.cfis + FIS_REG_H2D.counth], bl
mov edi, [esi + PORT_DATA.port] ; edi - address of HBA_PORT struct of port
; Wait on previous command to complete, before issuing new command.
stdcall ahci_port_wait, edi, AHCI_PORT_TIMEOUT
mov eax, [cmdslot]
bts [edi + HBA_PORT.command_issue], eax ; Issue the command
; Wait for command completion
stdcall ahci_port_cmd_wait, edi, eax;, AHCI_PORT_CMD_TIMEOUT
DEBUGF AHCI_DBGLVL, "sata_error register = 0x%x\n", [edi + HBA_PORT.sata_error]
DEBUGF AHCI_DBGLVL, "R/W completed\n"
; xor ecx, ecx
; mov esi, [vbuf_orig]
; .print_data:
; cmp ecx, 512
; jae .end_print_data
; mov al, byte [esi + ecx]
; mov byte [tmpstr], al
; mov byte [tmpstr + 1], 0
; DEBUGF 1, "0x%x(%s) ", al:2, tmpstr
; inc ecx
; jmp .print_data
; .end_print_data:
; DEBUGF 1, "\n"
popad
;mov eax, [cmdheader]
;mov eax, [eax + HBA_CMD_HDR.prdbc]
mov eax, [numsectors]
shl eax, 9 ; *= 512
ret
.fail:
popad
xor eax, eax
ret
endp
tmpstr rb 16
; Read sectors
; return value: 0 = success, otherwise = error
proc ahci_read stdcall pdata: dword, buffer: dword, startsector: qword, numsectors_ptr:dword
locals
numsectors dd ?
endl
pushad
mov ecx, [pdata]
mov ecx, [ecx + PORT_DATA.ctr_ptr]
mov ecx, [ecx + AHCI_CTR.mutex]
call mutex_lock
mov eax, [numsectors_ptr]
mov eax, [eax]
mov [numsectors], eax
DEBUGF AHCI_DBGLVL, " ahci_read: buffer = 0x%x, startsector = 0x%x:%x, numsectors = %u\n", [buffer], [startsector], [startsector + 4], eax
xor ecx, ecx ; how many sectors have been read
.read_loop:
cmp ecx, [numsectors]
jae .read_loop_end
; mov eax, [buffer]
; call get_pg_addr
; DEBUGF 1, "buf phys = 0x%x\n", eax
; mov eax, [buffer]
; add eax, 4096
; call get_pg_addr
; DEBUGF 1, "buf + 4096 phys = 0x%x\n", eax
mov ebx, [numsectors]
sub ebx, ecx
; DEBUGF 1, "buffer = 0x%x\n", [buffer]
stdcall ahci_rw_sectors, [pdata], [buffer], dword [startsector], dword [startsector + 4], ebx, 0
;; TODO check if eax == 0 ?
DEBUGF AHCI_DBGLVL, " EAX = 0x%x\n", eax
add [buffer], eax
shr eax, 9 ; /= 512
add ecx, eax
add dword [startsector], eax
adc dword [startsector + 4], 0
jmp .read_loop
.read_loop_end:
mov ecx, [pdata]
mov ecx, [ecx + PORT_DATA.ctr_ptr]
mov ecx, [ecx + AHCI_CTR.mutex]
call mutex_unlock
popad
xor eax, eax
ret
endp
; Write sectors
; return value: 0 = success, otherwise = error
proc ahci_write stdcall pdata: dword, buffer: dword, startsector: qword, numsectors_ptr:dword
locals
numsectors dd ?
endl
pushad
mov ecx, [pdata]
mov ecx, [ecx + PORT_DATA.ctr_ptr]
mov ecx, [ecx + AHCI_CTR.mutex]
call mutex_lock
mov eax, [numsectors_ptr]
mov eax, [eax]
mov [numsectors], eax
DEBUGF AHCI_DBGLVL, " ahci_write: buffer = 0x%x, startsector = 0x%x:%x, numsectors = %u\n", [buffer], [startsector], [startsector + 4], eax
xor ecx, ecx ; how many sectors have been read
.write_loop:
cmp ecx, [numsectors]
jae .write_loop_end
mov ebx, [numsectors]
sub ebx, ecx
stdcall ahci_rw_sectors, [pdata], [buffer], dword [startsector], dword [startsector + 4], ebx, 1
;; TODO check if eax == 0 ?
DEBUGF AHCI_DBGLVL, " EAX = 0x%x\n", eax
add [buffer], eax
shr eax, 9 ; /= 512
add ecx, eax
add dword [startsector], eax
adc dword [startsector + 4], 0
jmp .write_loop
.write_loop_end:
mov ecx, [pdata]
mov ecx, [ecx + PORT_DATA.ctr_ptr]
mov ecx, [ecx + AHCI_CTR.mutex]
call mutex_unlock
popad
xor eax, eax
ret
endp
; Start command engine
; in: eax - address of HBA_PORT structure
ahci_start_cmd:
.wait_cr: ; Wait until CR (bit15) is cleared
bt [eax + HBA_PORT.command], bit_AHCI_HBA_PxCMD_CR
jc .wait_cr
; Set FRE (bit4) and ST (bit0)
bts [eax + HBA_PORT.command], bit_AHCI_HBA_PxCMD_FRE
bts [eax + HBA_PORT.command], bit_AHCI_HBA_PxCMD_ST
; maybe here call ahci flush cmd ? TODO (see seakernel)
ret
; Stop command engine
; in: eax - address of HBA_PORT structure
ahci_stop_cmd:
btr [eax + HBA_PORT.command], bit_AHCI_HBA_PxCMD_ST ; Clear ST (bit0)
btr [eax + HBA_PORT.command], bit_AHCI_HBA_PxCMD_FRE ; Clear FRE (bit4)
.wait_fr_cr: ; Wait until FR (bit14), CR (bit15) are cleared
bt [eax + HBA_PORT.command], bit_AHCI_HBA_PxCMD_FR
jc .wait_fr_cr
bt [eax + HBA_PORT.command], bit_AHCI_HBA_PxCMD_CR
jc .wait_fr_cr
ret
; waits until the port is no longer busy before issuing a new command
; in: [port] - address of HBA_PORT structure
; [timeout] - timeout (in iterations)
; out: eax = 0 if success, 1 if timeout expired
proc ahci_port_wait stdcall, port: dword, timeout: dword
push ebx ecx
mov ebx, [port]
xor ecx, ecx
.wait:
cmp ecx, [timeout]
jae .wait_end
mov eax, [ebx + HBA_PORT.task_file_data]
and eax, ATA_DEV_BUSY or ATA_DEV_DRQ
test eax, eax
jz .wait_end
inc ecx
jmp .wait
.wait_end:
xor eax, eax
DEBUGF AHCI_DBGLVL, "port wait counter = %u\n", ecx
cmp ecx, [timeout] ; if they equal it means port is hung
setz al
pop ecx ebx
ret
endp
; Wait for command completion
; in: [port] - address of HBA_PORT structure
; [cmdslot] - number of command slot
; out: eax = 0 if success, 1 if error
proc ahci_port_cmd_wait stdcall, port: dword, cmdslot: dword ;, timeout: dword
push ebx ecx edx
mov ebx, [port]
mov edx, [cmdslot]
xor eax, eax
xor ecx, ecx
.wait:
bt [ebx + HBA_PORT.command_issue], edx
jnc .wait_end
bt [ebx + HBA_PORT.interrupt_status], bit_AHCI_HBA_PxIS_TFES ; check for Task File Error
jc .error
inc ecx
jmp .wait
.wait_end:
DEBUGF AHCI_DBGLVL, "port cmd wait counter = %u\n", ecx
bt [ebx + HBA_PORT.interrupt_status], bit_AHCI_HBA_PxIS_TFES ; check for Task File Error
jc .error
jmp .ret
.error:
mov eax, 1
.ret:
pop edx ecx ebx
ret
endp
; ; The commands may not take effect until the command
; ; register is read again by software, because reasons.
; ; in: eax - address of HBA_PORT structure
; ; out: eax - command register value
; ahci_flush_cmd:
; mov eax, [eax + HBA_PORT.command]
; ret
; ; Send command to port
; ; in: eax - address of HBA_PORT structure
; ; ebx - index of command slot
; ahci_send_cmd:
; push ecx
; mov [eax + HBA_PORT.interrupt_status], 0xFFFFFFFF
; mov cl, bl
; mov [eax + HBA_PORT.command_issue], 1
; shl [eax + HBA_PORT.command_issue], cl
; call ahci_flush_cmd
; pop ecx
; ret
; ---------------------------------------------------------------------------
; in: port - address of HBA_PORT structure
; portno - port index (0..31)
; pdata - address of PORT_DATA structure
; out:
; rebases port and fills pdata with mappings
proc ahci_port_rebase stdcall, port: dword, portno: dword, pdata: dword
locals
phys_page1 dd ?
virt_page1 dd ?
phys_page23 dd ?
virt_page23 dd ?
tmp dd ?
endl
pushad
DEBUGF 1, "Rebasing port %u\n", [portno]
mov eax, [port]
call ahci_stop_cmd
; Command list entry size = 32
; Command list entry maxim count = 32
; Command list maxim size = 32*32 = 1K per port
call alloc_page
mov [phys_page1], eax
stdcall map_io_mem, eax, 4096, PG_NOCACHE + PG_SWR ; map to virt memory so we can work with it
mov [virt_page1], eax
mov esi, [port]
mov ebx, [phys_page1]
mov [esi + HBA_PORT.command_list_base_l], ebx ; set the command list base
mov [esi + HBA_PORT.command_list_base_h], 0 ; zero upper 32 bits of addr cause we are 32 bit os
mov edi, [pdata]
mov ebx, [virt_page1]
mov [edi + PORT_DATA.clb], ebx ; set pdata->clb
mov eax, [port]
mov [edi + PORT_DATA.port], eax ; set pdata->port
mov eax, [portno] ; set pdata->portno
mov [edi + PORT_DATA.portno], eax
stdcall _memset, ebx, 0, 1024 ; zero out the command list
; FIS entry size = 256 bytes per port
mov eax, [phys_page1]
add eax, 1024
mov [esi + HBA_PORT.fis_base_l], eax
mov [esi + HBA_PORT.fis_base_h], 0
mov eax, [virt_page1]
add eax, 1024
mov [edi + PORT_DATA.fb], eax ; set pdata->fb
stdcall _memset, eax, 0, 256 ; zero out
stdcall alloc_pages, 32*(64 + 16 + 48 + PRDT_MAX_ENTRIES*16)/4096
mov [phys_page23], eax
stdcall map_io_mem, eax, 32*(64 + 16 + 48 + PRDT_MAX_ENTRIES*16), PG_NOCACHE + PG_SWR
mov [virt_page23], eax
; Command table size = N*32 per port
mov edx, [edi + PORT_DATA.clb] ; cmdheader array base
xor ecx, ecx
.for1:
cmp ecx, 32
jae .for1_end
mov ebx, ecx
shl ebx, BSF sizeof.HBA_CMD_HDR
add ebx, edx ; ebx = cmdheader[ecx]
mov [ebx + HBA_CMD_HDR.prdtl], PRDT_MAX_ENTRIES ; prdt entries per command table
; bytes per command table = 64+16+48+PRDT_MAX_ENTRIES*16 = N
push edx
; cmdheader[ecx].ctba = phys_page23 + ecx*N
mov [ebx + HBA_CMD_HDR.ctba], ecx
mov edx, [ebx + HBA_CMD_HDR.ctba]
imul edx, (64+16+48+PRDT_MAX_ENTRIES*16) ; *= N
mov [ebx + HBA_CMD_HDR.ctba], edx
mov eax, [ebx + HBA_CMD_HDR.ctba]
mov edx, [phys_page23]
add [ebx + HBA_CMD_HDR.ctba], edx
add eax, [virt_page23]
mov [tmp], eax ; tmp = virt_page23 + ecx*N
lea eax, [ecx*4 + edi + PORT_DATA.ctba_arr] ; eax = pdata->ctba_arr[ecx]
mov edx, [tmp]
mov [eax], edx ; pdata->ctba_arr[ecx] = virt_page23 + ecx*N
pop edx
mov [ebx + HBA_CMD_HDR.ctbau], 0
stdcall _memset, [eax], 0, 64+16+48+PRDT_MAX_ENTRIES*16 ; zero out
inc ecx
jmp .for1
.for1_end:
mov eax, [port]
call ahci_start_cmd
DEBUGF 1, "End rebasing port %u\n", [portno]
popad
ret
endp
; ----------------------------------------------------------- ; TODO check
; Find a free command list slot
; in: pdata - address of HBA_PORT structure
; out: eax - if not found -1, else slot index
proc ahci_find_cmdslot stdcall, pdata: dword
push ebx ecx edx esi
mov esi, [pdata]
mov eax, [esi + PORT_DATA.port]
; If not set in SACT and CI, the slot is free
mov ebx, [eax + HBA_PORT.sata_active]
or ebx, [eax + HBA_PORT.command_issue] ; ebx = slots
mov esi, [esi + PORT_DATA.ctr_ptr]
mov esi, [esi + AHCI_CTR.abar]
mov edx, [esi + HBA_MEM.cap]
shr edx, 8
and edx, 0xf
; DEBUGF 1, "Number of Command Slots on each port = %u\n", edx
xor ecx, ecx
.for1:
cmp ecx, edx
jae .for1_end
; if ((slots&1) == 0) return i;
bt ebx, 0
jc .cont1
mov eax, ecx
jmp .ret
.cont1:
shr ebx, 1
inc ecx
jmp .for1
.for1_end:
DEBUGF 1, "Cannot find free command list entry\n"
mov eax, -1
.ret:
pop esi edx ecx ebx
ret
endp
proc _memset stdcall, dest:dword, val:byte, cnt:dword ; doesnt clobber any registers
;DEBUGF DBG_INFO, "memset(%x, %u, %u)\n", [dest], [val], [cnt]
push eax ecx edi
mov edi, dword [dest]
mov al, byte [val]
mov ecx, dword [cnt]
rep stosb
pop edi ecx eax
ret
endp
; Swaps byte order in words
; base - address of first word
; len - how many words to swap bytes in
; doesnt clobber any registers
proc swap_bytes_in_words stdcall, base: dword, len: dword
push eax ebx ecx
xor ecx, ecx
mov ebx, [base]
.loop:
cmp ecx, [len]
jae .loop_end
mov ax, word [ebx + ecx*2]
xchg ah, al
mov word [ebx + ecx*2], ax
inc ecx
jmp .loop
.loop_end:
pop ecx ebx eax
ret
endp