Subversion Repositories Kolibri OS

Compare Revisions

No changes between revisions

Regard whitespace Rev 9183 → Rev 9191

/kernel/branches/kolibri-lldw/blkdev/ahci.inc
0,0 → 1,1403
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ;;
;; 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
 
struct AHCI_DATA
abar dd ? ; pointer to HBA Memory (BAR5) mapped to virtual kernelspace memory
pcidev dd ? ; pointer to corresponding PCIDEV structure
ends
 
; 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
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 AHCI_DATA
port_data_arr rb (sizeof.PORT_DATA*AHCI_MAX_PORTS)
ahci_mutex MUTEX
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:
hd_name db 'hd', 0, 0, 0
hd_counter dd 0
endg
 
; -----------------------------------------------------------------------
; detect ahci controller and initialize
align 4
ahci_init:
mov ecx, ahci_mutex
call mutex_init
 
mov ecx, ahci_controller
mov esi, pcidev_list
.find_ahci_ctr:
mov esi, [esi + PCIDEV.fd]
cmp esi, pcidev_list
jz .ahci_ctr_not_found
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
 
.ahci_ctr_not_found:
DEBUGF 1, "K: AHCI controller not found\n"
ret
 
.ahci_ctr_found:
mov [ahci_controller + AHCI_DATA.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
mov [ahci_controller + AHCI_DATA.abar], eax
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
 
; ; Print some register values to debug board
; mov esi, [ahci_controller + AHCI_DATA.abar]
; 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 esi, [ahci_controller + AHCI_DATA.abar]
;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;
 
mov [hd_counter], 0
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, port_data_arr
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
 
cmp [ecx + PORT_DATA.drive_type], AHCI_DEV_SATA
jne .after_add_disk ; skip adding disk code
; register disk in system:
;stdcall ahci_read_first_sector, ecx
 
push ecx
mov eax, [hd_counter]
inc [hd_counter]
xor edx, edx
mov ecx, 10
div ecx ; eax = hd_counter / 10, edx = hd_counter % 10
test eax, eax
jz .concat_one
add al, '0'
mov byte [hd_name + 2], al
add dl, '0'
mov byte [hd_name + 3], dl
jmp .endif1
.concat_one:
add dl, '0'
mov byte [hd_name + 2], dl
.endif1:
pop ecx
 
DEBUGF 1, "adding '%s'\n", hd_name
 
push ecx
stdcall disk_add, ahci_callbacks, hd_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:
 
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
mov edi, [esi + PORT_DATA.port] ; edi - address of HBA_PORT struct of port
 
mov eax, edi
call ahci_find_cmdslot
 
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
 
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
 
; 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, [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
.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
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
; 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
mov edi, [esi + PORT_DATA.port] ; edi - address of HBA_PORT struct of port
mov eax, edi
call ahci_find_cmdslot
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
 
; 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, ahci_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, ahci_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, ahci_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, ahci_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
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: eax - address of HBA_PORT structure
; out: eax - if not found -1, else slot index
ahci_find_cmdslot:
push ebx ecx edx esi
; 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, [ahci_controller + AHCI_DATA.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
 
 
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
Property changes:
Added: svn:eol-style
+native
\ No newline at end of property
/kernel/branches/kolibri-lldw/blkdev/disk.inc
0,0 → 1,1666
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ;;
;; Copyright (C) KolibriOS team 2011-2015. All rights reserved. ;;
;; Distributed under terms of the GNU General Public License ;;
;; ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
$Revision$
 
; =============================================================================
; ================================= Constants =================================
; =============================================================================
; Error codes for callback functions.
DISK_STATUS_OK = 0 ; success
DISK_STATUS_GENERAL_ERROR = -1; if no other code is suitable
DISK_STATUS_INVALID_CALL = 1 ; invalid input parameters
DISK_STATUS_NO_MEDIA = 2 ; no media present
DISK_STATUS_END_OF_MEDIA = 3 ; end of media while reading/writing data
DISK_STATUS_NO_MEMORY = 4 ; insufficient memory for driver operation
; Driver flags. Represent bits in DISK.DriverFlags.
DISK_NO_INSERT_NOTIFICATION = 1
; Media flags. Represent bits in DISKMEDIAINFO.Flags.
DISK_MEDIA_READONLY = 1
 
; If too many partitions are detected,there is probably an error on the disk.
; 256 partitions should be enough for any reasonable use.
; Also, the same number is limiting the number of MBRs to process; if
; too many MBRs are visible,there probably is a loop in the MBR structure.
MAX_NUM_PARTITIONS = 256
 
; =============================================================================
; ================================ Structures =================================
; =============================================================================
; This structure defines all callback functions for working with the physical
; device. They are implemented by a driver. Objects with this structure reside
; in a driver.
struct DISKFUNC
strucsize dd ?
; Size of the structure. This field is intended for possible extensions of
; this structure. If a new function is added to this structure and a driver
; implements an old version, the caller can detect this by checking .strucsize,
; so the driver remains compatible.
close dd ?
; The pointer to the function which frees all driver-specific resources for
; the disk.
; Optional, may be NULL.
; void close(void* userdata);
closemedia dd ?
; The pointer to the function which informs the driver that the kernel has
; finished all processing with the current media. If media is removed, the
; driver should decline all requests to that media with DISK_STATUS_NO_MEDIA,
; even if new media is inserted, until this function is called. If media is
; removed, a new call to 'disk_media_changed' is not allowed until this
; function is called.
; Optional, may be NULL (if media is not removable).
; void closemedia(void* userdata);
querymedia dd ?
; The pointer to the function which determines capabilities of the media.
; int querymedia(void* userdata, DISKMEDIAINFO* info);
; Return value: one of DISK_STATUS_*
read dd ?
; The pointer to the function which reads data from the device.
; int read(void* userdata, void* buffer, __int64 startsector, int* numsectors);
; input: *numsectors = number of sectors to read
; output: *numsectors = number of sectors which were successfully read
; Return value: one of DISK_STATUS_*
write dd ?
; The pointer to the function which writes data to the device.
; Optional, may be NULL.
; int write(void* userdata, void* buffer, __int64 startsector, int* numsectors);
; input: *numsectors = number of sectors to write
; output: *numsectors = number of sectors which were successfully written
; Return value: one of DISK_STATUS_*
flush dd ?
; The pointer to the function which flushes the internal device cache.
; Optional, may be NULL.
; int flush(void* userdata);
; Return value: one of DISK_STATUS_*
; Note that read/write are called by the cache manager, so a driver should not
; create a software cache. This function is implemented for flushing a hardware
; cache, if it exists.
adjust_cache_size dd ?
; The pointer to the function which returns the cache size for this device.
; Optional, may be NULL.
; unsigned int adjust_cache_size(void* userdata, unsigned int suggested_size);
; Return value: 0 = disable cache, otherwise = used cache size in bytes.
ends
 
; This structure holds information on a medium.
; Objects with this structure are allocated by the kernel as a part of the DISK
; structure and are filled by a driver in the 'querymedia' callback.
struct DISKMEDIAINFO
Flags dd ?
; Combination of DISK_MEDIA_* bits.
SectorSize dd ?
; Size of the sector.
Capacity dq ?
; Size of the media in sectors.
ends
 
; This structure represents the disk cache. To follow the old implementation,
; there are two distinct caches for a disk, one for "system" data,and the other
; for "application" data.
struct DISKCACHE
; The following fields are inherited from data32.inc:cache_ideX.
pointer dd ?
data_size dd ? ; unused
data dd ?
sad_size dd ?
search_start dd ?
sector_size_log dd ?
ends
 
; This structure represents a disk device and its media for the kernel.
; This structure is allocated by the kernel in the 'disk_add' function,
; freed in the 'disk_dereference' function.
struct DISK
; Fields of disk object
Next dd ?
Prev dd ?
; All disk devices are linked in one list with these two fields.
; Head of the list is the 'disk_list' variable.
Functions dd ?
; Pointer to the 'DISKFUNC' structure with driver functions.
Name dd ?
; Pointer to the string used for accesses through the global filesystem.
UserData dd ?
; This field is passed to all callback functions so a driver can decide which
; physical device is addressed.
DriverFlags dd ?
; Bitfield. Currently only DISK_NO_INSERT_NOTIFICATION bit is defined.
; If it is set, the driver will never issue 'disk_media_changed' notification
; with argument set to true, so the kernel must try to detect media during
; requests from the file system.
RefCount dd ?
; Count of active references to this structure. One reference is kept during
; the lifetime of the structure between 'disk_add' and 'disk_del'.
; Another reference is taken during any filesystem operation for this disk.
; One reference is added if media is inserted.
; The structure is destroyed when the reference count decrements to zero:
; this usually occurs in 'disk_del', but can be delayed to the end of last
; filesystem operation, if one is active.
MediaLock MUTEX
; Lock to protect the MEDIA structure. See the description after
; 'disk_list_mutex' for the locking strategy.
; Fields of media object
MediaInserted db ?
; 0 if media is not inserted, nonzero otherwise.
MediaUsed db ?
; 0 if media fields are not used, nonzero otherwise. If .MediaRefCount is
; nonzero, this field is nonzero too; however, when .MediaRefCount goes
; to zero, there is some time interval during which media object is still used.
dw ? ; padding
; The following fields are not valid unless either .MediaInserted is nonzero
; or they are accessed from a code which has obtained the reference when
; .MediaInserted was nonzero.
MediaRefCount dd ?
; Count of active references to the media object. One reference is kept during
; the lifetime of the media between two calls to 'disk_media_changed'.
; Another reference is taken during any filesystem operation for this media.
; The callback 'closemedia' is called when the reference count decrements to
; zero: this usually occurs in 'disk_media_changed', but can be delayed to the
; end of the last filesystem operation, if one is active.
MediaInfo DISKMEDIAINFO
; This field keeps information on the current media.
NumPartitions dd ?
; Number of partitions on this media.
Partitions dd ?
; Pointer to array of .NumPartitions pointers to PARTITION structures.
cache_size dd ?
; inherited from cache_ideX_size
CacheLock MUTEX
; Lock to protect both caches.
SysCache DISKCACHE
AppCache DISKCACHE
; Two caches for the disk.
ends
 
; This structure represents one partition for the kernel. This is a base
; template, the actual contents after common fields is determined by the
; file system code for this partition.
struct PARTITION
FirstSector dq ?
; First sector of the partition.
Length dq ?
; Length of the partition in sectors.
Disk dd ?
; Pointer to parent DISK structure.
FSUserFunctions dd ?
; Handlers for the sysfunction 70h. This field is a pointer to the following
; array. The first dword is pointer to disconnect handler.
; The first dword is a number of supported subfunctions, other dwords
; point to handlers of corresponding subfunctions.
; ...fs-specific data may follow...
ends
 
; This is an external structure, it represents an entry in the partition table.
struct PARTITION_TABLE_ENTRY
Bootable db ?
; 80h = bootable partition, 0 = non-bootable partition, other values = invalid
FirstHead db ?
FirstSector db ?
FirstTrack db ?
; Coordinates of first sector in CHS.
Type db ?
; Partition type, one of predefined constants. 0 = empty, several types denote
; extended partition (see process_partition_table_entry), we are not interested
; in other values.
LastHead db ?
LastSector db ?
LastTrack db ?
; Coordinates of last sector in CHS.
FirstAbsSector dd ?
; Coordinate of first sector in LBA.
Length dd ?
; Length of the partition in sectors.
ends
 
; GUID Partition Table Header, UEFI 2.6, Table 18
struct GPTH
Signature rb 8
; 'EFI PART'
Revision dd ?
; 0x00010000
HeaderSize dd ?
; Size of this header in bytes, must fit to one sector.
HeaderCRC32 dd ?
; Set this field to zero, compute CRC32 via 0xEDB88320, compare.
Reserved dd ?
; Must be zero.
MyLBA dq ?
; LBA of the sector containing this GPT header.
AlternateLBA dq ?
; LBA of the sector containing the other GPT header.
; AlternateLBA of Primary GPTH points to Backup one and vice versa.
FirstUsableLBA dq ?
; Only sectors between first and last UsableLBA may form partitions
LastUsableLBA dq ?
DiskGUID rb 16
; Globally Unique IDentifier
PartitionEntryLBA dq ?
; First LBA of Partition Entry Array.
; Length in bytes is computed as a product of two following fields.
NumberOfPartitionEntries dd ?
; Actual number of partitions depends on the contents of Partition Entry Array.
; A partition entry is unused if zeroed.
SizeOfPartitionEntry dd ? ; in bytes
PartitionEntryArrayCRC32 dd ?
; Same CRC as for GPT header.
ends
 
; GPT Partition Entry, UEFI 2.6, Table 19
struct GPE
PartitionTypeGUID rb 16
UniquePartitionGUID rb 16
StartingLBA dq ?
EndingLBA dq ?
; Length in sectors is EndingLBA - StartingLBA + 1.
Attributes dq ?
PartitionName rb 72
ends
 
; =============================================================================
; ================================ Global data ================================
; =============================================================================
iglobal
; The pseudo-item for the list of all DISK structures.
; Initialized to the empty list.
disk_list:
dd disk_list
dd disk_list
endg
uglobal
; This mutex guards all operations with the global list of DISK structures.
disk_list_mutex MUTEX
; * There are two dependent objects, a disk and a media. In the simplest case,
; disk and media are both non-removable. However, in the general case both
; can be removed at any time, simultaneously or only media,and this makes things
; complicated.
; * For efficiency, both disk and media objects are located in the one
; structure named DISK. However, logically they are different.
; * The following operations use data of disk object: adding (disk_add);
; deleting (disk_del); filesystem (fs_lfn which eventually calls
; dyndisk_handler or dyndisk_enum_root).
; * The following operations use data of media object: adding/removing
; (disk_media_changed); filesystem (fs_lfn which eventually calls
; dyndisk_handler; dyndisk_enum_root doesn't work with media).
; * Notifications disk_add, disk_media_changed, disk_del are synchronized
; between themselves, this is a requirement for the driver. However, file
; system operations are asynchronous, can be issued at any time by any
; thread.
; * We must prevent a situation when a filesystem operation thinks that the
; object is still valid but in fact the notification has destroyed the
; object. So we keep a reference counter for both disk and media and destroy
; the object when this counter goes to zero.
; * The driver must know when it is safe to free driver-allocated resources.
; The object can be alive even after death notification has completed.
; We use special callbacks to satisfy both assertions: 'close' for the disk
; and 'closemedia' for the media. The destruction of the object includes
; calling the corresponding callback.
; * Each filesystem operation keeps one reference for the disk and one
; reference for the media. Notification disk_del forces notification on the
; media death, so the reference counter for the disk is always not less than
; the reference counter for the media.
; * Two operations "get the object" and "increment the reference counter" can
; not be done simultaneously. We use a mutex to guard the consistency here.
; It must be a part of the container for the object, so that this mutex can
; be acquired as a part of getting the object from the container. The
; container for disk object is the global list, and this list is guarded by
; 'disk_list_mutex'. The container for media object is the disk object, and
; the corresponding mutex is DISK.MediaLock.
; * Notifications do not change the data of objects, they can only remove
; objects. Thus we don't need another synchronization at this level. If two
; filesystem operations are referencing the same filesystem data, this is
; better resolved at the level of the filesystem.
endg
 
iglobal
; The function 'disk_scan_partitions' needs three sector-sized buffers for
; MBR, bootsector and fs-temporary sector data. It can not use the static
; buffers always, since it can be called for two or more disks in parallel.
; However, this case is not typical. We reserve three static 512-byte buffers
; and a flag that these buffers are currently used. If 'disk_scan_partitions'
; detects that the buffers are currently used, it allocates buffers from the
; heap. Also, the heap is used when sector size is other than 512.
; The flag is implemented as a global dword variable. When the static buffers
; are not used, the value is -1. When the static buffers are used, the value
; is normally 0 and temporarily can become greater. The function increments
; this value. If the resulting value is zero, it uses the buffers and
; decrements the value when the job is done. Otherwise, it immediately
; decrements the value and uses buffers from the heap, allocated in the
; beginning and freed in the end.
partition_buffer_users dd -1
endg
uglobal
; The static buffers for MBR, bootsector and fs-temporary sector data.
align 16
mbr_buffer rb 512
bootsect_buffer rb 512
fs_tmp_buffer rb 512
endg
 
iglobal
; This is the array of default implementations of driver callbacks.
; Same as DRIVERFUNC structure except for the first field; all functions must
; have the default implementations.
align 4
disk_default_callbacks:
dd disk_default_close
dd disk_default_closemedia
dd disk_default_querymedia
dd disk_default_read
dd disk_default_write
dd disk_default_flush
dd disk_default_adjust_cache_size
endg
 
; =============================================================================
; ================================= Functions =================================
; =============================================================================
 
; This function registers a disk device.
; This includes:
; - allocating an internal structure describing this device;
; - registering this structure in the global filesystem.
; The function initializes the disk as if there is no media. If a media is
; present, the function 'disk_media_changed' should be called after this
; function succeeds.
; Parameters:
; [esp+4] = pointer to DISKFUNC structure with the callbacks
; [esp+8] = pointer to name (ASCIIZ string)
; [esp+12] = userdata to be passed to the callbacks as is.
; [esp+16] = flags, bitfield. Currently only DISK_NO_INSERT_NOTIFICATION bit
; is defined.
; Return value:
; NULL = operation has failed
; non-NULL = handle of the disk. This handle can be used
; in the operations with other Disk* functions.
; The handle is the pointer to the internal structure DISK.
disk_add:
push ebx esi ; save used registers to be stdcall
; 1. Allocate the DISK structure.
; 1a. Call the heap manager.
movi eax, sizeof.DISK
call malloc
; 1b. Check the result. If allocation failed, return (go to 9) with eax = 0.
test eax, eax
jz .nothing
; 2. Copy the disk name to the DISK structure.
; 2a. Get length of the name, including the terminating zero.
mov ebx, [esp+8+8] ; ebx = pointer to name
push eax ; save allocated pointer to DISK
xor eax, eax ; the argument of malloc() is in eax
@@:
inc eax
cmp byte [ebx+eax-1], 0
jnz @b
; 2b. Call the heap manager.
call malloc
; 2c. Check the result. If allocation failed, go to 7.
pop esi ; restore allocated pointer to DISK
test eax, eax
jz .free
; 2d. Store the allocated pointer to the DISK structure.
mov [esi+DISK.Name], eax
; 2e. Copy the name.
@@:
mov dl, [ebx]
mov [eax], dl
inc ebx
inc eax
test dl, dl
jnz @b
; 3. Copy other arguments of the function to the DISK structure.
mov eax, [esp+4+8]
mov [esi+DISK.Functions], eax
mov eax, [esp+12+8]
mov [esi+DISK.UserData], eax
mov eax, [esp+16+8]
mov [esi+DISK.DriverFlags], eax
; 4. Initialize other fields of the DISK structure.
; Media is not inserted, reference counter is 1.
lea ecx, [esi+DISK.MediaLock]
call mutex_init
xor eax, eax
mov dword [esi+DISK.MediaInserted], eax
mov [esi+DISK.MediaRefCount], eax
inc eax
mov [esi+DISK.RefCount], eax
; The DISK structure is initialized.
; 5. Insert the new structure to the global list.
; 5a. Acquire the mutex.
mov ecx, disk_list_mutex
call mutex_lock
; 5b. Insert item to the tail of double-linked list.
mov edx, disk_list
list_add_tail esi, edx ;esi= new edx= list head
; 5c. Release the mutex.
call mutex_unlock
; 6. Return with eax = pointer to DISK.
xchg eax, esi
jmp .nothing
.free:
; Memory allocation for DISK structure succeeded, but for disk name failed.
; 7. Free the DISK structure.
xchg eax, esi
call free
; 8. Return with eax = 0.
xor eax, eax
.nothing:
; 9. Return.
pop esi ebx ; restore used registers to be stdcall
ret 16 ; purge 4 dword arguments to be stdcall
 
; This function deletes a disk device from the global filesystem.
; This includes:
; - removing a media including all partitions;
; - deleting this structure from the global filesystem;
; - dereferencing the DISK structure and possibly destroying it.
; Parameters:
; [esp+4] = handle of the disk, i.e. the pointer to the DISK structure.
; Return value: none.
disk_del:
push esi ; save used registers to be stdcall
; 1. Force media to be removed. If the media is already removed, the
; call does nothing.
mov esi, [esp+4+4] ; esi = handle of the disk
stdcall disk_media_changed, esi, 0
; 2. Delete the structure from the global list.
; 2a. Acquire the mutex.
mov ecx, disk_list_mutex
call mutex_lock
; 2b. Delete item from double-linked list.
mov eax, [esi+DISK.Next]
mov edx, [esi+DISK.Prev]
mov [eax+DISK.Prev], edx
mov [edx+DISK.Next], eax
; 2c. Release the mutex.
call mutex_unlock
; 3. The structure still has one reference created in disk_add. Remove this
; reference. If there are no other references, disk_dereference will free the
; structure.
call disk_dereference
; 4. Return.
pop esi ; restore used registers to be stdcall
ret 4 ; purge 1 dword argument to be stdcall
 
; This is an internal function which removes a previously obtained reference
; to the disk. If this is the last reference, this function lets the driver
; finalize all associated data, and afterwards frees the DISK structure.
; esi = pointer to DISK structure
disk_dereference:
; 1. Decrement reference counter. Use atomic operation to correctly handle
; possible simultaneous calls.
lock dec [esi+DISK.RefCount]
; 2. If the result is nonzero, there are other references, so nothing to do.
; In this case, return (go to 4).
jnz .nothing
; 3. If we are here, we just removed the last reference and must destroy the
; disk object.
; 3a. Call the driver.
mov al, DISKFUNC.close
stdcall disk_call_driver
; 3b. Free the structure.
xchg eax, esi
push ebx
call free
pop ebx
; 4. Return.
.nothing:
ret
 
; This is an internal function which removes a previously obtained reference
; to the media. If this is the last reference, this function calls 'closemedia'
; callback to signal the driver that the processing has finished and it is safe
; to inform about a new media.
; esi = pointer to DISK structure
disk_media_dereference:
; 1. Decrement reference counter. Use atomic operation to correctly handle
; possible simultaneous calls.
lock dec [esi+DISK.MediaRefCount]
; 2. If the result is nonzero, there are other references, so nothing to do.
; In this case, return (go to 4).
jnz .nothing
; 3. If we are here, we just removed the last reference and must destroy the
; media object.
; Note that the same place inside the DISK structure is reused for all media
; objects, so we must guarantee that reusing does not happen while freeing.
; Reusing is only possible when someone processes a new media. There are two
; mutually exclusive variants:
; * driver issues media insert notifications (DISK_NO_INSERT_NOTIFICATION bit
; in DISK.DriverFlags is not set). In this case, we require from the driver
; that such notification (except for the first one) can occur only after a
; call to 'closemedia' callback.
; * driver does not issue media insert notifications. In this case, the kernel
; itself must sometimes check whether media is inserted. We have the flag
; DISK.MediaUsed, visible to the kernel. This flag signals to the other parts
; of kernel that the way is free.
; In the first case other parts of the kernel do not use DISK.MediaUsed, so it
; does not matter when this flag is cleared. In the second case this flag must
; be cleared after all other actions, including call to 'closemedia'.
; 3a. Free all partitions.
push esi edi
mov edi, [esi+DISK.NumPartitions]
mov esi, [esi+DISK.Partitions]
test edi, edi
jz .nofree
.freeloop:
lodsd
mov ecx, [eax+PARTITION.FSUserFunctions]
call dword [ecx]
dec edi
jnz .freeloop
.nofree:
pop edi esi
; 3b. Free the cache.
call disk_free_cache
; 3c. Call the driver.
mov al, DISKFUNC.closemedia
stdcall disk_call_driver
; 3d. Clear the flag.
mov [esi+DISK.MediaUsed], 0
.nothing:
ret
 
; This function is called by the driver and informs the kernel that the media
; has changed. If the media is non-removable, it is called exactly once
; immediately after 'disk_add' and once from 'disk_del'.
; Parameters:
; [esp+4] = handle of the disk, i.e. the pointer to the DISK structure.
; [esp+8] = new status of the media: zero = no media, nonzero = media inserted.
disk_media_changed:
push ebx esi edi ; save used registers to be stdcall
; 1. Remove the existing media, if it is present.
mov esi, [esp+4+12] ; esi = pointer to DISK
; 1a. Check whether it is present. Since DISK.MediaInserted is changed only
; in this function and calls to this function are synchronized, no lock is
; required for checking.
cmp [esi+DISK.MediaInserted], 0
jz .noremove
; We really need to remove the media.
; 1b. Acquire mutex.
lea ecx, [esi+DISK.MediaLock]
call mutex_lock
; 1c. Clear the flag.
mov [esi+DISK.MediaInserted], 0
; 1d. Release mutex.
call mutex_unlock
; 1e. Remove the "lifetime" reference and possibly destroy the structure.
call disk_media_dereference
.noremove:
; 2. Test whether there is new media.
cmp dword [esp+8+12], 0
jz .noinsert
; Yep, there is.
; 3. Process the new media. We assume that all media fields are available to
; use, see comments in 'disk_media_dereference' (this covers using by previous
; media referencers) and note that calls to this function are synchronized
; (this covers using by new media referencers).
; 3a. Call the 'querymedia' callback.
; .Flags are set to zero for possible future extensions.
lea edx, [esi+DISK.MediaInfo]
and [edx+DISKMEDIAINFO.Flags], 0
mov al, DISKFUNC.querymedia
stdcall disk_call_driver, edx
; 3b. Check the result of the callback. Abort if it failed.
test eax, eax
jnz .noinsert
; 3c. Allocate the cache unless disabled by the driver. Abort if failed.
call disk_init_cache
test al, al
jz .noinsert
; 3d. Acquire the lifetime reference for the media object.
inc [esi+DISK.MediaRefCount]
; 3e. Scan for partitions. Ignore result; the list of partitions is valid even
; on errors.
call disk_scan_partitions
; 3f. Media is inserted and available for use.
inc [esi+DISK.MediaInserted]
.noinsert:
; 4. Return.
pop edi esi ebx ; restore used registers to be stdcall
ret 8 ; purge 2 dword arguments to be stdcall
 
; This function is a thunk for all functions of a disk driver.
; It checks whether the referenced function is implemented in the driver.
; If so, this function jumps to the function in the driver.
; Otherwise, it jumps to the default implementation.
; al = offset of function in the DISKFUNC structure;
; esi = pointer to the DISK structure;
; stack is the same as for the corresponding function except that the
; first parameter (void* userdata) is prepended automatically.
disk_call_driver:
movzx eax, al ; eax = offset of function in the DISKFUNC structure
; 1. Prepend the first argument to the stack.
pop ecx ; ecx = return address
push [esi+DISK.UserData] ; add argument
push ecx ; save return address
; 2. Check that the required function is inside the table. If not, go to 5.
mov ecx, [esi+DISK.Functions]
cmp eax, [ecx+DISKFUNC.strucsize]
jae .default
; 3. Check that the required function is implemented. If not, go to 5.
mov ecx, [ecx+eax]
test ecx, ecx
jz .default
; 4. Jump to the required function.
jmp ecx
.default:
; 5. Driver does not implement the required function; use default implementation.
jmp dword [disk_default_callbacks+eax-4]
 
; The default implementation of DISKFUNC.querymedia.
disk_default_querymedia:
movi eax, DISK_STATUS_INVALID_CALL
ret 8
 
; The default implementation of DISKFUNC.read and DISKFUNC.write.
disk_default_read:
disk_default_write:
movi eax, DISK_STATUS_INVALID_CALL
ret 20
 
; The default implementation of DISKFUNC.close, DISKFUNC.closemedia and
; DISKFUNC.flush.
disk_default_close:
disk_default_closemedia:
disk_default_flush:
xor eax, eax
ret 4
 
; The default implementation of DISKFUNC.adjust_cache_size.
disk_default_adjust_cache_size:
mov eax, [esp+8]
ret 8
 
; This is an internal function called from 'disk_media_changed' when a new media
; is detected. It creates the list of partitions for the media.
; If media is not partitioned, then the list consists of one partition which
; covers all the media.
; esi = pointer to the DISK structure.
disk_scan_partitions:
; 1. Initialize .NumPartitions and .Partitions fields as zeros: empty list.
and [esi+DISK.NumPartitions], 0
and [esi+DISK.Partitions], 0
; 2. Acquire the buffer for MBR and bootsector tests. See the comment before
; the 'partition_buffer_users' variable.
mov eax, [esi+DISK.MediaInfo.SectorSize]
cmp eax, 512
jnz @f
mov ebx, mbr_buffer ; assume the global buffer is free
lock inc [partition_buffer_users]
jz .buffer_acquired ; yes, it is free
lock dec [partition_buffer_users] ; no, we must allocate
@@:
lea eax, [eax*3]
stdcall kernel_alloc, eax
test eax, eax
jz .nothing
xchg eax, ebx
.buffer_acquired:
; MBR/EBRs are organized in the chain. We use a loop over MBR/EBRs, but no
; more than MAX_NUM_PARTITION times.
; 3. Prepare things for the loop.
; ebp will hold the sector number for current MBR/EBR.
; [esp] will hold the sector number for current extended partition, if there
; is one.
; [esp+4] will hold the counter that prevents long loops.
push ebp ; save ebp
push MAX_NUM_PARTITIONS ; the counter of max MBRs to process
xor ebp, ebp ; start from sector zero
push ebp ; no extended partition yet
; 4. MBR is 512 bytes long. If sector size is less than 512 bytes,
; assume no MBR, no partitions and go to 11.
cmp [esi+DISK.MediaInfo.SectorSize], 512
jb .notmbr
.new_mbr:
; 5. Read the current sector.
; Note that 'read' callback operates with 64-bit sector numbers, so we must
; push additional zero as a high dword of sector number.
mov al, DISKFUNC.read
push 1
stdcall disk_call_driver, ebx, ebp, 0, esp
pop ecx
; 6. If the read has failed, abort the loop.
dec ecx
jnz .mbr_failed
; 7. Check the MBR/EBR signature. If it is wrong, abort the loop.
; Soon we will access the partition table which starts at ebx+0x1BE,
; so we can fill its address right now. If we do it now, then the addressing
; [ecx+0x40] is shorter than [ebx+0x1fe]: one-byte offset vs 4-bytes offset.
lea ecx, [ebx+0x1be] ; ecx -> partition table
cmp word [ecx+0x40], 0xaa55
jnz .mbr_failed
; 8. The MBR is treated differently from EBRs. For MBR we additionally need to
; execute step 10 and possibly step 11.
test ebp, ebp
jnz .mbr
; 9. Handle GUID Partition Table
; 9a. Check if MBR is protective
call is_protective_mbr
jnz .no_gpt
; 9b. If so, try to scan GPT headers
call disk_scan_gpt
; 9c. If any GPT header is valid, ignore MBR
jz .done
; Otherwise process legacy/protective MBR
.no_gpt:
; The partition table can be present or not present. In the first case, we just
; read the MBR. In the second case, we just read the bootsector for a
; filesystem.
; The following algorithm is used to distinguish between these cases.
; A. If at least one entry of the partition table is invalid, this is
; a bootsector. See the description of 'is_partition_table_entry' for
; definition of validity.
; B. If all entries are empty (filesystem type field is zero) and the first
; byte is jmp opcode (0EBh or 0E9h), this is a bootsector which happens to
; have zeros in the place of partition table.
; C. Otherwise, this is an MBR.
; 10. Test for MBR vs bootsector.
; 10a. Check entries. If any is invalid, go to 11 (rule A).
call is_partition_table_entry
jc .notmbr
add ecx, 10h
call is_partition_table_entry
jc .notmbr
add ecx, 10h
call is_partition_table_entry
jc .notmbr
add ecx, 10h
call is_partition_table_entry
jc .notmbr
; 10b. Check types of the entries. If at least one is nonzero, go to 12 (rule C).
mov al, [ecx-30h+PARTITION_TABLE_ENTRY.Type]
or al, [ecx-20h+PARTITION_TABLE_ENTRY.Type]
or al, [ecx-10h+PARTITION_TABLE_ENTRY.Type]
or al, [ecx+PARTITION_TABLE_ENTRY.Type]
jnz .mbr
; 10c. Empty partition table or bootsector with many zeroes? (rule B)
cmp byte [ebx], 0EBh
jz .notmbr
cmp byte [ebx], 0E9h
jnz .mbr
.notmbr:
; 11. This is not an MBR. The media is not partitioned. Create one partition
; which covers all the media and abort the loop.
stdcall disk_add_partition, 0, 0, \
dword [esi+DISK.MediaInfo.Capacity], dword [esi+DISK.MediaInfo.Capacity+4], esi
jmp .done
.mbr:
; 12. Process all entries of the new MBR/EBR
lea ecx, [ebx+0x1be] ; ecx -> partition table
push 0 ; assume no extended partition
call process_partition_table_entry
add ecx, 10h
call process_partition_table_entry
add ecx, 10h
call process_partition_table_entry
add ecx, 10h
call process_partition_table_entry
pop ebp
; 13. Test whether we found a new EBR and should continue the loop.
; 13a. If there was no next EBR, return.
test ebp, ebp
jz .done
; Ok, we have EBR.
; 13b. EBRs addresses are relative to the start of extended partition.
; For simplicity, just abort if an 32-bit overflow occurs; large disks
; are most likely partitioned with GPT, not MBR scheme, since the precise
; calculation here would increase limit just twice at the price of big
; compatibility problems.
pop eax ; load extended partition
add ebp, eax
jc .mbr_failed
; 13c. If extended partition has not yet started, start it.
test eax, eax
jnz @f
mov eax, ebp
@@:
; 13d. If the limit is not exceeded, continue the loop.
dec dword [esp]
push eax ; store extended partition
jnz .new_mbr
.mbr_failed:
.done:
; 14. Cleanup after the loop.
pop eax ; not important anymore
pop eax ; not important anymore
pop ebp ; restore ebp
; 15. Release the buffer.
; 15a. Test whether it is the global buffer or we have allocated it.
cmp ebx, mbr_buffer
jz .release_partition_buffer
; 15b. If we have allocated it, free it.
xchg eax, ebx
call free
jmp .nothing
; 15c. Otherwise, release reference.
.release_partition_buffer:
lock dec [partition_buffer_users]
.nothing:
; 16. Return.
ret
 
 
; This function is called from disk_scan_partitions to validate and parse
; primary and backup GPTs.
proc disk_scan_gpt
push ecx
; Scan primary GPT (second sector)
stdcall scan_gpt, 1, 0
test eax, eax
; There is no code to restore backup GPT if it's corrupt.
; Therefore just exit if Primary GPT has been parsed successfully.
jz .exit
DEBUGF 1, 'K : Primary GPT is corrupt, trying backup one\n'
mov eax, dword[esi+DISK.MediaInfo.Capacity+0]
mov edx, dword[esi+DISK.MediaInfo.Capacity+4]
sub eax, 1
sbb edx, 0
; Scan backup GPT (last sector)
stdcall scan_gpt, eax, edx
test eax, eax
jz .exit
DEBUGF 1, 'K : Backup GPT is also corrupt, fallback to legacy MBR\n'
.exit:
; Return value is ZF
pop ecx
ret
endp
 
 
; This function is called from disk_scan_gpt to process a single GPT.
proc scan_gpt _mylba:qword
locals
GPEA_len dd ? ; Length of GPT Partition Entry Array in bytes
endl
push ebx edi
; Allocalte memory for GPT header
mov eax, [esi+DISK.MediaInfo.SectorSize]
stdcall kernel_alloc, eax
test eax, eax
jz .fail
; Save pointer to stack, just in case
push eax
mov ebx, eax
; Read GPT header
mov al, DISKFUNC.read
push 1
stdcall disk_call_driver, ebx, dword[_mylba+0], dword[_mylba+4], esp
pop ecx
test eax, eax
jnz .fail_free_gpt
; Check signature
cmp dword[ebx+GPTH.Signature+0], 'EFI '
jnz .fail_free_gpt
cmp dword[ebx+GPTH.Signature+4], 'PART'
jnz .fail_free_gpt
; Check Revision
cmp [ebx+GPTH.Revision], 0x00010000
jnz .fail_free_gpt
; Compute and check CRC32
xor edx, edx
xchg edx, [ebx+GPTH.HeaderCRC32]
mov eax, -1
stdcall crc_32, 0xEDB88320, ebx, [ebx+GPTH.HeaderSize]
xor eax, -1
cmp eax, edx
jnz .fail_free_gpt
; Reserved must be zero
cmp [ebx+GPTH.Reserved], 0
jnz .fail_free_gpt
; MyLBA of GPT header at LBA X must equal X
mov eax, dword[ebx+GPTH.MyLBA+0]
mov edx, dword[ebx+GPTH.MyLBA+4]
cmp eax, dword[_mylba+0]
jnz .fail_free_gpt
cmp edx, dword[_mylba+4]
jnz .fail_free_gpt
; Capacity - MyLBA = AlternateLBA
mov eax, dword[esi+DISK.MediaInfo.Capacity+0]
mov edx, dword[esi+DISK.MediaInfo.Capacity+4]
sub eax, dword[_mylba+0]
sbb edx, dword[_mylba+4]
cmp eax, dword[ebx+GPTH.AlternateLBA+0]
jnz .fail_free_gpt
cmp edx, dword[ebx+GPTH.AlternateLBA+4]
jnz .fail_free_gpt
 
; Compute GPT Partition Entry Array (GPEA) length in bytes
mov eax, [ebx+GPTH.NumberOfPartitionEntries]
mul [ebx+GPTH.SizeOfPartitionEntry]
test edx, edx ; far too big
jnz .fail_free_gpt
; Round up to sector boundary
mov ecx, [esi+DISK.MediaInfo.SectorSize] ; power of two
dec ecx
add eax, ecx
jc .fail_free_gpt ; too big
not ecx
and eax, ecx
; We will need this length to compute CRC32 of GPEA
mov [GPEA_len], eax
; Allocate memory for GPEA
stdcall kernel_alloc, eax
test eax, eax
jz .fail_free_gpt
; Save to not juggle with registers
push eax
mov edi, eax
mov eax, [GPEA_len]
xor edx, edx
; Get the number of sectors GPEA fits into
div [esi+DISK.MediaInfo.SectorSize]
push eax ; esp = pointer to the number of sectors
mov al, DISKFUNC.read
stdcall disk_call_driver, edi, dword[ebx+GPTH.PartitionEntryLBA+0], \
dword[ebx+GPTH.PartitionEntryLBA+4], esp
test eax, eax
pop eax
jnz .fail_free_gpea_gpt
; Compute and check CRC32 of GPEA
mov eax, -1
stdcall crc_32, 0xEDB88320, edi, [GPEA_len]
xor eax, -1
cmp eax, [ebx+GPTH.PartitionEntryArrayCRC32]
jnz .fail_free_gpea_gpt
 
; Process partitions, skip zeroed ones.
.next_gpe:
xor eax, eax
mov ecx, [ebx+GPTH.SizeOfPartitionEntry]
repz scasb
jz .skip
add edi, ecx
sub edi, [ebx+GPTH.SizeOfPartitionEntry]
; Length of a partition in sectors is EndingLBA - StartingLBA + 1
mov eax, dword[edi+GPE.EndingLBA+0]
mov edx, dword[edi+GPE.EndingLBA+4]
sub eax, dword[edi+GPE.StartingLBA+0]
sbb edx, dword[edi+GPE.StartingLBA+4]
add eax, 1
adc edx, 0
push ebx
mov ebx, [ebp-8] ; three-sectors-sized buffer
stdcall disk_add_partition, dword[edi+GPE.StartingLBA+0], \
dword[edi+GPE.StartingLBA+4], eax, edx, esi
pop ebx
add edi, [ebx+GPTH.SizeOfPartitionEntry]
.skip:
dec [ebx+GPTH.NumberOfPartitionEntries]
jnz .next_gpe
 
; Pointers to GPT header and GPEA are on the stack
stdcall kernel_free
stdcall kernel_free
pop edi ebx
xor eax, eax
ret
.fail_free_gpea_gpt:
stdcall kernel_free
.fail_free_gpt:
stdcall kernel_free
.fail:
pop edi ebx
xor eax, eax
inc eax
ret
endp
 
; ecx = pointer to partition records array (MBR + 446)
is_protective_mbr:
push ecx edi
xor eax, eax
cmp [ecx-2], ax
jnz .exit
; Partition record 0 has specific fields
cmp [ecx+0], al
jnz .exit
cmp byte[ecx+4], 0xEE
jnz .exit
cmp dword[ecx+8], 1
jnz .exit
mov edi, -1
cmp [ecx+12], edi
jz @f
add edi, dword[esi+DISK.MediaInfo.Capacity+0]
cmp [ecx+12], edi
jnz .exit
@@:
; Check that partition records 1-3 are filled with zero
lea edi, [ecx+16]
mov ecx, 16*3/2 ; 3 partitions
repz scasw
.exit:
pop edi ecx
; Return value is ZF
ret
 
; This is an internal function called from disk_scan_partitions. It checks
; whether the entry pointed to by ecx is a valid entry of partition table.
; The entry is valid if the first byte is 0 or 80h, the first sector plus the
; length is less than twice the size of media. Multiplication by two is
; required since the size mentioned in the partition table can be slightly
; greater than the real size.
is_partition_table_entry:
; 1. Check .Bootable field.
mov al, [ecx+PARTITION_TABLE_ENTRY.Bootable]
and al, 7Fh
jnz .invalid
; 3. Calculate first sector + length. Note that .FirstAbsSector is relative
; to the MBR/EBR, so the real sum is ebp + .FirstAbsSector + .Length.
mov eax, ebp
xor edx, edx
add eax, [ecx+PARTITION_TABLE_ENTRY.FirstAbsSector]
adc edx, 0
add eax, [ecx+PARTITION_TABLE_ENTRY.Length]
adc edx, 0
; 4. Divide by two.
shr edx, 1
rcr eax, 1
; 5. Compare with capacity. If the subtraction (edx:eax) - .Capacity does not
; overflow, this is bad.
sub eax, dword [esi+DISK.MediaInfo.Capacity]
sbb edx, dword [esi+DISK.MediaInfo.Capacity+4]
jnc .invalid
.valid:
; 5. Return success: CF is cleared.
clc
ret
.invalid:
; 6. Return fail: CF is set.
stc
ret
 
; This is an internal function called from disk_scan_partitions. It processes
; the entry pointed to by ecx.
; * If the entry is invalid, just ignore this entry.
; * If the type is zero, just ignore this entry.
; * If the type is one of types for extended partition, store the address
; of this partition as the new MBR in [esp+4].
; * Otherwise, add the partition to the list of partitions for this disk.
; We don't use the type from the entry to identify the file system;
; fs-specific checks do this more reliably.
process_partition_table_entry:
; 1. Check for valid entry. If invalid, return (go to 5).
call is_partition_table_entry
jc .nothing
; 2. Check for empty entry. If invalid, return (go to 5).
mov al, [ecx+PARTITION_TABLE_ENTRY.Type]
test al, al
jz .nothing
; 3. Check for extended partition. If extended, go to 6.
irp type,\
0x05,\ ; DOS: extended partition
0x0f,\ ; WIN95: extended partition, LBA-mapped
0xc5,\ ; DRDOS/secured: extended partition
0xd5 ; Old Multiuser DOS secured: extended partition
{
cmp al, type
jz .extended
}
; 4. If we are here, that is a normal partition. Add it to the list.
; Note that the first sector is relative to MBR/EBR.
mov eax, ebp
xor edx, edx
add eax, [ecx+PARTITION_TABLE_ENTRY.FirstAbsSector]
adc edx, 0
push ecx
stdcall disk_add_partition, eax, edx, \
[ecx+PARTITION_TABLE_ENTRY.Length], 0, esi
pop ecx
.nothing:
; 5. Return.
ret
.extended:
; 6. If we are here, that is an extended partition. Store the address.
mov eax, [ecx+PARTITION_TABLE_ENTRY.FirstAbsSector]
mov [esp+4], eax
ret
 
; This is an internal function called from disk_scan_partitions and
; process_partition_table_entry. It adds one partition to the list of
; partitions for the media.
; Important note: start, length, disk MUST be present and
; MUST be in the same order as in PARTITION structure.
; esi duplicates [disk].
proc disk_add_partition stdcall uses ebx edi, start:qword, length:qword, disk:dword
; 1. Check that this partition will not exceed the limit on total number.
cmp [esi+DISK.NumPartitions], MAX_NUM_PARTITIONS
jae .nothing
; 2. Check that this partition does not overlap with any already registered
; partition. Since any file system assumes that the disk data will not change
; outside of its control, such overlap could be destructive.
; Since the number of partitions is usually very small and is guaranteed not
; to be large, the simple linear search is sufficient.
; 2a. Prepare the loop: edi will point to the current item of .Partitions
; array, ecx will be the current item, ebx will hold number of items left.
mov edi, [esi+DISK.Partitions]
mov ebx, [esi+DISK.NumPartitions]
test ebx, ebx
jz .partitionok
.scan_existing:
; 2b. Get the next partition.
mov ecx, [edi]
add edi, 4
; The range [.FirstSector, .FirstSector+.Length) must be either entirely to
; the left of [start, start+length) or entirely to the right.
; 2c. Subtract .FirstSector - start. The possible overflow distinguish between
; cases "to the left" (2e) and "to the right" (2d).
mov eax, dword [ecx+PARTITION.FirstSector]
mov edx, dword [ecx+PARTITION.FirstSector+4]
sub eax, dword [start]
sbb edx, dword [start+4]
jb .less
; 2d. .FirstSector is greater than or equal to start. Check that .FirstSector
; is greater than or equal to start+length; the subtraction
; (.FirstSector-start) - length must not cause overflow. Go to 2g if life is
; good or to 2f in the other case.
sub eax, dword [length]
sbb edx, dword [length+4]
jb .overlap
jmp .next_existing
.less:
; 2e. .FirstSector is less than start. Check that .FirstSector+.Length is less
; than or equal to start. If the addition (.FirstSector-start) + .Length does
; not cause overflow, then .FirstSector + .Length is strictly less than start;
; since the equality is also valid, use decrement preliminarily. Go to 2g or
; 2f depending on the overflow.
sub eax, 1
sbb edx, 0
add eax, dword [ecx+PARTITION.Length]
adc edx, dword [ecx+PARTITION.Length+4]
jnc .next_existing
.overlap:
; 2f. The partition overlaps with previously registered partition. Say warning
; and return with nothing done.
dbgstr 'two partitions overlap, ignoring the last one'
jmp .nothing
.next_existing:
; 2g. The partition does not overlap with the current partition. Continue the
; loop.
dec ebx
jnz .scan_existing
.partitionok:
; 3. The partition has passed tests. Reallocate the partitions array for a new
; entry.
; 3a. Call the allocator.
mov eax, [esi+DISK.NumPartitions]
inc eax ; one more entry
shl eax, 2 ; each entry is dword
call malloc
; 3b. Test the result. If failed, return with nothing done.
test eax, eax
jz .nothing
; 3c. Copy the old array to the new array.
mov edi, eax
push esi
mov ecx, [esi+DISK.NumPartitions]
mov esi, [esi+DISK.Partitions]
rep movsd
pop esi
; 3d. Set the field in the DISK structure to the new array.
xchg [esi+DISK.Partitions], eax
; 3e. Free the old array.
call free
; 4. Recognize the file system.
; 4a. Call the filesystem recognizer. It will allocate the PARTITION structure
; with possible filesystem-specific fields.
call disk_detect_partition
; 4b. Check return value. If zero, return with list not changed; so far only
; the array was reallocated, this is ok for other code.
test eax, eax
jz .nothing
; 5. Insert the new partition to the list.
stosd
inc [esi+DISK.NumPartitions]
; 6. Return.
.nothing:
ret
endp
 
; This is an internal function called from disk_add_partition.
; It tries to recognize the file system on the partition and allocates the
; corresponding PARTITION structure with filesystem-specific fields.
disk_detect_partition:
; This function inherits the stack frame from disk_add_partition. In stdcall
; with ebp-based frame arguments start from ebp+8, since [ebp]=saved ebp
; and [ebp+4]=return address.
virtual at ebp+8
.start dq ?
.length dq ?
.disk dd ?
end virtual
; 1. Read the bootsector to the buffer.
; When disk_add_partition is called, ebx contains a pointer to
; a three-sectors-sized buffer. This function saves ebx in the stack
; immediately before ebp.
mov ebx, [ebp-4] ; get buffer
add ebx, [esi+DISK.MediaInfo.SectorSize] ; advance over MBR data to bootsector data
add ebp, 8 ; ebp points to part of PARTITION structure
xor eax, eax ; first sector of the partition
call fs_read32_sys
push eax
; 2. Run tests for all supported filesystems. If at least one test succeeded,
; go to 4.
; For tests:
; ebp -> first three fields of PARTITION structure, .start, .length, .disk;
; [esp] = error code after bootsector read: 0 = ok, otherwise = failed,
; ebx points to the buffer for bootsector,
; ebx+[esi+DISK.MediaInfo.SectorSize] points to sector-sized buffer that can be used for anything.
call fat_create_partition
test eax, eax
jnz .success
call ntfs_create_partition
test eax, eax
jnz .success
call ext2_create_partition
test eax, eax
jnz .success
call xfs_create_partition
test eax, eax
jnz .success
; 3. No file system has recognized the volume, so just allocate the PARTITION
; structure without extra fields.
movi eax, sizeof.PARTITION
call malloc
test eax, eax
jz .nothing
mov edx, dword [ebp+PARTITION.FirstSector]
mov dword [eax+PARTITION.FirstSector], edx
mov edx, dword [ebp+PARTITION.FirstSector+4]
mov dword [eax+PARTITION.FirstSector+4], edx
mov edx, dword [ebp+PARTITION.Length]
mov dword [eax+PARTITION.Length], edx
mov edx, dword [ebp+PARTITION.Length+4]
mov dword [eax+PARTITION.Length+4], edx
mov [eax+PARTITION.Disk], esi
mov [eax+PARTITION.FSUserFunctions], default_fs_functions
.success:
.nothing:
sub ebp, 8 ; restore ebp
; 4. Return with eax = pointer to PARTITION or NULL.
pop ecx
ret
 
iglobal
align 4
default_fs_functions:
dd free
dd (default_fs_functions_end - default_fs_functions - 4) / 4
dd 0
dd 0
dd 0
dd 0
dd 0
dd default_fs_get_file_info
default_fs_functions_end:
endg
 
proc default_fs_get_file_info uses edi
movi eax, ERROR_UNSUPPORTED_FS
cmp byte[esi], 0
jnz .done
movi ecx, 40 ; len of BDFE without filename
cmp [ebx+f70s5arg.xflags], 0
jz @f
add ecx, 2 ; volume label requested, space for utf16 terminator
@@:
mov ebx, [ebx+f70s5arg.buf]
stdcall is_region_userspace, ebx, ecx
movi eax, ERROR_MEMORY_POINTER
jnz .done
mov edi, ebx
xor eax, eax
rep stosb
mov [ebx+bdfe.attr], 0x10 ; directory flag
mov word[ebx+bdfe.name], 0 ; word because of possible utf16
mov eax, dword[ebp+PARTITION.Length+DQ.lo]
mov edx, dword[ebp+PARTITION.Length+DQ.hi]
mov ecx, [ebp+PARTITION.Disk]
mov ecx, [ecx+DISK.MediaInfo.SectorSize]
bsf ecx, ecx
shld edx, eax, cl
shl eax, cl
mov [ebx+bdfe.size.lo], eax
mov [ebx+bdfe.size.hi], edx
xor eax, eax
.done:
ret
endp
 
; This function is called from file_system_lfn.
; This handler gets the control each time when fn 70 is called
; with unknown item of root subdirectory.
; in: esi = ebp -> path string
; out: if the handler processes path, it must not return in file_system_lfn,
; but instead pop return address and return directly to the caller
; otherwise simply return
dyndisk_handler:
push ebx edi ; save registers used in file_system_lfn
; 1. Acquire the mutex.
mov ecx, disk_list_mutex
call mutex_lock
; 2. Loop over the list of DISK structures.
; 2a. Initialize.
mov ebx, disk_list
.scan:
; 2b. Get the next item.
mov ebx, [ebx+DISK.Next]
; 2c. Check whether the list is done. If so, go to 3.
cmp ebx, disk_list
jz .notfound
; 2d. Compare names. If names match, go to 5.
mov edi, [ebx+DISK.Name]
push esi
@@:
; esi points to the name from fs operation; it is terminated by zero or slash.
lodsb
test al, al
jz .eoin_dec
cmp al, '/'
jz .eoin
; edi points to the disk name.
inc edi
; edi points to lowercase name, this is a requirement for the driver.
; Characters at esi can have any register. Lowercase the current character.
; This lowercasing works for latin letters and digits; since the disk name
; should not contain other symbols, this is ok.
or al, 20h
cmp al, [edi-1]
jz @b
.wrongname:
; 2f. Names don't match. Continue the loop.
pop esi
jmp .scan
.notfound:
; The loop is done and no name matches.
; 3. Release the mutex.
call mutex_unlock
; 4. Return normally.
pop edi ebx ; restore registers used in file_system_lfn
ret
; part of 2d: the name matches partially, but we must check that this is full
; equality.
.eoin_dec:
dec esi
.eoin:
cmp byte [edi], 0
jnz .wrongname
; We found the addressed DISK structure.
; 5. Reference the disk.
lock inc [ebx+DISK.RefCount]
; 6. Now we are sure that the DISK structure is not going to die at least
; while we are working with it, so release the global mutex.
call mutex_unlock
pop ecx ; pop from the stack saved value of esi
; 7. Acquire the mutex for media object.
pop edi ; restore edi
lea ecx, [ebx+DISK.MediaLock]
call mutex_lock
; 8. Get the media object. If it is not NULL, reference it.
xor edx, edx
cmp [ebx+DISK.MediaInserted], dl
jz @f
mov edx, ebx
inc [ebx+DISK.MediaRefCount]
@@:
; 9. Now we are sure that the media object, if it exists, is not going to die
; at least while we are working with it, so release the mutex for media object.
call mutex_unlock
mov ecx, ebx
pop ebx eax ; restore ebx, pop return address
; 10. Check whether the fs operation wants to enumerate partitions (go to 11)
; or work with some concrete partition (go to 12).
cmp byte [esi], 0
jnz .haspartition
; 11. The fs operation wants to enumerate partitions.
; Check whether the media is inserted.
mov esi, fs_dyndisk_next_nomedia
test edx, edx
jz @f
mov esi, fs_dyndisk_next
@@: ; Let the procedure from fs_lfn.inc do the job.
jmp file_system_lfn.maindir_noesi
 
.root:
pop ecx edx
xor eax, eax
cmp byte [ebx], 9
jz .cleanup_ecx
.access_denied:
movi eax, ERROR_ACCESS_DENIED
.cleanup_ecx:
mov [esp+32], eax
mov esi, ecx ; disk*dereference assume that esi points to DISK
test edx, edx ; if there are no media, we didn't reference it
jz @f
call disk_media_dereference
@@:
call disk_dereference
stdcall kernel_free, ebp
ret
 
.dyndisk_cleanup:
pop ecx edx
movi eax, ERROR_FILE_NOT_FOUND
jmp .cleanup_ecx
 
.haspartition:
; 12. The fs operation has specified some partition.
push edx ecx
xor eax, eax
lodsb
sub eax, '0'
jz .dyndisk_cleanup
cmp eax, 10
jnc .dyndisk_cleanup
mov ecx, eax
lodsb
cmp eax, '/'
jz @f
test eax, eax
jnz .dyndisk_cleanup
dec esi
@@:
cmp byte [esi], 0
jnz @f
cmp byte [ebx], 1
jz @f
cmp byte [ebx], 5
jnz .root
@@:
dec ecx ; convert to zero-based partition index
pop edx ; edx = pointer to DISK, dword [esp] = NULL or edx
; If the driver does not support insert notifications and we are the only fs
; operation with this disk, ask the driver whether the media
; was inserted/removed/changed. Otherwise, assume that media status is valid.
test byte [edx+DISK.DriverFlags], DISK_NO_INSERT_NOTIFICATION
jz .media_accurate
push ecx esi
mov esi, edx
cmp dword [esp+8], 0
jz .test_no_media
cmp [esi+DISK.MediaRefCount], 2
jnz .media_accurate_pop
lea edx, [esi+DISK.MediaInfo]
and [edx+DISKMEDIAINFO.Flags], 0
mov al, DISKFUNC.querymedia
stdcall disk_call_driver, edx
test eax, eax
jz .media_accurate_pop
stdcall disk_media_dereference ; drop our reference so that disk_media_changed could close the media
stdcall disk_media_changed, esi, 0
and dword [esp+8], 0 ; no media
.test_no_media:
stdcall disk_media_changed, esi, 1 ; issue fake notification
; if querymedia() inside disk_media_changed returns error, the notification is ignored
cmp [esi+DISK.MediaInserted], 0
jz .media_accurate_pop
lock inc [esi+DISK.MediaRefCount]
mov dword [esp+8], esi
.media_accurate_pop:
mov edx, esi
pop esi ecx
.media_accurate:
pop eax
test eax, eax
jz .nomedia
cmp ecx, [edx+DISK.NumPartitions]
jae .notfound2
mov eax, [edx+DISK.Partitions]
mov eax, [eax+ecx*4]
mov edi, [eax+PARTITION.FSUserFunctions]
mov ecx, [ebx]
cmp [edi+4], ecx
jbe .unsupported
cmp dword[edi+8+ecx*4], 0 ; user function not implemented
jz .unsupported
pushd edx ebp eax [edi+8+ecx*4]
cmp ecx, 10
jnz .callFS
or ecx, -1
mov edi, esi
xor eax, eax
repnz scasb
mov edx, edi
dec edi
mov al, '/'
std
repnz scasb
cld
inc edi
mov [edi], ah
mov ebp, [current_slot]
add ebp, APPDATA.cur_dir
pushd ebx esi edx [ebp] ebp edi
sub esi, 2
mov [ebp], esi
mov edi, edx
mov esi, [ebx+16]
mov eax, [ebx+20]
cmp eax, 4
jc @f
xor eax, eax
@@:
call getFullPath
pop edi ebp
mov byte [edi], '/'
popd [ebp] edi esi ebx
add edi, 2
test eax, eax
jz .errorRename
cmp byte [edi], 0
jz .errorRename
.callFS:
pop eax ebp
call eax
pop ebp edx
mov dword [esp+20], ebx
.cleanup:
mov dword [esp+32], eax
mov esi, edx
call disk_media_dereference
@@:
call disk_dereference
stdcall kernel_free, ebp
ret
 
.unsupported:
movi eax, ERROR_UNKNOWN_FS
cmp edi, default_fs_functions
jz .cleanup
movi eax, ERROR_UNSUPPORTED_FS
jmp .cleanup
 
.errorRename:
pop eax eax ebp edx
.notfound2:
movi eax, ERROR_FILE_NOT_FOUND
jmp .cleanup
 
.nomedia:
test ecx, ecx
jnz .notfound2
mov dword [esp+32], ERROR_DEVICE
mov esi, edx
jmp @b
 
; This is a callback for enumerating partitions called from
; file_system_lfn.maindir in the case of inserted media.
; It just increments eax until DISK.NumPartitions reached and then
; cleans up.
fs_dyndisk_next:
mov ecx, [esp+8]
cmp eax, [ecx+DISK.NumPartitions]
jae .nomore
inc eax
clc
ret
.nomore:
pusha
mov esi, ecx
call disk_media_dereference
call disk_dereference
popa
stc
ret
 
; This is a callback for enumerating partitions called from
; file_system_lfn.maindir in the case of missing media.
; In this case we create one pseudo-partition.
fs_dyndisk_next_nomedia:
cmp eax, 1
jae .nomore
inc eax
clc
ret
.nomore:
mov ecx, [esp+8]
pusha
mov esi, ecx
call disk_dereference
popa
stc
ret
 
; This function is called from file_system_lfn.
; This handler is called when virtual root is enumerated
; and must return all items which can be handled by this.
; It is called several times, first time with eax=0
; in: eax = 0 for first call, previously returned value for subsequent calls
; out: eax = 0 => no more items
; eax != 0 => buffer pointed to by edi contains name of item
dyndisk_enum_root:
push edx ; save register used in file_system_lfn
mov ecx, disk_list_mutex ; it will be useful
; 1. If this is the first call, acquire the mutex and initialize.
test eax, eax
jnz .notfirst
call mutex_lock
mov eax, disk_list
.notfirst:
; 2. Get next item.
mov eax, [eax+DISK.Next]
; 3. If there are no more items, go to 6.
cmp eax, disk_list
jz .last
; 4. Copy name from the DISK structure to edi.
push eax esi
mov esi, [eax+DISK.Name]
@@:
lodsb
stosb
test al, al
jnz @b
pop esi eax
; 5. Return with eax = item.
pop edx ; restore register used in file_system_lfn
ret
.last:
; 6. Release the mutex and return with eax = 0.
call mutex_unlock
xor eax, eax
pop edx ; restore register used in file_system_lfn
ret
Property changes:
Added: svn:eol-style
+native
\ No newline at end of property
Added: svn:keywords
+Revision
\ No newline at end of property
/kernel/branches/kolibri-lldw/blkdev/rd.inc
0,0 → 1,192
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ;;
;; Copyright (C) KolibriOS team 2013-2015. All rights reserved. ;;
;; Distributed under terms of the GNU General Public License ;;
;; ;;
;; RAMDISK functions ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
$Revision$
 
iglobal
align 4
ramdisk_functions:
dd .size
dd 0 ; no close() function
dd 0 ; no closemedia() function
dd ramdisk_querymedia
dd ramdisk_read
dd ramdisk_write
dd 0 ; no flush() function
dd ramdisk_adjust_cache_size
.size = $ - ramdisk_functions
endg
 
iglobal
align 4
ramdisk_actual_size dd RAMDISK_CAPACITY
endg
 
; This function is called early in boot process.
; It creates filesystem /rd/1 based on raw image data loaded by somebody before
; to memory named as RAMDISK with max size RAMDISK_CAPACITY, may be less.
proc ramdisk_init
iglobal
ramdisk_name db 'rd',0
endg
push ebx esi ; save used registers to be stdcall
; 1. Register the device and the (always inserted) media in the disk subsystem.
stdcall disk_add, ramdisk_functions, ramdisk_name, 0, 0
test eax, eax
jz .fail
mov ebx, eax
stdcall disk_media_changed, eax, 1
; 2. We don't know actual size of loaded image,
; so try to calculate it using partition structure,
; assuming that file systems fill the real size based on contents of the partition.
; 2a. Prepare for loop over partitions.
xor eax, eax
xor ecx, ecx
xor edx, edx
; 2b. Check that at least one partition was recognized.
cmp [ebx+DISK.NumPartitions], ecx
jz .fail
; 2c. Loop over partitions.
.partitions:
; For every partition, set edx to maximum between edx and end of partition.
mov esi, [ebx+DISK.Partitions]
mov esi, [esi+ecx*4]
mov eax, dword [esi+PARTITION.FirstSector]
add eax, dword [esi+PARTITION.Length]
cmp eax, edx
jb @f
mov edx, eax
@@:
inc ecx
cmp ecx, [ebx+DISK.NumPartitions]
jb .partitions
; 3. Reclaim unused memory, if any.
mov [ramdisk_actual_size], edx
add edx, 7 ; aligning up
shr edx, 3 ; 512-byte sectors -> 4096-byte pages
mov esi, RAMDISK_CAPACITY / 8 ; aligning down
sub esi, edx
jbe .no_reclaim
shl edx, 12
add edx, RAMDISK - OS_BASE
@@:
mov eax, edx
call free_page
add edx, 0x1000
dec esi
jnz @b
.no_reclaim:
mov eax, ebx
pop esi ebx ; restore used registers to be stdcall
ret
.fail:
dbgstr 'Failed to initialize ramdisk'
pop esi ebx ; restore used registers to be stdcall
ret
endp
 
; Returns information about disk media.
proc ramdisk_querymedia
virtual at esp+4
.userdata dd ?
.info dd ?
end virtual
; Media is always present, sector size is always 512 bytes.
mov edx, [.userdata]
mov ecx, [.info]
mov [ecx+DISKMEDIAINFO.Flags], 0
mov [ecx+DISKMEDIAINFO.SectorSize], 512
mov eax, [ramdisk_actual_size]
mov dword [ecx+DISKMEDIAINFO.Capacity], eax
mov dword [ecx+DISKMEDIAINFO.Capacity+4], 0
; Return zero as an indicator of success.
xor eax, eax
retn 8
endp
 
; Common procedure for reading and writing.
; operation = 0 for reading, operation = 1 for writing.
; Arguments of ramdisk_read and ramdisk_write are the same.
macro ramdisk_read_write operation
{
push esi edi ; save used registers to be stdcall
mov esi, [userdata]
mov edi, [numsectors_ptr]
; 1. Determine number of sectors to be transferred.
; This is either the requested number of sectors or number of sectors
; up to the disk boundary, depending of what is less.
xor ecx, ecx
; 1a. Test whether [start_sector] is less than RAMDISK_CAPACITY.
; If so, calculate number of sectors between [start_sector] and RAMDISK_CAPACITY.
; Otherwise, the actual number of sectors is zero.
cmp dword [start_sector+4], ecx
jnz .got_number
mov eax, [ramdisk_actual_size]
sub eax, dword [start_sector]
jbe .got_number
; 1b. Get the requested number of sectors.
mov ecx, [edi]
; 1c. If it is greater than number of sectors calculated in 1a, use the value
; from 1a.
cmp ecx, eax
jb .got_number
mov ecx, eax
.got_number:
; 2. Compare the actual number of sectors with requested. If they are
; equal, set eax (it will be the returned value) to zero. Otherwise,
; use DISK_STATUS_END_OF_MEDIA.
xor eax, eax
cmp ecx, [edi]
jz @f
mov al, DISK_STATUS_END_OF_MEDIA
@@:
; 3. Store the actual number of sectors.
mov [edi], ecx
; 4. Calculate source and destination addresses.
if operation = 0 ; reading?
mov esi, dword [start_sector]
shl esi, 9
add esi, RAMDISK
mov edi, [buffer]
else ; writing?
mov edi, dword [start_sector]
shl edi, 9
add edi, RAMDISK
mov esi, [buffer]
end if
; 5. Calculate number of dwords to be transferred.
shl ecx, 9-2
; 6. Copy data.
rep movsd
; 7. Return. The value in eax was calculated in step 2.
pop edi esi ; restore used registers to be stdcall
}
 
; Reads one or more sectors from the device.
proc ramdisk_read userdata:dword, buffer:dword, start_sector:qword, numsectors_ptr:dword
ramdisk_read_write 0
ret
endp
 
; Writes one or more sectors to the device.
proc ramdisk_write userdata:dword, buffer:dword, start_sector:qword, numsectors_ptr:dword
ramdisk_read_write 1
ret
endp
 
; The kernel calls this function when initializing cache subsystem for
; the media. This call allows the driver to adjust the cache size.
proc ramdisk_adjust_cache_size
virtual at esp+4
.userdata dd ?
.suggested_size dd ?
end virtual
; Since ramdisk does not need cache, just return 0.
xor eax, eax
retn 8
endp
Property changes:
Added: svn:eol-style
+native
\ No newline at end of property
Added: svn:keywords
+Rev
\ No newline at end of property
/kernel/branches/kolibri-lldw/blkdev/flp_drv.inc
0,0 → 1,960
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ;;
;; Copyright (C) KolibriOS team 2004-2015. All rights reserved. ;;
;; Distributed under terms of the GNU General Public License ;;
;; ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
$Revision$
 
 
;**********************************************************
; Direct work with floppy disk drive
;**********************************************************
; Source code author - Kulakov Vladimir Gennadievich.
; Adaptation and improvement - Mario79.
 
;give_back_application_data: ; give back to application
; mov edi,[TASK_BASE]
; mov edi,[edi+TASKDATA.mem_start]
; add edi,ecx
give_back_application_data_1:
mov esi, FDD_BUFF;FDD_DataBuffer ;0x40000
mov ecx, 128
cld
rep movsd
ret
 
;take_data_from_application: ; take from application
; mov esi,[TASK_BASE]
; mov esi,[esi+TASKDATA.mem_start]
; add esi,ecx
take_data_from_application_1:
mov edi, FDD_BUFF;FDD_DataBuffer ;0x40000
mov ecx, 128
cld
rep movsd
ret
 
; Controller operations result codes (FDC_Status)
FDC_Normal = 0 ; normal finish
FDC_TimeOut = 1 ; time out error
FDC_DiskNotFound = 2 ; no disk in drive
FDC_TrackNotFound = 3 ; track not found
FDC_SectorNotFound = 4 ; sector not found
 
; Maximum values of the sector coordinates (specified
; values correspond to the parameters of the standard
; 3-inch 1.44 MB floppy disk)
MAX_Track = 79
MAX_Head = 1
MAX_Sector = 18
 
uglobal
; Timer tick counter
TickCounter dd ?
; Operation completion code with the floppy disk drive controller
FDC_Status DB ?
; Interrupt flag from floppy disk drive
FDD_IntFlag DB ?
; The moment of the beginning of the last operation with FDD
FDD_Time DD ?
; Drive number
FDD_Type db 0
; Sector coordinates
FDD_Track DB ?
FDD_Head DB ?
FDD_Sector DB ?
 
; Operation result block
FDC_ST0 DB ?
FDC_ST1 DB ?
FDC_ST2 DB ?
FDC_C DB ?
FDC_H DB ?
FDC_R DB ?
FDC_N DB ?
; Read operation repetition counter
ReadRepCounter DB ?
; Recalibration operation repetition counter
RecalRepCounter DB ?
endg
; Memory area for storing the readed sector
;FDD_DataBuffer: times 512 db 0 ;DB 512 DUP (?)
fdd_motor_status db 0
timer_fdd_motor dd 0
 
;**************************************
;* INITIALIZATION OF DMA MODE FOR FDD *
;**************************************
Init_FDC_DMA:
pushad
mov al, 0
out 0x0c, al; reset the flip-flop to a known state.
mov al, 6 ; mask channel 2 so we can reprogram it.
out 0x0a, al
mov al, [dmamode]; 0x46 -> Read from floppy - 0x4A Write to floppy
out 0x0b, al
mov al, 0
out 0x0c, al; reset the flip-flop to a known state.
mov eax, 0xD000
out 0x04, al; set the channel 2 starting address to 0
shr eax, 8
out 0x04, al
shr eax, 8
out 0x81, al
mov al, 0
out 0x0c, al; reset flip-flop
mov al, 0xff;set count (actual size -1)
out 0x5, al
mov al, 0x1;[dmasize] ;(0x1ff = 511 / 0x23ff =9215)
out 0x5, al
mov al, 2
out 0xa, al
popad
ret
 
;***********************************
;* WRITE BYTE TO FDC DATA PORT *
;* Parameters: *
;* AL - byte to write. *
;***********************************
FDCDataOutput:
; DEBUGF 1,'K : FDCDataOutput(%x)',al
; pusha
push eax ecx edx
mov AH, AL ; remember byte to AH
; Reset controller state variable
mov [FDC_Status], FDC_Normal
; Check the readiness of the controller to receive data
mov DX, 3F4h ; (FDC state port)
mov ecx, 0x10000 ; set timeout counter
@@TestRS:
in AL, DX ; read the RS register
and AL, 0C0h ; get digits 6 and 7
cmp AL, 80h ; check digits 6 and 7
je @@OutByteToFDC
loop @@TestRS
; Time out error
; DEBUGF 1,' timeout\n'
mov [FDC_Status], FDC_TimeOut
jmp @@End_5
; Write byte to data port
@@OutByteToFDC:
inc DX
mov AL, AH
out DX, AL
; DEBUGF 1,' ok\n'
@@End_5:
; popa
pop edx ecx eax
ret
 
;******************************************
;* READ BYTE FROM FDC DATA PORT *
;* Procedure doesnt have input params. *
;* Output : *
;* AL - byte read. *
;******************************************
FDCDataInput:
push ECX
push DX
; Reset controller state variable
mov [FDC_Status], FDC_Normal
; Check the readiness of the controller to receive data
mov DX, 3F4h ;(FDC state port)
mov ecx, 0x10000 ; set timeout counter
@@TestRS_1:
in AL, DX ; read the RS register
and AL, 0C0h ; get digits 6 and 7
cmp AL, 0C0h ; check digits 6 and 7
je @@GetByteFromFDC
loop @@TestRS_1
; Time out error
; DEBUGF 1,'K : FDCDataInput: timeout\n'
mov [FDC_Status], FDC_TimeOut
jmp @@End_6
; Get byte from data port
@@GetByteFromFDC:
inc DX
in AL, DX
; DEBUGF 1,'K : FDCDataInput: %x\n',al
@@End_6:
pop DX
pop ECX
ret
 
;*********************************************
;* FDC INTERRUPT HANDLER *
;*********************************************
FDCInterrupt:
; dbgstr 'FDCInterrupt'
; Set the interrupt flag
mov [FDD_IntFlag], 1
mov al, 1
ret
 
;*******************************************
;* WAIT FOR INTERRUPT FROM FDC *
;*******************************************
WaitFDCInterrupt:
pusha
; Reset operation status byte
mov [FDC_Status], FDC_Normal
; Zero out the tick counter
mov eax, [timer_ticks]
mov [TickCounter], eax
; Wait for the floppy disk interrupt flag to be set
@@TestRS_2:
call change_task
cmp [FDD_IntFlag], 0
jnz @@End_7 ; interrupt occured
mov eax, [timer_ticks]
sub eax, [TickCounter]
cmp eax, 200;50 ;25 ;5 ; wait 5 ticks
jb @@TestRS_2
; jl @@TestRS_2
; Time out error
; dbgstr 'WaitFDCInterrupt: timeout'
mov [FDC_Status], FDC_TimeOut
@@End_7:
popa
ret
 
;***********************************
;* Turn on the motor of drive "A:" *
;***********************************
FDDMotorON:
; dbgstr 'FDDMotorON'
pusha
; cmp [fdd_motor_status],1
; je fdd_motor_on
mov al, [flp_number]
cmp [fdd_motor_status], al
je fdd_motor_on
; Reset the FDD controller
mov DX, 3F2h ; motor control port
mov AL, 0
out DX, AL
; Select and turn on the drive motor
cmp [flp_number], 1
jne FDDMotorON_B
; call FDDMotorOFF_B
mov AL, 1Ch ; Floppy A
jmp FDDMotorON_1
FDDMotorON_B:
; call FDDMotorOFF_A
mov AL, 2Dh ; Floppy B
FDDMotorON_1:
out DX, AL
; Zero out the tick counter
mov eax, [timer_ticks]
mov [TickCounter], eax
; wait 0.5 s
@@dT:
call change_task
mov eax, [timer_ticks]
sub eax, [TickCounter]
cmp eax, 50 ;10
jb @@dT
; Read results of RESET command
push 4
; DEBUGF 1,'K : floppy reset results:'
@@:
mov al, 8
call FDCDataOutput
call FDCDataInput
; DEBUGF 1,' %x',al
call FDCDataInput
; DEBUGF 1,' %x',al
dec dword [esp]
jnz @b
; DEBUGF 1,'\n'
pop eax
cmp [flp_number], 1
jne fdd_motor_on_B
mov [fdd_motor_status], 1
jmp fdd_motor_on
fdd_motor_on_B:
mov [fdd_motor_status], 2
fdd_motor_on:
call save_timer_fdd_motor
popa
ret
 
;*****************************************
;* SAVING TIME STAMP *
;*****************************************
save_timer_fdd_motor:
mov eax, [timer_ticks]
mov [timer_fdd_motor], eax
ret
 
;*****************************************
;* CHECK THE MOTOR SHUTDOWN DELAY *
;*****************************************
proc check_fdd_motor_status_has_work?
cmp [fdd_motor_status], 0
jz .no
mov eax, [timer_ticks]
sub eax, [timer_fdd_motor]
cmp eax, 500
jb .no
.yes:
xor eax, eax
inc eax
ret
.no:
xor eax, eax
ret
endp
 
align 4
check_fdd_motor_status:
cmp [fdd_motor_status], 0
je end_check_fdd_motor_status_1
mov eax, [timer_ticks]
sub eax, [timer_fdd_motor]
cmp eax, 500
jb end_check_fdd_motor_status
call FDDMotorOFF
mov [fdd_motor_status], 0
end_check_fdd_motor_status_1:
end_check_fdd_motor_status:
ret
 
;**********************************
;* TURN OFF MOTOR OF DRIVE *
;**********************************
FDDMotorOFF:
; dbgstr 'FDDMotorOFF'
push AX
push DX
cmp [flp_number], 1
jne FDDMotorOFF_1
call FDDMotorOFF_A
jmp FDDMotorOFF_2
FDDMotorOFF_1:
call FDDMotorOFF_B
FDDMotorOFF_2:
pop DX
pop AX
; clearing caching flags due to information obsolescence
or [floppy_media_flags+0], FLOPPY_MEDIA_NEED_RESCAN
or [floppy_media_flags+1], FLOPPY_MEDIA_NEED_RESCAN
ret
 
FDDMotorOFF_A:
mov DX, 3F2h ; motor control port
mov AL, 0Ch ; Floppy A
out DX, AL
ret
 
FDDMotorOFF_B:
mov DX, 3F2h ; motor control port
mov AL, 5h ; Floppy B
out DX, AL
ret
 
;*******************************
;* RECALIBRATE DRIVE "A:" *
;*******************************
RecalibrateFDD:
; dbgstr 'RecalibrateFDD'
pusha
call save_timer_fdd_motor
; Clear the interrupt flag
mov [FDD_IntFlag], 0
; Send the "Recalibration" command
mov AL, 07h
call FDCDataOutput
mov AL, [flp_number]
dec AL
call FDCDataOutput
; Wait for the operation to complete
call WaitFDCInterrupt
cmp [FDC_Status], 0
jne .fail
; Read results of RECALIBRATE command
; DEBUGF 1,'K : floppy recalibrate results:'
mov al, 8
call FDCDataOutput
call FDCDataInput
push eax
; DEBUGF 1,' %x',al
call FDCDataInput
; DEBUGF 1,' %x',al
; DEBUGF 1,'\n'
pop eax
test al, 0xC0
jz @f
mov [FDC_Status], FDC_DiskNotFound
@@:
.fail:
call save_timer_fdd_motor
popa
ret
 
;*****************************************************
;* TRACK SEARCH *
;* Parameters are passed through global variables: *
;* FDD_Track - track number (0-79); *
;* FDD_Head - head number (0-1). *
;* Result of operation is written to FDC_Status. *
;*****************************************************
SeekTrack:
; dbgstr 'SeekTrack'
pusha
call save_timer_fdd_motor
; Clear the interrupt flag
mov [FDD_IntFlag], 0
; Send "Search" command
mov AL, 0Fh
call FDCDataOutput
; Send head / drive number byte
mov AL, [FDD_Head]
shl AL, 2
call FDCDataOutput
; Send track number byte
mov AL, [FDD_Track]
call FDCDataOutput
; Wait for the operation to complete
call WaitFDCInterrupt
cmp [FDC_Status], FDC_Normal
jne @@Exit
; Save search result
mov AL, 08h
call FDCDataOutput
call FDCDataInput
mov [FDC_ST0], AL
call FDCDataInput
mov [FDC_C], AL
; Check search result
; Is search finished?
test [FDC_ST0], 100000b
je @@Err
; Is the specified track found?
mov AL, [FDC_C]
cmp AL, [FDD_Track]
jne @@Err
; Does the head number match the specified one?
; The H bit (Head Address) in ST0 will always return a "0" (c) 82077AA datasheet,
; description of SEEK command. So we can not verify the proper head.
; mov AL, [FDC_ST0]
; and AL, 100b
; shr AL, 2
; cmp AL, [FDD_Head]
; jne @@Err
; Operation completed successfully
; dbgstr 'SeekTrack: FDC_Normal'
mov [FDC_Status], FDC_Normal
jmp @@Exit
@@Err: ; Track not found
; dbgstr 'SeekTrack: FDC_TrackNotFound'
mov [FDC_Status], FDC_TrackNotFound
@@Exit:
call save_timer_fdd_motor
popa
ret
 
;*******************************************************
;* READING A DATA SECTOR *
;* Parameters are passed through global variables: *
;* FDD_Track - track number (0-79); *
;* FDD_Head - head number (0-1); *
;* FDD_Sector - sector number (1-18). *
;* Result of operation is written to FDC_Status. *
;* If the read operation is successful, the contents *
;* of the sector will be written to FDD_DataBuffer. *
;*******************************************************
ReadSector:
; dbgstr 'ReadSector'
pushad
call save_timer_fdd_motor
; Clear the interrupt flag
mov [FDD_IntFlag], 0
; Set transmit speed to 500 Kb / s
mov AX, 0
mov DX, 03F7h
out DX, AL
; Initialize the DMA channel
mov [dmamode], 0x46
call Init_FDC_DMA
; Send "Data read" command
mov AL, 0E6h ; reading in multi-track mode
call FDCDataOutput
mov AL, [FDD_Head]
shl AL, 2
or AL, [flp_number]
dec AL
call FDCDataOutput
mov AL, [FDD_Track]
call FDCDataOutput
mov AL, [FDD_Head]
call FDCDataOutput
mov AL, [FDD_Sector]
call FDCDataOutput
mov AL, 2 ; sector size code (512 byte)
call FDCDataOutput
mov AL, 18 ;+1; 3Fh ;number of sectors per track
call FDCDataOutput
mov AL, 1Bh ; GPL value
call FDCDataOutput
mov AL, 0FFh; DTL value
call FDCDataOutput
; Waiting for an interrupt at the end of the operation
call WaitFDCInterrupt
cmp [FDC_Status], FDC_Normal
jne @@Exit_1
; Read the operation completion status
call GetStatusInfo
test [FDC_ST0], 11011000b
jnz @@Err_1
; dbgstr 'ReadSector: FDC_Normal'
mov [FDC_Status], FDC_Normal
jmp @@Exit_1
@@Err_1:
; dbgstr 'ReadSector: FDC_SectorNotFound'
mov [FDC_Status], FDC_SectorNotFound
@@Exit_1:
call save_timer_fdd_motor
popad
ret
 
;*******************************************************
;* READ SECTOR (WITH RETRY OF OPERATION ON FAILURE) *
;* Parameters are passed through global variables: *
;* FDD_Track - track number (0-79); *
;* FDD_Head - head number (0-1); *
;* FDD_Sector - sector number (1-18). *
;* Result of operation is written to FDC_Status. *
;* If the read operation is successful, the contents *
;* of the sector will be written to FDD_DataBuffer. *
;*******************************************************
ReadSectWithRetr:
pusha
; Reset the recalibration repetition counter
mov [RecalRepCounter], 0
@@TryAgain:
; Reset the read operation retry counter
mov [ReadRepCounter], 0
@@ReadSector_1:
call ReadSector
cmp [FDC_Status], 0
je @@Exit_2
cmp [FDC_Status], 1
je @@Err_3
; Three times repeat reading
inc [ReadRepCounter]
cmp [ReadRepCounter], 3
jb @@ReadSector_1
; Three times repeat recalibration
call RecalibrateFDD
call SeekTrack
inc [RecalRepCounter]
cmp [RecalRepCounter], 3
jb @@TryAgain
@@Exit_2:
popa
ret
@@Err_3:
popa
ret
 
;*******************************************************
;* WRITE DATA SECTOR *
;* Parameters are passed through global variables: *
;* FDD_Track - track number (0-79); *
;* FDD_Head - head number (0-1); *
;* FDD_Sector - sector number (1-18). *
;* Result of operation is written to FDC_Status. *
;* If the write operation is successful, the contents *
;* of FDD_DataBuffer will be written to the sector *
;*******************************************************
WriteSector:
; dbgstr 'WriteSector'
pushad
call save_timer_fdd_motor
; Clear the interrupt flag
mov [FDD_IntFlag], 0
; Set transmit speed to 500 Kb / s
mov AX, 0
mov DX, 03F7h
out DX, AL
; Initialize the DMA channel
mov [dmamode], 0x4A
call Init_FDC_DMA
; Send "Write data" command
mov AL, 0xC5 ;0x45 ; write in multi-track mode
call FDCDataOutput
mov AL, [FDD_Head]
shl AL, 2
or AL, [flp_number]
dec AL
call FDCDataOutput
mov AL, [FDD_Track]
call FDCDataOutput
mov AL, [FDD_Head]
call FDCDataOutput
mov AL, [FDD_Sector]
call FDCDataOutput
mov AL, 2 ; sector size code (512 bytes)
call FDCDataOutput
mov AL, 18; 3Fh ; sectors per track
call FDCDataOutput
mov AL, 1Bh ; GPL value
call FDCDataOutput
mov AL, 0FFh; DTL value
call FDCDataOutput
; Waiting for an interrupt at the end of the operation
call WaitFDCInterrupt
cmp [FDC_Status], FDC_Normal
jne @@Exit_3
; Reading the completion status of the operation
call GetStatusInfo
test [FDC_ST0], 11000000b ;11011000b
jnz @@Err_2
mov [FDC_Status], FDC_Normal
jmp @@Exit_3
@@Err_2:
mov [FDC_Status], FDC_SectorNotFound
@@Exit_3:
call save_timer_fdd_motor
popad
ret
 
;*******************************************************
;* WRITE SECTOR (WITH REPEAT ON FAILURE) *
;* Parameters are passed through global variables: *
;* FDD_Track - track number (0-79); *
;* FDD_Head - head number (0-1); *
;* FDD_Sector - sector number (1-18). *
;* Result of operation is written to FDC_Status. *
;* If the write operation is successful, the contents *
;* of FDD_DataBuffer will be written to the sector *
;*******************************************************
WriteSectWithRetr:
pusha
; Reset the recalibration repetition counter
mov [RecalRepCounter], 0
@@TryAgain_1:
; Reset the read operation retry counter
mov [ReadRepCounter], 0
@@WriteSector_1:
call WriteSector
cmp [FDC_Status], 0
je @@Exit_4
cmp [FDC_Status], 1
je @@Err_4
; Three times repeat writing
inc [ReadRepCounter]
cmp [ReadRepCounter], 3
jb @@WriteSector_1
; Three times repeat recalibration
call RecalibrateFDD
call SeekTrack
inc [RecalRepCounter]
cmp [RecalRepCounter], 3
jb @@TryAgain_1
@@Exit_4:
popa
ret
@@Err_4:
popa
ret
 
;*********************************************
;* GET INFORMATION ABOUT THE RESULT OF THE OPERATION
;*********************************************
GetStatusInfo:
push AX
call FDCDataInput
mov [FDC_ST0], AL
call FDCDataInput
mov [FDC_ST1], AL
call FDCDataInput
mov [FDC_ST2], AL
call FDCDataInput
mov [FDC_C], AL
call FDCDataInput
mov [FDC_H], AL
call FDCDataInput
mov [FDC_R], AL
call FDCDataInput
mov [FDC_N], AL
pop AX
ret
 
; Interface for disk subsystem.
; Assume fixed capacity for 1.44M.
FLOPPY_CAPACITY = 2880 ; in sectors
 
iglobal
align 4
floppy_functions:
dd .size
dd 0 ; no close() function
dd 0 ; no closemedia() function
dd floppy_querymedia
dd floppy_read
dd floppy_write
dd 0 ; no flush() function
dd 0 ; no adjust_cache_size() function
.size = $ - floppy_functions
endg
 
uglobal
floppy_media_flags rb 2
n_sector dd 0 ; temporary save for sector value
flp_number db 0 ; 1- Floppy A, 2-Floppy B
old_track db 0 ; old value track
flp_label rb 15*2 ; Label and ID of inserted floppy disk
align 4
; Hardware does not allow to work with two floppies in parallel,
; so there is one mutex guarding access to any floppy.
floppy_mutex MUTEX
endg
; Meaning of bits in floppy_media_flags
FLOPPY_MEDIA_PRESENT = 1 ; media was present when last asked
FLOPPY_MEDIA_NEED_RESCAN = 2 ; media was possibly changed, need to rescan
FLOPPY_MEDIA_LABEL_CHANGED = 4 ; temporary state
 
iglobal
floppy1_name db 'fd',0
floppy2_name db 'fd2',0
endg
 
; This function is called in boot process.
; It creates filesystems /fd and/or /fd2, if the system has one/two floppy drives.
proc floppy_init
mov ecx, floppy_mutex
call mutex_init
; First floppy is present if [DRIVE_DATA] and 0xF0 is nonzero.
test byte [DRIVE_DATA], 0xF0
jz .no1
stdcall disk_add, floppy_functions, floppy1_name, 1, DISK_NO_INSERT_NOTIFICATION
.no1:
; Second floppy is present if [DRIVE_DATA] and 0x0F is nonzero.
test byte [DRIVE_DATA], 0x0F
jz .no2
stdcall disk_add, floppy_functions, floppy2_name, 2, DISK_NO_INSERT_NOTIFICATION
.no2:
ret
endp
 
; Returns information about disk media.
; Floppy drives do not support insert notifications,
; DISK_NO_INSERT_NOTIFICATION is set,
; the disk subsystem calls this function before each filesystem operation.
; If the media has changed, return error for the first call as signal
; to finalize work with old media and the true geometry for the second call.
; Assume that media is (possibly) changed anytime when motor is off.
proc floppy_querymedia
virtual at esp+4
.userdata dd ?
.info dd ?
end virtual
; 1. Acquire the global lock.
mov ecx, floppy_mutex
call mutex_lock
mov edx, [.userdata] ; 1 for /fd, 2 for /fd2
; 2. If the media was reported and has been changed, forget it and report an error.
mov al, [floppy_media_flags+edx-1]
and al, FLOPPY_MEDIA_PRESENT + FLOPPY_MEDIA_NEED_RESCAN
cmp al, FLOPPY_MEDIA_PRESENT + FLOPPY_MEDIA_NEED_RESCAN
jnz .not_reported
.no_media:
mov [floppy_media_flags+edx-1], 0
.return_no_media:
mov ecx, floppy_mutex
call mutex_unlock
mov eax, DISK_STATUS_NO_MEDIA
retn 8
.not_reported:
; 3. If we are in the temporary state LABEL_CHANGED, this is the second call
; after intermediate DISK_STATUS_NO_MEDIA due to media change;
; clear the flag and return the current geometry without rereading the bootsector.
cmp [floppy_media_flags+edx-1], FLOPPY_MEDIA_LABEL_CHANGED
jz .report_geometry
; 4. Try to read the bootsector.
mov [flp_number], dl
mov [FDC_Status], 0
call floppy_read_bootsector
; 5. If reading bootsector failed, assume that media is not present.
mov edx, [.userdata]
cmp [FDC_Status], 0
jnz .no_media
; 6. Check whether the previous status is "present". If not, go to 10.
push esi edi
imul edi, edx, 15
add edi, flp_label-15
mov esi, FDD_BUFF+39
mov ecx, 15
test [floppy_media_flags+edx-1], FLOPPY_MEDIA_PRESENT
jz .set_label
; 7. Compare the old label with the current one.
rep cmpsb
; 8. If the label has not changed, go to 11.
jz .ok
; 9. If the label has changed, store it, enter temporary state LABEL_CHANGED
; and report DISK_STATUS_NO_MEDIA.
; dbgstr 'floppy label changed'
add esi, ecx
add edi, ecx
mov ecx, 15
sub esi, ecx
sub edi, ecx
rep movsb
mov [floppy_media_flags+edx-1], FLOPPY_MEDIA_LABEL_CHANGED
pop edi esi
jmp .return_no_media
.set_label:
; 10. The previous state was "not present". Copy the label.
rep movsb
.ok:
pop edi esi
.report_geometry:
; 11. Fill DISKMEDIAINFO structure.
mov ecx, [.info]
and [ecx+DISKMEDIAINFO.Flags], 0
mov [ecx+DISKMEDIAINFO.SectorSize], 512
mov dword [ecx+DISKMEDIAINFO.Capacity], FLOPPY_CAPACITY
and dword [ecx+DISKMEDIAINFO.Capacity+4], 0
; 12. Update state: media is present, data are actual.
mov [floppy_media_flags+edx-1], FLOPPY_MEDIA_PRESENT
; 13. Release the global lock and return successful status.
mov ecx, floppy_mutex
call mutex_unlock
xor eax, eax
retn 8
endp
 
proc floppy_read_bootsector
pushad
mov [FDD_Track], 0 ; Cylinder
mov [FDD_Head], 0 ; Head
mov [FDD_Sector], 1 ; Sector
call FDDMotorON
call RecalibrateFDD
cmp [FDC_Status], 0
jne .nothing
call SeekTrack
cmp [FDC_Status], 0
jne .nothing
call ReadSectWithRetr
.nothing:
popad
ret
endp
 
read_chs_sector:
call calculate_chs
call ReadSectWithRetr
ret
 
save_chs_sector:
call calculate_chs
call WriteSectWithRetr
ret
 
calculate_chs:
mov bl, [FDD_Track]
mov [old_track], bl
mov ebx, 18
xor edx, edx
div ebx
inc edx
mov [FDD_Sector], dl
mov edx, eax
shr eax, 1
and edx, 1
mov [FDD_Track], al
mov [FDD_Head], dl
mov dl, [old_track]
cmp dl, [FDD_Track]
je no_seek_track_1
call SeekTrack
no_seek_track_1:
ret
 
; Writes one or more sectors to the device.
proc floppy_write
mov dl, 1
jmp floppy_read_write
endp
 
; Reads one or more sectors from the device.
proc floppy_read
mov dl, 0
endp
 
; Common part of floppy_read and floppy_write.
proc floppy_read_write userdata:dword, buffer:dword, start_sector:qword, numsectors_ptr:dword
virtual at ebp-8
.sectors_todo dd ?
.operation db ?
end virtual
push edx ; save operation code to [.operation]
; 1. Get number of sectors to read/write
; and zero number of sectors that were actually read/written.
mov eax, [numsectors_ptr]
push dword [eax] ; initialize [.sectors_todo]
and dword [eax], 0
push ebx esi edi ; save used registers to be stdcall
; 2. Acquire the global lock.
mov ecx, floppy_mutex
call mutex_lock
; 3. Set floppy number for this operation.
mov edx, [userdata]
mov [flp_number], dl
; 4. Read/write sector-by-sector.
.operation_loop:
; 4a. Check that the sector is inside the media.
cmp dword [start_sector+4], 0
jnz .end_of_media
mov eax, dword [start_sector]
cmp eax, FLOPPY_CAPACITY
jae .end_of_media
; 4b. For read operation, call read_chs_sector and then move data from FDD_BUFF to [buffer].
; For write operation, move data from [buffer] to FDD_BUFF and then call save_chs_sector.
cmp [.operation], 0
jz .read
mov esi, [buffer]
mov edi, FDD_BUFF
mov ecx, 512/4
rep movsd
mov [buffer], esi
call save_chs_sector
jmp @f
.read:
call read_chs_sector
mov esi, FDD_BUFF
mov edi, [buffer]
mov ecx, 512/4
rep movsd
mov [buffer], edi
@@:
; 4c. If there was an error, propagate it to the caller.
cmp [FDC_Status], 0
jnz .fail
; 4d. Otherwise, increment number of sectors processed and continue the loop.
mov eax, [numsectors_ptr]
inc dword [eax]
inc dword [start_sector]
dec [.sectors_todo]
jnz .operation_loop
; 5. Release the global lock and return with the correct status.
push 0
.return:
mov ecx, floppy_mutex
call mutex_unlock
pop eax
pop edi esi ebx ; restore used registers to be stdcall
ret ; this translates to leave/retn N and purges local variables
.fail:
push -1
jmp .return
.end_of_media:
push DISK_STATUS_END_OF_MEDIA
jmp .return
endp
Property changes:
Added: svn:eol-style
+native
\ No newline at end of property
Added: svn:keywords
+Rev
\ No newline at end of property
/kernel/branches/kolibri-lldw/blkdev/cd_drv.inc
0,0 → 1,1218
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ;;
;; Copyright (C) KolibriOS team 2004-2015. All rights reserved. ;;
;; Distributed under terms of the GNU General Public License ;;
;; ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
$Revision$
 
;-----------------------------------------------------------------------------
;**********************************************************
; Direct work with CD (ATAPI) device
;**********************************************************
; Author of a part of the source code - Kulakov Vladimir Gennadievich
; Adaptation, revision and development - Mario79, <Lrz>
 
; Maximum number of repeats of a read operation
MaxRetr = 10
; Maximum waiting time for ready to receive a command
; (in ticks)
BSYWaitTime = 1000 ;2
NoTickWaitTime = 0xfffff
CDBlockSize = 2048
;********************************************
;* READING SECTOR WITH REPEATS *
;* Repeated reads on failures *
;********************************************
ReadCDWRetr:
;-----------------------------------------------------------
; input : eax = block to read
; ebx = destination
;-----------------------------------------------------------
pushad
mov eax, [CDSectorAddress]
mov ebx, [CDDataBuf_pointer]
call cd_calculate_cache
xor edi, edi
add esi, 8
inc edi
;--------------------------------------
align 4
.hdreadcache:
cmp [esi], eax ; correct sector
je .yeshdcache
 
add esi, 8
inc edi
dec ecx
jnz .hdreadcache
 
call find_empty_slot_CD_cache ; ret in edi
 
push edi
push eax
call cd_calculate_cache_2
shl edi, 11
add edi, eax
mov [CDDataBuf_pointer], edi
pop eax
pop edi
 
call ReadCDWRetr_1
cmp [DevErrorCode], 0
jne .exit
 
mov [CDDataBuf_pointer], ebx
call cd_calculate_cache_1
lea esi, [edi*8+esi]
mov [esi], eax ; sector number
;--------------------------------------
.yeshdcache:
mov esi, edi
shl esi, 11 ;9
push eax
call cd_calculate_cache_2
add esi, eax
pop eax
mov edi, ebx ;[CDDataBuf_pointer]
mov ecx, 512 ;/4
cld
rep movsd ; move data
;--------------------------------------
.exit:
popad
ret
;-----------------------------------------------------------------------------
ReadCDWRetr_1:
pushad
; Loop until the command is successful or the number of attempts is over
mov ecx, MaxRetr
;--------------------------------------
align 4
@@NextRetr:
; Send a command
;*************************************************
;* FULL READ OF COMPACT DISK SECTOR *
;* User data, subchannel *
;* information and control information are read *
;* Input parameters are passed through global *
;* variables: *
;* ChannelNumber - channel number; *
;* DiskNumber - disc number on channel; *
;* CDSectorAddress - address of reading sector. *
;* The data is read into the CDDataBuf array. *
;*************************************************
;ReadCD:
push ecx
; Flush the packet command buffer
call clear_packet_buffer
; Generate a packet command to read a data sector
; Set the command code Read CD
mov [PacketCommand], byte 0x28 ;0xBE
; Set the sector address
mov ax, word [CDSectorAddress+2]
xchg al, ah
mov word [PacketCommand+2], ax
mov ax, word [CDSectorAddress]
xchg al, ah
mov word [PacketCommand+4], ax
; Set the number of sectors to read
mov [PacketCommand+8], byte 1
; Send a command
call SendPacketDatCommand
pop ecx
 
test eax, eax
jz @@End_4
 
or ecx, ecx ;{SPraid.simba} (for cd load)
jz @@End_4
 
dec ecx
 
cmp [timer_ticks_enable], 0
jne @f
 
mov eax, NoTickWaitTime
;--------------------------------------
align 4
.wait:
dec eax
jz @@NextRetr
 
jmp .wait
;--------------------------------------
align 4
@@:
loop @@NextRetr
;--------------------------------------
@@End_4:
mov dword [DevErrorCode], eax
popad
ret
;-----------------------------------------------------------------------------
; General purpose procedures to execute packet commands in PIO Mode
; Maximum allowable waiting time for the device to respond to a packet command (in ticks)
;-----------------------------------------------------------------------------
MaxCDWaitTime = 1000 ;200 ;10 seconds
uglobal
; Memory area for generating a packet command
PacketCommand:
rb 12 ;DB 12 DUP (?)
; address of reading data sector
CDSectorAddress: dd ?
; Start time of the next disk operation
TickCounter_1 dd 0
; Time to start waiting for device readiness
WURStartTime dd 0
; pointer to buffer to read data into
CDDataBuf_pointer dd 0
endg
;-----------------------------------------------------------------------------
;****************************************************
;* SEND TO ATAPI DEVICE PACKET COMMAND, *
;* THAT MEANS TRASMIT ONE DATA SECTOR OF SIZE *
;* 2048 BYTE FROM DEVICE TO HOST *
;* Input parameters are passed through global *
;* variables: *
;* ChannelNumber - channel number; *
;* DiskNumber - disk number on channel. *
;* PacketCommand - 12-byte command packet; *
;* CDBlockSize - size of receiving data block. *
; return eax DevErrorCode
;****************************************************
SendPacketDatCommand:
xor eax, eax
; Set CHS mode
mov byte [ATAAddressMode], al
; Send ATA command to send packet command
mov byte [ATAFeatures], al
mov byte [ATASectorCount], al
mov byte [ATASectorNumber], al
; Load the size of the sending block
mov [ATAHead], al
mov [ATACylinder], CDBlockSize
mov [ATACommand], 0xA0
call SendCommandToHDD_1
test eax, eax
jnz @@End_8 ; finish, saving the error code
; Waiting for the drive to be ready to receive a packet command
mov dx, [ATABasePortAddr]
add dx, 7 ; port 1x7h
mov ecx, NoTickWaitTime
;--------------------------------------
align 4
@@WaitDevice0:
cmp [timer_ticks_enable], 0
jne @f
 
dec ecx
jz @@Err1_1
 
jmp .test
;--------------------------------------
align 4
@@:
call change_task
; Check command execution time
mov eax, [timer_ticks]
sub eax, [TickCounter_1]
cmp eax, BSYWaitTime
ja @@Err1_1 ; time out error
; Check readiness
;--------------------------------------
align 4
.test:
in al, dx
test al, 0x80 ; BSY signal state
jnz @@WaitDevice0
 
test al, 1 ; ERR signal state
jnz @@Err6
 
test al, 0x8 ; DRQ signal state
jz @@WaitDevice0
; Send a packet command
cli
mov dx, [ATABasePortAddr]
mov ax, [PacketCommand]
out dx, ax
mov ax, [PacketCommand+2]
out dx, ax
mov ax, [PacketCommand+4]
out dx, ax
mov ax, [PacketCommand+6]
out dx, ax
mov ax, [PacketCommand+8]
out dx, ax
mov ax, [PacketCommand+10]
out dx, ax
sti
; Waiting for data to be ready
mov dx, [ATABasePortAddr]
add dx, 7 ; port 1x7h
mov ecx, NoTickWaitTime
;--------------------------------------
align 4
@@WaitDevice1:
cmp [timer_ticks_enable], 0
jne @f
 
dec ecx
jz @@Err1_1
 
jmp .test_1
;--------------------------------------
align 4
@@:
call change_task
; Check command execution time
mov eax, [timer_ticks]
sub eax, [TickCounter_1]
cmp eax, MaxCDWaitTime
ja @@Err1_1 ; time out error
; Check readiness
;--------------------------------------
align 4
.test_1:
in al, dx
test al, 0x80 ; BSY signal state
jnz @@WaitDevice1
 
test al, 1 ; ERR signal state
jnz @@Err6_temp
 
test al, 0x8 ; DRQ signal state
jz @@WaitDevice1
; Receive data block from controller
mov edi, [CDDataBuf_pointer]
; Load controller's data register address
mov dx, [ATABasePortAddr]
; Load the block size in bytes into the counter
xor ecx, ecx
mov cx, CDBlockSize
; Calculate block size in 16-bit words
shr cx, 1 ; divide block size by 2
; Receive data block
cli
cld
rep insw
sti
;--------------------------------------
; Successful completion of data receive
@@End_8:
xor eax, eax
ret
;--------------------------------------
; Write error code
@@Err1_1:
xor eax, eax
inc eax
ret
;--------------------------------------
@@Err6_temp:
mov eax, 7
ret
;--------------------------------------
@@Err6:
mov eax, 6
ret
;-----------------------------------------------------------------------------
;***********************************************
;* SEND TO ATAPI DEVICE PACKET COMMAND, *
;* THAT DOESNT MEAN TRANSMIT DATA *
;* Input parameters are passed through global *
;* variables: *
;* ChannelNumber - channel number; *
;* DiskNumber - disk number on channel. *
;* PacketCommand - 12-byte command packet. *
;***********************************************
SendPacketNoDatCommand:
pushad
xor eax, eax
; Set CHS mode
mov byte [ATAAddressMode], al
; Send ATA command to send packet command
mov byte [ATAFeatures], al
mov byte [ATASectorCount], al
mov byte [ATASectorNumber], al
mov word [ATACylinder], ax
mov byte [ATAHead], al
mov [ATACommand], 0xA0
call SendCommandToHDD_1
test eax, eax
jnz @@End_9 ; finish, saving the error code
; Waiting for the drive to be ready to receive a packet command
mov dx, [ATABasePortAddr]
add dx, 7 ; port 1x7h
;--------------------------------------
align 4
@@WaitDevice0_1:
call change_task
; Check waiting time
mov eax, [timer_ticks]
sub eax, [TickCounter_1]
cmp eax, BSYWaitTime
ja @@Err1_3 ; time out error
; Check readiness
in al, dx
test al, 0x80 ; BSY signal state
jnz @@WaitDevice0_1
 
test al, 1 ; ERR signal state
jnz @@Err6_1
 
test al, 0x8 ; DRQ signal state
jz @@WaitDevice0_1
; Send packet command
; cli
mov dx, [ATABasePortAddr]
mov ax, word [PacketCommand]
out dx, ax
mov ax, word [PacketCommand+2]
out dx, ax
mov ax, word [PacketCommand+4]
out dx, ax
mov ax, word [PacketCommand+6]
out dx, ax
mov ax, word [PacketCommand+8]
out dx, ax
mov ax, word [PacketCommand+10]
out dx, ax
; sti
cmp [ignore_CD_eject_wait], 1
je @@clear_DEC
; Waiting for confirmation of command receive
mov dx, [ATABasePortAddr]
add dx, 7 ; port 1x7h
;--------------------------------------
align 4
@@WaitDevice1_1:
call change_task
; Check command execution time
mov eax, [timer_ticks]
sub eax, [TickCounter_1]
cmp eax, MaxCDWaitTime
ja @@Err1_3 ; time out error
; Wait for device release
in al, dx
test al, 0x80 ; BSY signal state
jnz @@WaitDevice1_1
 
test al, 1 ; ERR signal state
jnz @@Err6_1
 
test al, 0x40 ; DRDY signal state
jz @@WaitDevice1_1
;--------------------------------------
@@clear_DEC:
and [DevErrorCode], 0
popad
ret
;--------------------------------------
; Write error code
@@Err1_3:
xor eax, eax
inc eax
jmp @@End_9
;--------------------------------------
@@Err6_1:
mov eax, 6
;--------------------------------------
@@End_9:
mov [DevErrorCode], eax
popad
ret
;-----------------------------------------------------------------------------
;****************************************************
;* SEND COMMAND TO GIVEN DISK *
;* Input parameters are passed through the global *
;* variables: *
;* ChannelNumber - channel number (1 or 2); *
;* DiskNumber - disk number (0 or 1); *
;* ATAFeatures - "features"; *
;* ATASectorCount - sector count; *
;* ATASectorNumber - initial sector number; *
;* ATACylinder - initial cylinder number; *
;* ATAHead - initial head number; *
;* ATAAddressMode - addressing mode (0-CHS, 1-LBA); *
;* ATACommand - command code. *
;* If the function finished successfully: *
;* in ATABasePortAddr - base address of HDD; *
;* in DevErrorCode - zero. *
;* If error has occured then in DevErrorCode will *
;* be the error code. *
;****************************************************
SendCommandToHDD_1:
; Check the addressing mode code
cmp [ATAAddressMode], 1
ja @@Err2_4
; Check the channel number correctness
movzx ebx, [ChannelNumber]
dec ebx
cmp ebx, 1
ja @@Err3_4
; Set the base address
shl ebx, 2
mov eax, [cdpos]
dec eax
shr eax, 2
imul eax, sizeof.IDE_DATA
add eax, IDE_controller_1
add eax, ebx
mov ax, [eax+IDE_DATA.BAR0_val]
mov [ATABasePortAddr], ax
; Waiting for HDD ready to receive a command
; Choose desired disk
mov dx, [ATABasePortAddr]
add dx, 6 ; address of the heads register
mov al, [DiskNumber]
cmp al, 1 ; check the disk number
ja @@Err4_4
 
shl al, 4
or al, 10100000b
out dx, al
; Waiting for disk ready
inc dx
mov eax, [timer_ticks]
mov [TickCounter_1], eax
mov ecx, NoTickWaitTime
;--------------------------------------
align 4
@@WaitHDReady_2:
cmp [timer_ticks_enable], 0
jne @f
 
dec ecx
jz @@Err1_4
 
jmp .test
;--------------------------------------
align 4
@@:
call change_task
; Check waiting time
mov eax, [timer_ticks]
sub eax, [TickCounter_1]
cmp eax, BSYWaitTime ;300 ; wait for 3 seconds
ja @@Err1_4 ; time out error
;--------------------------------------
align 4
.test:
in al, dx ; Read the state register
; Check the state of BSY signal
test al, 0x80
jnz @@WaitHDReady_2
; Check the state of DRQ signal
test al, 0x8
jnz @@WaitHDReady_2
; load command to controller's registers
cli
mov dx, [ATABasePortAddr]
inc dx ; "features" register
mov al, [ATAFeatures]
out dx, al
inc dx ; sector counter
mov al, [ATASectorCount]
out dx, al
inc dx ; sector number register
mov al, [ATASectorNumber]
out dx, al
inc dx ; cylinder number (low byte)
mov ax, [ATACylinder]
out dx, al
inc dx ; cylinder number (high byte)
mov al, ah
out dx, al
inc dx ; head number / disk number
mov al, [DiskNumber]
shl al, 4
cmp [ATAHead], 0xF ; check head number
ja @@Err5_4
 
or al, [ATAHead]
or al, 10100000b
mov ah, [ATAAddressMode]
shl ah, 6
or al, ah
out dx, al
; Send command
mov al, [ATACommand]
inc dx ; command register
out dx, al
sti
;--------------------------------------
@@End_10:
xor eax, eax
ret
;--------------------------------------
; Write error code
@@Err1_4:
xor eax, eax
inc eax
ret
;--------------------------------------
@@Err2_4:
mov eax, 2
ret
;--------------------------------------
@@Err3_4:
mov eax, 3
ret
;--------------------------------------
@@Err4_4:
mov eax, 4
ret
;--------------------------------------
@@Err5_4:
mov eax, 5
ret
;-----------------------------------------------------------------------------
;*************************************************
;* WAIT FOR THE DEVICE IS READY FOR WORK *
;* Input parameters are passed through global *
;* variables: *
;* ChannelNumber - channel number; *
;* DiskNumber - disk number on channel. *
;*************************************************
WaitUnitReady:
pusha
; Remember the peration start time
mov eax, [timer_ticks]
mov [WURStartTime], eax
; Clear the packet command buffer
call clear_packet_buffer
; Generate TEST UNIT READY command
mov [PacketCommand], word 0
; waiting loop for device readiness
mov ecx, NoTickWaitTime
;--------------------------------------
align 4
@@SendCommand:
; Send readiness check command
call SendPacketNoDatCommand
cmp [timer_ticks_enable], 0
jne @f
 
cmp [DevErrorCode], 0
je @@End_11
 
dec ecx
jz .Error
 
jmp @@SendCommand
;--------------------------------------
align 4
@@:
call change_task
; Check the error code
cmp [DevErrorCode], 0
je @@End_11
; Check waiting time
mov eax, [timer_ticks]
sub eax, [WURStartTime]
cmp eax, MaxCDWaitTime
jb @@SendCommand
;--------------------------------------
.Error:
; time out error
mov [DevErrorCode], 1
;--------------------------------------
@@End_11:
popa
ret
;-----------------------------------------------------------------------------
;*************************************************
;* FORBID DISK CHANGE *
;* Input parameters are passed through global *
;* variables: *
;* ChannelNumber - channel number; *
;* DiskNumber - disk number on channel. *
;*************************************************
prevent_medium_removal:
pusha
; Clear the packet command buffer
call clear_packet_buffer
; Set command code
mov [PacketCommand], byte 0x1E
; Set "Forbid" code
mov [PacketCommand+4], byte 11b
; Send command
call SendPacketNoDatCommand
mov eax, ATAPI_IDE0_lock
add eax, [cdpos]
dec eax
mov [eax], byte 1
popa
ret
;-----------------------------------------------------------------------------
;*************************************************
;* ALLOW DISK CHANGE *
;* Input parameters are passed through global *
;* variables: *
;* ChannelNumber - channel number; *
;* DiskNumber - disk number on channel. *
;*************************************************
allow_medium_removal:
pusha
; Clear the packet command buffer
call clear_packet_buffer
; Set command code
mov [PacketCommand], byte 0x1E
; unset "Forbid" code
mov [PacketCommand+4], byte 0
; Send command
call SendPacketNoDatCommand
mov eax, ATAPI_IDE0_lock
add eax, [cdpos]
dec eax
mov [eax], byte 0
popa
ret
;-----------------------------------------------------------------------------
;*************************************************
;* LOAD DISK TO THE DRIVE *
;* Input parameters are passed through global *
;* variables: *
;* ChannelNumber - channel number; *
;* DiskNumber - disk number on channel. *
;*************************************************
LoadMedium:
pusha
; Clear the packet command buffer
call clear_packet_buffer
; Generate START/STOP UNIT command
; Set command code
mov [PacketCommand], word 0x1B
; Set disk loading operation
mov [PacketCommand+4], word 00000011b
; Send command
call SendPacketNoDatCommand
popa
ret
;-----------------------------------------------------------------------------
;*************************************************
;* REMOVE THE DISK FROM THE DRIVE *
;* Input parameters are passed through global *
;* variables: *
;* ChannelNumber - channel number; *
;* DiskNumber - disk number on channel. *
;*************************************************
EjectMedium:
pusha
; Clear the packet command buffer
call clear_packet_buffer
; Generate START/STOP UNIT command
; Set command code
mov [PacketCommand], word 0x1B
; Set the operation to eject disk
mov [PacketCommand+4], word 00000010b
; Send command
call SendPacketNoDatCommand
popa
ret
;-----------------------------------------------------------------------------
;*************************************************
;* Check the event of pressing the eject button *
;* *
;* Input parameters are passed through global *
;* variables: *
;* ChannelNumber - channel number; *
;* DiskNumber - disk number on channel. *
;*************************************************
proc check_ATAPI_device_event_has_work?
mov eax, [timer_ticks]
sub eax, [timer_ATAPI_check]
cmp eax, 100
jb .no
 
xor eax, eax
inc eax
ret
;--------------------------------------
.no:
xor eax, eax
ret
endp
;-----------------------------------------------------------------------------
align 4
check_ATAPI_device_event:
pusha
mov eax, [timer_ticks]
sub eax, [timer_ATAPI_check]
cmp eax, 100
jb .end_1
 
pushfd
mov al, [DRIVE_DATA+1]
and al, 11b
cmp al, 10b
jz .ide3
;--------------------------------------
.ide2_1:
mov al, [DRIVE_DATA+1]
and al, 1100b
cmp al, 1000b
jz .ide2
;--------------------------------------
.ide1_1:
mov al, [DRIVE_DATA+1]
and al, 110000b
cmp al, 100000b
jz .ide1
;--------------------------------------
.ide0_1:
mov al, [DRIVE_DATA+1]
and al, 11000000b
cmp al, 10000000b
jz .ide0
;--------------------------------------
.ide7_1:
mov al, [DRIVE_DATA+6]
and al, 11b
cmp al, 10b
jz .ide7
;--------------------------------------
.ide6_1:
mov al, [DRIVE_DATA+6]
and al, 1100b
cmp al, 1000b
jz .ide6
;--------------------------------------
.ide5_1:
mov al, [DRIVE_DATA+6]
and al, 110000b
cmp al, 100000b
jz .ide5
;--------------------------------------
.ide4_1:
mov al, [DRIVE_DATA+6]
and al, 11000000b
cmp al, 10000000b
jz .ide4
;--------------------------------------
.ide11_1:
mov al, [DRIVE_DATA+11]
and al, 11b
cmp al, 10b
jz .ide11
;--------------------------------------
.ide10_1:
mov al, [DRIVE_DATA+11]
and al, 1100b
cmp al, 1000b
jz .ide10
;--------------------------------------
.ide9_1:
mov al, [DRIVE_DATA+11]
and al, 110000b
cmp al, 100000b
jz .ide9
;--------------------------------------
.ide8_1:
mov al, [DRIVE_DATA+11]
and al, 11000000b
cmp al, 10000000b
jz .ide8
;--------------------------------------
.end:
popfd
mov eax, [timer_ticks]
mov [timer_ATAPI_check], eax
;--------------------------------------
.end_1:
popa
ret
;-----------------------------------------------------------------------------
.ide3:
cli
cmp [ATAPI_IDE3_lock], 1
jne .ide2_1
 
cmp [cd_status], 0
jne .end
 
mov ecx, ide_channel2_mutex
call mutex_lock
call reserve_ok2
mov [ChannelNumber], 2
mov [DiskNumber], 1
mov [cdpos], 4
call GetEvent_StatusNotification
cmp [CDDataBuf+4], byte 1
jne @f
 
call .eject
;--------------------------------------
@@:
call syscall_cdaudio.free
jmp .ide2_1
;-----------------------------------------------------------------------------
.ide2:
cli
cmp [ATAPI_IDE2_lock], 1
jne .ide1_1
 
cmp [cd_status], 0
jne .end
 
mov ecx, ide_channel2_mutex
call mutex_lock
call reserve_ok2
mov [ChannelNumber], 2
mov [DiskNumber], 0
mov [cdpos], 3
call GetEvent_StatusNotification
cmp [CDDataBuf+4], byte 1
jne @f
 
call .eject
;--------------------------------------
@@:
call syscall_cdaudio.free
jmp .ide1_1
;-----------------------------------------------------------------------------
.ide1:
cli
cmp [ATAPI_IDE1_lock], 1
jne .ide0_1
 
cmp [cd_status], 0
jne .end
 
mov ecx, ide_channel1_mutex
call mutex_lock
call reserve_ok2
mov [ChannelNumber], 1
mov [DiskNumber], 1
mov [cdpos], 2
call GetEvent_StatusNotification
cmp [CDDataBuf+4], byte 1
jne @f
 
call .eject
;--------------------------------------
@@:
call syscall_cdaudio.free
jmp .ide0_1
;-----------------------------------------------------------------------------
.ide0:
cli
cmp [ATAPI_IDE0_lock], 1
jne .ide7_1
 
cmp [cd_status], 0
jne .end
 
mov ecx, ide_channel1_mutex
call mutex_lock
call reserve_ok2
mov [ChannelNumber], 1
mov [DiskNumber], 0
mov [cdpos], 1
call GetEvent_StatusNotification
cmp [CDDataBuf+4], byte 1
jne @f
 
call .eject
;--------------------------------------
@@:
call syscall_cdaudio.free
jmp .ide7_1
;-----------------------------------------------------------------------------
.ide7:
cli
cmp [ATAPI_IDE7_lock], 1
jne .ide6_1
 
cmp [cd_status], 0
jne .end
 
mov ecx, ide_channel4_mutex
call mutex_lock
call reserve_ok2
mov [ChannelNumber], 2
mov [DiskNumber], 1
mov [cdpos], 8
call GetEvent_StatusNotification
cmp [CDDataBuf+4], byte 1
jne @f
 
call .eject
;--------------------------------------
@@:
call syscall_cdaudio.free
jmp .ide6_1
;-----------------------------------------------------------------------------
.ide6:
cli
cmp [ATAPI_IDE6_lock], 1
jne .ide5_1
 
cmp [cd_status], 0
jne .end
 
mov ecx, ide_channel4_mutex
call mutex_lock
call reserve_ok2
mov [ChannelNumber], 2
mov [DiskNumber], 0
mov [cdpos], 7
call GetEvent_StatusNotification
cmp [CDDataBuf+4], byte 1
jne @f
 
call .eject
;--------------------------------------
@@:
call syscall_cdaudio.free
jmp .ide5_1
;-----------------------------------------------------------------------------
.ide5:
cli
cmp [ATAPI_IDE5_lock], 1
jne .ide4_1
 
cmp [cd_status], 0
jne .end
 
mov ecx, ide_channel3_mutex
call mutex_lock
call reserve_ok2
mov [ChannelNumber], 1
mov [DiskNumber], 1
mov [cdpos], 6
call GetEvent_StatusNotification
cmp [CDDataBuf+4], byte 1
jne @f
 
call .eject
;--------------------------------------
@@:
call syscall_cdaudio.free
jmp .ide4_1
;-----------------------------------------------------------------------------
.ide4:
cli
cmp [ATAPI_IDE4_lock], 1
jne .ide11_1
 
cmp [cd_status], 0
jne .end
 
mov ecx, ide_channel3_mutex
call mutex_lock
call reserve_ok2
mov [ChannelNumber], 1
mov [DiskNumber], 0
mov [cdpos], 5
call GetEvent_StatusNotification
cmp [CDDataBuf+4], byte 1
jne @f
 
call .eject
;--------------------------------------
@@:
call syscall_cdaudio.free
jmp .ide11_1
;-----------------------------------------------------------------------------
.ide11:
cli
cmp [ATAPI_IDE11_lock], 1
jne .ide10_1
 
cmp [cd_status], 0
jne .end
 
mov ecx, ide_channel6_mutex
call mutex_lock
call reserve_ok2
mov [ChannelNumber], 2
mov [DiskNumber], 1
mov [cdpos], 12
call GetEvent_StatusNotification
cmp [CDDataBuf+4], byte 1
jne @f
 
call .eject
;--------------------------------------
@@:
call syscall_cdaudio.free
jmp .ide10_1
;-----------------------------------------------------------------------------
.ide10:
cli
cmp [ATAPI_IDE10_lock], 1
jne .ide9_1
 
cmp [cd_status], 0
jne .end
 
mov ecx, ide_channel6_mutex
call mutex_lock
call reserve_ok2
mov [ChannelNumber], 2
mov [DiskNumber], 0
mov [cdpos], 11
call GetEvent_StatusNotification
cmp [CDDataBuf+4], byte 1
jne @f
 
call .eject
;--------------------------------------
@@:
call syscall_cdaudio.free
jmp .ide9_1
;-----------------------------------------------------------------------------
.ide9:
cli
cmp [ATAPI_IDE9_lock], 1
jne .ide8_1
 
cmp [cd_status], 0
jne .end
 
mov ecx, ide_channel5_mutex
call mutex_lock
call reserve_ok2
mov [ChannelNumber], 1
mov [DiskNumber], 1
mov [cdpos], 10
call GetEvent_StatusNotification
cmp [CDDataBuf+4], byte 1
jne @f
 
call .eject
;--------------------------------------
@@:
call syscall_cdaudio.free
jmp .ide8_1
;-----------------------------------------------------------------------------
.ide8:
cli
cmp [ATAPI_IDE8_lock], 1
jne .end
 
cmp [cd_status], 0
jne .end
 
mov ecx, ide_channel5_mutex
call mutex_lock
call reserve_ok2
mov [ChannelNumber], 1
mov [DiskNumber], 0
mov [cdpos], 9
call GetEvent_StatusNotification
cmp [CDDataBuf+4], byte 1
jne @f
 
call .eject
;--------------------------------------
@@:
call syscall_cdaudio.free
jmp .end
;-----------------------------------------------------------------------------
.eject:
call clear_CD_cache
call allow_medium_removal
mov [ignore_CD_eject_wait], 1
call EjectMedium
mov [ignore_CD_eject_wait], 0
ret
;-----------------------------------------------------------------------------
iglobal
timer_ATAPI_check dd 0
ATAPI_IDE0_lock db 0
ATAPI_IDE1_lock db 0
ATAPI_IDE2_lock db 0
ATAPI_IDE3_lock db 0
ATAPI_IDE4_lock db 0
ATAPI_IDE5_lock db 0
ATAPI_IDE6_lock db 0
ATAPI_IDE7_lock db 0
ATAPI_IDE8_lock db 0
ATAPI_IDE9_lock db 0
ATAPI_IDE10_lock db 0
ATAPI_IDE11_lock db 0
ignore_CD_eject_wait db 0
endg
;-----------------------------------------------------------------------------
;*************************************************
;* Get an event or device status message *
;* *
;* Input parameters are passed through global *
;* variables: *
;* ChannelNumber - channel number; *
;* DiskNumber - disk number on channel *
;*************************************************
GetEvent_StatusNotification:
pusha
mov [CDDataBuf_pointer], CDDataBuf
; Clear the packet command buffer
call clear_packet_buffer
; Set command code
mov [PacketCommand], byte 4Ah
mov [PacketCommand+1], byte 00000001b
; Set message class request
mov [PacketCommand+4], byte 00010000b
; Size of allocated area
mov [PacketCommand+7], byte 8h
mov [PacketCommand+8], byte 0h
; Send command
call SendPacketDatCommand
popa
ret
;-----------------------------------------------------------------------------
;*************************************************
; Read information from TOC (Table of contents) *
;* Input parameters are passed through global *
;* variables: *
;* ChannelNumber - channel number; *
;* DiskNumber - disk number on channel *
;*************************************************
Read_TOC:
pusha
mov [CDDataBuf_pointer], CDDataBuf
; Clear the packet command buffer
call clear_packet_buffer
; Generate a packet command to read a data sector
mov [PacketCommand], byte 0x43
; Set format
mov [PacketCommand+2], byte 1
; Size of allocated area
mov [PacketCommand+7], byte 0xFF
mov [PacketCommand+8], byte 0h
; Send a command
call SendPacketDatCommand
popa
ret
;-----------------------------------------------------------------------------
;*****************************************************
;* DETERMINE THE TOTAL NUMBER OF SECTORS ON THE DISK *
;* Input parameters are passed through global *
;* variables: *
;* ChannelNumber - channel number; *
;* DiskNumber - disk number on channel *
;*****************************************************
;ReadCapacity:
; pusha
;; Clear the packet command buffer
; call clear_packet_buffer
;; Set the buffer size in bytes
; mov [CDBlockSize],8
;; Generate READ CAPACITY command
; mov [PacketCommand],word 25h
;; Send command
; call SendPacketDatCommand
; popa
; ret
;-----------------------------------------------------------------------------
clear_packet_buffer:
; Clear the packet command buffer
and [PacketCommand], dword 0
and [PacketCommand+4], dword 0
and [PacketCommand+8], dword 0
ret
;-----------------------------------------------------------------------------
Property changes:
Added: svn:eol-style
+native
\ No newline at end of property
Added: svn:keywords
+Rev
\ No newline at end of property
/kernel/branches/kolibri-lldw/blkdev/bd_drv.inc
0,0 → 1,301
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ;;
;; Copyright (C) KolibriOS team 2004-2015. All rights reserved. ;;
;; Distributed under terms of the GNU General Public License ;;
;; ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
$Revision$
 
; Disk access through BIOS
iglobal
align 4
bd_callbacks:
dd bd_callbacks.end - bd_callbacks ; strucsize
dd 0 ; no close function
dd 0 ; no closemedia function
dd bd_querymedia
dd bd_read_interface
dd bd_write_interface
dd 0 ; no flush function
dd 0 ; use default cache size
.end:
endg
 
uglobal
bios_hdpos dd 0
bios_cur_sector dd ?
bios_read_len dd ?
cache_chain_ptr dd ?
int13_regs_in rb sizeof.v86_regs
int13_regs_out rb sizeof.v86_regs
cache_chain_size db ?
endg
 
struct BiosDiskData
DriveNumber db ?
IRQ db ?
ATADEVbit dw ?
SectorSize dd ?
Capacity dq ?
ends
;-----------------------------------------------------------------
proc bd_read_interface stdcall uses edi, \
userdata, buffer, startsector:qword, numsectors
; userdata = old [hdpos] = 80h + index in NumBiosDisks
; buffer = pointer to buffer for data
; startsector = 64-bit start sector
; numsectors = pointer to number of sectors on input,
; must be filled with number of sectors really read
locals
sectors_todo dd ?
endl
; 1. Initialize number of sectors: get number of requested sectors
; and say that no sectors were read yet.
mov ecx, [numsectors]
mov eax, [ecx]
mov dword [ecx], 0
mov [sectors_todo], eax
; 2. Acquire the global lock.
mov ecx, ide_mutex
call mutex_lock
; 3. Convert parameters to the form suitable for worker procedures.
; Underlying procedures do not know about 64-bit sectors.
; Worker procedures use global variables and edi for [buffer].
cmp dword [startsector+4], 0
jnz .fail
and [hd_error], 0
mov eax, [userdata]
mov [hdpos], eax
mov eax, dword [startsector]
mov edi, [buffer]
; 4. Worker procedures take one sectors per time, so loop over all sectors to read.
.sectors_loop:
call bd_read
cmp [hd_error], 0
jnz .fail
mov ecx, [numsectors]
inc dword [ecx] ; one more sector is read
dec [sectors_todo]
jz .done
inc eax
jnz .sectors_loop
; 5. Loop is done, either due to error or because everything is done.
; Release the global lock and return the corresponding status.
.fail:
mov ecx, ide_mutex
call mutex_unlock
or eax, -1
ret
.done:
mov ecx, ide_mutex
call mutex_unlock
xor eax, eax
ret
endp
;-----------------------------------------------------------------
proc bd_write_interface stdcall uses esi edi, \
userdata, buffer, startsector:qword, numsectors
; userdata = old [hdpos] = 80h + index in NumBiosDisks
; buffer = pointer to buffer with data
; startsector = 64-bit start sector
; numsectors = pointer to number of sectors on input,
; must be filled with number of sectors really written
locals
sectors_todo dd ?
endl
; 1. Initialize number of sectors: get number of requested sectors
; and say that no sectors were read yet.
mov ecx, [numsectors]
mov eax, [ecx]
mov dword [ecx], 0
mov [sectors_todo], eax
; 2. Acquire the global lock.
mov ecx, ide_mutex
call mutex_lock
; 3. Convert parameters to the form suitable for worker procedures.
; Underlying procedures do not know about 64-bit sectors.
; Worker procedures use global variables and esi for [buffer].
cmp dword [startsector+4], 0
jnz .fail
and [hd_error], 0
mov eax, [userdata]
mov [hdpos], eax
mov esi, [buffer]
lea edi, [startsector]
mov [cache_chain_ptr], edi
; 4. Worker procedures take max 16 sectors per time,
; loop until all sectors will be processed.
.sectors_loop:
mov ecx, 16
cmp ecx, [sectors_todo]
jbe @f
mov ecx, [sectors_todo]
@@:
mov [cache_chain_size], cl
call bd_write_cache_chain
cmp [hd_error], 0
jnz .fail
movzx ecx, [cache_chain_size]
mov eax, [numsectors]
add [eax], ecx
sub [sectors_todo], ecx
jz .done
add [edi], ecx
jc .fail
shl ecx, 9
add esi, ecx
jmp .sectors_loop
; 5. Loop is done, either due to error or because everything is done.
; Release the global lock and return the corresponding status.
.fail:
mov ecx, ide_mutex
call mutex_unlock
or eax, -1
ret
.done:
mov ecx, ide_mutex
call mutex_unlock
xor eax, eax
ret
endp
;-----------------------------------------------------------------
proc bd_querymedia stdcall, hd_data, mediainfo
mov edx, [mediainfo]
mov eax, [hd_data]
lea eax, [(eax-80h)*4]
lea eax, [BiosDisksData+eax*4]
mov [edx+DISKMEDIAINFO.Flags], 0
mov ecx, [eax+BiosDiskData.SectorSize]
mov [edx+DISKMEDIAINFO.SectorSize], ecx
mov ecx, dword [eax+BiosDiskData.Capacity+0]
mov eax, dword [eax+BiosDiskData.Capacity+4]
mov dword [edx+DISKMEDIAINFO.Capacity+0], ecx
mov dword [edx+DISKMEDIAINFO.Capacity+4], eax
xor eax, eax
ret
endp
;-----------------------------------------------------------------
bd_read:
push eax
push edx
mov edx, [bios_hdpos]
cmp edx, [hdpos]
jne .notread
mov edx, [bios_cur_sector]
cmp eax, edx
jb .notread
add edx, [bios_read_len]
dec edx
cmp eax, edx
ja .notread
sub eax, [bios_cur_sector]
shl eax, 9
add eax, (OS_BASE+0x99000)
push ecx esi
mov esi, eax
mov ecx, 512/4
cld
rep movsd
pop esi ecx
pop edx
pop eax
ret
.notread:
push ecx
mov dl, 42h
mov ecx, 16
call int13_call
pop ecx
test eax, eax
jnz .v86err
test edx, edx
jz .readerr
mov [bios_read_len], edx
mov edx, [hdpos]
mov [bios_hdpos], edx
pop edx
pop eax
mov [bios_cur_sector], eax
jmp bd_read
.readerr:
.v86err:
pop edx
pop eax
mov [hd_error], 1
jmp hd_read_error
;-----------------------------------------------------------------
bd_write_cache_chain:
pusha
mov edi, OS_BASE + 0x99000
movzx ecx, [cache_chain_size]
push ecx
shl ecx, 9-2
rep movsd
pop ecx
mov dl, 43h
mov eax, [cache_chain_ptr]
mov eax, [eax]
call int13_call
test eax, eax
jnz .v86err
cmp edx, ecx
jnz .writeerr
popa
ret
.v86err:
.writeerr:
popa
mov [hd_error], 1
jmp hd_write_error
;-----------------------------------------------------------------
int13_call:
; Because this code uses fixed addresses,
; it can not be run simultaniously by many threads.
; In current implementation it is protected by common mutex 'ide_status'
mov word [OS_BASE + 510h], 10h ; packet length
mov word [OS_BASE + 512h], cx ; number of sectors
mov dword [OS_BASE + 514h], 99000000h ; buffer 9900:0000
mov dword [OS_BASE + 518h], eax
and dword [OS_BASE + 51Ch], 0
push ebx ecx esi edi
mov ebx, int13_regs_in
mov edi, ebx
mov ecx, sizeof.v86_regs/4
xor eax, eax
rep stosd
mov byte [ebx+v86_regs.eax+1], dl
mov eax, [hdpos]
lea eax, [(eax-80h)*4]
lea eax, [BiosDisksData+eax*4]
mov dl, [eax]
mov byte [ebx+v86_regs.edx], dl
movzx edx, byte [eax+1]
; mov dl, 5
test edx, edx
jnz .hasirq
dec edx
jmp @f
.hasirq:
pushad
stdcall enable_irq, edx
popad
@@:
mov word [ebx+v86_regs.esi], 510h
mov word [ebx+v86_regs.ss], 9000h
mov word [ebx+v86_regs.esp], 09000h
mov word [ebx+v86_regs.eip], 500h
mov [ebx+v86_regs.eflags], 20200h
mov esi, [sys_v86_machine]
mov ecx, 0x502
push fs
call v86_start
pop fs
and [bios_hdpos], 0
pop edi esi ecx ebx
movzx edx, byte [OS_BASE + 512h]
test byte [int13_regs_out+v86_regs.eflags], 1
jnz @f
mov edx, ecx
@@:
ret
Property changes:
Added: svn:eol-style
+native
\ No newline at end of property
Added: svn:keywords
+Rev
\ No newline at end of property
/kernel/branches/kolibri-lldw/blkdev/hd_drv.inc
0,0 → 1,568
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ;;
;; Copyright (C) KolibriOS team 2004-2015. All rights reserved. ;;
;; Distributed under terms of the GNU General Public License ;;
;; ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
$Revision$
 
; HDD driver
 
struct HD_DATA
hdpos dw ?
hdid dw ?
hdbase dw ?
hd48 dw ?
sectors dq ?
ends
;-----------------------------------------------------------------
iglobal
align 4
ide_callbacks:
dd ide_callbacks.end - ide_callbacks
dd 0 ; no close function
dd 0 ; no closemedia function
dd ide_querymedia
dd ide_read
dd ide_write
dd 0 ; no flush function
dd 0 ; use default cache size
.end:
 
hd0_data HD_DATA 1, 0
hd1_data HD_DATA 2, 16
hd2_data HD_DATA 3, 0
hd3_data HD_DATA 4, 16
hd4_data HD_DATA 5, 0
hd5_data HD_DATA 6, 16
hd6_data HD_DATA 7, 0
hd7_data HD_DATA 8, 16
hd8_data HD_DATA 9, 0
hd9_data HD_DATA 10, 16
hd10_data HD_DATA 11, 0
hd11_data HD_DATA 12, 16
 
ide_mutex_table:
dd ide_channel1_mutex
dd ide_channel2_mutex
dd ide_channel3_mutex
dd ide_channel4_mutex
dd ide_channel5_mutex
dd ide_channel6_mutex
endg
;-----------------------------------------------------------------
uglobal
ide_mutex MUTEX
ide_channel1_mutex MUTEX
ide_channel2_mutex MUTEX
ide_channel3_mutex MUTEX
ide_channel4_mutex MUTEX
ide_channel5_mutex MUTEX
ide_channel6_mutex MUTEX
blockSize:
rb 4
sector:
rb 6
allow_dma_access db ?
IDE_common_irq_param db ?
eventPointer dd ?
eventID dd ?
endg
;-----------------------------------------------------------------
ide_read:
mov al, 25h ; READ DMA EXT
jmp ide_read_write
 
ide_write:
mov al, 35h ; WRITE DMA EXT
proc ide_read_write stdcall uses esi edi ebx, \
hd_data, buffer, startsector:qword, numsectors
; hd_data = pointer to hd*_data
; buffer = pointer to buffer with/for data
; startsector = 64-bit start sector
; numsectors = pointer to number of sectors on input,
; must be filled with number of sectors really read/written
locals
sectors_todo dd ?
channel_lock dd ?
endl
mov bl, al
; get number of requested sectors and say that no sectors were read yet
mov ecx, [numsectors]
mov eax, [ecx]
mov dword [ecx], 0
mov [sectors_todo], eax
; acquire the global lock
mov ecx, ide_mutex
call mutex_lock
mov ecx, [hd_data]
movzx ecx, [ecx+HD_DATA.hdpos]
dec ecx
shr ecx, 1
shl ecx, 2
mov ecx, [ecx + ide_mutex_table]
mov [channel_lock], ecx
call mutex_lock
; prepare worker procedures variables
mov esi, [buffer]
mov edi, esi
mov ecx, [hd_data]
movzx eax, [ecx+HD_DATA.hdbase]
mov [hdbase], eax
mov ax, [ecx+HD_DATA.hdid]
mov [hdid], eax
mov eax, dword [startsector]
mov [sector], eax
cmp [ecx+HD_DATA.hd48], 0
jz .LBA28
mov ax, word [startsector+4]
mov [sector+4], ax
movzx ecx, [ecx+HD_DATA.hdpos]
mov [hdpos], ecx
dec ecx
shr ecx, 2
imul ecx, sizeof.IDE_DATA
add ecx, IDE_controller_1
mov [IDE_controller_pointer], ecx
mov eax, [hdpos]
dec eax
and eax, 11b
shr eax, 1
add eax, ecx
cmp [eax+IDE_DATA.dma_hdd_channel_1], 1
jz .next
dec ebx ; READ/WRITE SECTOR(S) EXT
; LBA48 supports max 10000h sectors per time
; loop until all sectors will be processed
.next:
mov ecx, 8000h
cmp ecx, [sectors_todo]
jbe @f
mov ecx, [sectors_todo]
@@:
mov [blockSize], ecx
push ecx
call IDE_transfer
pop ecx
jc .out
mov eax, [numsectors]
add [eax], ecx
sub [sectors_todo], ecx
jz .out
add [sector], ecx
adc word [sector+4], 0
jmp .next
 
.LBA28:
add eax, [sectors_todo]
add eax, 0xF0000000
jc .out
sub bl, 5 ; READ/WRITE SECTOR(S)
; LBA28 supports max 256 sectors per time
; loop until all sectors will be processed
.next28:
mov ecx, 256
cmp ecx, [sectors_todo]
jbe @f
mov ecx, [sectors_todo]
@@:
mov [blockSize], ecx
push ecx
call IDE_transfer.LBA28
pop ecx
jc .out
mov eax, [numsectors]
add [eax], ecx
sub [sectors_todo], ecx
jz .out
add [sector], ecx
jmp .next28
 
; loop is done, either due to error or because everything is done
; release the global lock and return the corresponding status
.out:
sbb eax, eax
push eax
mov ecx, [channel_lock]
call mutex_unlock
mov ecx, ide_mutex
call mutex_unlock
pop eax
ret
endp
;-----------------------------------------------------------------
proc ide_querymedia stdcall, hd_data, mediainfo
mov eax, [mediainfo]
mov edx, [hd_data]
mov [eax+DISKMEDIAINFO.Flags], 0
mov [eax+DISKMEDIAINFO.SectorSize], 512
mov ecx, dword[edx+HD_DATA.sectors]
mov dword[eax+DISKMEDIAINFO.Capacity], ecx
mov ecx, dword[edx+HD_DATA.sectors+4]
mov dword[eax+DISKMEDIAINFO.Capacity+4], ecx
xor eax, eax
ret
endp
;-----------------------------------------------------------------
; input: esi -> buffer, bl = command, [sector], [blockSize]
; output: esi -> next block in buffer
; for pio read esi equal edi
IDE_transfer:
mov edx, [hdbase]
add edx, 6
mov al, byte [hdid]
add al, 224
out dx, al ; select the desired drive
call save_hd_wait_timeout
inc edx
@@:
call check_hd_wait_timeout
jc .hd_error
in al, dx
test al, 128 ; ready for command?
jnz @b
pushfd ; fill the ports
cli
mov edx, [hdbase]
inc edx
inc edx
mov al, [blockSize+1]
out dx, al ; Sector count (15:8)
inc edx
mov eax, [sector+3]
out dx, al ; LBA (31:24)
inc edx
shr eax, 8
out dx, al ; LBA (39:32)
inc edx
shr eax, 8
out dx, al ; LBA (47:40)
sub edx, 3
mov al, [blockSize]
out dx, al ; Sector count (7:0)
inc edx
mov eax, [sector]
out dx, al ; LBA (7:0)
inc edx
shr eax, 8
out dx, al ; LBA (15:8)
inc edx
shr eax, 8
out dx, al ; LBA (23:16)
inc edx
mov al, byte [hdid]
add al, 224
out dx, al
test bl, 1
jz .PIO
; DMA
mov dword [esp], 0x1000
call kernel_alloc
mov edi, eax
push eax
shl dword [blockSize], 9
mov eax, esi
add eax, [blockSize]
push eax
; check buffer pages physical addresses and fill the scatter-gather list
; buffer may be not aligned and may have size not divisible by page size
; [edi] = block physical address, [edi+4] = block size in bytes
; block addresses can not cross 10000h borders
mov ecx, esi
and ecx, 0xFFF
jz .aligned
mov eax, esi
call get_pg_addr
add eax, ecx
neg ecx
add ecx, 0x1000
mov [edi], eax
cmp ecx, [blockSize]
jnc .end
mov [edi+4], ecx
add esi, 0x1000
add edi, 8
sub [blockSize], ecx
.aligned:
mov eax, esi
call get_pg_addr
mov ecx, eax
mov [edi], eax
and ecx, 0xFFFF
neg ecx
add ecx, 0x10000
cmp [blockSize], ecx
jnc @f
mov ecx, [blockSize]
and ecx, 0xF000
jz .end
@@:
push ecx
@@:
add esi, 0x1000
add eax, 0x1000
sub ecx, 0x1000
jz @f
mov edx, eax
mov eax, esi
call get_pg_addr
cmp eax, edx
jz @b
@@:
pop edx
sub edx, ecx
mov [edi+4], edx
add edi, 8
sub [blockSize], edx
jnz .aligned
sub edi, 8
jmp @f
 
.end:
mov ecx, [blockSize]
mov [edi+4], ecx
@@:
mov byte [edi+7], 80h ; list end
pop esi
pop edi
; select controller Primary or Secondary
mov ecx, [IDE_controller_pointer]
mov dx, [ecx+IDE_DATA.RegsBaseAddres]
mov eax, [hdpos]
dec eax
test eax, 10b
jz @f
add edx, 8
@@:
add edx, 2 ; Bus Master IDE Status register
mov al, 6
out dx, al ; clear Error bit and Interrupt bit
 
add edx, 2 ; Bus Master IDE PRD Table Address
mov eax, edi
call get_pg_addr
out dx, eax ; send scatter-gather list physical address
 
push edx
mov edx, [hdbase]
add edx, 7 ; ATACommand
mov al, bl
out dx, al ; Start hard drive
pop edx
 
sub edx, 4 ; Bus Master IDE Command register
mov al, 1 ; set direction
cmp bl, 35h ; write
jz @f
add al, 8 ; read
@@:
out dx, al ; Start Bus Master
mov [IDE_common_irq_param], 14
mov eax, [hdpos]
dec eax
test eax, 10b
jz @f
inc [IDE_common_irq_param]
@@:
push edi esi ebx
xor ecx, ecx
xor esi, esi
call create_event
mov [eventPointer], eax
mov [eventID], edx
sti
mov ebx, edx
mov ecx, 300
call wait_event_timeout
test eax, eax
jnz @f
dbgstr 'IDE DMA IRQ timeout'
mov [IDE_common_irq_param], 0
mov eax, [eventPointer]
mov ebx, [eventID]
call destroy_event
mov [eventPointer], 0
@@:
pop ebx esi
call kernel_free
cmp [eventPointer], 0
jz .hd_error
ret
 
.LBA28:
mov edx, [hdbase]
add edx, 6
mov al, byte [hdid]
add al, 224
out dx, al ; select the desired drive
call save_hd_wait_timeout
inc edx
@@:
call check_hd_wait_timeout
jc .hd_error
in al, dx
test al, 128 ; ready for command?
jnz @b
pushfd ; fill the ports
cli
mov edx, [hdbase]
inc edx
inc edx
mov al, [blockSize]
out dx, al ; Sector count (7:0)
inc edx
mov eax, [sector]
out dx, al ; LBA (7:0)
inc edx
shr eax, 8
out dx, al ; LBA (15:8)
inc edx
shr eax, 8
out dx, al ; LBA (23:16)
inc edx
shr eax, 8
add al, byte [hdid]
add al, 224
out dx, al ; LBA (27:24)
.PIO:
inc edx ; ATACommand
mov al, bl
out dx, al ; Start hard drive
popfd
.sectorTransfer:
call save_hd_wait_timeout
in al, dx
in al, dx
in al, dx
in al, dx
@@:
call check_hd_wait_timeout
jc .hd_error
in al, dx
test al, 8 ; ready for transfer?
jz @b
cmp [hd_setup], 1 ; do not mark error for setup request
jz @f
test al, 1 ; previous command ended up with an error
jnz .pio_error
@@:
pushfd
cli
cld
mov ecx, 256
mov edx, [hdbase]
cmp bl, 30h
jnc .write
rep insw
jmp @f
 
.write:
rep outsw
@@:
popfd
add edx, 7
dec dword [blockSize]
jnz .sectorTransfer
ret
 
.pio_error:
dbgstr 'IDE PIO transfer error'
.hd_error:
cmp bl, 30h
jnc hd_write_error
;-----------------------------------------------------------------
hd_read_error:
dbgstr 'HD read error'
stc
ret
;-----------------------------------------------------------------
hd_write_error:
dbgstr 'HD write error'
stc
ret
;-----------------------------------------------------------------
save_hd_wait_timeout:
mov eax, [timer_ticks]
add eax, 300 ; 3 sec timeout
mov [hd_wait_timeout], eax
ret
;-----------------------------------------------------------------
check_hd_wait_timeout:
mov eax, [timer_ticks]
cmp [hd_wait_timeout], eax
jc @f
ret
 
@@:
dbgstr 'IDE device timeout'
stc
ret
;-----------------------------------------------------------------
align 4
IDE_irq_14_handler:
IDE_irq_15_handler:
IDE_common_irq_handler:
; Most of the time, we are here because we have requested
; a DMA transfer for the corresponding drive.
; However,
; a) we can be here because IDE IRQ is shared with some other device,
; that device has actually raised IRQ,
; it has nothing to do with IDE;
; b) we can be here because IDE controller just does not want
; to be silent and reacts to something even though
; we have, in theory, disabled IRQs.
; If the interrupt corresponds to our current request,
; remove the interrupt request and raise the event for the waiting code.
; In the case a), just return zero - not our interrupt.
; In the case b), remove the interrupt request and hope for the best.
; DEBUGF 1, 'K : IDE_irq_handler %x\n', [IDE_common_irq_param]:2
mov ecx, [esp+4]
mov dx, [ecx+IDE_DATA.RegsBaseAddres]
add edx, 2 ; Bus Master IDE Status register
in al, dx
test al, 4
jnz .interrupt_from_primary
add edx, 8
in al, dx
test al, 4
jnz .interrupt_from_secondary
xor eax, eax ; not our interrupt
ret
 
