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