Subversion Repositories Kolibri OS

Rev

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