.interrupt_from_primary:
out dx, al ; clear Interrupt bit
sub edx, 2
xor eax, eax
out dx, al ; clear Bus Master IDE Command register
mov dx, [ecx+IDE_DATA.BAR0_val]
add edx, 7
in al, dx ; read status register
cmp [IDE_common_irq_param], 14
jz .raise
.exit_our:
mov al, 1
ret
 
.interrupt_from_secondary:
out dx, al ; clear Interrupt bit
sub edx, 2
xor eax, eax
out dx, al ; clear Bus Master IDE Command register
mov dx, [ecx+IDE_DATA.BAR2_val]
add edx, 7
in al, dx ; read status register
cmp [IDE_common_irq_param], 15
jnz .exit_our
.raise:
cmp ecx, [IDE_controller_pointer]
jnz .exit_our
pushad
mov eax, [eventPointer]
mov ebx, [eventID]
xor edx, edx
xor esi, esi
call raise_event
popad
mov al, 1 ; remove the interrupt request
ret
Property changes:
Added: svn:eol-style
+native
\ No newline at end of property
Added: svn:keywords
+Rev
\ No newline at end of property
/kernel/branches/kolibri-lldw/blkdev/disk_cache.inc
0,0 → 1,1387
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ;;
;; Copyright (C) KolibriOS team 2011-2015. All rights reserved. ;;
;; Distributed under terms of the GNU General Public License ;;
;; ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
$Revision$
 
