Subversion Repositories Kolibri OS

Rev

Rev 4465 | Rev 5095 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

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