Subversion Repositories Kolibri OS

Rev

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