; Read/write functions try to do large operations,
; it is significantly faster than several small operations.
; This requires large buffers.
; We can't use input/output buffers directly - they can be controlled
; by user-mode application, so they can be modified between the operation
; and copying to/from cache, giving invalid data in cache.
; It is unclear how to use cache directly, currently cache items are
; allocated/freed sector-wise, so items for sequential sectors can be
; scattered over all the cache.
; So read/write functions allocate a temporary buffer which is
; 1) not greater than half of free memory and
; 2) not greater than the following constant.
CACHE_MAX_ALLOC_SIZE = 4 shl 20
 
; Legacy interface for filesystems fs_{read,write}32_{sys,app}
; gives only one sector for FS. However, per-sector reading is inefficient,
; so internally fs_read32_{sys,app} reads to the cache several sequential
; sectors, hoping that they will be useful.
; Total number of sectors is given by the following constant.
CACHE_LEGACY_READ_SIZE = 16
 
; This structure describes one item in the cache.
struct CACHE_ITEM
SectorLo dd ? ; low 32 bits of sector
SectorHi dd ? ; high 32 bits of sector
Status dd ? ; one of CACHE_ITEM_*
ends
 
; Possible values for CACHE_ITEM_*
CACHE_ITEM_EMPTY = 0
CACHE_ITEM_COPY = 1
CACHE_ITEM_MODIFIED = 2
 
