Subversion Repositories Kolibri OS

Rev

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-2015. All rights reserved. ;;
  4. ;; Distributed under terms of the GNU General Public License    ;;
  5. ;;                                                              ;;
  6. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  7.  
  8. $Revision: 5363 $
  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     ecx, [ebp+PARTITION.Disk]
  627.         cmp     dword [ecx+DISK.MediaInfo.Capacity+4], 0
  628.         jnz     @f
  629.         cmp     dword [ecx+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.         mov     ecx, [ebx+DISKCACHE.sector_size_log]
  759.         jmp     .found_in_cache
  760.  
  761. ; On error at steps 13-14, release the lock
  762. ; and pass the error to the caller.
  763. .floppy_read_error:
  764.         pop     ecx
  765. .floppy_cache_error:
  766.         mov     ecx, [ebp+PARTITION.Disk]
  767.         add     ecx, DISK.CacheLock
  768.         push    eax
  769.         call    mutex_unlock
  770.         pop     eax
  771.         jmp     .return_eax
  772.  
  773. ; This function is intended to replace the old 'hd_write' function when
  774. ; [hdd_appl_data] = 0, so its input/output parameters are the same, except
  775. ; that it can't use the global variables 'hd_error' and 'hdd_appl_data'.
  776. ; in: eax = sector, ebx = buffer, ebp = pointer to PARTITION structure
  777. ; eax is relative to partition start
  778. ; out: eax = error code; 0 = ok
  779. fs_write32_sys:
  780. ; Just call the advanced function.
  781.         push    ecx edx
  782.         xor     edx, edx
  783.         mov     ecx, 1
  784.         call    fs_write64_sys
  785.         pop     edx ecx
  786.         ret
  787.  
  788. ; This function is intended to replace the old 'hd_write' function when
  789. ; [hdd_appl_data] = 1, so its input/output parameters are the same, except
  790. ; that it can't use the global variables 'hd_error' and 'hdd_appl_data'.
  791. ; in: eax = sector, ebx = buffer, ebp = pointer to PARTITION structure
  792. ; eax is relative to partition start
  793. ; out: eax = error code; 0 = ok
  794. fs_write32_app:
  795. ; Just call the advanced function.
  796.         push    ecx edx
  797.         xor     edx, edx
  798.         mov     ecx, 1
  799.         call    fs_write64_app
  800.         pop     edx ecx
  801.         ret
  802.  
  803. ; Lookup for the given sector in the given cache.
  804. ; If the sector is not present, return error.
  805. ; The caller must acquire the cache lock.
  806. ; in: edx:eax = sector
  807. ; in: ebx -> DISKCACHE structure
  808. ; out: CF set if sector is not in cache
  809. ; out: ecx = sector_size_log
  810. ; out: esi -> sector:status
  811. ; out: edi -> sector data
  812. proc cache_lookup_read
  813.         mov     esi, [ebx+DISKCACHE.pointer]
  814.         add     esi, sizeof.CACHE_ITEM
  815.  
  816.         mov     edi, 1
  817.  
  818. .hdreadcache:
  819.  
  820.         cmp     [esi+CACHE_ITEM.Status], CACHE_ITEM_EMPTY
  821.         je      .nohdcache
  822.  
  823.         cmp     [esi+CACHE_ITEM.SectorLo], eax
  824.         jne     .nohdcache
  825.         cmp     [esi+CACHE_ITEM.SectorHi], edx
  826.         jne     .nohdcache
  827.         mov     ecx, [ebx+DISKCACHE.sector_size_log]
  828.         shl     edi, cl
  829.         add     edi, [ebx+DISKCACHE.data]
  830.         clc
  831.         ret
  832.  
  833. .nohdcache:
  834.  
  835.         add     esi, sizeof.CACHE_ITEM
  836.         inc     edi
  837.         cmp     edi, [ebx+DISKCACHE.sad_size]
  838.         jbe     .hdreadcache
  839.         stc
  840.         ret
  841. endp
  842.  
  843. ; Lookup for the given sector in the given cache.
  844. ; If the sector is not present, allocate space for it,
  845. ; possibly flushing data.
  846. ; in: edx:eax = sector
  847. ; in: ebx -> DISKCACHE structure
  848. ; in: ebp -> PARTITION structure
  849. ; out: eax = error code
  850. ; out: esi -> sector:status
  851. ; out: edi -> sector data
  852. proc cache_lookup_write
  853.         call    cache_lookup_read
  854.         jnc     .return0
  855.         push    edx eax
  856. ;-----------------------------------------------------------
  857. ; find empty or read slot, flush cache if next 12.5% is used by write
  858. ; output : ecx = cache slot
  859. ;-----------------------------------------------------------
  860. ; Note: the code is essentially inherited, so probably
  861. ; no analysis of efficiency were done.
  862. ; However, it works.
  863. .search_again:
  864.         mov     eax, [ebx+DISKCACHE.sad_size]
  865.         mov     ecx, [ebx+DISKCACHE.search_start]
  866.         shr     eax, 3
  867.         lea     esi, [ecx*sizeof.CACHE_ITEM/4]
  868.         shl     esi, 2
  869.         add     esi, [ebx+DISKCACHE.pointer]
  870. .search_for_empty:
  871.         inc     ecx
  872.         add     esi, sizeof.CACHE_ITEM
  873.         cmp     ecx, [ebx+DISKCACHE.sad_size]
  874.         jbe     .inside_cache
  875.         mov     ecx, 1
  876.         mov     esi, [ebx+DISKCACHE.pointer]
  877.         add     esi, sizeof.CACHE_ITEM
  878. .inside_cache:
  879.         cmp     [esi+CACHE_ITEM.Status], CACHE_ITEM_MODIFIED
  880.         jb      .found_slot             ; it's empty or read
  881.         dec     eax
  882.         jnz     .search_for_empty
  883.         stdcall write_cache64, [ebp+PARTITION.Disk] ; no empty slots found, write all
  884.         test    eax, eax
  885.         jne     .found_slot_access_denied
  886.         jmp     .search_again           ; and start again
  887. .found_slot:
  888.         mov     [ebx+DISKCACHE.search_start], ecx
  889.         popd    [esi+CACHE_ITEM.SectorLo]
  890.         popd    [esi+CACHE_ITEM.SectorHi]
  891.         mov     [esi+CACHE_ITEM.Status], CACHE_ITEM_EMPTY
  892.         mov     edi, ecx
  893.         mov     ecx, [ebx+DISKCACHE.sector_size_log]
  894.         shl     edi, cl
  895.         add     edi, [ebx+DISKCACHE.data]
  896. .return0:
  897.         xor     eax, eax        ; success
  898.         ret
  899. .found_slot_access_denied:
  900.         add     esp, 8
  901.         ret
  902. endp
  903.  
  904. ; Flush the given cache.
  905. ; The caller must acquire the cache lock.
  906. ; in: ebx -> DISKCACHE
  907. ; in: first argument in stdcall convention -> PARTITION
  908. proc write_cache64
  909. ; 1. Setup stack frame.
  910.         push    esi edi         ; save used registers to be stdcall
  911.         sub     esp, .local_vars_size   ; reserve space for local vars
  912. virtual at esp
  913. .local_vars:
  914. .cache_end      dd      ?       ; item past the end of the cache
  915. .size_left      dd      ?       ; items left to scan
  916. .current_ptr    dd      ?       ; pointer to the current item
  917. ;
  918. ; Write operations are coalesced in chains,
  919. ; one chain describes a sequential interval of sectors,
  920. ; they can be sequential or scattered in the cache.
  921. .sequential     dd      ?
  922. ; boolean variable, 1 if the current chain is sequential in the cache,
  923. ; 0 if additional buffer is needed to perform the operation
  924. .chain_start_pos dd     ?       ; data of chain start item
  925. .chain_start_ptr dd     ?       ; pointer to chain start item
  926. .chain_size     dd      ?       ; chain size (thanks, C.O.)
  927. .iteration_size dd      ?
  928. ; If the chain size is too large, split the operation to several iterations.
  929. ; This is size in sectors for one iterations.
  930. .iteration_buffer dd    ?       ; temporary buffer for non-sequential chains
  931. .local_vars_size = $ - .local_vars
  932.                 rd      2       ; saved registers
  933.                 dd      ?       ; return address
  934. .disk           dd      ?       ; first argument
  935. end virtual
  936. ; 1. If there is no cache for this disk, nothing to do, just return zero.
  937.         cmp     [ebx+DISKCACHE.pointer], 0
  938.         jz      .return0
  939. ; 2. Prepare for the loop: initialize current pointer and .size_left,
  940. ; calculate .cache_end.
  941.         mov     ecx, [ebx+DISKCACHE.sad_size]
  942.         mov     [.size_left], ecx
  943.         lea     ecx, [ecx*sizeof.CACHE_ITEM/4]
  944.         shl     ecx, 2
  945.         mov     esi, [ebx+DISKCACHE.pointer]
  946.         add     esi, sizeof.CACHE_ITEM
  947.         add     ecx, esi
  948.         mov     [.cache_end], ecx
  949. ; 3. Main loop: go over all items, go to 5 for every modified item.
  950. .look:
  951.         cmp     [esi+CACHE_ITEM.Status], CACHE_ITEM_MODIFIED
  952.         jz      .begin_write
  953. .look_next:
  954.         add     esi, sizeof.CACHE_ITEM
  955.         dec     [.size_left]
  956.         jnz     .look
  957. ; 4. Return success.
  958. .return0:
  959.         xor     eax, eax
  960. .return:
  961.         add     esp, .local_vars_size
  962.         pop     edi esi         ; restore used registers to be stdcall
  963.         ret     4               ; return popping one argument
  964. .begin_write:
  965. ; We have found a modified item.
  966. ; 5. Prepare for chain finding: save the current item, initialize chain variables.
  967.         mov     [.current_ptr], esi
  968. ; Initialize chain as sequential zero-length starting at the current item.
  969.         mov     [.chain_start_ptr], esi
  970.         mov     eax, [ebx+DISKCACHE.sad_size]
  971.         sub     eax, [.size_left]
  972.         inc     eax
  973.         mov     ecx, [ebx+DISKCACHE.sector_size_log]
  974.         shl     eax, cl
  975.         add     eax, [ebx+DISKCACHE.data]
  976.         mov     [.chain_start_pos], eax
  977.         mov     [.chain_size], 0
  978.         mov     [.sequential], 1
  979. ; 6. Expand the chain backward.
  980. ; Note: the main loop in step 2 looks for items sequentially,
  981. ; so the previous item is not modified. If the previous sector
  982. ; is present in the cache, it automatically makes the chain scattered.
  983. ; 6a. Calculate sector number: one before the sector for the current item.
  984.         mov     eax, [esi+CACHE_ITEM.SectorLo]
  985.         mov     edx, [esi+CACHE_ITEM.SectorHi]
  986.         sub     eax, 1
  987.         sbb     edx, 0
  988. .find_chain_start:
  989. ; 6b. For each sector where the previous item does not expand the chain,
  990. ; call the lookup function without adding to the cache.
  991.         call    cache_lookup_read
  992. ; 6c. If the sector is not found in cache or is not modified, stop expanding
  993. ; and advance to step 7.
  994.         jc      .found_chain_start
  995.         cmp     [esi+CACHE_ITEM.Status], CACHE_ITEM_MODIFIED
  996.         jnz     .found_chain_start
  997. ; 6d. We have found a new block that expands the chain backwards.
  998. ; It makes the chain non-sequential.
  999. ; Normally, sectors come in sequential blocks, so try to look at previous items
  1000. ; before returning to 6b; if there is a sequential block indeed, this saves some
  1001. ; time instead of many full-fledged lookups.
  1002.         mov     [.sequential], 0
  1003.         mov     [.chain_start_pos], edi
  1004. .look_backward:
  1005. ; 6e. For each sector, update chain start pos/ptr, decrement sector number,
  1006. ; look at the previous item.
  1007.         mov     [.chain_start_ptr], esi
  1008.         inc     [.chain_size]
  1009.         sub     eax, 1
  1010.         sbb     edx, 0
  1011.         sub     esi, sizeof.CACHE_ITEM
  1012. ; If the previous item exists...
  1013.         cmp     esi, [ebx+DISKCACHE.pointer]
  1014.         jbe     .find_chain_start
  1015. ; ...describes the correct sector...
  1016.         cmp     [esi+CACHE_ITEM.SectorLo], eax
  1017.         jnz     .find_chain_start
  1018.         cmp     [esi+CACHE_ITEM.SectorHi], edx
  1019.         jnz     .find_chain_start
  1020. ; ...and is modified...
  1021.         cmp     [esi+CACHE_ITEM.Status], CACHE_ITEM_MODIFIED
  1022.         jnz     .found_chain_start
  1023. ; ...expand the chain one sector backwards and continue the loop at 6e.
  1024. ; Otherwise, advance to step 7 if the previous item describes the correct sector
  1025. ; but is not modified, and return to step 6b otherwise.
  1026.         mov     edi, 1
  1027.         shl     edi, cl
  1028.         sub     [.chain_start_pos], edi
  1029.         jmp     .look_backward
  1030. .found_chain_start:
  1031. ; 7. Expand the chain forward.
  1032. ; 7a. Prepare for the loop at 7b:
  1033. ; set esi = pointer to current item, edx:eax = current sector.
  1034.         mov     esi, [.current_ptr]
  1035.         mov     eax, [esi+CACHE_ITEM.SectorLo]
  1036.         mov     edx, [esi+CACHE_ITEM.SectorHi]
  1037. .look_forward:
  1038. ; 7b. First, look at the next item. If it describes the next sector:
  1039. ; if it is modified, expand the chain with that sector and continue this step,
  1040. ; if it is not modified, the chain is completed, so advance to step 8.
  1041.         inc     [.chain_size]
  1042.         add     eax, 1
  1043.         adc     edx, 0
  1044.         add     esi, sizeof.CACHE_ITEM
  1045.         cmp     esi, [.cache_end]
  1046.         jae     .find_chain_end
  1047.         cmp     [esi+CACHE_ITEM.SectorLo], eax
  1048.         jnz     .find_chain_end
  1049.         cmp     [esi+CACHE_ITEM.SectorHi], edx
  1050.         jnz     .find_chain_end
  1051.         cmp     [esi+CACHE_ITEM.Status], CACHE_ITEM_MODIFIED
  1052.         jnz     .found_chain_end
  1053.         jmp     .look_forward
  1054. .find_chain_end:
  1055. ; 7c. Otherwise, call the lookup function.
  1056.         call    cache_lookup_read
  1057. ; 7d. If the next sector is present in the cache and is modified,
  1058. ; mark the chain as non-sequential and continue to step 7b.
  1059.         jc      .found_chain_end
  1060.         cmp     [esi+CACHE_ITEM.Status], CACHE_ITEM_MODIFIED
  1061.         jnz     .found_chain_end
  1062.         mov     [.sequential], 0
  1063.         jmp     .look_forward
  1064. .found_chain_end:
  1065. ; 8. Decide whether the chain is sequential or scattered.
  1066. ; Advance to step 9 for sequential chains, go to step 10 for scattered chains.
  1067.         cmp     [.sequential], 0
  1068.         jz      .write_non_sequential
  1069. .write_sequential:
  1070. ; 9. Write a sequential chain to disk.
  1071. ; 9a. Pass the entire chain to the driver.
  1072.         mov     eax, [.chain_start_ptr]
  1073.         lea     ecx, [.chain_size]
  1074.         push    ecx     ; numsectors
  1075.         pushd   [eax+CACHE_ITEM.SectorHi]       ; startsector
  1076.         pushd   [eax+CACHE_ITEM.SectorLo]       ; startsector
  1077.         push    [.chain_start_pos+12]           ; buffer
  1078.         mov     esi, [ebp+PARTITION.Disk]
  1079.         mov     al, DISKFUNC.write
  1080.         call    disk_call_driver
  1081. ; 9b. If failed, pass the error code to the driver.
  1082.         test    eax, eax
  1083.         jnz     .return
  1084. ; 9c. If succeeded, mark all sectors in the chain as not-modified,
  1085. ; advance current item and number of items left to skip the chain.
  1086.         mov     esi, [.current_ptr]
  1087.         mov     eax, [.chain_size]
  1088.         sub     [.size_left], eax
  1089. @@:
  1090.         mov     [esi+CACHE_ITEM.Status], CACHE_ITEM_COPY
  1091.         add     esi, sizeof.CACHE_ITEM
  1092.         dec     eax
  1093.         jnz     @b
  1094. ; 9d. Continue the main loop at step 2 if there are more sectors.
  1095. ; Return success otherwise.
  1096.         cmp     [.size_left], 0
  1097.         jnz     .look
  1098.         jmp     .return0
  1099. .write_non_sequential:
  1100. ; Write a non-sequential chain to the disk.
  1101. ; 10. Allocate a temporary buffer.
  1102. ; Use [.chain_size] sectors, but
  1103. ; not greater than CACHE_MAX_ALLOC_SIZE bytes
  1104. ; and not greater than half of free memory.
  1105.         mov     eax, [pg_data.pages_free]
  1106.         shr     eax, 1
  1107.         jz      .nomemory
  1108.         cmp     eax, CACHE_MAX_ALLOC_SIZE shr 12
  1109.         jbe     @f
  1110.         mov     eax, CACHE_MAX_ALLOC_SIZE shr 12
  1111. @@:
  1112.         shl     eax, 12
  1113.         shr     eax, cl
  1114.         jz      .nomemory
  1115.         cmp     eax, [.chain_size]
  1116.         jbe     @f
  1117.         mov     eax, [.chain_size]
  1118. @@:
  1119.         mov     [.iteration_size], eax
  1120.         shl     eax, cl
  1121.         stdcall kernel_alloc, eax
  1122.         test    eax, eax
  1123.         jz      .nomemory
  1124.         mov     [.iteration_buffer], eax
  1125. .write_non_sequential_iteration:
  1126. ; 11. Split the chain so that each iteration fits in the allocated buffer.
  1127. ; Iteration size is the minimum of chain size and allocated size.
  1128.         mov     eax, [.chain_size]
  1129.         cmp     eax, [.iteration_size]
  1130.         jae     @f
  1131.         mov     [.iteration_size], eax
  1132. @@:
  1133. ; 12. Prepare arguments for the driver.
  1134.         mov     esi, [.chain_start_ptr]
  1135.         mov     edi, [.iteration_buffer]
  1136.         push    [.iteration_size]
  1137.         push    esp     ; numsectors
  1138.         push    [esi+CACHE_ITEM.SectorHi]       ; startsector
  1139.         push    [esi+CACHE_ITEM.SectorLo]       ; startsector
  1140.         push    edi     ; buffer
  1141. ; 13. Copy data from the cache to the temporary buffer,
  1142. ; advancing chain_start pos/ptr and marking sectors as not-modified.
  1143. ; 13a. Prepare for the loop: push number of sectors to process.
  1144.         push    [.iteration_size+20]    ; temporary variable
  1145. .copy_loop:
  1146. ; 13b. For each sector, copy the data.
  1147. ; Note that edi is advanced automatically.
  1148.         mov     esi, [.chain_start_pos+24]
  1149.         mov     ecx, [ebx+DISKCACHE.sector_size_log]
  1150.         mov     eax, 1
  1151.         shl     eax, cl
  1152.         mov     ecx, eax
  1153.         shr     ecx, 2
  1154.         rep movsd
  1155.         mov     ecx, eax ; keep for 13e
  1156. ; 13c. Mark the item as not-modified.
  1157.         mov     esi, [.chain_start_ptr+24]
  1158.         mov     [esi+CACHE_ITEM.Status], CACHE_ITEM_COPY
  1159. ; 13d. Check whether the next sector continues the chain.
  1160. ; If so, advance to 13e. Otherwise, go to 13f.
  1161.         mov     eax, [esi+CACHE_ITEM.SectorLo]
  1162.         mov     edx, [esi+CACHE_ITEM.SectorHi]
  1163.         add     esi, sizeof.CACHE_ITEM
  1164.         add     eax, 1
  1165.         adc     edx, 0
  1166.         cmp     esi, [.cache_end+24]
  1167.         jae     .no_forward
  1168.         cmp     [esi+CACHE_ITEM.SectorLo], eax
  1169.         jnz     .no_forward
  1170.         cmp     [esi+CACHE_ITEM.SectorHi], edx
  1171.         jnz     .no_forward
  1172. ; 13e. Increment position/pointer to the chain and
  1173. ; continue the loop.
  1174.         add     [.chain_start_pos+24], ecx
  1175.         mov     [.chain_start_ptr+24], esi
  1176.         dec     dword [esp]
  1177.         jnz     .copy_loop
  1178.         jmp     .copy_done
  1179. .no_forward:
  1180. ; 13f. Call the lookup function without adding to the cache.
  1181. ; Update position/pointer with returned value.
  1182. ; Note: for the last sector in the chain, edi/esi may contain
  1183. ; garbage; we are not going to use them in this case.
  1184.         push    edi
  1185.         call    cache_lookup_read
  1186.         mov     [.chain_start_pos+28], edi
  1187.         mov     [.chain_start_ptr+28], esi
  1188.         pop     edi
  1189.         dec     dword [esp]
  1190.         jnz     .copy_loop
  1191. .copy_done:
  1192. ; 13g. Restore the stack after 13a.
  1193.         pop     ecx
  1194. ; 14. Call the driver.
  1195.         mov     esi, [ebp+PARTITION.Disk]
  1196.         mov     al, DISKFUNC.write
  1197.         call    disk_call_driver
  1198.         pop     ecx     ; numsectors
  1199. ; 15. If the driver has returned an error, free the buffer allocated at step 10
  1200. ; and pass the error to the caller.
  1201. ; Otherwise, remove the processed part from the chain and continue iterations
  1202. ; starting in step 11 if there are more data to process.
  1203.         test    eax, eax
  1204.         jnz     .nonsequential_error
  1205.         sub     [.chain_size], ecx
  1206.         jnz     .write_non_sequential_iteration
  1207. ; 16. The chain is written. Free the temporary buffer
  1208. ; and continue the loop at step 2.
  1209.         stdcall kernel_free, [.iteration_buffer]
  1210.         mov     esi, [.current_ptr]
  1211.         jmp     .look_next
  1212. .nonsequential_error:
  1213.         push    eax
  1214.         stdcall kernel_free, [.iteration_buffer+4]
  1215.         pop     eax
  1216.         jmp     .return
  1217. .nomemory:
  1218.         mov     eax, DISK_STATUS_NO_MEMORY
  1219.         jmp     .return
  1220. endp
  1221.  
  1222. ; This internal function is called from disk_add to initialize the caching for
  1223. ; a new DISK.
  1224. ; The algorithm is inherited from getcache.inc: take 1/32 part of the available
  1225. ; physical memory, round down to 8 pages, limit by 128K from below and by 1M
  1226. ; from above. Reserve 1/8 part of the cache for system data and 7/8 for app
  1227. ; data.
  1228. ; After the size is calculated, but before the cache is allocated, the device
  1229. ; driver can adjust the size. In particular, setting size to zero disables
  1230. ; caching: there is no sense in a cache for a ramdisk. In fact, such action
  1231. ; is most useful example of a non-trivial adjustment.
  1232. ; esi = pointer to DISK structure
  1233. disk_init_cache:
  1234. ; 1. Verify sector size. The code requires it to be a power of 2 not less than 4.
  1235. ; In the name of sanity check that sector size is not too small or too large.
  1236.         bsf     ecx, [esi+DISK.MediaInfo.SectorSize]
  1237.         jz      .invalid_sector_size
  1238.         mov     eax, 1
  1239.         shl     eax, cl
  1240.         cmp     eax, [esi+DISK.MediaInfo.SectorSize]
  1241.         jnz     .invalid_sector_size
  1242.         cmp     ecx, 6
  1243.         jb      .invalid_sector_size
  1244.         cmp     ecx, 14
  1245.         jbe     .normal_sector_size
  1246. .invalid_sector_size:
  1247.         DEBUGF 1,'K : sector size %x is invalid\n',[esi+DISK.MediaInfo.SectorSize]
  1248.         xor     eax, eax
  1249.         ret
  1250. .normal_sector_size:
  1251.         mov     [esi+DISK.SysCache.sector_size_log], ecx
  1252.         mov     [esi+DISK.AppCache.sector_size_log], ecx
  1253. ; 2. Calculate the suggested cache size.
  1254. ; 2a. Get the size of free physical memory in pages.
  1255.         mov     eax, [pg_data.pages_free]
  1256. ; 2b. Use the value to calculate the size.
  1257.         shl     eax, 12 - 5     ; 1/32 of it in bytes
  1258.         and     eax, -8*4096    ; round down to the multiple of 8 pages
  1259. ; 2c. Force lower and upper limits.
  1260.         cmp     eax, 1024*1024
  1261.         jb      @f
  1262.         mov     eax, 1024*1024
  1263. @@:
  1264.         cmp     eax, 128*1024
  1265.         ja      @f
  1266.         mov     eax, 128*1024
  1267. @@:
  1268. ; 2d. Give a chance to the driver to adjust the size.
  1269.         push    eax
  1270.         mov     al, DISKFUNC.adjust_cache_size
  1271.         call    disk_call_driver
  1272. ; Cache size calculated.
  1273.         mov     [esi+DISK.cache_size], eax
  1274.         test    eax, eax
  1275.         jz      .nocache
  1276. ; 3. Allocate memory for the cache.
  1277. ; 3a. Call the allocator.
  1278.         stdcall kernel_alloc, eax
  1279.         test    eax, eax
  1280.         jnz     @f
  1281. ; 3b. If it failed, say a message and return with eax = 0.
  1282.         dbgstr 'no memory for disk cache'
  1283.         jmp     .nothing
  1284. @@:
  1285. ; 4. Fill two DISKCACHE structures.
  1286.         mov     [esi+DISK.SysCache.pointer], eax
  1287.         lea     ecx, [esi+DISK.CacheLock]
  1288.         call    mutex_init
  1289. ; The following code is inherited from getcache.inc.
  1290.         mov     edx, [esi+DISK.SysCache.pointer]
  1291.         and     [esi+DISK.SysCache.search_start], 0
  1292.         and     [esi+DISK.AppCache.search_start], 0
  1293.         mov     eax, [esi+DISK.cache_size]
  1294.         shr     eax, 3
  1295.         mov     [esi+DISK.SysCache.data_size], eax
  1296.         add     edx, eax
  1297.         imul    eax, 7
  1298.         mov     [esi+DISK.AppCache.data_size], eax
  1299.         mov     [esi+DISK.AppCache.pointer], edx
  1300.  
  1301.         mov     eax, [esi+DISK.SysCache.data_size]
  1302.         call    calculate_cache_slots
  1303.         add     eax, [esi+DISK.SysCache.pointer]
  1304.         mov     [esi+DISK.SysCache.data], eax
  1305.         mov     [esi+DISK.SysCache.sad_size], ecx
  1306.  
  1307.         push    edi
  1308.         mov     edi, [esi+DISK.SysCache.pointer]
  1309.         lea     ecx, [(ecx+1)*3]
  1310.         xor     eax, eax
  1311.         rep stosd
  1312.         pop     edi
  1313.  
  1314.         mov     eax, [esi+DISK.AppCache.data_size]
  1315.         call    calculate_cache_slots
  1316.         add     eax, [esi+DISK.AppCache.pointer]
  1317.         mov     [esi+DISK.AppCache.data], eax
  1318.         mov     [esi+DISK.AppCache.sad_size], ecx
  1319.  
  1320.         push    edi
  1321.         mov     edi, [esi+DISK.AppCache.pointer]
  1322.         lea     ecx, [(ecx+1)*3]
  1323.         xor     eax, eax
  1324.         rep stosd
  1325.         pop     edi
  1326.  
  1327. ; 5. Return with nonzero al.
  1328.         mov     al, 1
  1329. ; 6. Return.
  1330. .nothing:
  1331.         ret
  1332. ; No caching is required for this driver. Zero cache pointers and return with
  1333. ; nonzero al.
  1334. .nocache:
  1335.         mov     [esi+DISK.SysCache.pointer], eax
  1336.         mov     [esi+DISK.AppCache.pointer], eax
  1337.         mov     al, 1
  1338.         ret
  1339.  
  1340. calculate_cache_slots:
  1341.         push    eax
  1342.         mov     ecx, [esi+DISK.MediaInfo.SectorSize]
  1343.         add     ecx, sizeof.CACHE_ITEM
  1344.         xor     edx, edx
  1345.         div     ecx
  1346.         mov     ecx, eax
  1347.         imul    eax, [esi+DISK.MediaInfo.SectorSize]
  1348.         sub     [esp], eax
  1349.         pop     eax
  1350.         dec     ecx
  1351.         ret
  1352.  
  1353.  
  1354. ; This internal function is called from disk_media_dereference to free the
  1355. ; allocated cache, if there is one.
  1356. ; esi = pointer to DISK structure
  1357. disk_free_cache:
  1358. ; The algorithm is straightforward.
  1359.         mov     eax, [esi+DISK.SysCache.pointer]
  1360.         test    eax, eax
  1361.         jz      .nothing
  1362.         stdcall kernel_free, eax
  1363. .nothing:
  1364.         ret
  1365.  
  1366. ; This function flushes all modified data from both caches for the given DISK.
  1367. ; esi = pointer to DISK
  1368. disk_sync:
  1369. ; The algorithm is straightforward.
  1370.         cmp     [esi+DISK.SysCache.pointer], 0
  1371.         jz      .nothing
  1372.         lea     ecx, [esi+DISK.CacheLock]
  1373.         call    mutex_lock
  1374.         push    ebx
  1375.         push    esi     ; for second write_cache64
  1376.         push    esi     ; for first write_cache64
  1377.         lea     ebx, [esi+DISK.SysCache]
  1378.         call    write_cache64
  1379.         add     ebx, DISK.AppCache - DISK.SysCache
  1380.         call    write_cache64
  1381.         pop     ebx
  1382.         lea     ecx, [esi+DISK.CacheLock]
  1383.         call    mutex_unlock
  1384. .nothing:
  1385.         mov     al, DISKFUNC.flush
  1386.         call    disk_call_driver
  1387.         ret
  1388.