Subversion Repositories Kolibri OS

Rev

Rev 4429 | Go to most recent revision | Blame | Last modification | View Log | Download | RSS feed

  1. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  2. ;;                                                              ;;
  3. ;; Copyright (C) KolibriOS team 2011-2014. All rights reserved. ;;
  4. ;; Distributed under terms of the GNU General Public License    ;;
  5. ;;                                                              ;;
  6. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  7.  
  8. $Revision: 4133 $
  9.  
  10. ; Read/write functions try to do large operations,
  11. ; it is significantly faster than several small operations.
  12. ; This requires large buffers.
  13. ; We can't use input/output buffers directly - they can be controlled
  14. ; by user-mode application, so they can be modified between the operation
  15. ; and copying to/from cache, giving invalid data in cache.
  16. ; It is unclear how to use cache directly, currently cache items are
  17. ; allocated/freed sector-wise, so items for sequential sectors can be
  18. ; scattered over all the cache.
  19. ; So read/write functions allocate a temporary buffer which is
  20. ; 1) not greater than half of free memory and
  21. ; 2) not greater than the following constant.
  22. CACHE_MAX_ALLOC_SIZE = 4 shl 20
  23.  
  24. ; Legacy interface for filesystems fs_{read,write}32_{sys,app}
  25. ; gives only one sector for FS. However, per-sector reading is inefficient,
  26. ; so internally fs_read32_{sys,app} reads to the cache several sequential
  27. ; sectors, hoping that they will be useful.
  28. ; Total number of sectors is given by the following constant.
  29. CACHE_LEGACY_READ_SIZE = 16
  30.  
  31. ; This structure describes one item in the cache.
  32. struct CACHE_ITEM
  33. SectorLo        dd      ?       ; low 32 bits of sector
  34. SectorHi        dd      ?       ; high 32 bits of sector
  35. Status          dd      ?       ; one of CACHE_ITEM_*
  36. ends
  37.  
  38. ; Possible values for CACHE_ITEM_*
  39. CACHE_ITEM_EMPTY = 0
  40. CACHE_ITEM_COPY = 1
  41. CACHE_ITEM_MODIFIED = 2
  42.  
  43. ; Read several sequential sectors using cache #1.
  44. ; in: edx:eax = start sector, relative to start of partition
  45. ; in: ecx = number of sectors to read
  46. ; in: ebx -> buffer
  47. ; in: ebp -> PARTITION
  48. ; out: eax = error code, 0 = ok
  49. ; out: ecx = number of sectors that were read
  50. fs_read64_sys:
  51. ; Save ebx, set ebx to SysCache and let the common part do its work.
  52.         push    ebx
  53.         mov     ebx, [ebp+PARTITION.Disk]
  54.         add     ebx, DISK.SysCache
  55.         jmp     fs_read64_common
  56.  
  57. ; Read several sequential sectors using cache #2.
  58. ; in: edx:eax = start sector, relative to start of partition
  59. ; in: ecx = number of sectors to read
  60. ; in: edi -> buffer
  61. ; in: ebp -> PARTITION
  62. ; out: eax = error code, 0 = ok
  63. ; out: ecx = number of sectors that were read
  64. fs_read64_app:
  65. ; Save ebx, set ebx to AppCache and let the common part do its work.
  66.         push    ebx
  67.         mov     ebx, [ebp+PARTITION.Disk]
  68.         add     ebx, DISK.AppCache
  69.  
  70. ; Common part of fs_read64_{app,sys}:
  71. ; read several sequential sectors using the given cache.
  72. fs_read64_common:
  73. ; 1. Setup stack frame.
  74.         push    esi edi         ; save used registers to be stdcall
  75.         push    0               ; initialize .error_code
  76.         push    ebx edx eax ecx ecx     ; initialize stack variables
  77. virtual at esp
  78. .local_vars:
  79. .num_sectors_orig dd    ?
  80. ; Number of sectors that should be read. Used to generate output value of ecx.
  81. .num_sectors    dd      ?
  82. ; Number of sectors that remain to be read. Decreases from .num_sectors_orig to 0.
  83. .sector_lo      dd      ?       ; low 32 bits of the current sector
  84. .sector_hi      dd      ?       ; high 32 bits of the current sector
  85. .cache          dd      ?       ; pointer to DISKCACHE
  86. .error_code     dd      ?       ; current status
  87. .local_vars_size = $ - .local_vars
  88. .saved_regs     rd      2
  89. .buffer         dd      ?       ; filled by fs_read64_{sys,app}
  90. end virtual
  91. ; 2. Validate parameters against partition length:
  92. ; immediately return error if edx:eax are beyond partition end,
  93. ; decrease .num_sectors and .num_sectors_orig, if needed,
  94. ; so that the entire operation fits in the partition limits.
  95.         mov     eax, dword [ebp+PARTITION.Length]
  96.         mov     edx, dword [ebp+PARTITION.Length+4]
  97.         sub     eax, [.sector_lo]
  98.         sbb     edx, [.sector_hi]
  99.         jb      .end_of_media
  100.         jnz     .no_end_of_media
  101.         cmp     ecx, eax
  102.         jbe     .no_end_of_media
  103. ; If .num_sectors got decreased, set status to DISK_STATUS_END_OF_MEDIA;
  104. ; if all subsequent operations would be successful, this would become the final
  105. ; status, otherwise this would be rewritten by failed operation.
  106.         mov     [.num_sectors], eax
  107.         mov     [.num_sectors_orig], eax
  108.         mov     [.error_code], DISK_STATUS_END_OF_MEDIA
  109. .no_end_of_media:
  110. ; 3. If number of sectors to read is zero, either because zero-sectors operation
  111. ; was requested or because it got decreased to zero due to partition limits,
  112. ; just return the current status.
  113.         cmp     [.num_sectors], 0
  114.         jz      .return
  115. ; 4. Shift sector from partition-relative to absolute.
  116.         mov     eax, dword [ebp+PARTITION.FirstSector]
  117.         mov     edx, dword [ebp+PARTITION.FirstSector+4]
  118.         add     [.sector_lo], eax
  119.         adc     [.sector_hi], edx
  120. ; 5. If the cache is disabled, pass the request directly to the driver.
  121.         mov     edi, [.buffer]
  122.         cmp     [ebx+DISKCACHE.pointer], 0
  123.         jz      .nocache
  124. ; 6. Look for sectors in the cache, sequentially from the beginning.
  125. ; Stop at the first sector that is not in the cache
  126. ; or when all sectors were read from the cache.
  127. ; 6a. Acquire the lock.
  128.         mov     ecx, [ebp+PARTITION.Disk]
  129.         add     ecx, DISK.CacheLock
  130.         call    mutex_lock
  131. .lookup_in_cache_loop:
  132. ; 6b. For each sector, call the lookup function without adding to the cache.
  133.         mov     eax, [.sector_lo]
  134.         mov     edx, [.sector_hi]
  135.         call    cache_lookup_read
  136. ; 6c. If it has failed, the sector is not in cache;
  137. ; release the lock and go to 7.
  138.         jc      .not_found_in_cache
  139. ; The sector is found in cache.
  140. ; 6d. Copy data for the caller.
  141. ; Note that buffer in edi is advanced automatically.
  142.         mov     esi, ecx
  143.         shl     esi, 9
  144.         add     esi, [ebx+DISKCACHE.data]
  145.         mov     ecx, 512/4
  146.         rep movsd
  147. ; 6e. Advance the sector.
  148.         add     [.sector_lo], 1
  149.         adc     [.sector_hi], 0
  150. ; 6f. Decrement number of sectors left.
  151. ; If all sectors were read, release the lock and return.
  152.         dec     [.num_sectors]
  153.         jnz     .lookup_in_cache_loop
  154. ; Release the lock acquired at 6a.
  155.         mov     ecx, [ebp+PARTITION.Disk]
  156.         add     ecx, DISK.CacheLock
  157.         call    mutex_unlock
  158. .return:
  159.         mov     eax, [.error_code]
  160.         mov     ecx, [.num_sectors_orig]
  161.         sub     ecx, [.num_sectors]
  162. .nothing:
  163.         add     esp, .local_vars_size
  164.         pop     edi esi ebx     ; restore used registers to be stdcall
  165.         ret
  166. .not_found_in_cache:
  167. ; Release the lock acquired at 6a.
  168.         mov     ecx, [ebp+PARTITION.Disk]
  169.         add     ecx, DISK.CacheLock
  170.         call    mutex_unlock
  171. ; The current sector is not present in the cache.
  172. ; Ask the driver to read all requested not-yet-read sectors,
  173. ; put results in the cache.
  174. ; Also, see the comment before the definition of CACHE_MAX_ALLOC_SIZE.
  175. ; 7. Allocate buffer for operations.
  176. ; Normally, create buffer that is sufficient for all remaining data.
  177. ; However, for extra-large requests make an upper limit:
  178. ; do not use more than half of the free memory
  179. ; or more than CACHE_MAX_ALLOC_SIZE bytes.
  180.         mov     ebx, [pg_data.pages_free]
  181.         shr     ebx, 1
  182.         jz      .nomemory
  183.         cmp     ebx, CACHE_MAX_ALLOC_SIZE shr 12
  184.         jbe     @f
  185.         mov     ebx, CACHE_MAX_ALLOC_SIZE shr 12
  186. @@:
  187.         shl     ebx, 12 - 9
  188.         cmp     ebx, [.num_sectors]
  189.         jbe     @f
  190.         mov     ebx, [.num_sectors]
  191. @@:
  192.         mov     eax, ebx
  193.         shl     eax, 9
  194.         stdcall kernel_alloc, eax
  195. ; If failed, return the appropriate error code.
  196.         test    eax, eax
  197.         jz      .nomemory
  198.         mov     esi, eax
  199. ; Split the request to chunks that fit in the allocated buffer.
  200. .read_loop:
  201. ; 8. Get iteration size: either size of allocated buffer in sectors
  202. ; or number of sectors left, select what is smaller.
  203.         cmp     ebx, [.num_sectors]
  204.         jbe     @f
  205.         mov     ebx, [.num_sectors]
  206. @@:
  207. ; 9. Create second portion of local variables.
  208. ; Note that variables here and above are esp-relative;
  209. ; it means that all addresses should be corrected when esp is changing.
  210.         push    ebx esi esi
  211.         push    ebx
  212. ; In particular, num_sectors is now [.num_sectors+.local_vars2_size].
  213. virtual at esp
  214. .local_vars2:
  215. .current_num_sectors    dd      ?       ; number of sectors that were read
  216. .current_buffer         dd      ?
  217. ; pointer inside .allocated_buffer that points
  218. ; to the beginning of not-processed data
  219. .allocated_buffer       dd      ?       ; saved in safe place
  220. .iteration_size         dd      ?       ; saved in safe place
  221. .local_vars2_size = $ - .local_vars2
  222. end virtual
  223. ; 10. Call the driver, reading the next chunk.
  224.         push    esp     ; numsectors
  225.         push    [.sector_hi+.local_vars2_size+4] ; startsector
  226.         push    [.sector_lo+.local_vars2_size+8] ; startsector
  227.         push    esi     ; buffer
  228.         mov     esi, [ebp+PARTITION.Disk]
  229.         mov     al, DISKFUNC.read
  230.         call    disk_call_driver
  231. ; If failed, save error code.
  232.         test    eax, eax
  233.         jz      @f
  234.         mov     [.error_code+.local_vars2_size], eax
  235. @@:
  236. ; 11. Copy data for the caller.
  237. ; Note that buffer in edi is advanced automatically.
  238.         cmp     [.current_num_sectors], 0
  239.         jz      .copy_done
  240.         mov     ecx, [.current_num_sectors]
  241.         shl     ecx, 9-2
  242.         mov     esi, [.allocated_buffer]
  243.         rep movsd
  244. ; 12. Copy data to the cache.
  245. ; 12a. Acquire the lock.
  246.         mov     ebx, [.cache+.local_vars2_size]
  247.         mov     ecx, [ebp+PARTITION.Disk]
  248.         add     ecx, DISK.CacheLock
  249.         call    mutex_lock
  250. ; 12b. Prepare for the loop: save edi and create a local variable that
  251. ; stores number of sectors to be copied.
  252.         push    edi
  253.         push    [.current_num_sectors+4]
  254. .store_to_cache:
  255. ; 12c. For each sector, call the lookup function with adding to the cache, if not yet.
  256.         mov     eax, [.sector_lo+.local_vars2_size+8]
  257.         mov     edx, [.sector_hi+.local_vars2_size+8]
  258.         call    cache_lookup_write
  259.         test    eax, eax
  260.         jnz     .cache_error
  261. ; 12d. For each sector, copy data, mark the item as not-modified copy of the disk,
  262. ; advance .current_buffer and .sector_hi:.sector_lo to the next sector.
  263.         mov     [esi+CACHE_ITEM.Status], CACHE_ITEM_COPY
  264.         mov     esi, [.current_buffer+8]
  265.         mov     edi, ecx
  266.         shl     edi, 9
  267.         add     edi, [ebx+DISKCACHE.data]
  268.         mov     ecx, 512/4
  269.         rep movsd
  270.         mov     [.current_buffer+8], esi
  271.         add     [.sector_lo+.local_vars2_size+8], 1
  272.         adc     [.sector_hi+.local_vars2_size+8], 0
  273. ; 12e. Continue the loop 12c-12d until all sectors are read.
  274.         dec     dword [esp]
  275.         jnz     .store_to_cache
  276. .cache_error:
  277. ; 12f. Restore after the loop: pop the local variable and restore edi.
  278.         pop     ecx
  279.         pop     edi
  280. ; 12g. Release the lock.
  281.         mov     ecx, [ebp+PARTITION.Disk]
  282.         add     ecx, DISK.CacheLock
  283.         call    mutex_unlock
  284. .copy_done:
  285. ; 13. Remove portion of local variables created at step 9.
  286.         pop     ecx
  287.         pop     esi esi ebx
  288. ; 14. Continue iterations while number of sectors read by the driver
  289. ; is equal to number of sectors requested and there are additional sectors.
  290.         cmp     ecx, ebx
  291.         jnz     @f
  292.         sub     [.num_sectors], ebx
  293.         jnz     .read_loop
  294. @@:
  295. ; 15. Free the buffer allocated at step 7 and return.
  296.         stdcall kernel_free, esi
  297.         jmp     .return
  298.  
  299. ; Special branches:
  300. .nomemory:
  301. ; memory allocation failed at step 7: return the corresponding error
  302.         mov     [.error_code], DISK_STATUS_NO_MEMORY
  303.         jmp     .return
  304. .nocache:
  305. ; step 5, after correcting number of sectors to fit in partition limits
  306. ; and advancing partition-relative sector to absolute,
  307. ; sees that cache is disabled: pass corrected request to the driver
  308.         lea     eax, [.num_sectors]
  309.         push    eax             ; numsectors
  310.         push    [.sector_hi+4]  ; startsector
  311.         push    [.sector_lo+8]  ; startsector
  312.         push    edi     ; buffer
  313.         mov     esi, [ebp+PARTITION.Disk]
  314.         mov     al, DISKFUNC.read
  315.         call    disk_call_driver
  316.         test    eax, eax
  317.         jnz     @f
  318.         mov     eax, [.error_code]
  319. @@:
  320.         mov     ecx, [.num_sectors]
  321.         jmp     .nothing
  322. .end_of_media:
  323. ; requested sector is beyond the partition end: return the corresponding error
  324.         mov     [.error_code], DISK_STATUS_END_OF_MEDIA
  325.         jmp     .return
  326.  
  327. ; Write several sequential sectors using cache #1.
  328. ; in: edx:eax = start sector
  329. ; in: ecx = number of sectors to write
  330. ; in: ebx -> buffer
  331. ; in: ebp -> PARTITION
  332. ; out: eax = error code, 0 = ok
  333. ; out: ecx = number of sectors that were written
  334. fs_write64_sys:
  335. ; Save ebx, set ebx to SysCache and let the common part do its work.
  336.         push    ebx
  337.         mov     ebx, [ebp+PARTITION.Disk]
  338.         add     ebx, DISK.SysCache
  339.         jmp     fs_write64_common
  340.  
  341. ; Write several sequential sectors using cache #2.
  342. ; in: edx:eax = start sector
  343. ; in: ecx = number of sectors to write
  344. ; in: ebx -> buffer
  345. ; in: ebp -> PARTITION
  346. ; out: eax = error code, 0 = ok
  347. ; out: ecx = number of sectors that were written
  348. fs_write64_app:
  349. ; Save ebx, set ebx to AppCache and let the common part do its work.
  350.         push    ebx
  351.         mov     ebx, [ebp+PARTITION.Disk]
  352.         add     ebx, DISK.AppCache
  353.  
  354. ; Common part of fs_write64_{app,sys}:
  355. ; write several sequential sectors using the given cache.
  356. fs_write64_common:
  357. ; 1. Setup stack frame.
  358.         push    esi edi         ; save used registers to be stdcall
  359.         push    0               ; initialize .error_code
  360.         push    edx eax ecx ecx ; initialize stack variables
  361.         push    [.buffer-4]     ; copy [.buffer] to [.cur_buffer]
  362.                                 ; -4 is due to esp-relative addressing
  363. virtual at esp
  364. .local_vars:
  365. .cur_buffer     dd      ?       ; pointer to data that are currently copying
  366. .num_sectors_orig dd    ?
  367. ; Number of sectors that should be written. Used to generate output value of ecx.
  368. .num_sectors    dd      ?
  369. ; Number of sectors that remain to be written.
  370. .sector_lo      dd      ?       ; low 32 bits of the current sector
  371. .sector_hi      dd      ?       ; high 32 bits of the current sector
  372. .error_code     dd      ?       ; current status
  373. .local_vars_size = $ - .local_vars
  374. .saved_regs     rd      2
  375. .buffer         dd      ?       ; filled by fs_write64_{sys,app}
  376. end virtual
  377. ; 2. Validate parameters against partition length:
  378. ; immediately return error if edx:eax are beyond partition end,
  379. ; decrease .num_sectors and .num_sectors_orig, if needed,
  380. ; so that the entire operation fits in the partition limits.
  381.         mov     eax, dword [ebp+PARTITION.Length]
  382.         mov     edx, dword [ebp+PARTITION.Length+4]
  383.         sub     eax, [.sector_lo]
  384.         sbb     edx, [.sector_hi]
  385.         jb      .end_of_media
  386.         jnz     .no_end_of_media
  387.         cmp     ecx, eax
  388.         jbe     .no_end_of_media
  389. ; If .num_sectors got decreased, set status to DISK_STATUS_END_OF_MEDIA;
  390. ; if all subsequent operations would be successful, this would become the final
  391. ; status, otherwise this would be rewritten by failed operation.
  392.         mov     [.num_sectors], eax
  393.         mov     [.num_sectors_orig], eax
  394.         mov     [.error_code], DISK_STATUS_END_OF_MEDIA
  395. .no_end_of_media:
  396. ; 3. If number of sectors to write is zero, either because zero-sectors operation
  397. ; was requested or because it got decreased to zero due to partition limits,
  398. ; just return the current status.
  399.         cmp     [.num_sectors], 0
  400.         jz      .return
  401. ; 4. Shift sector from partition-relative to absolute.
  402.         mov     eax, dword [ebp+PARTITION.FirstSector]
  403.         mov     edx, dword [ebp+PARTITION.FirstSector+4]
  404.         add     [.sector_lo], eax
  405.         adc     [.sector_hi], edx
  406. ; 5. If the cache is disabled, pass the request directly to the driver.
  407.         cmp     [ebx+DISKCACHE.pointer], 0
  408.         jz      .nocache
  409. ; 6. Store sectors in the cache, sequentially from the beginning.
  410. ; 6a. Acquire the lock.
  411.         mov     ecx, [ebp+PARTITION.Disk]
  412.         add     ecx, DISK.CacheLock
  413.         call    mutex_lock
  414. .lookup_in_cache_loop:
  415. ; 6b. For each sector, call the lookup function with adding to the cache, if not yet.
  416.         mov     eax, [.sector_lo]
  417.         mov     edx, [.sector_hi]
  418.         call    cache_lookup_write
  419.         test    eax, eax
  420.         jnz     .cache_error
  421. ; 6c. For each sector, copy data, mark the item as modified and not saved,
  422. ; advance .current_buffer to the next sector.
  423.         mov     [esi+CACHE_ITEM.Status], CACHE_ITEM_MODIFIED
  424.         mov     esi, [.cur_buffer]
  425.         mov     edi, ecx
  426.         shl     edi, 9
  427.         add     edi, [ebx+DISKCACHE.data]
  428.         mov     ecx, 512/4
  429.         rep movsd
  430.         mov     [.cur_buffer], esi
  431. ; 6d. Remove the sector from the other cache.
  432. ; Normally it should not be there, but prefetching could put to the app cache
  433. ; data that normally should belong to the sys cache and vice versa.
  434. ; Note: this requires that both caches must be protected by the same lock.
  435.         mov     eax, [.sector_lo]
  436.         mov     edx, [.sector_hi]
  437.         push    ebx
  438.         sub     ebx, [ebp+PARTITION.Disk]
  439.         xor     ebx, DISK.SysCache xor DISK.AppCache
  440.         add     ebx, [ebp+PARTITION.Disk]
  441.         call    cache_lookup_read
  442.         jc      @f
  443.         mov     [esi+CACHE_ITEM.Status], CACHE_ITEM_EMPTY
  444. @@:
  445.         pop     ebx
  446. ; 6e. Advance .sector_hi:.sector_lo to the next sector.
  447.         add     [.sector_lo], 1
  448.         adc     [.sector_hi], 0
  449. ; 6f. Continue the loop at 6b-6e until all sectors are processed.
  450.         dec     [.num_sectors]
  451.         jnz     .lookup_in_cache_loop
  452. .unlock_return:
  453. ; 6g. Release the lock and return.
  454.         mov     ecx, [ebp+PARTITION.Disk]
  455.         add     ecx, DISK.CacheLock
  456.         call    mutex_unlock
  457. .return:
  458.         mov     eax, [.error_code]
  459.         mov     ecx, [.num_sectors_orig]
  460.         sub     ecx, [.num_sectors]
  461. .nothing:
  462.         add     esp, .local_vars_size
  463.         pop     edi esi ebx
  464.         ret
  465.  
  466. ; Special branches:
  467. .cache_error:
  468. ; error at flushing the cache while adding sector to the cache:
  469. ; return the error from the lookup function
  470.         mov     [.error_code], eax
  471.         jmp     .unlock_return
  472. .end_of_media:
  473. ; requested sector is beyond the partition end: return the corresponding error
  474.         mov     eax, DISK_STATUS_END_OF_MEDIA
  475.         xor     ecx, ecx
  476.         jmp     .nothing
  477. .nocache:
  478. ; step 5, after correcting number of sectors to fit in partition limits
  479. ; and advancing partition-relative sector to absolute,
  480. ; sees that cache is disabled: pass corrected request to the driver
  481.         lea     eax, [.num_sectors]
  482.         push    eax             ; numsectors
  483.         push    [.sector_hi+4]  ; startsector
  484.         push    [.sector_lo+8]  ; startsector
  485.         push    [.buffer+12]    ; buffer
  486.         mov     esi, [ebp+PARTITION.Disk]
  487.         mov     al, DISKFUNC.write
  488.         call    disk_call_driver
  489.         mov     ecx, [.num_sectors]
  490.         jmp     .nothing
  491.  
  492. ; Legacy. Use fs_read64_sys instead.
  493. ; This function is intended to replace the old 'hd_read' function when
  494. ; [hdd_appl_data] = 0, so its input/output parameters are the same, except
  495. ; that it can't use the global variables 'hd_error' and 'hdd_appl_data'.
  496. ; in: eax = sector, ebx = buffer, ebp = pointer to PARTITION structure
  497. ; eax is relative to partition start
  498. ; out: eax = error code; 0 = ok
  499. fs_read32_sys:
  500. ; Save ebx, set ebx to SysCache and let the common part do its work.
  501.         push    ebx
  502.         mov     ebx, [ebp+PARTITION.Disk]
  503.         add     ebx, DISK.SysCache
  504.         jmp     fs_read32_common
  505.  
  506. ; Legacy. Use fs_read64_app instead.
  507. ; This function is intended to replace the old 'hd_read' function when
  508. ; [hdd_appl_data] = 1, so its input/output parameters are the same, except
  509. ; that it can't use the global variables 'hd_error' and 'hdd_appl_data'.
  510. ; in: eax = sector, ebx = buffer, ebp = pointer to PARTITION structure
  511. ; eax is relative to partition start
  512. ; out: eax = error code; 0 = ok
  513. fs_read32_app:
  514. ; Save ebx, set ebx to AppCache and let the common part do its work.
  515.         push    ebx
  516.         mov     ebx, [ebp+PARTITION.Disk]
  517.         add     ebx, DISK.AppCache
  518.  
  519. ; This label is the common part of fs_read32_sys and fs_read32_app.
  520. fs_read32_common:
  521. ; 1. Check that the required sector is inside the partition. If no, return
  522. ; DISK_STATUS_END_OF_MEDIA.
  523.         cmp     dword [ebp+PARTITION.Length+4], 0
  524.         jnz     @f
  525.         cmp     dword [ebp+PARTITION.Length], eax
  526.         ja      @f
  527.         mov     eax, DISK_STATUS_END_OF_MEDIA
  528.         pop     ebx
  529.         ret
  530. @@:
  531. ; 2. Get the absolute sector on the disk.
  532.         push    ecx edx esi edi
  533.         xor     edx, edx
  534.         add     eax, dword [ebp+PARTITION.FirstSector]
  535.         adc     edx, dword [ebp+PARTITION.FirstSector+4]
  536. ; 3. If there is no cache for this disk, just pass the request to the driver.
  537.         cmp     [ebx+DISKCACHE.pointer], 0
  538.         jnz     .scancache
  539.         push    1
  540.         push    esp     ; numsectors
  541.         push    edx     ; startsector
  542.         push    eax     ; startsector
  543.         pushd   [esp+32]; buffer
  544.         mov     esi, [ebp+PARTITION.Disk]
  545.         mov     al, DISKFUNC.read
  546.         call    disk_call_driver
  547.         pop     ecx
  548.         pop     edi esi edx ecx
  549.         pop     ebx
  550.         ret
  551. .scancache:
  552.         push    ebx edx eax
  553. virtual at esp
  554. .local_vars:
  555. .sector_lo      dd      ?
  556. .sector_hi      dd      ?
  557. .cache          dd      ?
  558. .local_vars_size = $ - .local_vars
  559. .saved_regs     rd      4
  560. .buffer         dd      ?
  561. end virtual
  562. ; 4. Scan for the requested sector in the cache.
  563. ; If found, copy the data and return.
  564. ; 4a. Acquire the lock.
  565.         mov     ecx, [ebp+PARTITION.Disk]
  566.         add     ecx, DISK.CacheLock
  567.         call    mutex_lock
  568. ; 4b. Call the lookup function without adding to the cache.
  569.         mov     eax, [.sector_lo]
  570.         mov     edx, [.sector_hi]
  571.         call    cache_lookup_read
  572. ; If not found, go to 5.
  573.         jc      .not_found_in_cache
  574. .found_in_cache:
  575. ; 4c. Copy the data.
  576.         mov     edi, [.buffer]
  577.         mov     esi, ecx
  578.         shl     esi, 9
  579.         add     esi, [ebx+DISKCACHE.data]
  580.         mov     ecx, 512/4
  581.         rep movsd
  582. ; 4d. Release the lock and return success.
  583.         mov     ecx, [ebp+PARTITION.Disk]
  584.         add     ecx, DISK.CacheLock
  585.         call    mutex_unlock
  586. .return:
  587.         xor     eax, eax
  588. .return_eax:
  589.         add     esp, .local_vars_size
  590.         pop     edi esi edx ecx
  591.         pop     ebx
  592.         ret
  593. .not_found_in_cache:
  594. ; 5. Decide whether we need to prefetch further sectors.
  595. ; If so, advance to 6. If not, go to 13.
  596. ; Assume that devices < 3MB are floppies which are slow
  597. ; (ramdisk does not have a cache, so we don't even get here for ramdisk).
  598. ; This is a dirty hack, but the entire function is somewhat hacky. Use fs_read64*.
  599.         mov     eax, [ebp+PARTITION.Disk]
  600.         cmp     dword [eax+DISK.MediaInfo.Capacity+4], 0
  601.         jnz     @f
  602.         cmp     dword [eax+DISK.MediaInfo.Capacity], 3 shl (20-9)
  603.         jb      .floppy
  604. @@:
  605. ; We want to prefetch CACHE_LEGACY_READ_SIZE sectors.
  606. ; 6. Release the lock acquired at step 4a.
  607.         mov     ecx, [ebp+PARTITION.Disk]
  608.         add     ecx, DISK.CacheLock
  609.         call    mutex_unlock
  610. ; 7. Allocate buffer for CACHE_LEGACY_READ_SIZE sectors.
  611.         stdcall kernel_alloc, CACHE_LEGACY_READ_SIZE shl 9
  612. ; If failed, return the corresponding error code.
  613.         test    eax, eax
  614.         jz      .nomemory
  615. ; 8. Create second portion of local variables.
  616.         push    eax eax
  617.         push    CACHE_LEGACY_READ_SIZE
  618. virtual at esp
  619. .local_vars2:
  620. .num_sectors            dd      ?       ; number of sectors left
  621. .current_buffer         dd      ?       ; pointer to data that are currently copying
  622. .allocated_buffer       dd      ?       ; saved at safe place
  623. .local_vars2_size = $ - .local_vars2
  624. end virtual
  625. ; 9. Call the driver to read CACHE_LEGACY_READ_SIZE sectors.
  626.         push    esp     ; numsectors
  627.         push    [.sector_hi+.local_vars2_size+4]        ; startsector
  628.         push    [.sector_lo+.local_vars2_size+8]        ; startsector
  629.         push    eax     ; buffer
  630.         mov     esi, [ebp+PARTITION.Disk]
  631.         mov     al, DISKFUNC.read
  632.         call    disk_call_driver
  633. ; Note: we're ok if at least one sector is read,
  634. ; read error somewhere after that just limits data to be put in cache.
  635.         cmp     [.num_sectors], 0
  636.         jz      .read_error
  637. ; 10. Copy data for the caller.
  638.         mov     esi, [.allocated_buffer]
  639.         mov     edi, [.buffer+.local_vars2_size]
  640.         mov     ecx, 512/4
  641.         rep movsd
  642. ; 11. Store all sectors that were successfully read to the cache.
  643. ; 11a. Acquire the lock.
  644.         mov     ecx, [ebp+PARTITION.Disk]
  645.         add     ecx, DISK.CacheLock
  646.         call    mutex_lock
  647. .store_to_cache:
  648. ; 11b. For each sector, call the lookup function with adding to the cache, if not yet.
  649.         mov     eax, [.sector_lo+.local_vars2_size]
  650.         mov     edx, [.sector_hi+.local_vars2_size]
  651.         call    cache_lookup_write
  652.         test    eax, eax
  653.         jnz     .cache_error
  654. ; 11c. For each sector, copy data, mark the item as not-modified copy of the disk,
  655. ; advance .current_buffer and .sector_hi:.sector_lo to the next sector.
  656.         mov     [esi+CACHE_ITEM.Status], CACHE_ITEM_COPY
  657.         mov     esi, [.current_buffer]
  658.         mov     edi, ecx
  659.         shl     edi, 9
  660.         add     edi, [ebx+DISKCACHE.data]
  661.         mov     ecx, 512/4
  662.         rep movsd
  663.         mov     [.current_buffer], esi
  664.         add     [.sector_lo+.local_vars2_size], 1
  665.         adc     [.sector_hi+.local_vars2_size], 0
  666. ; 11d. Continue the loop at 11b-11c until all sectors are processed.
  667.         dec     [.num_sectors]
  668.         jnz     .store_to_cache
  669. .cache_error:
  670. ; 11e. Release the lock.
  671.         mov     ecx, [ebp+PARTITION.Disk]
  672.         add     ecx, DISK.CacheLock
  673.         call    mutex_unlock
  674. .copy_done:
  675. ; 12. Remove portion of local variables created at step 8,
  676. ; free the buffer allocated at step 7 and return.
  677.         pop     ecx ecx
  678.         stdcall kernel_free
  679.         jmp     .return
  680. .read_error:
  681. ; If no sectors were read, free the buffer allocated at step 7
  682. ; and pass the error to the caller.
  683.         push    eax
  684.         stdcall kernel_free, [.allocated_buffer+4]
  685.         pop     eax
  686.         add     esp, .local_vars2_size
  687.         jmp     .return_eax
  688. .nomemory:
  689.         mov     eax, DISK_STATUS_NO_MEMORY
  690.         jmp     .return_eax
  691. .floppy:
  692. ; We don't want to prefetch anything, just read one sector.
  693. ; We are still holding the lock acquired at step 4a.
  694. ; 13. Call the lookup function adding sector to the cache.
  695.         call    cache_lookup_write
  696.         test    eax, eax
  697.         jnz     .floppy_cache_error
  698. ; 14. Mark the item as empty for the case of read error.
  699.         mov     [esi+CACHE_ITEM.Status], CACHE_ITEM_EMPTY
  700.         push    ecx
  701.  
  702. ; 15. Call the driver to read one sector.
  703.         push    1
  704.         push    esp
  705.         push    edx
  706.         push    [.sector_lo+16]
  707.         shl     ecx, 9
  708.         add     ecx, [ebx+DISKCACHE.data]
  709.         push    ecx
  710.         mov     esi, [ebp+PARTITION.Disk]
  711.         mov     al, DISKFUNC.read
  712.         call    disk_call_driver
  713.         pop     ecx
  714.         dec     ecx
  715.         jnz     .floppy_read_error
  716. ; 16. Get the slot and pointer to the cache item,
  717. ; change the status to not-modified copy of the disk
  718. ; and go to 4c.
  719.         pop     ecx
  720.         lea     esi, [ecx*sizeof.CACHE_ITEM/4]
  721.         shl     esi, 2
  722.         add     esi, [ebx+DISKCACHE.pointer]
  723.         mov     [esi+CACHE_ITEM.Status], CACHE_ITEM_COPY
  724.         jmp     .found_in_cache
  725.  
  726. ; On error at steps 13-15, release the lock
  727. ; and pass the error to the caller.
  728. .floppy_read_error:
  729.         pop     ecx
  730. .floppy_cache_error:
  731.         mov     ecx, [ebp+PARTITION.Disk]
  732.         add     ecx, DISK.CacheLock
  733.         push    eax
  734.         call    mutex_unlock
  735.         pop     eax
  736.         jmp     .return_eax
  737.  
  738. ; This function is intended to replace the old 'hd_write' function when
  739. ; [hdd_appl_data] = 0, so its input/output parameters are the same, except
  740. ; that it can't use the global variables 'hd_error' and 'hdd_appl_data'.
  741. ; in: eax = sector, ebx = buffer, ebp = pointer to PARTITION structure
  742. ; eax is relative to partition start
  743. ; out: eax = error code; 0 = ok
  744. fs_write32_sys:
  745. ; Just call the advanced function.
  746.         push    ecx edx
  747.         xor     edx, edx
  748.         mov     ecx, 1
  749.         call    fs_write64_sys
  750.         pop     edx ecx
  751.         ret
  752.  
  753. ; This function is intended to replace the old 'hd_write' function when
  754. ; [hdd_appl_data] = 1, so its input/output parameters are the same, except
  755. ; that it can't use the global variables 'hd_error' and 'hdd_appl_data'.
  756. ; in: eax = sector, ebx = buffer, ebp = pointer to PARTITION structure
  757. ; eax is relative to partition start
  758. ; out: eax = error code; 0 = ok
  759. fs_write32_app:
  760. ; Just call the advanced function.
  761.         push    ecx edx
  762.         xor     edx, edx
  763.         mov     ecx, 1
  764.         call    fs_write64_app
  765.         pop     edx ecx
  766.         ret
  767.  
  768. ; Lookup for the given sector in the given cache.
  769. ; If the sector is not present, return error.
  770. ; The caller must acquire the cache lock.
  771. ; in: edx:eax = sector
  772. ; in: ebx -> DISKCACHE structure
  773. ; out: CF set if sector is not in cache
  774. ; out: ecx = index in cache
  775. ; out: esi -> sector:status
  776. proc cache_lookup_read
  777.         mov     esi, [ebx+DISKCACHE.pointer]
  778.         add     esi, sizeof.CACHE_ITEM
  779.  
  780.         mov     ecx, 1
  781.  
  782. .hdreadcache:
  783.  
  784.         cmp     [esi+CACHE_ITEM.Status], CACHE_ITEM_EMPTY
  785.         je      .nohdcache
  786.  
  787.         cmp     [esi+CACHE_ITEM.SectorLo], eax
  788.         jne     .nohdcache
  789.         cmp     [esi+CACHE_ITEM.SectorHi], edx
  790.         jne     .nohdcache
  791.         clc
  792.         ret
  793.  
  794. .nohdcache:
  795.  
  796.         add     esi, sizeof.CACHE_ITEM
  797.         inc     ecx
  798.         cmp     ecx, [ebx+DISKCACHE.sad_size]
  799.         jbe     .hdreadcache
  800.         stc
  801.         ret
  802. endp
  803.  
  804. ; Lookup for the given sector in the given cache.
  805. ; If the sector is not present, allocate space for it,
  806. ; possibly flushing data.
  807. ; in: edx:eax = sector
  808. ; in: ebx -> DISKCACHE structure
  809. ; in: ebp -> PARTITION structure
  810. ; out: eax = error code
  811. ; out: ecx = index in cache
  812. ; out: esi -> sector:status
  813. proc cache_lookup_write
  814.         call    cache_lookup_read
  815.         jnc     .return0
  816.         push    edx eax
  817. ;-----------------------------------------------------------
  818. ; find empty or read slot, flush cache if next 12.5% is used by write
  819. ; output : ecx = cache slot
  820. ;-----------------------------------------------------------
  821. ; Note: the code is essentially inherited, so probably
  822. ; no analysis of efficiency were done.
  823. ; However, it works.
  824. .search_again:
  825.         mov     eax, [ebx+DISKCACHE.sad_size]
  826.         mov     ecx, [ebx+DISKCACHE.search_start]
  827.         shr     eax, 3
  828.         lea     esi, [ecx*sizeof.CACHE_ITEM/4]
  829.         shl     esi, 2
  830.         add     esi, [ebx+DISKCACHE.pointer]
  831. .search_for_empty:
  832.         inc     ecx
  833.         add     esi, sizeof.CACHE_ITEM
  834.         cmp     ecx, [ebx+DISKCACHE.sad_size]
  835.         jbe     .inside_cache
  836.         mov     ecx, 1
  837.         mov     esi, [ebx+DISKCACHE.pointer]
  838.         add     esi, sizeof.CACHE_ITEM
  839. .inside_cache:
  840.         cmp     [esi+CACHE_ITEM.Status], CACHE_ITEM_MODIFIED
  841.         jb      .found_slot             ; it's empty or read
  842.         dec     eax
  843.         jnz     .search_for_empty
  844.         stdcall write_cache64, [ebp+PARTITION.Disk] ; no empty slots found, write all
  845.         test    eax, eax
  846.         jne     .found_slot_access_denied
  847.         jmp     .search_again           ; and start again
  848. .found_slot:
  849.         mov     [ebx+DISKCACHE.search_start], ecx
  850.         popd    [esi+CACHE_ITEM.SectorLo]
  851.         popd    [esi+CACHE_ITEM.SectorHi]
  852. .return0:
  853.         xor     eax, eax        ; success
  854.         ret
  855. .found_slot_access_denied:
  856.         add     esp, 8
  857.         ret
  858. endp
  859.  
  860. ; Flush the given cache.
  861. ; The caller must acquire the cache lock.
  862. ; in: ebx -> DISKCACHE
  863. ; in: first argument in stdcall convention -> PARTITION
  864. proc write_cache64
  865. ; 1. Setup stack frame.
  866.         push    esi edi         ; save used registers to be stdcall
  867.         sub     esp, .local_vars_size   ; reserve space for local vars
  868. virtual at esp
  869. .local_vars:
  870. .cache_end      dd      ?       ; item past the end of the cache
  871. .size_left      dd      ?       ; items left to scan
  872. .current_ptr    dd      ?       ; pointer to the current item
  873. ;
  874. ; Write operations are coalesced in chains,
  875. ; one chain describes a sequential interval of sectors,
  876. ; they can be sequential or scattered in the cache.
  877. .sequential     dd      ?
  878. ; boolean variable, 1 if the current chain is sequential in the cache,
  879. ; 0 if additional buffer is needed to perform the operation
  880. .chain_start_pos dd     ?       ; slot of chain start item
  881. .chain_start_ptr dd     ?       ; pointer to chain start item
  882. .chain_size     dd      ?       ; chain size (thanks, C.O.)
  883. .iteration_size dd      ?
  884. ; If the chain size is too large, split the operation to several iterations.
  885. ; This is size in sectors for one iterations.
  886. .iteration_buffer dd    ?       ; temporary buffer for non-sequential chains
  887. .local_vars_size = $ - .local_vars
  888.                 rd      2       ; saved registers
  889.                 dd      ?       ; return address
  890. .disk           dd      ?       ; first argument
  891. end virtual
  892. ; 1. If there is no cache for this disk, nothing to do, just return zero.
  893.         cmp     [ebx+DISKCACHE.pointer], 0
  894.         jz      .return0
  895. ; 2. Prepare for the loop: initialize current pointer and .size_left,
  896. ; calculate .cache_end.
  897.         mov     ecx, [ebx+DISKCACHE.sad_size]
  898.         mov     [.size_left], ecx
  899.         lea     ecx, [ecx*sizeof.CACHE_ITEM/4]
  900.         shl     ecx, 2
  901.         mov     esi, [ebx+DISKCACHE.pointer]
  902.         add     esi, sizeof.CACHE_ITEM
  903.         add     ecx, esi
  904.         mov     [.cache_end], ecx
  905. ; 3. Main loop: go over all items, go to 5 for every modified item.
  906. .look:
  907.         cmp     [esi+CACHE_ITEM.Status], CACHE_ITEM_MODIFIED
  908.         jz      .begin_write
  909. .look_next:
  910.         add     esi, sizeof.CACHE_ITEM
  911.         dec     [.size_left]
  912.         jnz     .look
  913. ; 4. Return success.
  914. .return0:
  915.         xor     eax, eax
  916. .return:
  917.         add     esp, .local_vars_size
  918.         pop     edi esi         ; restore used registers to be stdcall
  919.         ret     4               ; return popping one argument
  920. .begin_write:
  921. ; We have found a modified item.
  922. ; 5. Prepare for chain finding: save the current item, initialize chain variables.
  923.         mov     [.current_ptr], esi
  924. ; Initialize chain as sequential zero-length starting at the current item.
  925.         mov     [.chain_start_ptr], esi
  926.         mov     eax, [ebx+DISKCACHE.sad_size]
  927.         sub     eax, [.size_left]
  928.         inc     eax
  929.         mov     [.chain_start_pos], eax
  930.         mov     [.chain_size], 0
  931.         mov     [.sequential], 1
  932. ; 6. Expand the chain backward.
  933. ; Note: the main loop in step 2 looks for items sequentially,
  934. ; so the previous item is not modified. If the previous sector
  935. ; is present in the cache, it automatically makes the chain scattered.
  936. ; 6a. Calculate sector number: one before the sector for the current item.
  937.         mov     eax, [esi+CACHE_ITEM.SectorLo]
  938.         mov     edx, [esi+CACHE_ITEM.SectorHi]
  939.         sub     eax, 1
  940.         sbb     edx, 0
  941. .find_chain_start:
  942. ; 6b. For each sector where the previous item does not expand the chain,
  943. ; call the lookup function without adding to the cache.
  944.         call    cache_lookup_read
  945. ; 6c. If the sector is not found in cache or is not modified, stop expanding
  946. ; and advance to step 7.
  947.         jc      .found_chain_start
  948.         cmp     [esi+CACHE_ITEM.Status], CACHE_ITEM_MODIFIED
  949.         jnz     .found_chain_start
  950. ; 6d. We have found a new block that expands the chain backwards.
  951. ; It makes the chain non-sequential.
  952. ; Normally, sectors come in sequential blocks, so try to look at previous items
  953. ; before returning to 6b; if there is a sequential block indeed, this saves some
  954. ; time instead of many full-fledged lookups.
  955.         mov     [.sequential], 0
  956.         mov     [.chain_start_pos], ecx
  957. .look_backward:
  958. ; 6e. For each sector, update chain start pos/ptr, decrement sector number,
  959. ; look at the previous item.
  960.         mov     [.chain_start_ptr], esi
  961.         inc     [.chain_size]
  962.         sub     eax, 1
  963.         sbb     edx, 0
  964.         sub     esi, sizeof.CACHE_ITEM
  965. ; If the previous item exists...
  966.         cmp     esi, [ebx+DISKCACHE.pointer]
  967.         jbe     .find_chain_start
  968. ; ...describes the correct sector...
  969.         cmp     [esi+CACHE_ITEM.SectorLo], eax
  970.         jnz     .find_chain_start
  971.         cmp     [esi+CACHE_ITEM.SectorHi], edx
  972.         jnz     .find_chain_start
  973. ; ...and is modified...
  974.         cmp     [esi+CACHE_ITEM.Status], CACHE_ITEM_MODIFIED
  975.         jnz     .found_chain_start
  976. ; ...expand the chain one sector backwards and continue the loop at 6e.
  977. ; Otherwise, advance to step 7 if the previous item describes the correct sector
  978. ; but is not modified, and return to step 6b otherwise.
  979.         dec     [.chain_start_pos]
  980.         jmp     .look_backward
  981. .found_chain_start:
  982. ; 7. Expand the chain forward.
  983. ; 7a. Prepare for the loop at 7b:
  984. ; set esi = pointer to current item, edx:eax = current sector.
  985.         mov     esi, [.current_ptr]
  986.         mov     eax, [esi+CACHE_ITEM.SectorLo]
  987.         mov     edx, [esi+CACHE_ITEM.SectorHi]
  988. .look_forward:
  989. ; 7b. First, look at the next item. If it describes the next sector:
  990. ; if it is modified, expand the chain with that sector and continue this step,
  991. ; if it is not modified, the chain is completed, so advance to step 8.
  992.         inc     [.chain_size]
  993.         add     eax, 1
  994.         adc     edx, 0
  995.         add     esi, sizeof.CACHE_ITEM
  996.         cmp     esi, [.cache_end]
  997.         jae     .find_chain_end
  998.         cmp     [esi+CACHE_ITEM.SectorLo], eax
  999.         jnz     .find_chain_end
  1000.         cmp     [esi+CACHE_ITEM.SectorHi], edx
  1001.         jnz     .find_chain_end
  1002.         cmp     [esi+CACHE_ITEM.Status], CACHE_ITEM_MODIFIED
  1003.         jnz     .found_chain_end
  1004.         jmp     .look_forward
  1005. .find_chain_end:
  1006. ; 7c. Otherwise, call the lookup function.
  1007.         call    cache_lookup_read
  1008. ; 7d. If the next sector is present in the cache and is modified,
  1009. ; mark the chain as non-sequential and continue to step 7b.
  1010.         jc      .found_chain_end
  1011.         cmp     [esi+CACHE_ITEM.Status], CACHE_ITEM_MODIFIED
  1012.         jnz     .found_chain_end
  1013.         mov     [.sequential], 0
  1014.         jmp     .look_forward
  1015. .found_chain_end:
  1016. ; 8. Decide whether the chain is sequential or scattered.
  1017. ; Advance to step 9 for sequential chains, go to step 10 for scattered chains.
  1018.         cmp     [.sequential], 0
  1019.         jz      .write_non_sequential
  1020. .write_sequential:
  1021. ; 9. Write a sequential chain to disk.
  1022. ; 9a. Pass the entire chain to the driver.
  1023.         mov     eax, [.chain_start_ptr]
  1024.         mov     edx, [.chain_start_pos]
  1025.         shl     edx, 9
  1026.         add     edx, [ebx+DISKCACHE.data]
  1027.         lea     ecx, [.chain_size]
  1028.         push    ecx     ; numsectors
  1029.         pushd   [eax+CACHE_ITEM.SectorHi]       ; startsector
  1030.         pushd   [eax+CACHE_ITEM.SectorLo]       ; startsector
  1031.         push    edx     ; buffer
  1032.         mov     esi, [ebp+PARTITION.Disk]
  1033.         mov     al, DISKFUNC.write
  1034.         call    disk_call_driver
  1035. ; 9b. If failed, pass the error code to the driver.
  1036.         test    eax, eax
  1037.         jnz     .return
  1038. ; 9c. If succeeded, mark all sectors in the chain as not-modified,
  1039. ; advance current item and number of items left to skip the chain.
  1040.         mov     esi, [.current_ptr]
  1041.         mov     eax, [.chain_size]
  1042.         sub     [.size_left], eax
  1043. @@:
  1044.         mov     [esi+CACHE_ITEM.Status], CACHE_ITEM_COPY
  1045.         add     esi, sizeof.CACHE_ITEM
  1046.         dec     eax
  1047.         jnz     @b
  1048. ; 9d. Continue the main loop at step 2 if there are more sectors.
  1049. ; Return success otherwise.
  1050.         cmp     [.size_left], 0
  1051.         jnz     .look
  1052.         jmp     .return0
  1053. .write_non_sequential:
  1054. ; Write a non-sequential chain to the disk.
  1055. ; 10. Allocate a temporary buffer.
  1056. ; Use [.chain_size] sectors, but
  1057. ; not greater than CACHE_MAX_ALLOC_SIZE bytes
  1058. ; and not greater than half of free memory.
  1059.         mov     eax, [pg_data.pages_free]
  1060.         shr     eax, 1
  1061.         jz      .nomemory
  1062.         cmp     eax, CACHE_MAX_ALLOC_SIZE shr 12
  1063.         jbe     @f
  1064.         mov     eax, CACHE_MAX_ALLOC_SIZE shr 12
  1065. @@:
  1066.         shl     eax, 12 - 9
  1067.         cmp     eax, [.chain_size]
  1068.         jbe     @f
  1069.         mov     eax, [.chain_size]
  1070. @@:
  1071.         mov     [.iteration_size], eax
  1072.         shl     eax, 9
  1073.         stdcall kernel_alloc, eax
  1074.         test    eax, eax
  1075.         jz      .nomemory
  1076.         mov     [.iteration_buffer], eax
  1077. .write_non_sequential_iteration:
  1078. ; 11. Split the chain so that each iteration fits in the allocated buffer.
  1079. ; Iteration size is the minimum of chain size and allocated size.
  1080.         mov     eax, [.chain_size]
  1081.         cmp     eax, [.iteration_size]
  1082.         jae     @f
  1083.         mov     [.iteration_size], eax
  1084. @@:
  1085. ; 12. Prepare arguments for the driver.
  1086.         mov     esi, [.chain_start_ptr]
  1087.         mov     edi, [.iteration_buffer]
  1088.         push    [.iteration_size]
  1089.         push    esp     ; numsectors
  1090.         push    [esi+CACHE_ITEM.SectorHi]       ; startsector
  1091.         push    [esi+CACHE_ITEM.SectorLo]       ; startsector
  1092.         push    edi     ; buffer
  1093. ; 13. Copy data from the cache to the temporary buffer,
  1094. ; advancing chain_start pos/ptr and marking sectors as not-modified.
  1095. ; 13a. Prepare for the loop: push number of sectors to process.
  1096.         push    [.iteration_size+20]    ; temporary variable
  1097. .copy_loop:
  1098. ; 13b. For each sector, copy the data.
  1099. ; Note that edi is advanced automatically.
  1100.         mov     esi, [.chain_start_pos+24]
  1101.         shl     esi, 9
  1102.         add     esi, [ebx+DISKCACHE.data]
  1103.         mov     ecx, 512/4
  1104.         rep movsd
  1105. ; 13c. Mark the item as not-modified.
  1106.         mov     esi, [.chain_start_ptr+24]
  1107.         mov     [esi+CACHE_ITEM.Status], CACHE_ITEM_COPY
  1108. ; 13d. Check whether the next sector continues the chain.
  1109. ; If so, advance to 13e. Otherwise, go to 13f.
  1110.         mov     eax, [esi+CACHE_ITEM.SectorLo]
  1111.         mov     edx, [esi+CACHE_ITEM.SectorHi]
  1112.         add     esi, sizeof.CACHE_ITEM
  1113.         add     eax, 1
  1114.         adc     edx, 0
  1115.         cmp     esi, [.cache_end+24]
  1116.         jae     .no_forward
  1117.         cmp     [esi+CACHE_ITEM.SectorLo], eax
  1118.         jnz     .no_forward
  1119.         cmp     [esi+CACHE_ITEM.SectorHi], edx
  1120.         jnz     .no_forward
  1121. ; 13e. Increment position/pointer to the chain and
  1122. ; continue the loop.
  1123.         inc     [.chain_start_pos+24]
  1124.         mov     [.chain_start_ptr+24], esi
  1125.         dec     dword [esp]
  1126.         jnz     .copy_loop
  1127.         jmp     .copy_done
  1128. .no_forward:
  1129. ; 13f. Call the lookup function without adding to the cache.
  1130. ; Update position/pointer with returned value.
  1131. ; Note: for the last sector in the chain, ecx/esi may contain
  1132. ; garbage; we are not going to use them in this case.
  1133.         call    cache_lookup_read
  1134.         mov     [.chain_start_pos+24], ecx
  1135.         mov     [.chain_start_ptr+24], esi
  1136.         dec     dword [esp]
  1137.         jnz     .copy_loop
  1138. .copy_done:
  1139. ; 13g. Restore the stack after 13a.
  1140.         pop     ecx
  1141. ; 14. Call the driver.
  1142.         mov     esi, [ebp+PARTITION.Disk]
  1143.         mov     al, DISKFUNC.write
  1144.         call    disk_call_driver
  1145.         pop     ecx     ; numsectors
  1146. ; 15. If the driver has returned an error, free the buffer allocated at step 10
  1147. ; and pass the error to the caller.
  1148. ; Otherwise, remove the processed part from the chain and continue iterations
  1149. ; starting in step 11 if there are more data to process.
  1150.         test    eax, eax
  1151.         jnz     .nonsequential_error
  1152.         sub     [.chain_size], ecx
  1153.         jnz     .write_non_sequential_iteration
  1154. ; 16. The chain is written. Free the temporary buffer
  1155. ; and continue the loop at step 2.
  1156.         stdcall kernel_free, [.iteration_buffer]
  1157.         mov     esi, [.current_ptr]
  1158.         jmp     .look_next
  1159. .nonsequential_error:
  1160.         push    eax
  1161.         stdcall kernel_free, [.iteration_buffer+4]
  1162.         pop     eax
  1163.         jmp     .return
  1164. .nomemory:
  1165.         mov     eax, DISK_STATUS_NO_MEMORY
  1166.         jmp     .return
  1167. endp
  1168.  
  1169. ; This internal function is called from disk_add to initialize the caching for
  1170. ; a new DISK.
  1171. ; The algorithm is inherited from getcache.inc: take 1/32 part of the available
  1172. ; physical memory, round down to 8 pages, limit by 128K from below and by 1M
  1173. ; from above. Reserve 1/8 part of the cache for system data and 7/8 for app
  1174. ; data.
  1175. ; After the size is calculated, but before the cache is allocated, the device
  1176. ; driver can adjust the size. In particular, setting size to zero disables
  1177. ; caching: there is no sense in a cache for a ramdisk. In fact, such action
  1178. ; is most useful example of a non-trivial adjustment.
  1179. ; esi = pointer to DISK structure
  1180. disk_init_cache:
  1181. ; 1. Calculate the suggested cache size.
  1182. ; 1a. Get the size of free physical memory in pages.
  1183.         mov     eax, [pg_data.pages_free]
  1184. ; 1b. Use the value to calculate the size.
  1185.         shl     eax, 12 - 5     ; 1/32 of it in bytes
  1186.         and     eax, -8*4096    ; round down to the multiple of 8 pages
  1187. ; 1c. Force lower and upper limits.
  1188.         cmp     eax, 1024*1024
  1189.         jb      @f
  1190.         mov     eax, 1024*1024
  1191. @@:
  1192.         cmp     eax, 128*1024
  1193.         ja      @f
  1194.         mov     eax, 128*1024
  1195. @@:
  1196. ; 1d. Give a chance to the driver to adjust the size.
  1197.         push    eax
  1198.         mov     al, DISKFUNC.adjust_cache_size
  1199.         call    disk_call_driver
  1200. ; Cache size calculated.
  1201.         mov     [esi+DISK.cache_size], eax
  1202.         test    eax, eax
  1203.         jz      .nocache
  1204. ; 2. Allocate memory for the cache.
  1205. ; 2a. Call the allocator.
  1206.         stdcall kernel_alloc, eax
  1207.         test    eax, eax
  1208.         jnz     @f
  1209. ; 2b. If it failed, say a message and return with eax = 0.
  1210.         dbgstr 'no memory for disk cache'
  1211.         jmp     .nothing
  1212. @@:
  1213. ; 3. Fill two DISKCACHE structures.
  1214.         mov     [esi+DISK.SysCache.pointer], eax
  1215.         lea     ecx, [esi+DISK.CacheLock]
  1216.         call    mutex_init
  1217. ; The following code is inherited from getcache.inc.
  1218.         mov     edx, [esi+DISK.SysCache.pointer]
  1219.         and     [esi+DISK.SysCache.search_start], 0
  1220.         and     [esi+DISK.AppCache.search_start], 0
  1221.         mov     eax, [esi+DISK.cache_size]
  1222.         shr     eax, 3
  1223.         mov     [esi+DISK.SysCache.data_size], eax
  1224.         add     edx, eax
  1225.         imul    eax, 7
  1226.         mov     [esi+DISK.AppCache.data_size], eax
  1227.         mov     [esi+DISK.AppCache.pointer], edx
  1228.  
  1229.         mov     eax, [esi+DISK.SysCache.data_size]
  1230.         push    ebx
  1231.         call    calculate_for_hd64
  1232.         pop     ebx
  1233.         add     eax, [esi+DISK.SysCache.pointer]
  1234.         mov     [esi+DISK.SysCache.data], eax
  1235.         mov     [esi+DISK.SysCache.sad_size], ecx
  1236.  
  1237.         push    edi
  1238.         mov     edi, [esi+DISK.SysCache.pointer]
  1239.         lea     ecx, [(ecx+1)*3]
  1240.         xor     eax, eax
  1241.         rep stosd
  1242.         pop     edi
  1243.  
  1244.         mov     eax, [esi+DISK.AppCache.data_size]
  1245.         push    ebx
  1246.         call    calculate_for_hd64
  1247.         pop     ebx
  1248.         add     eax, [esi+DISK.AppCache.pointer]
  1249.         mov     [esi+DISK.AppCache.data], eax
  1250.         mov     [esi+DISK.AppCache.sad_size], ecx
  1251.  
  1252.         push    edi
  1253.         mov     edi, [esi+DISK.AppCache.pointer]
  1254.         lea     ecx, [(ecx+1)*3]
  1255.         xor     eax, eax
  1256.         rep stosd
  1257.         pop     edi
  1258.  
  1259. ; 4. Return with nonzero al.
  1260.         mov     al, 1
  1261. ; 5. Return.
  1262. .nothing:
  1263.         ret
  1264. ; No caching is required for this driver. Zero cache pointers and return with
  1265. ; nonzero al.
  1266. .nocache:
  1267.         mov     [esi+DISK.SysCache.pointer], eax
  1268.         mov     [esi+DISK.AppCache.pointer], eax
  1269.         mov     al, 1
  1270.         ret
  1271.  
  1272. calculate_for_hd64:
  1273.         push    eax
  1274.         mov     ebx, eax
  1275.         shr     eax, 9
  1276.         lea     eax, [eax*3]
  1277.         shl     eax, 2
  1278.         sub     ebx, eax
  1279.         shr     ebx, 9
  1280.         mov     ecx, ebx
  1281.         shl     ebx, 9
  1282.         pop     eax
  1283.         sub     eax, ebx
  1284.         dec     ecx
  1285.         ret
  1286.  
  1287.  
  1288. ; This internal function is called from disk_media_dereference to free the
  1289. ; allocated cache, if there is one.
  1290. ; esi = pointer to DISK structure
  1291. disk_free_cache:
  1292. ; The algorithm is straightforward.
  1293.         mov     eax, [esi+DISK.SysCache.pointer]
  1294.         test    eax, eax
  1295.         jz      .nothing
  1296.         stdcall kernel_free, eax
  1297. .nothing:
  1298.         ret
  1299.  
  1300. ; This function flushes all modified data from both caches for the given DISK.
  1301. ; esi = pointer to DISK
  1302. disk_sync:
  1303. ; The algorithm is straightforward.
  1304.         cmp     [esi+DISK.SysCache.pointer], 0
  1305.         jz      .nothing
  1306.         lea     ecx, [esi+DISK.CacheLock]
  1307.         call    mutex_lock
  1308.         push    ebx
  1309.         push    esi     ; for second write_cache64
  1310.         push    esi     ; for first write_cache64
  1311.         lea     ebx, [esi+DISK.SysCache]
  1312.         call    write_cache64
  1313.         add     ebx, DISK.AppCache - DISK.SysCache
  1314.         call    write_cache64
  1315.         pop     ebx
  1316.         lea     ecx, [esi+DISK.CacheLock]
  1317.         call    mutex_unlock
  1318. .nothing:
  1319.         mov     al, DISKFUNC.flush
  1320.         call    disk_call_driver
  1321.         ret
  1322.