; Read several sequential sectors using cache #1.
; in: edx:eax = start sector, relative to start of partition
; in: ecx = number of sectors to read
; in: ebx -> buffer
; in: ebp -> PARTITION
; out: eax = error code, 0 = ok
; out: ecx = number of sectors that were read
fs_read64_sys:
; Save ebx, set ebx to SysCache and let the common part do its work.
push ebx ebx
mov ebx, [ebp+PARTITION.Disk]
add ebx, DISK.SysCache
jmp fs_read64_common
 
; Read several sequential sectors using cache #2.
; in: edx:eax = start sector, relative to start of partition
; in: ecx = number of sectors to read
; in: edi -> buffer
; in: ebp -> PARTITION
; out: eax = error code, 0 = ok
; out: ecx = number of sectors that were read
fs_read64_app:
; Save ebx, set ebx to AppCache and let the common part do its work.
push ebx ebx
mov ebx, [ebp+PARTITION.Disk]
add ebx, DISK.AppCache
 
; Common part of fs_read64_{app,sys}:
; read several sequential sectors using the given cache.
fs_read64_common:
; 1. Setup stack frame.
push esi edi ; save used registers to be stdcall
push 0 ; initialize .error_code
push ebx edx eax ecx ecx ; initialize stack variables
virtual at esp
.local_vars:
.num_sectors_orig dd ?
; Number of sectors that should be read. Used to generate output value of ecx.
.num_sectors dd ?
; Number of sectors that remain to be read. Decreases from .num_sectors_orig to 0.
.sector_lo dd ? ; low 32 bits of the current sector
.sector_hi dd ? ; high 32 bits of the current sector
.cache dd ? ; pointer to DISKCACHE
.error_code dd ? ; current status
.local_vars_size = $ - .local_vars
.saved_regs rd 2
.buffer dd ? ; filled by fs_read64_{sys,app}
end virtual
; 2. Validate parameters against partition length:
; immediately return error if edx:eax are beyond partition end,
; decrease .num_sectors and .num_sectors_orig, if needed,
; so that the entire operation fits in the partition limits.
mov eax, dword [ebp+PARTITION.Length]
mov edx, dword [ebp+PARTITION.Length+4]
sub eax, [.sector_lo]
sbb edx, [.sector_hi]
jb .end_of_media
jnz .no_end_of_media
cmp ecx, eax
jbe .no_end_of_media
; If .num_sectors got decreased, set status to DISK_STATUS_END_OF_MEDIA;
; if all subsequent operations would be successful, this would become the final
; status, otherwise this would be rewritten by failed operation.
mov [.num_sectors], eax
mov [.num_sectors_orig], eax
mov [.error_code], DISK_STATUS_END_OF_MEDIA
.no_end_of_media:
; 3. If number of sectors to read is zero, either because zero-sectors operation
; was requested or because it got decreased to zero due to partition limits,
; just return the current status.
cmp [.num_sectors], 0
jz .return
; 4. Shift sector from partition-relative to absolute.
mov eax, dword [ebp+PARTITION.FirstSector]
mov edx, dword [ebp+PARTITION.FirstSector+4]
add [.sector_lo], eax
adc [.sector_hi], edx
; 5. If the cache is disabled, pass the request directly to the driver.
cmp [ebx+DISKCACHE.pointer], 0
jz .nocache
; 6. Look for sectors in the cache, sequentially from the beginning.
; Stop at the first sector that is not in the cache
; or when all sectors were read from the cache.
; 6a. Acquire the lock.
mov ecx, [ebp+PARTITION.Disk]
add ecx, DISK.CacheLock
call mutex_lock
.lookup_in_cache_loop:
; 6b. For each sector, call the lookup function without adding to the cache.
mov eax, [.sector_lo]
mov edx, [.sector_hi]
call cache_lookup_read
; 6c. If it has failed, the sector is not in cache;
; release the lock and go to 7.
jc .not_found_in_cache
; The sector is found in cache.
; 6d. Copy data for the caller, advance [.buffer].
mov esi, edi
mov edi, [.buffer]
mov eax, 1
shl eax, cl
mov ecx, eax
shr ecx, 2
rep movsd
mov [.buffer], edi
; 6e. Advance the sector.
add [.sector_lo], 1
adc [.sector_hi], 0
; 6f. Decrement number of sectors left.
; If all sectors were read, release the lock and return.
dec [.num_sectors]
jnz .lookup_in_cache_loop
; Release the lock acquired at 6a.
mov ecx, [ebp+PARTITION.Disk]
add ecx, DISK.CacheLock
call mutex_unlock
.return:
mov eax, [.error_code]
mov ecx, [.num_sectors_orig]
sub ecx, [.num_sectors]
.nothing:
add esp, .local_vars_size
pop edi esi ebx ebx ; restore used registers to be stdcall
ret
.not_found_in_cache:
; Release the lock acquired at 6a.
mov ecx, [ebp+PARTITION.Disk]
add ecx, DISK.CacheLock
call mutex_unlock
; The current sector is not present in the cache.
; Ask the driver to read all requested not-yet-read sectors,
; put results in the cache.
; Also, see the comment before the definition of CACHE_MAX_ALLOC_SIZE.
; 7. Allocate buffer for operations.
; Normally, create buffer that is sufficient for all remaining data.
; However, for extra-large requests make an upper limit:
; do not use more than half of the free memory
; or more than CACHE_MAX_ALLOC_SIZE bytes.
mov ecx, [ebx+DISKCACHE.sector_size_log]
mov ebx, [pg_data.pages_free]
shr ebx, 1
jz .nomemory
cmp ebx, CACHE_MAX_ALLOC_SIZE shr 12
jbe @f
mov ebx, CACHE_MAX_ALLOC_SIZE shr 12
@@:
shl ebx, 12
shr ebx, cl
jz .nomemory
cmp ebx, [.num_sectors]
jbe @f
mov ebx, [.num_sectors]
@@:
mov eax, ebx
shl eax, cl
stdcall kernel_alloc, eax
; If failed, return the appropriate error code.
test eax, eax
jz .nomemory
mov esi, eax
; Split the request to chunks that fit in the allocated buffer.
.read_loop:
; 8. Get iteration size: either size of allocated buffer in sectors
; or number of sectors left, select what is smaller.
cmp ebx, [.num_sectors]
jbe @f
mov ebx, [.num_sectors]
@@:
; 9. Create second portion of local variables.
; Note that variables here and above are esp-relative;
; it means that all addresses should be corrected when esp is changing.
push ebx esi esi
push ebx
; In particular, num_sectors is now [.num_sectors+.local_vars2_size].
virtual at esp
.local_vars2:
.current_num_sectors dd ? ; number of sectors that were read
.current_buffer dd ?
; pointer inside .allocated_buffer that points
; to the beginning of not-processed data
.allocated_buffer dd ? ; saved in safe place
.iteration_size dd ? ; saved in safe place
.local_vars2_size = $ - .local_vars2
end virtual
; 10. Call the driver, reading the next chunk.
push esp ; numsectors
push [.sector_hi+.local_vars2_size+4] ; startsector
push [.sector_lo+.local_vars2_size+8] ; startsector
push esi ; buffer
mov esi, [ebp+PARTITION.Disk]
mov al, DISKFUNC.read
call disk_call_driver
; If failed, save error code.
test eax, eax
jz @f
mov [.error_code+.local_vars2_size], eax
@@:
; 11. Copy data for the caller, advance .buffer.
cmp [.current_num_sectors], 0
jz .copy_done
mov ebx, [.cache+.local_vars2_size]
mov eax, [.current_num_sectors]
mov ecx, [ebx+DISKCACHE.sector_size_log]
shl eax, cl
mov esi, [.allocated_buffer]
mov edi, [.buffer+.local_vars2_size]
mov ecx, eax
shr ecx, 2
rep movsd
mov [.buffer+.local_vars2_size], edi
; 12. Copy data to the cache.
; 12a. Acquire the lock.
mov ecx, [ebp+PARTITION.Disk]
add ecx, DISK.CacheLock
call mutex_lock
; 12b. Prepare for the loop: create a local variable that
; stores number of sectors to be copied.
push [.current_num_sectors]
.store_to_cache:
; 12c. For each sector, call the lookup function with adding to the cache, if not yet.
mov eax, [.sector_lo+.local_vars2_size+4]
mov edx, [.sector_hi+.local_vars2_size+4]
call cache_lookup_write
test eax, eax
jnz .cache_error
; 12d. If the sector was already present in the cache as modified,
; data that were read at step 10 for this sector are obsolete,
; so rewrite data for the caller from the cache.
cmp [esi+CACHE_ITEM.Status], CACHE_ITEM_MODIFIED
jnz .not_modified
mov esi, edi
mov edi, [.buffer+.local_vars2_size+4]
mov eax, [esp]
shl eax, cl
sub edi, eax
mov eax, 1
shl eax, cl
mov ecx, eax
shr ecx, 2
rep movsd
add [.current_buffer+4], eax
jmp .sector_done
.not_modified:
; 12e. For each not-modified sector,
; copy data, mark the item as not-modified copy of the disk,
; advance .current_buffer and .sector_hi:.sector_lo to the next sector.
mov [esi+CACHE_ITEM.Status], CACHE_ITEM_COPY
mov eax, 1
shl eax, cl
mov esi, [.current_buffer+4]
mov ecx, eax
shr ecx, 2
rep movsd
mov [.current_buffer+4], esi
.sector_done:
add [.sector_lo+.local_vars2_size+4], 1
adc [.sector_hi+.local_vars2_size+4], 0
; 12f. Continue the loop 12c-12e until all sectors are read.
dec dword [esp]
jnz .store_to_cache
.cache_error:
; 12g. Restore after the loop: pop the local variable.
pop ecx
; 12h. Release the lock.
mov ecx, [ebp+PARTITION.Disk]
add ecx, DISK.CacheLock
call mutex_unlock
.copy_done:
; 13. Remove portion of local variables created at step 9.
pop ecx
pop esi esi ebx
; 14. Continue iterations while number of sectors read by the driver
; is equal to number of sectors requested and there are additional sectors.
cmp ecx, ebx
jnz @f
sub [.num_sectors], ebx
jnz .read_loop
@@:
; 15. Free the buffer allocated at step 7 and return.
stdcall kernel_free, esi
jmp .return
 
