/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 |