From ccb3bac301eefeeb9abf2659810324c085b1d567 Mon Sep 17 00:00:00 2001 From: CleverMouse Date: Mon, 23 Apr 2012 09:23:53 +0000 Subject: [PATCH] driver for managing temporary memory-based disks git-svn-id: svn://kolibrios.org@2644 a494cfbc-eb01-0410-851d-a64ba20cac60 --- kernel/trunk/drivers/tmpdisk.asm | 295 +++++++++++++++++++++++ kernel/trunk/drivers/tmpdisk_fat.inc | 327 ++++++++++++++++++++++++++ kernel/trunk/drivers/tmpdisk_work.inc | 144 ++++++++++++ 3 files changed, 766 insertions(+) create mode 100644 kernel/trunk/drivers/tmpdisk.asm create mode 100644 kernel/trunk/drivers/tmpdisk_fat.inc create mode 100644 kernel/trunk/drivers/tmpdisk_work.inc diff --git a/kernel/trunk/drivers/tmpdisk.asm b/kernel/trunk/drivers/tmpdisk.asm new file mode 100644 index 0000000000..0bae727c69 --- /dev/null +++ b/kernel/trunk/drivers/tmpdisk.asm @@ -0,0 +1,295 @@ +; Disk driver to create FAT16/FAT32 memory-based temporary disk aka RAM disk. +; (c) CleverMouse + +; Note: in the ideal world, a disk driver should not care about a file system +; on it. In the current world, however, there is no way to format a disk in +; FAT, so this part of file-system-specific operations is included in the +; driver. + +; When this driver is loading, it registers itself in the system and does +; nothing more. When loaded, this driver controls pseudo-disk devices +; named /tmp#/, where # is a digit from 0 to 9. The driver does not create +; any device by itself, waiting for instructions from an application. +; The driver responds to the following IOCTLs from a control application: +SRV_GETVERSION equ 0 ; input ignored, + ; output = dword API_VERSION +DEV_ADD_DISK equ 1 ; input = structure add_disk_struc, + ; no output +DEV_DEL_DISK equ 2 ; input = structure del_disk_struc, + ; no output +; For all IOCTLs the driver returns one of the following error codes: +NO_ERROR equ 0 +ERROR_INVALID_IOCTL equ 1 ; unknown IOCTL code, wrong input/output size... +ERROR_INVALID_ID equ 2 ; .DiskId must be from 0 to 9 +ERROR_SIZE_TOO_LARGE equ 3 ; .DiskSize is too large +ERROR_SIZE_TOO_SMALL equ 4 ; .DiskSize is too small +ERROR_NO_MEMORY equ 5 ; memory allocation failed + + +API_VERSION equ 1 +; Input structures: +struc add_disk_struc +{ +.DiskSize dd ? ; disk size in sectors, 1 sector = 512 bytes +; Note: .DiskSize is the full size, including FAT service data. +; Size for useful data is slightly less than this number. +.DiskId db ? ; from 0 to 9 +.sizeof: +} +virtual at 0 +add_disk_struc add_disk_struc +end virtual +struc del_disk_struc +{ +.DiskId db ? ; from 0 to 9 +.sizeof: +} +virtual at 0 +del_disk_struc del_disk_struc +end virtual + +max_num_disks equ 10 + +; standard driver stuff +format MS COFF + +DEBUG equ 0 +include 'proc32.inc' +include 'imports.inc' + +public START +public version + +struc IOCTL +{ + .handle dd ? + .io_code dd ? + .input dd ? + .inp_size dd ? + .output dd ? + .out_size dd ? +} + +virtual at 0 +IOCTL IOCTL +end virtual + +section '.flat' code readable align 16 +; the start procedure (see the description above) +proc START +; This procedure is called in two situations: +; when the driver is loading and when the system is shutting down. +; 1. Check that the driver is loading; do nothing unless so. + xor eax, eax ; set return value in case we will do nothing + cmp dword [esp+4], 1 + jne .nothing +; 2. Register the driver in the system. + stdcall RegService, my_service, service_proc +; 3. Return the value returned by RegService back to the system. +.nothing: + retn 4 +endp + +; Service procedure for the driver - handle all IOCTL requests for the driver. +; The description of handled IOCTLs is located in the start of this file. +proc service_proc +; 1. Save used registers to be stdcall. +; Note: this shifts esp, so the first parameter [esp+4] becomes [esp+16]. +; Note: edi is used not by this procedure itself, but by worker procedures. + push ebx esi edi +; 2. Get parameter from the stack: [esp+16] is the first parameter, +; pointer to IOCTL structure. + mov edx, [esp+16] ; edx -> IOCTL +; 3. Set the return value to 'invalid IOCTL'. +; Now, if one of conditions for IOCTL does not met, the code +; can simply return the value already loaded. + mov al, ERROR_INVALID_IOCTL +; 4. Get request code and select a handler for the code. + mov ecx, [edx+IOCTL.io_code] + test ecx, ecx ; check for SRV_GETVERSION + jnz .no.srv_getversion +; 4. This is SRV_GETVERSION request, no input, 4 bytes output, API_VERSION. +; 4a. Output size must be at least 4 bytes. + cmp [edx+IOCTL.out_size], 4 + jl .return +; 4b. Write result to the output buffer. + mov eax, [edx+IOCTL.output] + mov dword [eax], API_VERSION +; 4c. Return success. + xor eax, eax + jmp .return +.no.srv_getversion: + dec ecx ; check for DEV_ADD_DISK + jnz .no.dev_add_disk +; 5. This is DEV_ADD_DISK request, input is add_disk_struc, output is 1 byte +; 5a. Input size must be exactly add_disk_struc.sizeof bytes. + cmp [edx+IOCTL.inp_size], add_disk_struc.sizeof + jnz .return +; 5b. Load input parameters and call the worker procedure. + mov eax, [edx+IOCTL.input] + movzx ebx, [eax+add_disk_struc.DiskId] + mov esi, [eax+add_disk_struc.DiskSize] + call add_disk +; 5c. Return back to the caller the value from the worker procedure. + jmp .return +.no.dev_add_disk: + dec ecx ; check for DEV_DEL_DISK + jnz .return +; 6. This is DEV_DEL_DISK request, input is del_disk_struc +; 6a. Input size must be exactly del_disk_struc.sizeof bytes. + cmp [edx+IOCTL.inp_size], del_disk_struc.sizeof + jnz .return +; 6b. Load input parameters and call the worker procedure. + mov eax, [edx+IOCTL.input] + movzx ebx, [eax+del_disk_struc.DiskId] + call del_disk +; 6c. Return back to the caller the value from the worker procedure. +.return: +; 7. Exit. +; 7a. The code above returns a value in al for efficiency, +; propagate it to eax. + movzx eax, al +; 7b. Restore used registers to be stdcall. + pop edi esi ebx +; 7c. Return, popping one argument. + retn 4 +endp + +; The worker procedure for DEV_ADD_DISK request. +; Creates a memory-based disk of given size and formats it in FAT16/32. +; Called with ebx = disk id, esi = disk size, +; returns error code in al. +proc add_disk +; 1. Check that disk id is correct and free. +; Otherwise, return the corresponding error code. + mov al, ERROR_INVALID_ID + cmp ebx, max_num_disks + jae .return + cmp [disk_pointers+ebx*4], 0 + jnz .return +; 2. Check that the size is reasonable. +; Otherwise, return the corresponding error code. + mov al, ERROR_SIZE_TOO_LARGE + cmp esi, MAX_SIZE + ja .return + mov al, ERROR_SIZE_TOO_SMALL + cmp esi, MIN_FAT16_SIZE + jb .return +; 3. Allocate memory for the disk, store the pointer in edi. +; If failed, return the corresponding error code. + mov eax, esi + shl eax, 9 + stdcall KernelAlloc, eax + mov edi, eax + test eax, eax + mov al, ERROR_NO_MEMORY + jz .return +; 4. Store the pointer and the size in the global variables. +; It is possible, though very unlikely, that two threads +; have called this function in parallel with the same id, +; so [disk_pointers+ebx*4] could be filled by another thread. +; Play extra safe and store new value only if old value is zero. + xor eax, eax + lock cmpxchg [disk_pointers+ebx*4], edi + jz @f +; Otherwise, free the allocated memory and return the corresponding error code. + stdcall KernelFree, edi + mov al, ERROR_INVALID_ID + jmp .return +@@: + mov [disk_sizes+ebx*4], esi +; 5. Call the worker procedure for formatting this disk. +; It should not fail. + call format_disk +; 6. Register the disk in the system. +; 6a. Generate name as /tmp#, where # = ebx + '0'. Use two dwords in the stack. + push 0 + push 'tmp' + mov eax, esp ; eax points to 'tmp' + zero byte + zero dword + lea ecx, [ebx+'0'] ; ecx = digit + mov [eax+3], cl ; eax points to 'tmp#' + zero dword +; 6b. Call the kernel API. Use disk id as 'userdata' parameter for callbacks. + stdcall DiskAdd, disk_functions, eax, ebx, 0 +; 6c. Restore the stack after 6a. + pop ecx ecx +; 6c. Check the result. If DiskAdd has failed, cleanup and return +; ERROR_NO_MEMORY, this is the most probable or even the only reason to fail. + test eax, eax + jnz @f + mov [disk_sizes+ebx*4], 0 + mov [disk_pointers+ebx*4], 0 + stdcall KernelFree, edi + mov al, ERROR_NO_MEMORY + jmp .return +@@: + push eax +; 6d. Notify the kernel that media is inserted. + stdcall DiskMediaChanged, eax, 1 +; 6e. Disk is fully configured; store its handle in the global variable +; and return success. + pop [disk_handles+ebx*4] + xor eax, eax +; 7. Return. +.return: + retn +endp + +; The worker procedure for DEV_DEL_DISK request. +; Deletes a previously created memory-based disk. +; Called with ebx = disk id, +; returns error code in al. +proc del_disk +; 1. Check that disk id is correct. +; Otherwise, return the corresponding error code. + mov al, ERROR_INVALID_ID + cmp ebx, max_num_disks + jae .return +; 2. Get the disk handle, simultaneously clearing the global variable. + xor edx, edx + xchg edx, [disk_handles+ebx*4] +; 3. Check that the handle is non-zero. +; Otherwise, return the corresponding error code. + test edx, edx + jz .return +; 4. Delete the disk from the system. + stdcall DiskDel, edx +; 5. Return success. +; Note that we can't free memory yet; it will be done in tmpdisk_close. + xor eax, eax +.return: + retn +endp + +; Include implementation of tmpdisk_* callbacks. +include 'tmpdisk_work.inc' +; Include FAT-specific code. +include 'tmpdisk_fat.inc' + +; initialized data +align 4 +disk_functions: + dd disk_functions_end - disk_functions + dd tmpdisk_close + dd 0 ; no need in .closemedia + dd tmpdisk_querymedia + dd tmpdisk_read + dd tmpdisk_write + dd 0 ; no need in .flush + dd tmpdisk_adjust_cache_size +disk_functions_end: +; disk_handles = array of values for Disk* kernel functions +label disk_handles dword +times max_num_disks dd 0 +; disk_pointers = array of pointers to disk data +label disk_pointers dword +times max_num_disks dd 0 +; disk_sizes = array of disk sizes +label disk_sizes dword +times max_num_disks dd 0 + +version dd 0x00060006 +my_service db 'tmpdisk',0 + +; uninitialized data +; actually, not used here +;section '.data' data readable writable align 16 ; standard driver stuff diff --git a/kernel/trunk/drivers/tmpdisk_fat.inc b/kernel/trunk/drivers/tmpdisk_fat.inc new file mode 100644 index 0000000000..1fe0a726f6 --- /dev/null +++ b/kernel/trunk/drivers/tmpdisk_fat.inc @@ -0,0 +1,327 @@ +; FAT-specific code for tmpdisk.asm. +; Formats a disk to FAT16 or FAT32, depending on size. +; Note: formatting is adjusted for memory-based disks. Although the resulting +; image is a valid FAT32 volume, it has no "spare" sectors, e.g. second copy +; of FAT or place for second sector of MS FAT32 bootloader. + +; Some constants +FAT16_ROOTDIR_SECTORS = 16 ; can be changed, but why not? +; FAT16: +; 1 bootsector, +; min 0xFF5 sectors for data, +; min (0xFF5*2/512) = 16 sectors per FAT, we use only one copy, +; FAT16_ROOTDIR_SECTORS for root directory +MIN_FAT16_SIZE = 1 + 16 + FAT16_ROOTDIR_SECTORS + 0xFF5 +; FAT32: +; 1 bootsector, +; 1 sector for fsinfo, +; min 0xFFF5 sectors for data, +; min (0xFFF5*4/512) = 512 sectors per FAT, we use only one copy +MIN_FAT32_SIZE = 1 + 1 + 512 + 0xFFF5 +MAX_SIZE = 1 shl (30 - 9) ; 1G in 512-byte sectors + +; Initializes FATxx structures on the disk. +; Called with edi = pointer to disk data, esi = size of disk. +proc format_disk +; Determine FAT type and jump to the corresponding handler. + cmp esi, MIN_FAT32_SIZE + jae format_disk_fat32 +; Fall through to format_disk_fat16. +endp + +; Structure of FAT16 bootsector. Field names are from MS spec. +struc FAT16BOOT +{ +.BS_jmpBoot rb 3 +.BS_OEMName rb 8 +.BPB_BytsPerSec dw ? +.BPB_SecsPerClus db ? +.BPB_RsvdSecCnt dw ? +.BPB_NumFATs db ? +.BPB_RootEntCnt dw ? +.BPB_TotSec16 dw ? +.BPB_Media db ? +.BPB_FATSz16 dw ? +.BPB_SecPerTrk dw ? +.BPB_NumHeads dw ? +.BPB_HiddSec dd ? +.BPB_TotSec32 dd ? +.BS_DrvNum db ? +.BS_Reserved1 db ? +.BS_BootSig db ? +.BS_VolID dd ? +.BS_VolLab rb 11 +.BS_FilSysType rb 8 +} +virtual at 0 +FAT16BOOT FAT16BOOT +end virtual + +; Initializes FAT16 structures on the disk. +; Called with edi = pointer to disk data, esi = size of disk. +format_disk_fat16: +; 1. Calculate number of clusters. +; 1a. There are fixed-sized areas and there are data+FAT; +; every cluster uses 512 bytes in data area and 2 bytes in FAT area. + lea eax, [esi-1-FAT16_ROOTDIR_SECTORS] +; two following lines are equivalent to edx = floor(eax*512/514) + mov ecx, 0xFF00FF01 + mul ecx ; edx = number of clusters +; 1b. Force the number be less than 0xfff5. + mov eax, 0xFFF4 + cmp edx, eax + jb @f + mov edx, eax +@@: +; 2. Zero all system areas on the disk. + lea ecx, [256*(1+FAT16_ROOTDIR_SECTORS)/2+edx+255] + and ecx, not 255 + shr ecx, 1 + xor eax, eax + push edi + rep stosd + pop edi +; 3. Generate the bootsector. +; 3a. Copy static stub. + push esi edi + mov esi, fat16bootsector_stub + mov ecx, fat16bootsector_stub_size + rep movsb + pop edi esi + mov word [edi+510], 0xAA55 +; 3b. Set fields which depend on size. + cmp esi, 0x10000 + jae .size_is_32bit + mov [edi+FAT16BOOT.BPB_TotSec16], si + jmp .size_written +.size_is_32bit: + mov [edi+FAT16BOOT.BPB_TotSec32], esi +.size_written: + lea eax, [edx+255] + shr eax, 8 + mov [edi+FAT16BOOT.BPB_FATSz16], ax +; 3c. Generate volume ID. + call generate_volume_id + mov [edi+FAT16BOOT.BS_VolID], eax +; 4. Initialize FAT. + mov dword [edi+512], 0xFFFFFFF8 +; 5. Return. + ret + +; Structure of FAT32 bootsector. Field names are from MS spec. +struc FAT32BOOT +{ +.BS_jmpBoot rb 3 +.BS_OEMName rb 8 +.BPB_BytsPerSec dw ? +.BPB_SecsPerClus db ? +.BPB_RsvdSecCnt dw ? +.BPB_NumFATs db ? +.BPB_RootEntCnt dw ? +.BPB_TotSec16 dw ? +.BPB_Media db ? +.BPB_FATSz16 dw ? +.BPB_SecPerTrk dw ? +.BPB_NumHeads dw ? +.BPB_HiddSec dd ? +.BPB_TotSec32 dd ? +.BPB_FATSz32 dd ? +.BPB_ExtFlags dw ? +.BPB_FSVer dw ? +.BPB_RootClus dd ? +.BPB_FSInfo dw ? +.BPB_BkBootSec dw ? +.BPB_Reserved rb 12 +.BS_DrvNum db ? +.BS_Reserved1 db ? +.BS_BootSig db ? +.BS_VolID dd ? +.BS_VolLab rb 11 +.BS_FilSysType rb 8 +} +virtual at 0 +FAT32BOOT FAT32BOOT +end virtual + +; Initializes FAT32 structures on the disk. +; Called with edi = pointer to disk data, esi = size of disk. +format_disk_fat32: +; 1. Calculate number of clusters. +; 1a. There is fixed-sized area and there are data+FAT; +; every cluster uses 512 bytes in data area and 4 bytes in FAT area. + lea eax, [esi-1-1] +; two following lines are equivalent to edx=floor(eax*512/516) if eax<10000000h + mov ecx, 0xFE03F810 + mul ecx ; edx = number of clusters +; 2. Zero all system areas on the disk and first cluster of data, +; used for root directory. + lea ecx, [128*(1+1+1)+edx+127] + and ecx, not 127 + xor eax, eax + push edi + rep stosd + pop edi +; 3. Generate the bootsector. +; 3a. Copy static stub. + push esi edi + mov esi, fat32bootsector_stub + mov ecx, fat32bootsector_stub_size + rep movsb + pop edi esi + mov word [edi+510], 0xAA55 +; 3b. Set fields which depend on size. + mov [edi+FAT32BOOT.BPB_TotSec32], esi + lea eax, [edx+127] + shr eax, 7 + mov [edi+FAT32BOOT.BPB_FATSz32], eax +; 3c. Generate volume ID. + call generate_volume_id + mov [edi+FAT32BOOT.BS_VolID], eax +; 4. Initialize fsinfo sector. + mov dword [edi+512], 'RRaA' + mov dword [edi+512+484], 'rrAa' + dec edx ; one cluster is occupied by root dir + mov dword [edi+512+488], edx ; free count + mov byte [edi+512+492], 3 ; first free cluster + mov word [edi+512+510], 0xAA55 +; 5. Initialize FAT. + mov dword [edi+512*2], 0x0FFFFFF8 + mov dword [edi+512*2+4], 0x0FFFFFFF + mov dword [edi+512*2+8], 0x0FFFFFFF +; 6. Return. + ret + +; Generate volume serial number, which should try to be unique for each volume. +; Use CMOS date+time, copy-pasted from fat32.inc. +generate_volume_id: + call get_time_for_file + mov cx, ax + call get_date_for_file + shl eax, 16 + mov ax, cx + ret + +; Three following procedures are copy-pasted from fat32.inc. +bcd2bin: +;---------------------------------- +; input : AL=BCD number (eg. 0x11) +; output : AH=0 +; AL=decimal number (eg. 11) +;---------------------------------- + xor ah, ah + shl ax, 4 + shr al, 4 + aad + ret + +get_date_for_file: +;----------------------------------------------------- +; Get date from CMOS and pack day,month,year in AX +; DATE bits 0..4 : day of month 0..31 +; 5..8 : month of year 1..12 +; 9..15 : count of years from 1980 +;----------------------------------------------------- + mov al, 0x7 ;day + out 0x70, al + in al, 0x71 + call bcd2bin + ror eax, 5 + + mov al, 0x8 ;month + out 0x70, al + in al, 0x71 + call bcd2bin + ror eax, 4 + + mov al, 0x9 ;year + out 0x70, al + in al, 0x71 + call bcd2bin + add ax, 20 ;because CMOS return only the two last + ;digit (eg. 2000 -> 00 , 2001 -> 01) and we + rol eax, 9 ;need the difference with 1980 (eg. 2001-1980) + ret + + +get_time_for_file: +;----------------------------------------------------- +; Get time from CMOS and pack hour,minute,second in AX +; TIME bits 0..4 : second (the low bit is lost) +; 5..10 : minute 0..59 +; 11..15 : hour 0..23 +;----------------------------------------------------- + mov al, 0x0 ;second + out 0x70, al + in al, 0x71 + call bcd2bin + ror eax, 6 + + mov al, 0x2 ;minute + out 0x70, al + in al, 0x71 + call bcd2bin + ror eax, 6 + + mov al, 0x4 ;hour + out 0x70, al + in al, 0x71 + call bcd2bin + rol eax, 11 + ret + +; some data +fat16bootsector_stub: + db 0EBh, 3Ch, 90h ; BS_jmpBoot + db 'KOLIBRI ' ; BS_OEMName + dw 512 ; BPB_BytsPerSec + db 1 ; BPB_SecsPerClus + dw 1 ; BPB_RsvdSecCnt + db 1 ; BPB_NumFATs + dw FAT16_ROOTDIR_SECTORS*16 ; BPB_RootEntCnt + dw 0 ; BPB_TotSec16, filled in format_disk_fat16 + db 0F8h ; BPB_Media + dw 0 ; BPB_FATSz16, filled in format_disk_fat16 + dw 32 ; BPB_SecPerTrk + dw 128 ; BPB_NumHeads + dd 0 ; BPB_HiddSec + dd 0 ; BPB_TotSec32, filled in format_disk_fat16 + db 80h ; BS_DrvNum + db 0 ; BS_Reserved1 + db 29h ; BS_BootSig + dd 0 ; BS_VolID, filled in format_disk_fat16 + db 'NO NAME ' ; BS_VolLab + db 'FAT16 ' ; BS_FilSysType +; just in case add some meaningful bytes if someone tries to boot + db 0CDh, 19h, 0EBh, 0FEh ; int 19h, jmp $ +fat16bootsector_stub_size = $ - fat16bootsector_stub +fat32bootsector_stub: + db 0EBh, 58h, 90h ; BS_jmpBoot + db 'KOLIBRI ' ; BS_OEMName + dw 512 ; BPB_BytsPerSec + db 1 ; BPB_SecsPerClus + dw 1 ; BPB_RsvdSecCnt + db 1 ; BPB_NumFATs + dw 0 ; BPB_RootEntCnt + dw 0 ; BPB_TotSec16 + db 0F8h ; BPB_Media + dw 0 ; BPB_FATSz16 + dw 32 ; BPB_SecPerTrk + dw 128 ; BPB_NumHeads + dd 0 ; BPB_HiddSec + dd 0 ; BPB_TotSec32, filled in format_disk_fat32 + dd 0 ; BPB_FATSz32, filled in format_disk_fat32 + dw 0 ; BPB_ExtFlags + dw 0 ; BPB_FSVer + dd 2 ; BPB_RootClus + dw 1 ; BPB_FSInfo + dw 0 ; BPB_BkBootSec + rb 12 ; BPB_Reserved + db 80h ; BS_DrvNum + db 0 ; BS_Reserved1 + db 29h ; BS_BootSig + dd 0 ; BS_VolID, filled in format_disk_fat32 + db 'NO NAME ' ; BS_VolLab + db 'FAT32 ' ; BS_FilSysType +; same bytes as in fat16bootsector_stub + db 0CDh, 19h, 0EBh, 0FEh ; int 19h, jmp $ +fat32bootsector_stub_size = $ - fat32bootsector_stub diff --git a/kernel/trunk/drivers/tmpdisk_work.inc b/kernel/trunk/drivers/tmpdisk_work.inc new file mode 100644 index 0000000000..7f514373d6 --- /dev/null +++ b/kernel/trunk/drivers/tmpdisk_work.inc @@ -0,0 +1,144 @@ +; Callbacks which implement tmpdisk-specific disk functions for tmpdisk.asm. + +; The first argument of every callback is .userdata = userdata arg of AddDisk. +; For tmpdisk, .userdata is the disk id, one of 0,...,max_num_disks-1. + +DISK_STATUS_OK = 0 ; success +DISK_STATUS_GENERAL_ERROR = -1; if no other code is suitable +DISK_STATUS_INVALID_CALL = 1 ; invalid input parameters +DISK_STATUS_NO_MEDIA = 2 ; no media present +DISK_STATUS_END_OF_MEDIA = 3 ; end of media while reading/writing data + +; The last function that is called for the given disk. The kernel calls it when +; the kernel has finished all operations with the disk and it is safe to free +; all driver-specific data identified by 'userdata'. +proc tmpdisk_close + virtual at esp+4 + .userdata dd ? + end virtual +; Free the memory for disk and zero global variables. + mov edx, [.userdata] + mov [disk_sizes+edx*4], 0 + xor eax, eax + xchg eax, [disk_pointers+edx*4] + stdcall KernelFree, eax + retn 4 +endp + +struc DISKMEDIAINFO +{ + .flags dd ? +DISK_MEDIA_READONLY = 1 + .sectorsize dd ? + .capacity dq ? +} +virtual at 0 +DISKMEDIAINFO DISKMEDIAINFO +end virtual + +; Returns information about disk media. +proc tmpdisk_querymedia + virtual at esp+4 + .userdata dd ? + .info dd ? + end virtual +; Media is always present, sector size is always 512 bytes, +; the size of disk in sectors is stored in a global variable. + mov edx, [.userdata] + mov ecx, [.info] + mov [ecx+DISKMEDIAINFO.flags], 0 + mov [ecx+DISKMEDIAINFO.sectorsize], 512 + mov eax, [disk_sizes+edx*4] + mov dword [ecx+DISKMEDIAINFO.capacity], eax + mov dword [ecx+DISKMEDIAINFO.capacity+4], 0 +; Return zero as an indicator of success. + xor eax, eax + retn 8 +endp + +; Reads one or more sectors from the device. +tmpdisk_read: + xor edx, edx ; 0 = reading + jmp tmpdisk_readwrite + +; Writes one or more sectors to the device. +tmpdisk_write: + mov dl, 1 ; 1 = writing +; Fall through to tmpdisk_readwrite. + +; Common procedure for reading and writing. +; dl = 0 for reading, dl = 1 for writing. +; Arguments of tmpdisk_read and tmpdisk_write are the same, +; they continue to be stack arguments of this procedure. +proc tmpdisk_readwrite \ + userdata:dword, \ + buffer:dword, \ + start_sector:qword, \ + numsectors_ptr:dword +; 1. Save used registers to be stdcall. + push esi edi + mov esi, [userdata] + mov edi, [numsectors_ptr] +; 1. Determine number of sectors to be transferred. +; This is either the requested number of sectors or number of sectors +; up to the disk boundary, depending of what is less. + xor ecx, ecx +; 1a. Test whether [start_sector] is less than [disk_sizes] for selected disk. +; If so, calculate number of sectors between [start_sector] and [disk_sizes]. +; Otherwise, the actual number of sectors is zero. + cmp dword [start_sector+4], ecx + jnz .got_number + mov eax, [disk_sizes+esi*4] + sub eax, dword [start_sector] + jbe .got_number +; 1b. Get the requested number of sectors. + mov ecx, [edi] +; 1c. If it is greater than number of sectors calculated in 1a, use the value +; from 1a. + cmp ecx, eax + jb .got_number + mov ecx, eax +.got_number: +; 2. Compare the actual number of sectors with requested. If they are +; equal, set eax (it will be the returned value) to zero. Otherwise, +; use DISK_STATUS_END_OF_MEDIA. + xor eax, eax + cmp ecx, [edi] + jz @f + mov al, DISK_STATUS_END_OF_MEDIA +@@: +; 3. Store the actual number of sectors. + mov [edi], ecx +; 4. Calculate source and destination addresses. + mov edi, dword [start_sector] + shl edi, 9 + add edi, [disk_pointers+esi*4] + mov esi, [buffer] +; 5. Calculate number of dwords to be transferred. + shl ecx, 9-2 +; 6. Now esi = [buffer], edi = pointer inside disk. +; This is normal for write operations; +; exchange esi and edi for read operations. + test dl, dl + jnz @f + xchg esi, edi +@@: +; 7. Copy data. + rep movsd +; 8. Restore used registers to be stdcall and return. +; The value in eax was calculated in step 2. + pop edi esi + ret +endp + +; The kernel calls this function when initializing cache subsystem for +; the media. This call allows the driver to adjust the cache size. +proc tmpdisk_adjust_cache_size + virtual at esp+4 + .userdata dd ? + .suggested_size dd ? + end virtual +; Since tmpdisk does not need cache, just return 0. + xor eax, eax + retn 8 +endp