; Special branches:
.nomemory:
; memory allocation failed at step 7: return the corresponding error
mov [.error_code], DISK_STATUS_NO_MEMORY
jmp .return
.nocache:
; step 5, after correcting number of sectors to fit in partition limits
; and advancing partition-relative sector to absolute,
; sees that cache is disabled: pass corrected request to the driver
lea eax, [.num_sectors]
push eax ; numsectors
push [.sector_hi+4] ; startsector
push [.sector_lo+8] ; startsector
push [.buffer+12] ; buffer
mov esi, [ebp+PARTITION.Disk]
mov al, DISKFUNC.read
call disk_call_driver
test eax, eax
jnz @f
mov eax, [.error_code]
@@:
mov ecx, [.num_sectors]
jmp .nothing
.end_of_media:
; requested sector is beyond the partition end: return the corresponding error
mov [.error_code], DISK_STATUS_END_OF_MEDIA
jmp .return
 
; Write several sequential sectors using cache #1.
; in: edx:eax = start sector
; in: ecx = number of sectors to write
; in: ebx -> buffer
; in: ebp -> PARTITION
; out: eax = error code, 0 = ok
; out: ecx = number of sectors that were written
fs_write64_sys:
; Save ebx, set ebx to SysCache and let the common part do its work.
push ebx
mov ebx, [ebp+PARTITION.Disk]
add ebx, DISK.SysCache
jmp fs_write64_common
 
