mirror of
https://github.com/KolibriOS/kolibrios.git
synced 2024-12-17 04:12:34 +03:00
disk cache: support for sector sizes other than 512 bytes
git-svn-id: svn://kolibrios.org@5089 a494cfbc-eb01-0410-851d-a64ba20cac60
This commit is contained in:
parent
99959f9109
commit
9d022746fd
@ -108,6 +108,7 @@ struct DISKCACHE
|
|||||||
data dd ?
|
data dd ?
|
||||||
sad_size dd ?
|
sad_size dd ?
|
||||||
search_start dd ?
|
search_start dd ?
|
||||||
|
sector_size_log dd ?
|
||||||
ends
|
ends
|
||||||
|
|
||||||
; This structure represents a disk device and its media for the kernel.
|
; This structure represents a disk device and its media for the kernel.
|
||||||
@ -271,13 +272,13 @@ disk_list_mutex MUTEX
|
|||||||
endg
|
endg
|
||||||
|
|
||||||
iglobal
|
iglobal
|
||||||
; The function 'disk_scan_partitions' needs three 512-byte buffers for
|
; The function 'disk_scan_partitions' needs three sector-sized buffers for
|
||||||
; MBR, bootsector and fs-temporary sector data. It can not use the static
|
; MBR, bootsector and fs-temporary sector data. It can not use the static
|
||||||
; buffers always, since it can be called for two or more disks in parallel.
|
; buffers always, since it can be called for two or more disks in parallel.
|
||||||
; However, this case is not typical. We reserve three static 512-byte buffers
|
; However, this case is not typical. We reserve three static 512-byte buffers
|
||||||
; and a flag that these buffers are currently used. If 'disk_scan_partitions'
|
; and a flag that these buffers are currently used. If 'disk_scan_partitions'
|
||||||
; detects that the buffers are currently used, it allocates buffers from the
|
; detects that the buffers are currently used, it allocates buffers from the
|
||||||
; heap.
|
; heap. Also, the heap is used when sector size is other than 512.
|
||||||
; The flag is implemented as a global dword variable. When the static buffers
|
; The flag is implemented as a global dword variable. When the static buffers
|
||||||
; are not used, the value is -1. When the static buffers are used, the value
|
; are not used, the value is -1. When the static buffers are used, the value
|
||||||
; is normally 0 and temporarily can become greater. The function increments
|
; is normally 0 and temporarily can become greater. The function increments
|
||||||
@ -638,28 +639,25 @@ disk_scan_partitions:
|
|||||||
; 1. Initialize .NumPartitions and .Partitions fields as zeros: empty list.
|
; 1. Initialize .NumPartitions and .Partitions fields as zeros: empty list.
|
||||||
and [esi+DISK.NumPartitions], 0
|
and [esi+DISK.NumPartitions], 0
|
||||||
and [esi+DISK.Partitions], 0
|
and [esi+DISK.Partitions], 0
|
||||||
; 2. Currently we can work only with 512-bytes sectors. Check this restriction.
|
; 2. Acquire the buffer for MBR and bootsector tests. See the comment before
|
||||||
; The only exception is 2048-bytes CD/DVD, but they are not supported yet by
|
|
||||||
; this code.
|
|
||||||
cmp [esi+DISK.MediaInfo.SectorSize], 512
|
|
||||||
jz .doscan
|
|
||||||
DEBUGF 1,'K : sector size is %d, only 512 is supported\n',[esi+DISK.MediaInfo.SectorSize]
|
|
||||||
ret
|
|
||||||
.doscan:
|
|
||||||
; 3. Acquire the buffer for MBR and bootsector tests. See the comment before
|
|
||||||
; the 'partition_buffer_users' variable.
|
; the 'partition_buffer_users' variable.
|
||||||
|
mov eax, [esi+DISK.MediaInfo.SectorSize]
|
||||||
|
cmp eax, 512
|
||||||
|
jnz @f
|
||||||
mov ebx, mbr_buffer ; assume the global buffer is free
|
mov ebx, mbr_buffer ; assume the global buffer is free
|
||||||
lock inc [partition_buffer_users]
|
lock inc [partition_buffer_users]
|
||||||
jz .buffer_acquired ; yes, it is free
|
jz .buffer_acquired ; yes, it is free
|
||||||
lock dec [partition_buffer_users] ; no, we must allocate
|
lock dec [partition_buffer_users] ; no, we must allocate
|
||||||
stdcall kernel_alloc, 512*3
|
@@:
|
||||||
|
lea eax, [eax*3]
|
||||||
|
stdcall kernel_alloc, eax
|
||||||
test eax, eax
|
test eax, eax
|
||||||
jz .nothing
|
jz .nothing
|
||||||
xchg eax, ebx
|
xchg eax, ebx
|
||||||
.buffer_acquired:
|
.buffer_acquired:
|
||||||
; MBR/EBRs are organized in the chain. We use a loop over MBR/EBRs, but no
|
; MBR/EBRs are organized in the chain. We use a loop over MBR/EBRs, but no
|
||||||
; more than MAX_NUM_PARTITION times.
|
; more than MAX_NUM_PARTITION times.
|
||||||
; 4. Prepare things for the loop.
|
; 3. Prepare things for the loop.
|
||||||
; ebp will hold the sector number for current MBR/EBR.
|
; ebp will hold the sector number for current MBR/EBR.
|
||||||
; [esp] will hold the sector number for current extended partition, if there
|
; [esp] will hold the sector number for current extended partition, if there
|
||||||
; is one.
|
; is one.
|
||||||
@ -668,6 +666,10 @@ disk_scan_partitions:
|
|||||||
push MAX_NUM_PARTITIONS ; the counter of max MBRs to process
|
push MAX_NUM_PARTITIONS ; the counter of max MBRs to process
|
||||||
xor ebp, ebp ; start from sector zero
|
xor ebp, ebp ; start from sector zero
|
||||||
push ebp ; no extended partition yet
|
push ebp ; no extended partition yet
|
||||||
|
; 4. MBR is 512 bytes long. If sector size is less than 512 bytes,
|
||||||
|
; assume no MBR, no partitions and go to 10.
|
||||||
|
cmp [esi+DISK.MediaInfo.SectorSize], 512
|
||||||
|
jb .notmbr
|
||||||
.new_mbr:
|
.new_mbr:
|
||||||
; 5. Read the current sector.
|
; 5. Read the current sector.
|
||||||
; Note that 'read' callback operates with 64-bit sector numbers, so we must
|
; Note that 'read' callback operates with 64-bit sector numbers, so we must
|
||||||
@ -986,7 +988,7 @@ end virtual
|
|||||||
; a three-sectors-sized buffer. This function saves ebx in the stack
|
; a three-sectors-sized buffer. This function saves ebx in the stack
|
||||||
; immediately before ebp.
|
; immediately before ebp.
|
||||||
mov ebx, [ebp-4] ; get buffer
|
mov ebx, [ebp-4] ; get buffer
|
||||||
add ebx, 512 ; advance over MBR data to bootsector data
|
add ebx, [esi+DISK.MediaInfo.SectorSize] ; advance over MBR data to bootsector data
|
||||||
add ebp, 8 ; ebp points to part of PARTITION structure
|
add ebp, 8 ; ebp points to part of PARTITION structure
|
||||||
xor eax, eax ; first sector of the partition
|
xor eax, eax ; first sector of the partition
|
||||||
call fs_read32_sys
|
call fs_read32_sys
|
||||||
@ -997,7 +999,7 @@ end virtual
|
|||||||
; ebp -> first three fields of PARTITION structure, .start, .length, .disk;
|
; ebp -> first three fields of PARTITION structure, .start, .length, .disk;
|
||||||
; [esp] = error code after bootsector read: 0 = ok, otherwise = failed,
|
; [esp] = error code after bootsector read: 0 = ok, otherwise = failed,
|
||||||
; ebx points to the buffer for bootsector,
|
; ebx points to the buffer for bootsector,
|
||||||
; ebx+512 points to 512-bytes buffer that can be used for anything.
|
; ebx+[esi+DISK.MediaInfo.SectorSize] points to sector-sized buffer that can be used for anything.
|
||||||
call fat_create_partition
|
call fat_create_partition
|
||||||
test eax, eax
|
test eax, eax
|
||||||
jnz .success
|
jnz .success
|
||||||
|
@ -118,7 +118,6 @@ end virtual
|
|||||||
add [.sector_lo], eax
|
add [.sector_lo], eax
|
||||||
adc [.sector_hi], edx
|
adc [.sector_hi], edx
|
||||||
; 5. If the cache is disabled, pass the request directly to the driver.
|
; 5. If the cache is disabled, pass the request directly to the driver.
|
||||||
mov edi, [.buffer]
|
|
||||||
cmp [ebx+DISKCACHE.pointer], 0
|
cmp [ebx+DISKCACHE.pointer], 0
|
||||||
jz .nocache
|
jz .nocache
|
||||||
; 6. Look for sectors in the cache, sequentially from the beginning.
|
; 6. Look for sectors in the cache, sequentially from the beginning.
|
||||||
@ -137,13 +136,15 @@ end virtual
|
|||||||
; release the lock and go to 7.
|
; release the lock and go to 7.
|
||||||
jc .not_found_in_cache
|
jc .not_found_in_cache
|
||||||
; The sector is found in cache.
|
; The sector is found in cache.
|
||||||
; 6d. Copy data for the caller.
|
; 6d. Copy data for the caller, advance [.buffer].
|
||||||
; Note that buffer in edi is advanced automatically.
|
mov esi, edi
|
||||||
mov esi, ecx
|
mov edi, [.buffer]
|
||||||
shl esi, 9
|
mov eax, 1
|
||||||
add esi, [ebx+DISKCACHE.data]
|
shl eax, cl
|
||||||
mov ecx, 512/4
|
mov ecx, eax
|
||||||
|
shr ecx, 2
|
||||||
rep movsd
|
rep movsd
|
||||||
|
mov [.buffer], edi
|
||||||
; 6e. Advance the sector.
|
; 6e. Advance the sector.
|
||||||
add [.sector_lo], 1
|
add [.sector_lo], 1
|
||||||
adc [.sector_hi], 0
|
adc [.sector_hi], 0
|
||||||
@ -177,6 +178,7 @@ end virtual
|
|||||||
; However, for extra-large requests make an upper limit:
|
; However, for extra-large requests make an upper limit:
|
||||||
; do not use more than half of the free memory
|
; do not use more than half of the free memory
|
||||||
; or more than CACHE_MAX_ALLOC_SIZE bytes.
|
; or more than CACHE_MAX_ALLOC_SIZE bytes.
|
||||||
|
mov ecx, [ebx+DISKCACHE.sector_size_log]
|
||||||
mov ebx, [pg_data.pages_free]
|
mov ebx, [pg_data.pages_free]
|
||||||
shr ebx, 1
|
shr ebx, 1
|
||||||
jz .nomemory
|
jz .nomemory
|
||||||
@ -184,13 +186,15 @@ end virtual
|
|||||||
jbe @f
|
jbe @f
|
||||||
mov ebx, CACHE_MAX_ALLOC_SIZE shr 12
|
mov ebx, CACHE_MAX_ALLOC_SIZE shr 12
|
||||||
@@:
|
@@:
|
||||||
shl ebx, 12 - 9
|
shl ebx, 12
|
||||||
|
shr ebx, cl
|
||||||
|
jz .nomemory
|
||||||
cmp ebx, [.num_sectors]
|
cmp ebx, [.num_sectors]
|
||||||
jbe @f
|
jbe @f
|
||||||
mov ebx, [.num_sectors]
|
mov ebx, [.num_sectors]
|
||||||
@@:
|
@@:
|
||||||
mov eax, ebx
|
mov eax, ebx
|
||||||
shl eax, 9
|
shl eax, cl
|
||||||
stdcall kernel_alloc, eax
|
stdcall kernel_alloc, eax
|
||||||
; If failed, return the appropriate error code.
|
; If failed, return the appropriate error code.
|
||||||
test eax, eax
|
test eax, eax
|
||||||
@ -233,28 +237,31 @@ end virtual
|
|||||||
jz @f
|
jz @f
|
||||||
mov [.error_code+.local_vars2_size], eax
|
mov [.error_code+.local_vars2_size], eax
|
||||||
@@:
|
@@:
|
||||||
; 11. Copy data for the caller.
|
; 11. Copy data for the caller, advance .buffer.
|
||||||
; Note that buffer in edi is advanced automatically.
|
|
||||||
cmp [.current_num_sectors], 0
|
cmp [.current_num_sectors], 0
|
||||||
jz .copy_done
|
jz .copy_done
|
||||||
mov ecx, [.current_num_sectors]
|
mov ebx, [.cache+.local_vars2_size]
|
||||||
shl ecx, 9-2
|
mov eax, [.current_num_sectors]
|
||||||
|
mov ecx, [ebx+DISKCACHE.sector_size_log]
|
||||||
|
shl eax, cl
|
||||||
mov esi, [.allocated_buffer]
|
mov esi, [.allocated_buffer]
|
||||||
|
mov edi, [.buffer+.local_vars2_size]
|
||||||
|
mov ecx, eax
|
||||||
|
shr ecx, 2
|
||||||
rep movsd
|
rep movsd
|
||||||
|
mov [.buffer+.local_vars2_size], edi
|
||||||
; 12. Copy data to the cache.
|
; 12. Copy data to the cache.
|
||||||
; 12a. Acquire the lock.
|
; 12a. Acquire the lock.
|
||||||
mov ebx, [.cache+.local_vars2_size]
|
|
||||||
mov ecx, [ebp+PARTITION.Disk]
|
mov ecx, [ebp+PARTITION.Disk]
|
||||||
add ecx, DISK.CacheLock
|
add ecx, DISK.CacheLock
|
||||||
call mutex_lock
|
call mutex_lock
|
||||||
; 12b. Prepare for the loop: save edi and create a local variable that
|
; 12b. Prepare for the loop: create a local variable that
|
||||||
; stores number of sectors to be copied.
|
; stores number of sectors to be copied.
|
||||||
push edi
|
push [.current_num_sectors]
|
||||||
push [.current_num_sectors+4]
|
|
||||||
.store_to_cache:
|
.store_to_cache:
|
||||||
; 12c. For each sector, call the lookup function with adding to the cache, if not yet.
|
; 12c. For each sector, call the lookup function with adding to the cache, if not yet.
|
||||||
mov eax, [.sector_lo+.local_vars2_size+8]
|
mov eax, [.sector_lo+.local_vars2_size+4]
|
||||||
mov edx, [.sector_hi+.local_vars2_size+8]
|
mov edx, [.sector_hi+.local_vars2_size+4]
|
||||||
call cache_lookup_write
|
call cache_lookup_write
|
||||||
test eax, eax
|
test eax, eax
|
||||||
jnz .cache_error
|
jnz .cache_error
|
||||||
@ -263,39 +270,39 @@ end virtual
|
|||||||
; so rewrite data for the caller from the cache.
|
; so rewrite data for the caller from the cache.
|
||||||
cmp [esi+CACHE_ITEM.Status], CACHE_ITEM_MODIFIED
|
cmp [esi+CACHE_ITEM.Status], CACHE_ITEM_MODIFIED
|
||||||
jnz .not_modified
|
jnz .not_modified
|
||||||
mov esi, ecx
|
mov esi, edi
|
||||||
shl esi, 9
|
mov edi, [.buffer+.local_vars2_size+4]
|
||||||
add esi, [ebx+DISKCACHE.data]
|
mov eax, [esp]
|
||||||
mov edi, [esp+4]
|
shl eax, cl
|
||||||
mov ecx, [esp]
|
sub edi, eax
|
||||||
shl ecx, 9-2
|
mov eax, 1
|
||||||
sub edi, ecx
|
shl eax, cl
|
||||||
mov ecx, 512/4
|
mov ecx, eax
|
||||||
|
shr ecx, 2
|
||||||
rep movsd
|
rep movsd
|
||||||
add [.current_buffer+8], 512
|
add [.current_buffer+4], eax
|
||||||
jmp .sector_done
|
jmp .sector_done
|
||||||
.not_modified:
|
.not_modified:
|
||||||
; 12e. For each not-modified sector,
|
; 12e. For each not-modified sector,
|
||||||
; copy data, mark the item as not-modified copy of the disk,
|
; copy data, mark the item as not-modified copy of the disk,
|
||||||
; advance .current_buffer and .sector_hi:.sector_lo to the next sector.
|
; advance .current_buffer and .sector_hi:.sector_lo to the next sector.
|
||||||
mov [esi+CACHE_ITEM.Status], CACHE_ITEM_COPY
|
mov [esi+CACHE_ITEM.Status], CACHE_ITEM_COPY
|
||||||
mov esi, [.current_buffer+8]
|
mov eax, 1
|
||||||
mov edi, ecx
|
shl eax, cl
|
||||||
shl edi, 9
|
mov esi, [.current_buffer+4]
|
||||||
add edi, [ebx+DISKCACHE.data]
|
mov ecx, eax
|
||||||
mov ecx, 512/4
|
shr ecx, 2
|
||||||
rep movsd
|
rep movsd
|
||||||
mov [.current_buffer+8], esi
|
mov [.current_buffer+4], esi
|
||||||
.sector_done:
|
.sector_done:
|
||||||
add [.sector_lo+.local_vars2_size+8], 1
|
add [.sector_lo+.local_vars2_size+4], 1
|
||||||
adc [.sector_hi+.local_vars2_size+8], 0
|
adc [.sector_hi+.local_vars2_size+4], 0
|
||||||
; 12f. Continue the loop 12c-12e until all sectors are read.
|
; 12f. Continue the loop 12c-12e until all sectors are read.
|
||||||
dec dword [esp]
|
dec dword [esp]
|
||||||
jnz .store_to_cache
|
jnz .store_to_cache
|
||||||
.cache_error:
|
.cache_error:
|
||||||
; 12g. Restore after the loop: pop the local variable and restore edi.
|
; 12g. Restore after the loop: pop the local variable.
|
||||||
pop ecx
|
pop ecx
|
||||||
pop edi
|
|
||||||
; 12h. Release the lock.
|
; 12h. Release the lock.
|
||||||
mov ecx, [ebp+PARTITION.Disk]
|
mov ecx, [ebp+PARTITION.Disk]
|
||||||
add ecx, DISK.CacheLock
|
add ecx, DISK.CacheLock
|
||||||
@ -328,7 +335,7 @@ end virtual
|
|||||||
push eax ; numsectors
|
push eax ; numsectors
|
||||||
push [.sector_hi+4] ; startsector
|
push [.sector_hi+4] ; startsector
|
||||||
push [.sector_lo+8] ; startsector
|
push [.sector_lo+8] ; startsector
|
||||||
push edi ; buffer
|
push [.buffer+12] ; buffer
|
||||||
mov esi, [ebp+PARTITION.Disk]
|
mov esi, [ebp+PARTITION.Disk]
|
||||||
mov al, DISKFUNC.read
|
mov al, DISKFUNC.read
|
||||||
call disk_call_driver
|
call disk_call_driver
|
||||||
@ -440,11 +447,11 @@ end virtual
|
|||||||
; 6c. For each sector, copy data, mark the item as modified and not saved,
|
; 6c. For each sector, copy data, mark the item as modified and not saved,
|
||||||
; advance .current_buffer to the next sector.
|
; advance .current_buffer to the next sector.
|
||||||
mov [esi+CACHE_ITEM.Status], CACHE_ITEM_MODIFIED
|
mov [esi+CACHE_ITEM.Status], CACHE_ITEM_MODIFIED
|
||||||
|
mov eax, 1
|
||||||
|
shl eax, cl
|
||||||
mov esi, [.cur_buffer]
|
mov esi, [.cur_buffer]
|
||||||
mov edi, ecx
|
mov ecx, eax
|
||||||
shl edi, 9
|
shr ecx, 2
|
||||||
add edi, [ebx+DISKCACHE.data]
|
|
||||||
mov ecx, 512/4
|
|
||||||
rep movsd
|
rep movsd
|
||||||
mov [.cur_buffer], esi
|
mov [.cur_buffer], esi
|
||||||
; 6d. Remove the sector from the other cache.
|
; 6d. Remove the sector from the other cache.
|
||||||
@ -592,11 +599,12 @@ end virtual
|
|||||||
jc .not_found_in_cache
|
jc .not_found_in_cache
|
||||||
.found_in_cache:
|
.found_in_cache:
|
||||||
; 4c. Copy the data.
|
; 4c. Copy the data.
|
||||||
|
mov esi, edi
|
||||||
mov edi, [.buffer]
|
mov edi, [.buffer]
|
||||||
mov esi, ecx
|
mov eax, 1
|
||||||
shl esi, 9
|
shl eax, cl
|
||||||
add esi, [ebx+DISKCACHE.data]
|
mov ecx, eax
|
||||||
mov ecx, 512/4
|
shr ecx, 2
|
||||||
rep movsd
|
rep movsd
|
||||||
; 4d. Release the lock and return success.
|
; 4d. Release the lock and return success.
|
||||||
mov ecx, [ebp+PARTITION.Disk]
|
mov ecx, [ebp+PARTITION.Disk]
|
||||||
@ -627,7 +635,10 @@ end virtual
|
|||||||
add ecx, DISK.CacheLock
|
add ecx, DISK.CacheLock
|
||||||
call mutex_unlock
|
call mutex_unlock
|
||||||
; 7. Allocate buffer for CACHE_LEGACY_READ_SIZE sectors.
|
; 7. Allocate buffer for CACHE_LEGACY_READ_SIZE sectors.
|
||||||
stdcall kernel_alloc, CACHE_LEGACY_READ_SIZE shl 9
|
mov eax, CACHE_LEGACY_READ_SIZE
|
||||||
|
mov ecx, [ebx+DISKCACHE.sector_size_log]
|
||||||
|
shl eax, cl
|
||||||
|
stdcall kernel_alloc, eax
|
||||||
; If failed, return the corresponding error code.
|
; If failed, return the corresponding error code.
|
||||||
test eax, eax
|
test eax, eax
|
||||||
jz .nomemory
|
jz .nomemory
|
||||||
@ -656,7 +667,11 @@ end virtual
|
|||||||
; 10. Copy data for the caller.
|
; 10. Copy data for the caller.
|
||||||
mov esi, [.allocated_buffer]
|
mov esi, [.allocated_buffer]
|
||||||
mov edi, [.buffer+.local_vars2_size]
|
mov edi, [.buffer+.local_vars2_size]
|
||||||
mov ecx, 512/4
|
mov ecx, [ebx+DISKCACHE.sector_size_log]
|
||||||
|
mov eax, 1
|
||||||
|
shl eax, cl
|
||||||
|
mov ecx, eax
|
||||||
|
shr ecx, 2
|
||||||
rep movsd
|
rep movsd
|
||||||
; 11. Store all sectors that were successfully read to the cache.
|
; 11. Store all sectors that were successfully read to the cache.
|
||||||
; 11a. Acquire the lock.
|
; 11a. Acquire the lock.
|
||||||
@ -671,19 +686,19 @@ end virtual
|
|||||||
test eax, eax
|
test eax, eax
|
||||||
jnz .cache_error
|
jnz .cache_error
|
||||||
; 11c. Ignore sectors marked as modified: for them the cache is more recent that disk data.
|
; 11c. Ignore sectors marked as modified: for them the cache is more recent that disk data.
|
||||||
|
mov eax, 1
|
||||||
|
shl eax, cl
|
||||||
cmp [esi+CACHE_ITEM.Status], CACHE_ITEM_MODIFIED
|
cmp [esi+CACHE_ITEM.Status], CACHE_ITEM_MODIFIED
|
||||||
jnz .not_modified
|
jnz .not_modified
|
||||||
add [.current_buffer], 512
|
add [.current_buffer], eax
|
||||||
jmp .sector_done
|
jmp .sector_done
|
||||||
.not_modified:
|
.not_modified:
|
||||||
; 11d. For each sector, copy data, mark the item as not-modified copy of the disk,
|
; 11d. For each sector, copy data, mark the item as not-modified copy of the disk,
|
||||||
; advance .current_buffer and .sector_hi:.sector_lo to the next sector.
|
; advance .current_buffer and .sector_hi:.sector_lo to the next sector.
|
||||||
mov [esi+CACHE_ITEM.Status], CACHE_ITEM_COPY
|
mov [esi+CACHE_ITEM.Status], CACHE_ITEM_COPY
|
||||||
mov esi, [.current_buffer]
|
mov esi, [.current_buffer]
|
||||||
mov edi, ecx
|
mov ecx, eax
|
||||||
shl edi, 9
|
shr ecx, 2
|
||||||
add edi, [ebx+DISKCACHE.data]
|
|
||||||
mov ecx, 512/4
|
|
||||||
rep movsd
|
rep movsd
|
||||||
mov [.current_buffer], esi
|
mov [.current_buffer], esi
|
||||||
.sector_done:
|
.sector_done:
|
||||||
@ -721,16 +736,14 @@ end virtual
|
|||||||
call cache_lookup_write
|
call cache_lookup_write
|
||||||
test eax, eax
|
test eax, eax
|
||||||
jnz .floppy_cache_error
|
jnz .floppy_cache_error
|
||||||
push ecx
|
push esi
|
||||||
|
|
||||||
; 14. Call the driver to read one sector.
|
; 14. Call the driver to read one sector.
|
||||||
push 1
|
push 1
|
||||||
push esp
|
push esp
|
||||||
push edx
|
push edx
|
||||||
push [.sector_lo+16]
|
push [.sector_lo+16]
|
||||||
shl ecx, 9
|
push edi
|
||||||
add ecx, [ebx+DISKCACHE.data]
|
|
||||||
push ecx
|
|
||||||
mov esi, [ebp+PARTITION.Disk]
|
mov esi, [ebp+PARTITION.Disk]
|
||||||
mov al, DISKFUNC.read
|
mov al, DISKFUNC.read
|
||||||
call disk_call_driver
|
call disk_call_driver
|
||||||
@ -740,10 +753,7 @@ end virtual
|
|||||||
; 15. Get the slot and pointer to the cache item,
|
; 15. Get the slot and pointer to the cache item,
|
||||||
; change the status to not-modified copy of the disk
|
; change the status to not-modified copy of the disk
|
||||||
; and go to 4c.
|
; and go to 4c.
|
||||||
pop ecx
|
pop esi
|
||||||
lea esi, [ecx*sizeof.CACHE_ITEM/4]
|
|
||||||
shl esi, 2
|
|
||||||
add esi, [ebx+DISKCACHE.pointer]
|
|
||||||
mov [esi+CACHE_ITEM.Status], CACHE_ITEM_COPY
|
mov [esi+CACHE_ITEM.Status], CACHE_ITEM_COPY
|
||||||
jmp .found_in_cache
|
jmp .found_in_cache
|
||||||
|
|
||||||
@ -795,13 +805,14 @@ fs_write32_app:
|
|||||||
; in: edx:eax = sector
|
; in: edx:eax = sector
|
||||||
; in: ebx -> DISKCACHE structure
|
; in: ebx -> DISKCACHE structure
|
||||||
; out: CF set if sector is not in cache
|
; out: CF set if sector is not in cache
|
||||||
; out: ecx = index in cache
|
; out: ecx = sector_size_log
|
||||||
; out: esi -> sector:status
|
; out: esi -> sector:status
|
||||||
|
; out: edi -> sector data
|
||||||
proc cache_lookup_read
|
proc cache_lookup_read
|
||||||
mov esi, [ebx+DISKCACHE.pointer]
|
mov esi, [ebx+DISKCACHE.pointer]
|
||||||
add esi, sizeof.CACHE_ITEM
|
add esi, sizeof.CACHE_ITEM
|
||||||
|
|
||||||
mov ecx, 1
|
mov edi, 1
|
||||||
|
|
||||||
.hdreadcache:
|
.hdreadcache:
|
||||||
|
|
||||||
@ -812,14 +823,17 @@ proc cache_lookup_read
|
|||||||
jne .nohdcache
|
jne .nohdcache
|
||||||
cmp [esi+CACHE_ITEM.SectorHi], edx
|
cmp [esi+CACHE_ITEM.SectorHi], edx
|
||||||
jne .nohdcache
|
jne .nohdcache
|
||||||
|
mov ecx, [ebx+DISKCACHE.sector_size_log]
|
||||||
|
shl edi, cl
|
||||||
|
add edi, [ebx+DISKCACHE.data]
|
||||||
clc
|
clc
|
||||||
ret
|
ret
|
||||||
|
|
||||||
.nohdcache:
|
.nohdcache:
|
||||||
|
|
||||||
add esi, sizeof.CACHE_ITEM
|
add esi, sizeof.CACHE_ITEM
|
||||||
inc ecx
|
inc edi
|
||||||
cmp ecx, [ebx+DISKCACHE.sad_size]
|
cmp edi, [ebx+DISKCACHE.sad_size]
|
||||||
jbe .hdreadcache
|
jbe .hdreadcache
|
||||||
stc
|
stc
|
||||||
ret
|
ret
|
||||||
@ -832,8 +846,8 @@ endp
|
|||||||
; in: ebx -> DISKCACHE structure
|
; in: ebx -> DISKCACHE structure
|
||||||
; in: ebp -> PARTITION structure
|
; in: ebp -> PARTITION structure
|
||||||
; out: eax = error code
|
; out: eax = error code
|
||||||
; out: ecx = index in cache
|
|
||||||
; out: esi -> sector:status
|
; out: esi -> sector:status
|
||||||
|
; out: edi -> sector data
|
||||||
proc cache_lookup_write
|
proc cache_lookup_write
|
||||||
call cache_lookup_read
|
call cache_lookup_read
|
||||||
jnc .return0
|
jnc .return0
|
||||||
@ -874,6 +888,10 @@ proc cache_lookup_write
|
|||||||
popd [esi+CACHE_ITEM.SectorLo]
|
popd [esi+CACHE_ITEM.SectorLo]
|
||||||
popd [esi+CACHE_ITEM.SectorHi]
|
popd [esi+CACHE_ITEM.SectorHi]
|
||||||
mov [esi+CACHE_ITEM.Status], CACHE_ITEM_EMPTY
|
mov [esi+CACHE_ITEM.Status], CACHE_ITEM_EMPTY
|
||||||
|
mov edi, ecx
|
||||||
|
mov ecx, [ebx+DISKCACHE.sector_size_log]
|
||||||
|
shl edi, cl
|
||||||
|
add edi, [ebx+DISKCACHE.data]
|
||||||
.return0:
|
.return0:
|
||||||
xor eax, eax ; success
|
xor eax, eax ; success
|
||||||
ret
|
ret
|
||||||
@ -902,7 +920,7 @@ virtual at esp
|
|||||||
.sequential dd ?
|
.sequential dd ?
|
||||||
; boolean variable, 1 if the current chain is sequential in the cache,
|
; boolean variable, 1 if the current chain is sequential in the cache,
|
||||||
; 0 if additional buffer is needed to perform the operation
|
; 0 if additional buffer is needed to perform the operation
|
||||||
.chain_start_pos dd ? ; slot of chain start item
|
.chain_start_pos dd ? ; data of chain start item
|
||||||
.chain_start_ptr dd ? ; pointer to chain start item
|
.chain_start_ptr dd ? ; pointer to chain start item
|
||||||
.chain_size dd ? ; chain size (thanks, C.O.)
|
.chain_size dd ? ; chain size (thanks, C.O.)
|
||||||
.iteration_size dd ?
|
.iteration_size dd ?
|
||||||
@ -951,6 +969,9 @@ end virtual
|
|||||||
mov eax, [ebx+DISKCACHE.sad_size]
|
mov eax, [ebx+DISKCACHE.sad_size]
|
||||||
sub eax, [.size_left]
|
sub eax, [.size_left]
|
||||||
inc eax
|
inc eax
|
||||||
|
mov ecx, [ebx+DISKCACHE.sector_size_log]
|
||||||
|
shl eax, cl
|
||||||
|
add eax, [ebx+DISKCACHE.data]
|
||||||
mov [.chain_start_pos], eax
|
mov [.chain_start_pos], eax
|
||||||
mov [.chain_size], 0
|
mov [.chain_size], 0
|
||||||
mov [.sequential], 1
|
mov [.sequential], 1
|
||||||
@ -978,7 +999,7 @@ end virtual
|
|||||||
; before returning to 6b; if there is a sequential block indeed, this saves some
|
; before returning to 6b; if there is a sequential block indeed, this saves some
|
||||||
; time instead of many full-fledged lookups.
|
; time instead of many full-fledged lookups.
|
||||||
mov [.sequential], 0
|
mov [.sequential], 0
|
||||||
mov [.chain_start_pos], ecx
|
mov [.chain_start_pos], edi
|
||||||
.look_backward:
|
.look_backward:
|
||||||
; 6e. For each sector, update chain start pos/ptr, decrement sector number,
|
; 6e. For each sector, update chain start pos/ptr, decrement sector number,
|
||||||
; look at the previous item.
|
; look at the previous item.
|
||||||
@ -1001,7 +1022,9 @@ end virtual
|
|||||||
; ...expand the chain one sector backwards and continue the loop at 6e.
|
; ...expand the chain one sector backwards and continue the loop at 6e.
|
||||||
; Otherwise, advance to step 7 if the previous item describes the correct sector
|
; Otherwise, advance to step 7 if the previous item describes the correct sector
|
||||||
; but is not modified, and return to step 6b otherwise.
|
; but is not modified, and return to step 6b otherwise.
|
||||||
dec [.chain_start_pos]
|
mov edi, 1
|
||||||
|
shl edi, cl
|
||||||
|
sub [.chain_start_pos], edi
|
||||||
jmp .look_backward
|
jmp .look_backward
|
||||||
.found_chain_start:
|
.found_chain_start:
|
||||||
; 7. Expand the chain forward.
|
; 7. Expand the chain forward.
|
||||||
@ -1046,14 +1069,11 @@ end virtual
|
|||||||
; 9. Write a sequential chain to disk.
|
; 9. Write a sequential chain to disk.
|
||||||
; 9a. Pass the entire chain to the driver.
|
; 9a. Pass the entire chain to the driver.
|
||||||
mov eax, [.chain_start_ptr]
|
mov eax, [.chain_start_ptr]
|
||||||
mov edx, [.chain_start_pos]
|
|
||||||
shl edx, 9
|
|
||||||
add edx, [ebx+DISKCACHE.data]
|
|
||||||
lea ecx, [.chain_size]
|
lea ecx, [.chain_size]
|
||||||
push ecx ; numsectors
|
push ecx ; numsectors
|
||||||
pushd [eax+CACHE_ITEM.SectorHi] ; startsector
|
pushd [eax+CACHE_ITEM.SectorHi] ; startsector
|
||||||
pushd [eax+CACHE_ITEM.SectorLo] ; startsector
|
pushd [eax+CACHE_ITEM.SectorLo] ; startsector
|
||||||
push edx ; buffer
|
push [.chain_start_pos+12] ; buffer
|
||||||
mov esi, [ebp+PARTITION.Disk]
|
mov esi, [ebp+PARTITION.Disk]
|
||||||
mov al, DISKFUNC.write
|
mov al, DISKFUNC.write
|
||||||
call disk_call_driver
|
call disk_call_driver
|
||||||
@ -1088,13 +1108,15 @@ end virtual
|
|||||||
jbe @f
|
jbe @f
|
||||||
mov eax, CACHE_MAX_ALLOC_SIZE shr 12
|
mov eax, CACHE_MAX_ALLOC_SIZE shr 12
|
||||||
@@:
|
@@:
|
||||||
shl eax, 12 - 9
|
shl eax, 12
|
||||||
|
shr eax, cl
|
||||||
|
jz .nomemory
|
||||||
cmp eax, [.chain_size]
|
cmp eax, [.chain_size]
|
||||||
jbe @f
|
jbe @f
|
||||||
mov eax, [.chain_size]
|
mov eax, [.chain_size]
|
||||||
@@:
|
@@:
|
||||||
mov [.iteration_size], eax
|
mov [.iteration_size], eax
|
||||||
shl eax, 9
|
shl eax, cl
|
||||||
stdcall kernel_alloc, eax
|
stdcall kernel_alloc, eax
|
||||||
test eax, eax
|
test eax, eax
|
||||||
jz .nomemory
|
jz .nomemory
|
||||||
@ -1123,10 +1145,13 @@ end virtual
|
|||||||
; 13b. For each sector, copy the data.
|
; 13b. For each sector, copy the data.
|
||||||
; Note that edi is advanced automatically.
|
; Note that edi is advanced automatically.
|
||||||
mov esi, [.chain_start_pos+24]
|
mov esi, [.chain_start_pos+24]
|
||||||
shl esi, 9
|
mov ecx, [ebx+DISKCACHE.sector_size_log]
|
||||||
add esi, [ebx+DISKCACHE.data]
|
mov eax, 1
|
||||||
mov ecx, 512/4
|
shl eax, cl
|
||||||
|
mov ecx, eax
|
||||||
|
shr ecx, 2
|
||||||
rep movsd
|
rep movsd
|
||||||
|
mov ecx, eax ; keep for 13e
|
||||||
; 13c. Mark the item as not-modified.
|
; 13c. Mark the item as not-modified.
|
||||||
mov esi, [.chain_start_ptr+24]
|
mov esi, [.chain_start_ptr+24]
|
||||||
mov [esi+CACHE_ITEM.Status], CACHE_ITEM_COPY
|
mov [esi+CACHE_ITEM.Status], CACHE_ITEM_COPY
|
||||||
@ -1145,7 +1170,7 @@ end virtual
|
|||||||
jnz .no_forward
|
jnz .no_forward
|
||||||
; 13e. Increment position/pointer to the chain and
|
; 13e. Increment position/pointer to the chain and
|
||||||
; continue the loop.
|
; continue the loop.
|
||||||
inc [.chain_start_pos+24]
|
add [.chain_start_pos+24], ecx
|
||||||
mov [.chain_start_ptr+24], esi
|
mov [.chain_start_ptr+24], esi
|
||||||
dec dword [esp]
|
dec dword [esp]
|
||||||
jnz .copy_loop
|
jnz .copy_loop
|
||||||
@ -1153,11 +1178,13 @@ end virtual
|
|||||||
.no_forward:
|
.no_forward:
|
||||||
; 13f. Call the lookup function without adding to the cache.
|
; 13f. Call the lookup function without adding to the cache.
|
||||||
; Update position/pointer with returned value.
|
; Update position/pointer with returned value.
|
||||||
; Note: for the last sector in the chain, ecx/esi may contain
|
; Note: for the last sector in the chain, edi/esi may contain
|
||||||
; garbage; we are not going to use them in this case.
|
; garbage; we are not going to use them in this case.
|
||||||
|
push edi
|
||||||
call cache_lookup_read
|
call cache_lookup_read
|
||||||
mov [.chain_start_pos+24], ecx
|
mov [.chain_start_pos+28], edi
|
||||||
mov [.chain_start_ptr+24], esi
|
mov [.chain_start_ptr+28], esi
|
||||||
|
pop edi
|
||||||
dec dword [esp]
|
dec dword [esp]
|
||||||
jnz .copy_loop
|
jnz .copy_loop
|
||||||
.copy_done:
|
.copy_done:
|
||||||
@ -1203,13 +1230,32 @@ endp
|
|||||||
; is most useful example of a non-trivial adjustment.
|
; is most useful example of a non-trivial adjustment.
|
||||||
; esi = pointer to DISK structure
|
; esi = pointer to DISK structure
|
||||||
disk_init_cache:
|
disk_init_cache:
|
||||||
; 1. Calculate the suggested cache size.
|
; 1. Verify sector size. The code requires it to be a power of 2 not less than 4.
|
||||||
; 1a. Get the size of free physical memory in pages.
|
; In the name of sanity check that sector size is not too small or too large.
|
||||||
|
bsf ecx, [esi+DISK.MediaInfo.SectorSize]
|
||||||
|
jz .invalid_sector_size
|
||||||
|
mov eax, 1
|
||||||
|
shl eax, cl
|
||||||
|
cmp eax, [esi+DISK.MediaInfo.SectorSize]
|
||||||
|
jnz .invalid_sector_size
|
||||||
|
cmp ecx, 6
|
||||||
|
jb .invalid_sector_size
|
||||||
|
cmp ecx, 14
|
||||||
|
jbe .normal_sector_size
|
||||||
|
.invalid_sector_size:
|
||||||
|
DEBUGF 1,'K : sector size %x is invalid\n',[esi+DISK.MediaInfo.SectorSize]
|
||||||
|
xor eax, eax
|
||||||
|
ret
|
||||||
|
.normal_sector_size:
|
||||||
|
mov [esi+DISK.SysCache.sector_size_log], ecx
|
||||||
|
mov [esi+DISK.AppCache.sector_size_log], ecx
|
||||||
|
; 2. Calculate the suggested cache size.
|
||||||
|
; 2a. Get the size of free physical memory in pages.
|
||||||
mov eax, [pg_data.pages_free]
|
mov eax, [pg_data.pages_free]
|
||||||
; 1b. Use the value to calculate the size.
|
; 2b. Use the value to calculate the size.
|
||||||
shl eax, 12 - 5 ; 1/32 of it in bytes
|
shl eax, 12 - 5 ; 1/32 of it in bytes
|
||||||
and eax, -8*4096 ; round down to the multiple of 8 pages
|
and eax, -8*4096 ; round down to the multiple of 8 pages
|
||||||
; 1c. Force lower and upper limits.
|
; 2c. Force lower and upper limits.
|
||||||
cmp eax, 1024*1024
|
cmp eax, 1024*1024
|
||||||
jb @f
|
jb @f
|
||||||
mov eax, 1024*1024
|
mov eax, 1024*1024
|
||||||
@ -1218,7 +1264,7 @@ disk_init_cache:
|
|||||||
ja @f
|
ja @f
|
||||||
mov eax, 128*1024
|
mov eax, 128*1024
|
||||||
@@:
|
@@:
|
||||||
; 1d. Give a chance to the driver to adjust the size.
|
; 2d. Give a chance to the driver to adjust the size.
|
||||||
push eax
|
push eax
|
||||||
mov al, DISKFUNC.adjust_cache_size
|
mov al, DISKFUNC.adjust_cache_size
|
||||||
call disk_call_driver
|
call disk_call_driver
|
||||||
@ -1226,16 +1272,16 @@ disk_init_cache:
|
|||||||
mov [esi+DISK.cache_size], eax
|
mov [esi+DISK.cache_size], eax
|
||||||
test eax, eax
|
test eax, eax
|
||||||
jz .nocache
|
jz .nocache
|
||||||
; 2. Allocate memory for the cache.
|
; 3. Allocate memory for the cache.
|
||||||
; 2a. Call the allocator.
|
; 3a. Call the allocator.
|
||||||
stdcall kernel_alloc, eax
|
stdcall kernel_alloc, eax
|
||||||
test eax, eax
|
test eax, eax
|
||||||
jnz @f
|
jnz @f
|
||||||
; 2b. If it failed, say a message and return with eax = 0.
|
; 3b. If it failed, say a message and return with eax = 0.
|
||||||
dbgstr 'no memory for disk cache'
|
dbgstr 'no memory for disk cache'
|
||||||
jmp .nothing
|
jmp .nothing
|
||||||
@@:
|
@@:
|
||||||
; 3. Fill two DISKCACHE structures.
|
; 4. Fill two DISKCACHE structures.
|
||||||
mov [esi+DISK.SysCache.pointer], eax
|
mov [esi+DISK.SysCache.pointer], eax
|
||||||
lea ecx, [esi+DISK.CacheLock]
|
lea ecx, [esi+DISK.CacheLock]
|
||||||
call mutex_init
|
call mutex_init
|
||||||
@ -1252,9 +1298,7 @@ disk_init_cache:
|
|||||||
mov [esi+DISK.AppCache.pointer], edx
|
mov [esi+DISK.AppCache.pointer], edx
|
||||||
|
|
||||||
mov eax, [esi+DISK.SysCache.data_size]
|
mov eax, [esi+DISK.SysCache.data_size]
|
||||||
push ebx
|
call calculate_cache_slots
|
||||||
call calculate_for_hd64
|
|
||||||
pop ebx
|
|
||||||
add eax, [esi+DISK.SysCache.pointer]
|
add eax, [esi+DISK.SysCache.pointer]
|
||||||
mov [esi+DISK.SysCache.data], eax
|
mov [esi+DISK.SysCache.data], eax
|
||||||
mov [esi+DISK.SysCache.sad_size], ecx
|
mov [esi+DISK.SysCache.sad_size], ecx
|
||||||
@ -1267,9 +1311,7 @@ disk_init_cache:
|
|||||||
pop edi
|
pop edi
|
||||||
|
|
||||||
mov eax, [esi+DISK.AppCache.data_size]
|
mov eax, [esi+DISK.AppCache.data_size]
|
||||||
push ebx
|
call calculate_cache_slots
|
||||||
call calculate_for_hd64
|
|
||||||
pop ebx
|
|
||||||
add eax, [esi+DISK.AppCache.pointer]
|
add eax, [esi+DISK.AppCache.pointer]
|
||||||
mov [esi+DISK.AppCache.data], eax
|
mov [esi+DISK.AppCache.data], eax
|
||||||
mov [esi+DISK.AppCache.sad_size], ecx
|
mov [esi+DISK.AppCache.sad_size], ecx
|
||||||
@ -1281,9 +1323,9 @@ disk_init_cache:
|
|||||||
rep stosd
|
rep stosd
|
||||||
pop edi
|
pop edi
|
||||||
|
|
||||||
; 4. Return with nonzero al.
|
; 5. Return with nonzero al.
|
||||||
mov al, 1
|
mov al, 1
|
||||||
; 5. Return.
|
; 6. Return.
|
||||||
.nothing:
|
.nothing:
|
||||||
ret
|
ret
|
||||||
; No caching is required for this driver. Zero cache pointers and return with
|
; No caching is required for this driver. Zero cache pointers and return with
|
||||||
@ -1294,18 +1336,16 @@ disk_init_cache:
|
|||||||
mov al, 1
|
mov al, 1
|
||||||
ret
|
ret
|
||||||
|
|
||||||
calculate_for_hd64:
|
calculate_cache_slots:
|
||||||
push eax
|
push eax
|
||||||
mov ebx, eax
|
mov ecx, [esi+DISK.MediaInfo.SectorSize]
|
||||||
shr eax, 9
|
add ecx, sizeof.CACHE_ITEM
|
||||||
lea eax, [eax*3]
|
xor edx, edx
|
||||||
shl eax, 2
|
div ecx
|
||||||
sub ebx, eax
|
mov ecx, eax
|
||||||
shr ebx, 9
|
imul eax, [esi+DISK.MediaInfo.SectorSize]
|
||||||
mov ecx, ebx
|
sub [esp], eax
|
||||||
shl ebx, 9
|
|
||||||
pop eax
|
pop eax
|
||||||
sub eax, ebx
|
|
||||||
dec ecx
|
dec ecx
|
||||||
ret
|
ret
|
||||||
|
|
||||||
|
@ -59,6 +59,8 @@ endp
|
|||||||
;---------------------------------------------------------------------
|
;---------------------------------------------------------------------
|
||||||
proc ext2_create_partition
|
proc ext2_create_partition
|
||||||
push ebx
|
push ebx
|
||||||
|
cmp dword [esi+DISK.MediaInfo.SectorSize], 512
|
||||||
|
jnz .fail
|
||||||
|
|
||||||
mov eax, 2 ; Superblock starts at 1024-bytes.
|
mov eax, 2 ; Superblock starts at 1024-bytes.
|
||||||
add ebx, 512 ; Get pointer to fs-specific buffer.
|
add ebx, 512 ; Get pointer to fs-specific buffer.
|
||||||
|
@ -154,6 +154,9 @@ fat_create_partition.return0:
|
|||||||
xor eax, eax
|
xor eax, eax
|
||||||
ret
|
ret
|
||||||
fat_create_partition:
|
fat_create_partition:
|
||||||
|
; sector size must be 512
|
||||||
|
cmp dword [esi+DISK.MediaInfo.SectorSize], 512
|
||||||
|
jnz .return0
|
||||||
; bootsector must have been successfully read
|
; bootsector must have been successfully read
|
||||||
cmp dword [esp+4], 0
|
cmp dword [esp+4], 0
|
||||||
jnz .return0
|
jnz .return0
|
||||||
|
@ -152,6 +152,8 @@ ntfs_test_bootsec:
|
|||||||
ret
|
ret
|
||||||
|
|
||||||
proc ntfs_create_partition
|
proc ntfs_create_partition
|
||||||
|
cmp dword [esi+DISK.MediaInfo.SectorSize], 512
|
||||||
|
jnz .nope
|
||||||
mov edx, dword [ebp+PARTITION.Length]
|
mov edx, dword [ebp+PARTITION.Length]
|
||||||
cmp dword [esp+4], 0
|
cmp dword [esp+4], 0
|
||||||
jz .boot_read_ok
|
jz .boot_read_ok
|
||||||
|
@ -25,6 +25,8 @@ include 'xfs.inc'
|
|||||||
; returns 0 (not XFS or invalid) / pointer to partition structure
|
; returns 0 (not XFS or invalid) / pointer to partition structure
|
||||||
xfs_create_partition:
|
xfs_create_partition:
|
||||||
push ebx ecx edx esi edi
|
push ebx ecx edx esi edi
|
||||||
|
cmp dword [esi+DISK.MediaInfo.SectorSize], 512
|
||||||
|
jnz .error
|
||||||
cmp dword[ebx + xfs_sb.sb_magicnum], XFS_SB_MAGIC ; signature
|
cmp dword[ebx + xfs_sb.sb_magicnum], XFS_SB_MAGIC ; signature
|
||||||
jne .error
|
jne .error
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user