Subversion Repositories Kolibri OS

Rev

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