; Write several sequential sectors using cache #2.
; in: edx:eax = start sector
; in: ecx = number of sectors to write
; in: ebx -> buffer
; in: ebp -> PARTITION
; out: eax = error code, 0 = ok
; out: ecx = number of sectors that were written
fs_write64_app:
; Save ebx, set ebx to AppCache and let the common part do its work.
push ebx
mov ebx, [ebp+PARTITION.Disk]
add ebx, DISK.AppCache
 
; Common part of fs_write64_{app,sys}:
; write several sequential sectors using the given cache.
fs_write64_common:
; 1. Setup stack frame.
push esi edi ; save used registers to be stdcall
push 0 ; initialize .error_code
push edx eax ecx ecx ; initialize stack variables
push [.buffer-4] ; copy [.buffer] to [.cur_buffer]
; -4 is due to esp-relative addressing
virtual at esp
.local_vars:
.cur_buffer dd ? ; pointer to data that are currently copying
.num_sectors_orig dd ?
; Number of sectors that should be written. Used to generate output value of ecx.
.num_sectors dd ?
; Number of sectors that remain to be written.
.sector_lo dd ? ; low 32 bits of the current sector
.sector_hi dd ? ; high 32 bits of the current sector
.error_code dd ? ; current status
.local_vars_size = $ - .local_vars
.saved_regs rd 2
.buffer dd ? ; filled by fs_write64_{sys,app}
end virtual
; 2. Validate parameters against partition length:
; immediately return error if edx:eax are beyond partition end,
; decrease .num_sectors and .num_sectors_orig, if needed,
; so that the entire operation fits in the partition limits.
mov eax, dword [ebp+PARTITION.Length]
mov edx, dword [ebp+PARTITION.Length+4]
sub eax, [.sector_lo]
sbb edx, [.sector_hi]
jb .end_of_media
jnz .no_end_of_media
cmp ecx, eax
jbe .no_end_of_media
; If .num_sectors got decreased, set status to DISK_STATUS_END_OF_MEDIA;
; if all subsequent operations would be successful, this would become the final
; status, otherwise this would be rewritten by failed operation.
mov [.num_sectors], eax
mov [.num_sectors_orig], eax
mov [.error_code], DISK_STATUS_END_OF_MEDIA
.no_end_of_media:
; 3. If number of sectors to write is zero, either because zero-sectors operation
; was requested or because it got decreased to zero due to partition limits,
; just return the current status.
cmp [.num_sectors], 0
jz .return
; 4. Shift sector from partition-relative to absolute.
mov eax, dword [ebp+PARTITION.FirstSector]
mov edx, dword [ebp+PARTITION.FirstSector+4]
add [.sector_lo], eax
adc [.sector_hi], edx
; 5. If the cache is disabled, pass the request directly to the driver.
cmp [ebx+DISKCACHE.pointer], 0
jz .nocache
; 6. Store sectors in the cache, sequentially from the beginning.
; 6a. Acquire the lock.
mov ecx, [ebp+PARTITION.Disk]
add ecx, DISK.CacheLock
call mutex_lock
.lookup_in_cache_loop:
; 6b. For each sector, call the lookup function with adding to the cache, if not yet.
mov eax, [.sector_lo]
mov edx, [.sector_hi]
call cache_lookup_write
test eax, eax
jnz .cache_error
; 6c. For each sector, copy data, mark the item as modified and not saved,
; advance .current_buffer to the next sector.
mov [esi+CACHE_ITEM.Status], CACHE_ITEM_MODIFIED
mov eax, 1
shl eax, cl
mov esi, [.cur_buffer]
mov ecx, eax
shr ecx, 2
rep movsd
mov [.cur_buffer], esi
; 6d. Remove the sector from the other cache.
; Normally it should not be there, but prefetching could put to the app cache
; data that normally should belong to the sys cache and vice versa.
; Note: this requires that both caches must be protected by the same lock.
mov eax, [.sector_lo]
mov edx, [.sector_hi]
push ebx
sub ebx, [ebp+PARTITION.Disk]
xor ebx, DISK.SysCache xor DISK.AppCache
add ebx, [ebp+PARTITION.Disk]
call cache_lookup_read
jc @f
mov [esi+CACHE_ITEM.Status], CACHE_ITEM_EMPTY
@@:
pop ebx
; 6e. Advance .sector_hi:.sector_lo to the next sector.
add [.sector_lo], 1
adc [.sector_hi], 0
; 6f. Continue the loop at 6b-6e until all sectors are processed.
dec [.num_sectors]
jnz .lookup_in_cache_loop
.unlock_return:
; 6g. Release the lock and return.
mov ecx, [ebp+PARTITION.Disk]
add ecx, DISK.CacheLock
call mutex_unlock
.return:
mov eax, [.error_code]
mov ecx, [.num_sectors_orig]
sub ecx, [.num_sectors]
.nothing:
add esp, .local_vars_size
pop edi esi ebx
ret
 
; Special branches:
.cache_error:
; error at flushing the cache while adding sector to the cache:
; return the error from the lookup function
mov [.error_code], eax
jmp .unlock_return
.end_of_media:
; requested sector is beyond the partition end: return the corresponding error
mov eax, DISK_STATUS_END_OF_MEDIA
xor ecx, ecx
jmp .nothing
.nocache:
; step 5, after correcting number of sectors to fit in partition limits
; and advancing partition-relative sector to absolute,
; sees that cache is disabled: pass corrected request to the driver
lea eax, [.num_sectors]
push eax ; numsectors
push [.sector_hi+4] ; startsector
push [.sector_lo+8] ; startsector
push [.buffer+12] ; buffer
mov esi, [ebp+PARTITION.Disk]
mov al, DISKFUNC.write
call disk_call_driver
mov ecx, [.num_sectors]
jmp .nothing
 
; Legacy. Use fs_read64_sys instead.
; This function is intended to replace the old 'hd_read' function when
; [hdd_appl_data] = 0, so its input/output parameters are the same, except
; that it can't use the global variables 'hd_error' and 'hdd_appl_data'.
; in: eax = sector, ebx = buffer, ebp = pointer to PARTITION structure
; eax is relative to partition start
; out: eax = error code; 0 = ok
fs_read32_sys:
; Save ebx, set ebx to SysCache and let the common part do its work.
push ebx
mov ebx, [ebp+PARTITION.Disk]
add ebx, DISK.SysCache
jmp fs_read32_common
 
; Legacy. Use fs_read64_app instead.
; This function is intended to replace the old 'hd_read' function when
; [hdd_appl_data] = 1, so its input/output parameters are the same, except
; that it can't use the global variables 'hd_error' and 'hdd_appl_data'.
; in: eax = sector, ebx = buffer, ebp = pointer to PARTITION structure
; eax is relative to partition start
; out: eax = error code; 0 = ok
fs_read32_app:
; Save ebx, set ebx to AppCache and let the common part do its work.
push ebx
mov ebx, [ebp+PARTITION.Disk]
add ebx, DISK.AppCache
 
; This label is the common part of fs_read32_sys and fs_read32_app.
fs_read32_common:
; 1. Check that the required sector is inside the partition. If no, return
; DISK_STATUS_END_OF_MEDIA.
cmp dword [ebp+PARTITION.Length+4], 0
jnz @f
cmp dword [ebp+PARTITION.Length], eax
ja @f
mov eax, DISK_STATUS_END_OF_MEDIA
pop ebx
ret
@@:
; 2. Get the absolute sector on the disk.
push ecx edx esi edi
xor edx, edx
add eax, dword [ebp+PARTITION.FirstSector]
adc edx, dword [ebp+PARTITION.FirstSector+4]
; 3. If there is no cache for this disk, just pass the request to the driver.
cmp [ebx+DISKCACHE.pointer], 0
jnz .scancache
push 1
push esp ; numsectors
push edx ; startsector
push eax ; startsector
pushd [esp+32]; buffer
mov esi, [ebp+PARTITION.Disk]
mov al, DISKFUNC.read
call disk_call_driver
pop ecx
pop edi esi edx ecx
pop ebx
ret
.scancache:
push ebx edx eax
virtual at esp
.local_vars:
.sector_lo dd ?
.sector_hi dd ?
.cache dd ?
.local_vars_size = $ - .local_vars
.saved_regs rd 4
.buffer dd ?
end virtual
; 4. Scan for the requested sector in the cache.
; If found, copy the data and return.
; 4a. Acquire the lock.
mov ecx, [ebp+PARTITION.Disk]
add ecx, DISK.CacheLock
call mutex_lock
; 4b. Call the lookup function without adding to the cache.
mov eax, [.sector_lo]
mov edx, [.sector_hi]
call cache_lookup_read
; If not found, go to 5.
jc .not_found_in_cache
.found_in_cache:
; 4c. Copy the data.
mov esi, edi
mov edi, [.buffer]
mov eax, 1
shl eax, cl
mov ecx, eax
shr ecx, 2
rep movsd
; 4d. Release the lock and return success.
mov ecx, [ebp+PARTITION.Disk]
add ecx, DISK.CacheLock
call mutex_unlock
.return:
xor eax, eax
.return_eax:
add esp, .local_vars_size
pop edi esi edx ecx
pop ebx
ret
.not_found_in_cache:
; 5. Decide whether we need to prefetch further sectors.
; If so, advance to 6. If not, go to 13.
; Assume that devices < 3MB are floppies which are slow
; (ramdisk does not have a cache, so we don't even get here for ramdisk).
; This is a dirty hack, but the entire function is somewhat hacky. Use fs_read64*.
mov ecx, [ebp+PARTITION.Disk]
cmp dword [ecx+DISK.MediaInfo.Capacity+4], 0
jnz @f
cmp dword [ecx+DISK.MediaInfo.Capacity], 3 shl (20-9)
jb .floppy
@@:
; We want to prefetch CACHE_LEGACY_READ_SIZE sectors.
; 6. Release the lock acquired at step 4a.
mov ecx, [ebp+PARTITION.Disk]
add ecx, DISK.CacheLock
call mutex_unlock
; 7. Allocate buffer for CACHE_LEGACY_READ_SIZE sectors.
mov eax, CACHE_LEGACY_READ_SIZE
mov ecx, [ebx+DISKCACHE.sector_size_log]
shl eax, cl
stdcall kernel_alloc, eax
; If failed, return the corresponding error code.
test eax, eax
jz .nomemory
; 8. Create second portion of local variables.
push eax eax
push CACHE_LEGACY_READ_SIZE
virtual at esp
.local_vars2:
.num_sectors dd ? ; number of sectors left
.current_buffer dd ? ; pointer to data that are currently copying
.allocated_buffer dd ? ; saved at safe place
.local_vars2_size = $ - .local_vars2
end virtual
; 9. Call the driver to read CACHE_LEGACY_READ_SIZE sectors.
push esp ; numsectors
push [.sector_hi+.local_vars2_size+4] ; startsector
push [.sector_lo+.local_vars2_size+8] ; startsector
push eax ; buffer
mov esi, [ebp+PARTITION.Disk]
mov al, DISKFUNC.read
call disk_call_driver
; Note: we're ok if at least one sector is read,
; read error somewhere after that just limits data to be put in cache.
cmp [.num_sectors], 0
jz .read_error
; 10. Copy data for the caller.
mov esi, [.allocated_buffer]
mov edi, [.buffer+.local_vars2_size]
mov ecx, [ebx+DISKCACHE.sector_size_log]
mov eax, 1
shl eax, cl
mov ecx, eax
shr ecx, 2
rep movsd
; 11. Store all sectors that were successfully read to the cache.
; 11a. Acquire the lock.
mov ecx, [ebp+PARTITION.Disk]
add ecx, DISK.CacheLock
call mutex_lock
.store_to_cache:
; 11b. For each sector, call the lookup function with adding to the cache, if not yet.
mov eax, [.sector_lo+.local_vars2_size]
mov edx, [.sector_hi+.local_vars2_size]
call cache_lookup_write
test eax, eax
jnz .cache_error
; 11c. Ignore sectors marked as modified: for them the cache is more recent that disk data.
mov eax, 1
shl eax, cl
cmp [esi+CACHE_ITEM.Status], CACHE_ITEM_MODIFIED
jnz .not_modified
add [.current_buffer], eax
jmp .sector_done
.not_modified:
; 11d. For each sector, copy data, mark the item as not-modified copy of the disk,
; advance .current_buffer and .sector_hi:.sector_lo to the next sector.
mov [esi+CACHE_ITEM.Status], CACHE_ITEM_COPY
mov esi, [.current_buffer]
mov ecx, eax
shr ecx, 2
rep movsd
mov [.current_buffer], esi
.sector_done:
add [.sector_lo+.local_vars2_size], 1
adc [.sector_hi+.local_vars2_size], 0
; 11e. Continue the loop at 11b-11d until all sectors are processed.
dec [.num_sectors]
jnz .store_to_cache
.cache_error:
; 11f. Release the lock.
mov ecx, [ebp+PARTITION.Disk]
add ecx, DISK.CacheLock
call mutex_unlock
.copy_done:
; 12. Remove portion of local variables created at step 8,
; free the buffer allocated at step 7 and return.
pop ecx ecx
stdcall kernel_free
jmp .return
.read_error:
; If no sectors were read, free the buffer allocated at step 7
; and pass the error to the caller.
push eax
stdcall kernel_free, [.allocated_buffer+4]
pop eax
add esp, .local_vars2_size
jmp .return_eax
.nomemory:
mov eax, DISK_STATUS_NO_MEMORY
jmp .return_eax
.floppy:
; We don't want to prefetch anything, just read one sector.
; We are still holding the lock acquired at step 4a.
; 13. Call the lookup function adding sector to the cache.
call cache_lookup_write
test eax, eax
jnz .floppy_cache_error
push esi
 
; 14. Call the driver to read one sector.
push 1
push esp
push edx
push [.sector_lo+16]
push edi
mov esi, [ebp+PARTITION.Disk]
mov al, DISKFUNC.read
call disk_call_driver
pop ecx
dec ecx
jnz .floppy_read_error
; 15. Get the slot and pointer to the cache item,
; change the status to not-modified copy of the disk
; and go to 4c.
pop esi
mov [esi+CACHE_ITEM.Status], CACHE_ITEM_COPY
mov ecx, [ebx+DISKCACHE.sector_size_log]
jmp .found_in_cache
 
; On error at steps 13-14, release the lock
; and pass the error to the caller.
.floppy_read_error:
pop ecx
.floppy_cache_error:
mov ecx, [ebp+PARTITION.Disk]
add ecx, DISK.CacheLock
push eax
call mutex_unlock
pop eax
jmp .return_eax
 
; This function is intended to replace the old 'hd_write' function when
; [hdd_appl_data] = 0, so its input/output parameters are the same, except
; that it can't use the global variables 'hd_error' and 'hdd_appl_data'.
; in: eax = sector, ebx = buffer, ebp = pointer to PARTITION structure
; eax is relative to partition start
; out: eax = error code; 0 = ok
fs_write32_sys:
; Just call the advanced function.
push ecx edx
xor edx, edx
mov ecx, 1
call fs_write64_sys
pop edx ecx
ret
 
; This function is intended to replace the old 'hd_write' function when
; [hdd_appl_data] = 1, so its input/output parameters are the same, except
; that it can't use the global variables 'hd_error' and 'hdd_appl_data'.
; in: eax = sector, ebx = buffer, ebp = pointer to PARTITION structure
; eax is relative to partition start
; out: eax = error code; 0 = ok
fs_write32_app:
; Just call the advanced function.
push ecx edx
xor edx, edx
mov ecx, 1
call fs_write64_app
pop edx ecx
ret
 
; Lookup for the given sector in the given cache.
; If the sector is not present, return error.
; The caller must acquire the cache lock.
; in: edx:eax = sector
; in: ebx -> DISKCACHE structure
; out: CF set if sector is not in cache
; out: ecx = sector_size_log
; out: esi -> sector:status
; out: edi -> sector data
proc cache_lookup_read
mov esi, [ebx+DISKCACHE.pointer]
add esi, sizeof.CACHE_ITEM
 
mov edi, 1
 
.hdreadcache:
 
cmp [esi+CACHE_ITEM.Status], CACHE_ITEM_EMPTY
je .nohdcache
 
cmp [esi+CACHE_ITEM.SectorLo], eax
jne .nohdcache
cmp [esi+CACHE_ITEM.SectorHi], edx
jne .nohdcache
mov ecx, [ebx+DISKCACHE.sector_size_log]
shl edi, cl
add edi, [ebx+DISKCACHE.data]
clc
ret
 
.nohdcache:
 
add esi, sizeof.CACHE_ITEM
inc edi
cmp edi, [ebx+DISKCACHE.sad_size]
jbe .hdreadcache
stc
ret
endp
 
; Lookup for the given sector in the given cache.
; If the sector is not present, allocate space for it,
; possibly flushing data.
; in: edx:eax = sector
; in: ebx -> DISKCACHE structure
; in: ebp -> PARTITION structure
; out: eax = error code
; out: esi -> sector:status
; out: edi -> sector data
proc cache_lookup_write
call cache_lookup_read
jnc .return0
push edx eax
;-----------------------------------------------------------
; find empty or read slot, flush cache if next 12.5% is used by write
; output : ecx = cache slot
;-----------------------------------------------------------
; Note: the code is essentially inherited, so probably
; no analysis of efficiency were done.
; However, it works.
.search_again:
mov eax, [ebx+DISKCACHE.sad_size]
mov ecx, [ebx+DISKCACHE.search_start]
shr eax, 3
lea esi, [ecx*sizeof.CACHE_ITEM/4]
shl esi, 2
add esi, [ebx+DISKCACHE.pointer]
.search_for_empty:
inc ecx
add esi, sizeof.CACHE_ITEM
cmp ecx, [ebx+DISKCACHE.sad_size]
jbe .inside_cache
mov ecx, 1
mov esi, [ebx+DISKCACHE.pointer]
add esi, sizeof.CACHE_ITEM
.inside_cache:
cmp [esi+CACHE_ITEM.Status], CACHE_ITEM_MODIFIED
jb .found_slot ; it's empty or read
dec eax
jnz .search_for_empty
stdcall write_cache64, [ebp+PARTITION.Disk] ; no empty slots found, write all
test eax, eax
jne .found_slot_access_denied
jmp .search_again ; and start again
.found_slot:
mov [ebx+DISKCACHE.search_start], ecx
popd [esi+CACHE_ITEM.SectorLo]
popd [esi+CACHE_ITEM.SectorHi]
mov [esi+CACHE_ITEM.Status], CACHE_ITEM_EMPTY
mov edi, ecx
mov ecx, [ebx+DISKCACHE.sector_size_log]
shl edi, cl
add edi, [ebx+DISKCACHE.data]
.return0:
xor eax, eax ; success
ret
.found_slot_access_denied:
add esp, 8
ret
endp
 
; Flush the given cache.
; The caller must acquire the cache lock.
; in: ebx -> DISKCACHE
; in: first argument in stdcall convention -> PARTITION
proc write_cache64
; 1. Setup stack frame.
push esi edi ; save used registers to be stdcall
sub esp, .local_vars_size ; reserve space for local vars
virtual at esp
.local_vars:
.cache_end dd ? ; item past the end of the cache
.size_left dd ? ; items left to scan
.current_ptr dd ? ; pointer to the current item
;
; Write operations are coalesced in chains,
; one chain describes a sequential interval of sectors,
; they can be sequential or scattered in the cache.
.sequential dd ?
; boolean variable, 1 if the current chain is sequential in the cache,
; 0 if additional buffer is needed to perform the operation
.chain_start_pos dd ? ; data of chain start item
.chain_start_ptr dd ? ; pointer to chain start item
.chain_size dd ? ; chain size (thanks, C.O.)
.iteration_size dd ?
; If the chain size is too large, split the operation to several iterations.
; This is size in sectors for one iterations.
.iteration_buffer dd ? ; temporary buffer for non-sequential chains
.local_vars_size = $ - .local_vars
rd 2 ; saved registers
dd ? ; return address
.disk dd ? ; first argument
end virtual
; 1. If there is no cache for this disk, nothing to do, just return zero.
cmp [ebx+DISKCACHE.pointer], 0
jz .return0
; 2. Prepare for the loop: initialize current pointer and .size_left,
; calculate .cache_end.
mov ecx, [ebx+DISKCACHE.sad_size]
mov [.size_left], ecx
lea ecx, [ecx*sizeof.CACHE_ITEM/4]
shl ecx, 2
mov esi, [ebx+DISKCACHE.pointer]
add esi, sizeof.CACHE_ITEM
add ecx, esi
mov [.cache_end], ecx
; 3. Main loop: go over all items, go to 5 for every modified item.
.look:
cmp [esi+CACHE_ITEM.Status], CACHE_ITEM_MODIFIED
jz .begin_write
.look_next:
add esi, sizeof.CACHE_ITEM
dec [.size_left]
jnz .look
; 4. Return success.
.return0:
xor eax, eax
.return:
add esp, .local_vars_size
pop edi esi ; restore used registers to be stdcall
ret 4 ; return popping one argument
.begin_write:
; We have found a modified item.
; 5. Prepare for chain finding: save the current item, initialize chain variables.
mov [.current_ptr], esi
; Initialize chain as sequential zero-length starting at the current item.
mov [.chain_start_ptr], esi
mov eax, [ebx+DISKCACHE.sad_size]
sub eax, [.size_left]
inc eax
mov ecx, [ebx+DISKCACHE.sector_size_log]
shl eax, cl
add eax, [ebx+DISKCACHE.data]
mov [.chain_start_pos], eax
mov [.chain_size], 0
mov [.sequential], 1
; 6. Expand the chain backward.
; Note: the main loop in step 2 looks for items sequentially,
; so the previous item is not modified. If the previous sector
; is present in the cache, it automatically makes the chain scattered.
; 6a. Calculate sector number: one before the sector for the current item.
mov eax, [esi+CACHE_ITEM.SectorLo]
mov edx, [esi+CACHE_ITEM.SectorHi]
sub eax, 1
sbb edx, 0
.find_chain_start:
; 6b. For each sector where the previous item does not expand the chain,
; call the lookup function without adding to the cache.
call cache_lookup_read
; 6c. If the sector is not found in cache or is not modified, stop expanding
; and advance to step 7.
jc .found_chain_start
cmp [esi+CACHE_ITEM.Status], CACHE_ITEM_MODIFIED
jnz .found_chain_start
; 6d. We have found a new block that expands the chain backwards.
; It makes the chain non-sequential.
; Normally, sectors come in sequential blocks, so try to look at previous items
; before returning to 6b; if there is a sequential block indeed, this saves some
; time instead of many full-fledged lookups.
mov [.sequential], 0
mov [.chain_start_pos], edi
.look_backward:
; 6e. For each sector, update chain start pos/ptr, decrement sector number,
; look at the previous item.
mov [.chain_start_ptr], esi
inc [.chain_size]
sub eax, 1
sbb edx, 0
sub esi, sizeof.CACHE_ITEM
; If the previous item exists...
cmp esi, [ebx+DISKCACHE.pointer]
jbe .find_chain_start
; ...describes the correct sector...
cmp [esi+CACHE_ITEM.SectorLo], eax
jnz .find_chain_start
cmp [esi+CACHE_ITEM.SectorHi], edx
jnz .find_chain_start
; ...and is modified...
cmp [esi+CACHE_ITEM.Status], CACHE_ITEM_MODIFIED
jnz .found_chain_start
; ...expand the chain one sector backwards and continue the loop at 6e.
; Otherwise, advance to step 7 if the previous item describes the correct sector
; but is not modified, and return to step 6b otherwise.
mov edi, 1
shl edi, cl
sub [.chain_start_pos], edi
jmp .look_backward
.found_chain_start:
; 7. Expand the chain forward.
; 7a. Prepare for the loop at 7b:
; set esi = pointer to current item, edx:eax = current sector.
mov esi, [.current_ptr]
mov eax, [esi+CACHE_ITEM.SectorLo]
mov edx, [esi+CACHE_ITEM.SectorHi]
.look_forward:
; 7b. First, look at the next item. If it describes the next sector:
; if it is modified, expand the chain with that sector and continue this step,
; if it is not modified, the chain is completed, so advance to step 8.
inc [.chain_size]
add eax, 1
adc edx, 0
add esi, sizeof.CACHE_ITEM
cmp esi, [.cache_end]
jae .find_chain_end
cmp [esi+CACHE_ITEM.SectorLo], eax
jnz .find_chain_end
cmp [esi+CACHE_ITEM.SectorHi], edx
jnz .find_chain_end
cmp [esi+CACHE_ITEM.Status], CACHE_ITEM_MODIFIED
jnz .found_chain_end
jmp .look_forward
.find_chain_end:
; 7c. Otherwise, call the lookup function.
call cache_lookup_read
; 7d. If the next sector is present in the cache and is modified,
; mark the chain as non-sequential and continue to step 7b.
jc .found_chain_end
cmp [esi+CACHE_ITEM.Status], CACHE_ITEM_MODIFIED
jnz .found_chain_end
mov [.sequential], 0
jmp .look_forward
.found_chain_end:
; 8. Decide whether the chain is sequential or scattered.
; Advance to step 9 for sequential chains, go to step 10 for scattered chains.
cmp [.sequential], 0
jz .write_non_sequential
.write_sequential:
; 9. Write a sequential chain to disk.
; 9a. Pass the entire chain to the driver.
mov eax, [.chain_start_ptr]
lea ecx, [.chain_size]
push ecx ; numsectors
pushd [eax+CACHE_ITEM.SectorHi] ; startsector
pushd [eax+CACHE_ITEM.SectorLo] ; startsector
push [.chain_start_pos+12] ; buffer
mov esi, [ebp+PARTITION.Disk]
mov al, DISKFUNC.write
call disk_call_driver
; 9b. If failed, pass the error code to the driver.
test eax, eax
jnz .return
; 9c. If succeeded, mark all sectors in the chain as not-modified,
; advance current item and number of items left to skip the chain.
mov esi, [.current_ptr]
mov eax, [.chain_size]
sub [.size_left], eax
@@:
mov [esi+CACHE_ITEM.Status], CACHE_ITEM_COPY
add esi, sizeof.CACHE_ITEM
dec eax
jnz @b
; 9d. Continue the main loop at step 2 if there are more sectors.
; Return success otherwise.
cmp [.size_left], 0
jnz .look
jmp .return0
.write_non_sequential:
; Write a non-sequential chain to the disk.
; 10. Allocate a temporary buffer.
; Use [.chain_size] sectors, but
; not greater than CACHE_MAX_ALLOC_SIZE bytes
; and not greater than half of free memory.
mov eax, [pg_data.pages_free]
shr eax, 1
jz .nomemory
cmp eax, CACHE_MAX_ALLOC_SIZE shr 12
jbe @f
mov eax, CACHE_MAX_ALLOC_SIZE shr 12
@@:
shl eax, 12
shr eax, cl
jz .nomemory
cmp eax, [.chain_size]
jbe @f
mov eax, [.chain_size]
@@:
mov [.iteration_size], eax
shl eax, cl
stdcall kernel_alloc, eax
test eax, eax
jz .nomemory
mov [.iteration_buffer], eax
.write_non_sequential_iteration:
; 11. Split the chain so that each iteration fits in the allocated buffer.
; Iteration size is the minimum of chain size and allocated size.
mov eax, [.chain_size]
cmp eax, [.iteration_size]
jae @f
mov [.iteration_size], eax
@@:
; 12. Prepare arguments for the driver.
mov esi, [.chain_start_ptr]
mov edi, [.iteration_buffer]
push [.iteration_size]
push esp ; numsectors
push [esi+CACHE_ITEM.SectorHi] ; startsector
push [esi+CACHE_ITEM.SectorLo] ; startsector
push edi ; buffer
; 13. Copy data from the cache to the temporary buffer,
; advancing chain_start pos/ptr and marking sectors as not-modified.
; 13a. Prepare for the loop: push number of sectors to process.
push [.iteration_size+20] ; temporary variable
.copy_loop:
; 13b. For each sector, copy the data.
; Note that edi is advanced automatically.
mov esi, [.chain_start_pos+24]
mov ecx, [ebx+DISKCACHE.sector_size_log]
mov eax, 1
shl eax, cl
mov ecx, eax
shr ecx, 2
rep movsd
mov ecx, eax ; keep for 13e
; 13c. Mark the item as not-modified.
mov esi, [.chain_start_ptr+24]
mov [esi+CACHE_ITEM.Status], CACHE_ITEM_COPY
; 13d. Check whether the next sector continues the chain.
; If so, advance to 13e. Otherwise, go to 13f.
mov eax, [esi+CACHE_ITEM.SectorLo]
mov edx, [esi+CACHE_ITEM.SectorHi]
add esi, sizeof.CACHE_ITEM
add eax, 1
adc edx, 0
cmp esi, [.cache_end+24]
jae .no_forward
cmp [esi+CACHE_ITEM.SectorLo], eax
jnz .no_forward
cmp [esi+CACHE_ITEM.SectorHi], edx
jnz .no_forward
; 13e. Increment position/pointer to the chain and
; continue the loop.
add [.chain_start_pos+24], ecx
mov [.chain_start_ptr+24], esi
dec dword [esp]
jnz .copy_loop
jmp .copy_done
.no_forward:
; 13f. Call the lookup function without adding to the cache.
; Update position/pointer with returned value.
; Note: for the last sector in the chain, edi/esi may contain
; garbage; we are not going to use them in this case.
push edi
call cache_lookup_read
mov [.chain_start_pos+28], edi
mov [.chain_start_ptr+28], esi
pop edi
dec dword [esp]
jnz .copy_loop
.copy_done:
; 13g. Restore the stack after 13a.
pop ecx
; 14. Call the driver.
mov esi, [ebp+PARTITION.Disk]
mov al, DISKFUNC.write
call disk_call_driver
pop ecx ; numsectors
; 15. If the driver has returned an error, free the buffer allocated at step 10
; and pass the error to the caller.
; Otherwise, remove the processed part from the chain and continue iterations
; starting in step 11 if there are more data to process.
test eax, eax
jnz .nonsequential_error
sub [.chain_size], ecx
jnz .write_non_sequential_iteration
; 16. The chain is written. Free the temporary buffer
; and continue the loop at step 2.
stdcall kernel_free, [.iteration_buffer]
mov esi, [.current_ptr]
jmp .look_next
.nonsequential_error:
push eax
stdcall kernel_free, [.iteration_buffer+4]
pop eax
jmp .return
.nomemory:
mov eax, DISK_STATUS_NO_MEMORY
jmp .return
endp
 
; This internal function is called from disk_add to initialize the caching for
; a new DISK.
; The algorithm is inherited from getcache.inc: take 1/32 part of the available
; physical memory, round down to 8 pages, limit by 128K from below and by 1M
; from above. Reserve 1/8 part of the cache for system data and 7/8 for app
; data.
; After the size is calculated, but before the cache is allocated, the device
; driver can adjust the size. In particular, setting size to zero disables
; caching: there is no sense in a cache for a ramdisk. In fact, such action
; is most useful example of a non-trivial adjustment.
; esi = pointer to DISK structure
disk_init_cache:
; 1. Verify sector size. The code requires it to be a power of 2 not less than 4.
; In the name of sanity check that sector size is not too small or too large.
bsf ecx, [esi+DISK.MediaInfo.SectorSize]
jz .invalid_sector_size
mov eax, 1
shl eax, cl
cmp eax, [esi+DISK.MediaInfo.SectorSize]
jnz .invalid_sector_size
cmp ecx, 6
jb .invalid_sector_size
cmp ecx, 14
jbe .normal_sector_size
.invalid_sector_size:
DEBUGF 1,'K : sector size %x is invalid\n',[esi+DISK.MediaInfo.SectorSize]
xor eax, eax
ret
.normal_sector_size:
mov [esi+DISK.SysCache.sector_size_log], ecx
mov [esi+DISK.AppCache.sector_size_log], ecx
; 2. Calculate the suggested cache size.
; 2a. Get the size of free physical memory in pages.
mov eax, [pg_data.pages_free]
; 2b. Use the value to calculate the size.
shl eax, 12 - 5 ; 1/32 of it in bytes
and eax, -8*4096 ; round down to the multiple of 8 pages
; 2c. Force lower and upper limits.
cmp eax, 1024*1024
jb @f
mov eax, 1024*1024
@@:
cmp eax, 128*1024
ja @f
mov eax, 128*1024
@@:
; 2d. Give a chance to the driver to adjust the size.
push eax
mov al, DISKFUNC.adjust_cache_size
call disk_call_driver
; Cache size calculated.
mov [esi+DISK.cache_size], eax
test eax, eax
jz .nocache
; 3. Allocate memory for the cache.
; 3a. Call the allocator.
stdcall kernel_alloc, eax
test eax, eax
jnz @f
; 3b. If it failed, say a message and return with eax = 0.
dbgstr 'no memory for disk cache'
jmp .nothing
@@:
; 4. Fill two DISKCACHE structures.
mov [esi+DISK.SysCache.pointer], eax
lea ecx, [esi+DISK.CacheLock]
call mutex_init
; The following code is inherited from getcache.inc.
mov edx, [esi+DISK.SysCache.pointer]
and [esi+DISK.SysCache.search_start], 0
and [esi+DISK.AppCache.search_start], 0
mov eax, [esi+DISK.cache_size]
shr eax, 3
mov [esi+DISK.SysCache.data_size], eax
add edx, eax
imul eax, 7
mov [esi+DISK.AppCache.data_size], eax
mov [esi+DISK.AppCache.pointer], edx
 
mov eax, [esi+DISK.SysCache.data_size]
call calculate_cache_slots
add eax, [esi+DISK.SysCache.pointer]
mov [esi+DISK.SysCache.data], eax
mov [esi+DISK.SysCache.sad_size], ecx
 
push edi
mov edi, [esi+DISK.SysCache.pointer]
lea ecx, [(ecx+1)*3]
xor eax, eax
rep stosd
pop edi
 
mov eax, [esi+DISK.AppCache.data_size]
call calculate_cache_slots
add eax, [esi+DISK.AppCache.pointer]
mov [esi+DISK.AppCache.data], eax
mov [esi+DISK.AppCache.sad_size], ecx
 
push edi
mov edi, [esi+DISK.AppCache.pointer]
lea ecx, [(ecx+1)*3]
xor eax, eax
rep stosd
pop edi
 
; 5. Return with nonzero al.
mov al, 1
; 6. Return.
.nothing:
ret
; No caching is required for this driver. Zero cache pointers and return with
; nonzero al.
.nocache:
mov [esi+DISK.SysCache.pointer], eax
mov [esi+DISK.AppCache.pointer], eax
mov al, 1
ret
 
calculate_cache_slots:
push eax
mov ecx, [esi+DISK.MediaInfo.SectorSize]
add ecx, sizeof.CACHE_ITEM
xor edx, edx
div ecx
mov ecx, eax
imul eax, [esi+DISK.MediaInfo.SectorSize]
sub [esp], eax
pop eax
dec ecx
ret
 
 
; This internal function is called from disk_media_dereference to free the
; allocated cache, if there is one.
; esi = pointer to DISK structure
disk_free_cache:
; The algorithm is straightforward.
mov eax, [esi+DISK.SysCache.pointer]
test eax, eax
jz .nothing
stdcall kernel_free, eax
.nothing:
ret
 
; This function flushes all modified data from both caches for the given DISK.
; esi = pointer to DISK
disk_sync:
; The algorithm is straightforward.
cmp [esi+DISK.SysCache.pointer], 0
jz .nothing
lea ecx, [esi+DISK.CacheLock]
call mutex_lock
push ebx
push esi ; for second write_cache64
push esi ; for first write_cache64
lea ebx, [esi+DISK.SysCache]
call write_cache64
add ebx, DISK.AppCache - DISK.SysCache
call write_cache64
pop ebx
lea ecx, [esi+DISK.CacheLock]
call mutex_unlock
.nothing:
mov al, DISKFUNC.flush
call disk_call_driver
ret
Property changes:
Added: svn:eol-style
+native
\ No newline at end of property
Added: svn:keywords
+Revision
\ No newline at end of property
/kernel/branches/kolibri-lldw/blkdev/fdc.inc
0,0 → 1,68
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ;;
;; Copyright (C) KolibriOS team 2004-2015. All rights reserved. ;;
;; Copyright (C) MenuetOS 2000-2004 Ville Mikael Turjanmaa ;;
;; Distributed under terms of the GNU General Public License ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
$Revision$
 
 
uglobal
dmasize db 0x0
dmamode db 0x0
endg
 
fdc_init: ;start with clean tracks.
mov edi, OS_BASE+0xD201
mov al, 0
mov ecx, 160
rep stosb
ret
 
save_image:
cmp [ramdisk_actual_size], FLOPPY_CAPACITY
jnz .fail
pusha
mov ecx, floppy_mutex
call mutex_lock
mov [flp_number], bl
call floppy_read_bootsector
cmp [FDC_Status], 0
jne .unnecessary_save_image
mov [FDD_Track], 0; Цилиндр
mov [FDD_Head], 0; Сторона
mov [FDD_Sector], 1; Сектор
mov esi, RAMDISK
call SeekTrack
.save_image_1:
call take_data_from_application_1
call WriteSectWithRetr
; call WriteSector
cmp [FDC_Status], 0
jne .unnecessary_save_image
inc [FDD_Sector]
cmp [FDD_Sector], 19
jne .save_image_1
mov [FDD_Sector], 1
inc [FDD_Head]
cmp [FDD_Head], 2
jne .save_image_1
mov [FDD_Head], 0
inc [FDD_Track]
call SeekTrack
cmp [FDD_Track], 80
jne .save_image_1
.unnecessary_save_image:
cmp [FDC_Status], 0
pushf
mov ecx, floppy_mutex
call mutex_unlock
popf
popa
jnz .fail
xor eax, eax
ret
.fail:
movi eax, 1
ret
Property changes:
Added: svn:eol-style
+native
\ No newline at end of property
Added: svn:keywords
+Rev
\ No newline at end of property
/kernel/branches/kolibri-lldw/blkdev/ide_cache.inc
0,0 → 1,202
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ;;
;; Copyright (C) KolibriOS team 2004-2015. All rights reserved. ;;
;; Distributed under terms of the GNU General Public License ;;
;; ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
;**************************************************************************
;
; [cache_ide[X]_pointer]
; or [cache_ide[X]_data_pointer] first entry in cache list
;
; +0 - lba sector
; +4 - state of cache sector
; 0 = empty
; 1 = used for read ( same as in hd )
; 2 = used for write ( differs from hd )
;
; [cache_ide[X]_system_data]
; or [cache_ide[x]_appl_data] - cache entries
;
;**************************************************************************
 
$Revision$
 
align 4
find_empty_slot_CD_cache:
;-----------------------------------------------------------
; find empty or read slot, flush cache if next 10% is used by write
; output : edi = cache slot
;-----------------------------------------------------------
.search_again:
call cd_calculate_cache_3
.search_for_empty:
inc edi
call cd_calculate_cache_4
jbe .inside_cache
mov edi, 1
.inside_cache:
call cd_calculate_cache_5
ret
;--------------------------------------------------------------------
clear_CD_cache:
DEBUGF 1, 'K : clear_CD_cache\n'
pusha
 
mov esi, [cdpos]
dec esi
imul esi, sizeof.IDE_CACHE
add esi, cache_ide0
 
xor eax, eax
 
mov [esi+IDE_CACHE.search_start], eax
mov ecx, [esi+IDE_CACHE.system_sad_size]
mov edi, [esi+IDE_CACHE.pointer]
call .clear
 
mov [esi+IDE_CACHE.appl_search_start], eax
mov ecx, [esi+IDE_CACHE.appl_sad_size]
mov edi, [esi+IDE_CACHE.data_pointer]
call .clear
 
popa
ret
;--------------------------------------
.clear:
shl ecx, 1
cld
rep stosd
ret
;--------------------------------------------------------------------
align 4
cd_calculate_cache:
; 1 - IDE0 ... 12 - IDE11
push eax
 
mov eax, [cdpos]
dec eax
imul eax, sizeof.IDE_CACHE
add eax, cache_ide0
 
cmp [cd_appl_data], 0
jne @f
 
mov ecx, [eax+IDE_CACHE.system_sad_size]
mov esi, [eax+IDE_CACHE.pointer]
pop eax
ret
;--------------------------------------
@@:
mov ecx, [eax+IDE_CACHE.appl_sad_size]
mov esi, [eax+IDE_CACHE.data_pointer]
pop eax
ret
;--------------------------------------------------------------------
align 4
cd_calculate_cache_1:
; 1 - IDE0 ... 12 - IDE11
push eax
 
mov eax, [cdpos]
dec eax
imul eax, sizeof.IDE_CACHE
add eax, cache_ide0
 
cmp [cd_appl_data], 0
jne @f
 
mov esi, [eax+IDE_CACHE.pointer]
pop eax
ret
;--------------------------------------
@@:
mov esi, [eax+IDE_CACHE.data_pointer]
pop eax
ret
;--------------------------------------------------------------------
align 4
cd_calculate_cache_2:
; 1 - IDE0 ... 12 - IDE11
mov eax, [cdpos]
dec eax
imul eax, sizeof.IDE_CACHE
add eax, cache_ide0
 
cmp [cd_appl_data], 0
jne @f
 
mov eax, [eax+IDE_CACHE.system_data]
ret
;--------------------------------------
@@:
mov eax, [eax+IDE_CACHE.appl_data]
ret
;--------------------------------------------------------------------
align 4
cd_calculate_cache_3:
; 1 - IDE0 ... 12 - IDE11
push eax
 
mov eax, [cdpos]
dec eax
imul eax, sizeof.IDE_CACHE
add eax, cache_ide0
 
cmp [cd_appl_data], 0
jne @f
 
mov edi, [eax+IDE_CACHE.search_start]
pop eax
ret
;--------------------------------------
@@:
mov edi, [eax+IDE_CACHE.appl_search_start]
pop eax
ret
;--------------------------------------------------------------------
align 4
cd_calculate_cache_4:
; 1 - IDE0 ... 12 - IDE11
push eax
 
mov eax, [cdpos]
dec eax
imul eax, sizeof.IDE_CACHE
add eax, cache_ide0
 
cmp [cd_appl_data], 0
jne @f
 
cmp edi, [eax+IDE_CACHE.system_sad_size]
pop eax
ret
;--------------------------------------
@@:
cmp edi, [eax+IDE_CACHE.appl_sad_size]
pop eax
ret
;--------------------------------------------------------------------
align 4
cd_calculate_cache_5:
; 1 - IDE0 ... 12 - IDE11
push eax
 
mov eax, [cdpos]
dec eax
imul eax, sizeof.IDE_CACHE
add eax, cache_ide0
 
cmp [cd_appl_data], 0
jne @f
 
mov [eax+IDE_CACHE.search_start], edi
pop eax
ret
;--------------------------------------
@@:
mov [eax+IDE_CACHE.appl_search_start], edi
pop eax
ret
;--------------------------------------------------------------------
Property changes:
Added: svn:eol-style
+native
\ No newline at end of property
Added: svn:keywords
+Rev
\ No newline at end of property
/kernel/branches/kolibri-lldw/blkdev/rdsave.inc
0,0 → 1,33
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ;;
;; Copyright (C) KolibriOS team 2004-2015. All rights reserved. ;;
;; Distributed under terms of the GNU General Public License ;;
;; ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
$Revision$
 
 
iglobal
saverd_fileinfo:
dd 2 ; subfunction: write
dd 0 ; (reserved)
dd 0 ; (reserved)
.size:
dd 0
dd RAMDISK
db 0
.name:
dd ?
endg
sysfn_saveramdisk: ; 18.6 = SAVE FLOPPY IMAGE (HD version only)
mov ebx, saverd_fileinfo
mov [ebx+21], ecx
mov eax, [ramdisk_actual_size]
shl eax, 9
mov [ebx+12], eax
pushad
call file_system_lfn_protected ;in ebx
popad
mov [esp+32], eax
ret
Property changes:
Added: svn:eol-style
+native
\ No newline at end of property
Added: svn:keywords
+Rev
\ No newline at end of property
/kernel/branches/kolibri-lldw/blkdev/.
Property changes:
Added: svn:ignore
+*.mnt
+lang.inc
+*.bat
+out.txt
+scin*
+*.obj