Subversion Repositories Kolibri OS

Rev

Rev 3742 | Rev 4273 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed

  1. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  2. ;;                                                              ;;
  3. ;; Copyright (C) KolibriOS team 2011-2012. All rights reserved. ;;
  4. ;; Distributed under terms of the GNU General Public License    ;;
  5. ;;                                                              ;;
  6. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  7.  
  8. $Revision: 3913 $
  9.  
  10. ; =============================================================================
  11. ; ================================= Constants =================================
  12. ; =============================================================================
  13. ; Error codes for callback functions.
  14. DISK_STATUS_OK              = 0 ; success
  15. DISK_STATUS_GENERAL_ERROR   = -1; if no other code is suitable
  16. DISK_STATUS_INVALID_CALL    = 1 ; invalid input parameters
  17. DISK_STATUS_NO_MEDIA        = 2 ; no media present
  18. DISK_STATUS_END_OF_MEDIA    = 3 ; end of media while reading/writing data
  19. ; Driver flags. Represent bits in DISK.DriverFlags.
  20. DISK_NO_INSERT_NOTIFICATION = 1
  21. ; Media flags. Represent bits in DISKMEDIAINFO.Flags.
  22. DISK_MEDIA_READONLY = 1
  23.  
  24. ; If too many partitions are detected,there is probably an error on the disk.
  25. ; 256 partitions should be enough for any reasonable use.
  26. ; Also, the same number is limiting the number of MBRs to process; if
  27. ; too many MBRs are visible,there probably is a loop in the MBR structure.
  28. MAX_NUM_PARTITIONS = 256
  29.  
  30. ; =============================================================================
  31. ; ================================ Structures =================================
  32. ; =============================================================================
  33. ; This structure defines all callback functions for working with the physical
  34. ; device. They are implemented by a driver. Objects with this structure reside
  35. ; in a driver.
  36. struct  DISKFUNC
  37.         strucsize       dd ?
  38. ; Size of the structure. This field is intended for possible extensions of
  39. ; this structure. If a new function is added to this structure and a driver
  40. ; implements an old version, the caller can detect this by checking .strucsize,
  41. ; so the driver remains compatible.
  42.         close           dd ?
  43. ; The pointer to the function which frees all driver-specific resources for
  44. ; the disk.
  45. ; Optional, may be NULL.
  46. ; void close(void* userdata);
  47.         closemedia      dd ?
  48. ; The pointer to the function which informs the driver that the kernel has
  49. ; finished all processing with the current media. If media is removed, the
  50. ; driver should decline all requests to that media with DISK_STATUS_NO_MEDIA,
  51. ; even if new media is inserted, until this function is called. If media is
  52. ; removed, a new call to 'disk_media_changed' is not allowed until this
  53. ; function is called.
  54. ; Optional, may be NULL (if media is not removable).
  55. ; void closemedia(void* userdata);
  56.         querymedia      dd ?
  57. ; The pointer to the function which determines capabilities of the media.
  58. ; int querymedia(void* userdata, DISKMEDIAINFO* info);
  59. ; Return value: one of DISK_STATUS_*
  60.         read            dd ?
  61. ; The pointer to the function which reads data from the device.
  62. ; int read(void* userdata, void* buffer, __int64 startsector, int* numsectors);
  63. ; input: *numsectors = number of sectors to read
  64. ; output: *numsectors = number of sectors which were successfully read
  65. ; Return value: one of DISK_STATUS_*
  66.         write           dd ?
  67. ; The pointer to the function which writes data to the device.
  68. ; Optional, may be NULL.
  69. ; int write(void* userdata, void* buffer, __int64 startsector, int* numsectors);
  70. ; input: *numsectors = number of sectors to write
  71. ; output: *numsectors = number of sectors which were successfully written
  72. ; Return value: one of DISK_STATUS_*
  73.         flush           dd ?
  74. ; The pointer to the function which flushes the internal device cache.
  75. ; Optional, may be NULL.
  76. ; int flush(void* userdata);
  77. ; Return value: one of DISK_STATUS_*
  78. ; Note that read/write are called by the cache manager, so a driver should not
  79. ; create a software cache. This function is implemented for flushing a hardware
  80. ; cache, if it exists.
  81.         adjust_cache_size       dd ?
  82. ; The pointer to the function which returns the cache size for this device.
  83. ; Optional, may be NULL.
  84. ; unsigned int adjust_cache_size(unsigned int suggested_size);
  85. ; Return value: 0 = disable cache, otherwise = used cache size in bytes.
  86. ends
  87.  
  88. ; This structure holds information on a medium.
  89. ; Objects with this structure are allocated by the kernel as a part of the DISK
  90. ; structure and are filled by a driver in the 'querymedia' callback.
  91. struct  DISKMEDIAINFO
  92.         Flags           dd ?
  93. ; Combination of DISK_MEDIA_* bits.
  94.         SectorSize      dd ?
  95. ; Size of the sector.
  96.         Capacity        dq ?
  97. ; Size of the media in sectors.
  98. ends
  99.  
  100. ; This structure represents the disk cache. To follow the old implementation,
  101. ; there are two distinct caches for a disk, one for "system" data,and the other
  102. ; for "application" data.
  103. struct  DISKCACHE
  104.         mutex           MUTEX
  105. ; Lock to protect the cache.
  106. ; The following fields are inherited from data32.inc:cache_ideX.
  107.         pointer         dd ?
  108.         data_size       dd ?    ; unused
  109.         data            dd ?
  110.         sad_size        dd ?
  111.         search_start    dd ?
  112. ends
  113.  
  114. ; This structure represents a disk device and its media for the kernel.
  115. ; This structure is allocated by the kernel in the 'disk_add' function,
  116. ; freed in the 'disk_dereference' function.
  117. struct  DISK
  118. ; Fields of disk object
  119.         Next            dd ?
  120.         Prev            dd ?
  121. ; All disk devices are linked in one list with these two fields.
  122. ; Head of the list is the 'disk_list' variable.
  123.         Functions       dd ?
  124. ; Pointer to the 'DISKFUNC' structure with driver functions.
  125.         Name            dd ?
  126. ; Pointer to the string used for accesses through the global filesystem.
  127.         UserData        dd ?
  128. ; This field is passed to all callback functions so a driver can decide which
  129. ; physical device is addressed.
  130.         DriverFlags     dd ?
  131. ; Bitfield. Currently only DISK_NO_INSERT_NOTIFICATION bit is defined.
  132. ; If it is set, the driver will never issue 'disk_media_changed' notification
  133. ; with argument set to true, so the kernel must try to detect media during
  134. ; requests from the file system.
  135.         RefCount        dd ?
  136. ; Count of active references to this structure. One reference is kept during
  137. ; the lifetime of the structure between 'disk_add' and 'disk_del'.
  138. ; Another reference is taken during any filesystem operation for this disk.
  139. ; One reference is added if media is inserted.
  140. ; The structure is destroyed when the reference count decrements to zero:
  141. ; this usually occurs in 'disk_del', but can be delayed to the end of last
  142. ; filesystem operation, if one is active.
  143.         MediaLock       MUTEX
  144. ; Lock to protect the MEDIA structure. See the description after
  145. ; 'disk_list_mutex' for the locking strategy.
  146. ; Fields of media object
  147.         MediaInserted   db ?
  148. ; 0 if media is not inserted, nonzero otherwise.
  149.         MediaUsed       db ?
  150. ; 0 if media fields are not used, nonzero otherwise. If .MediaRefCount is
  151. ; nonzero, this field is nonzero too; however, when .MediaRefCount goes
  152. ; to zero, there is some time interval during which media object is still used.
  153.                         dw ? ; padding
  154. ; The following fields are not valid unless either .MediaInserted is nonzero
  155. ; or they are accessed from a code which has obtained the reference when
  156. ; .MediaInserted was nonzero.
  157.         MediaRefCount   dd ?
  158. ; Count of active references to the media object. One reference is kept during
  159. ; the lifetime of the media between two calls to 'disk_media_changed'.
  160. ; Another reference is taken during any filesystem operation for this media.
  161. ; The callback 'closemedia' is called when the reference count decrements to
  162. ; zero: this usually occurs in 'disk_media_changed', but can be delayed to the
  163. ; end of the last filesystem operation, if one is active.
  164.         MediaInfo       DISKMEDIAINFO
  165. ; This field keeps information on the current media.
  166.         NumPartitions   dd ?
  167. ; Number of partitions on this media.
  168.         Partitions      dd ?
  169. ; Pointer to array of .NumPartitions pointers to PARTITION structures.
  170.         cache_size      dd ?
  171. ; inherited from cache_ideX_size
  172.         SysCache        DISKCACHE
  173.         AppCache        DISKCACHE
  174. ; Two caches for the disk.
  175. ends
  176.  
  177. ; This structure represents one partition for the kernel. This is a base
  178. ; template, the actual contents after common fields is determined by the
  179. ; file system code for this partition.
  180. struct  PARTITION
  181.         FirstSector     dq ?
  182. ; First sector of the partition.
  183.         Length          dq ?
  184. ; Length of the partition in sectors.
  185.         Disk            dd ?
  186. ; Pointer to parent DISK structure.
  187.         FSUserFunctions dd ?
  188. ; Handlers for the sysfunction 70h. This field is a pointer to the following
  189. ; array. The first dword is pointer to disconnect handler.
  190. ; The first dword is a number of supported subfunctions, other dwords
  191. ; point to handlers of corresponding subfunctions.
  192. ; ...fs-specific data may follow...
  193. ends
  194.  
  195. ; This is an external structure, it represents an entry in the partition table.
  196. struct  PARTITION_TABLE_ENTRY
  197.         Bootable        db ?
  198. ; 80h = bootable partition, 0 = non-bootable partition, other values = invalid
  199.         FirstHead       db ?
  200.         FirstSector     db ?
  201.         FirstTrack      db ?
  202. ; Coordinates of first sector in CHS.
  203.         Type            db ?
  204. ; Partition type, one of predefined constants. 0 = empty, several types denote
  205. ; extended partition (see process_partition_table_entry), we are not interested
  206. ; in other values.
  207.         LastHead        db ?
  208.         LastSector      db ?
  209.         LastTrack       db ?
  210. ; Coordinates of last sector in CHS.
  211.         FirstAbsSector  dd ?
  212. ; Coordinate of first sector in LBA.
  213.         Length          dd ?
  214. ; Length of the partition in sectors.
  215. ends
  216.  
  217. ; =============================================================================
  218. ; ================================ Global data ================================
  219. ; =============================================================================
  220. iglobal
  221. ; The pseudo-item for the list of all DISK structures.
  222. ; Initialized to the empty list.
  223. disk_list:
  224.         dd      disk_list
  225.         dd      disk_list
  226. endg
  227. uglobal
  228. ; This mutex guards all operations with the global list of DISK structures.
  229. disk_list_mutex MUTEX
  230. ; * There are two dependent objects, a disk and a media. In the simplest case,
  231. ;   disk and media are both non-removable. However, in the general case both
  232. ;   can be removed at any time, simultaneously or only media,and this makes things
  233. ;   complicated.
  234. ; * For efficiency, both disk and media objects are located in the one
  235. ;   structure named DISK. However, logically they are different.
  236. ; * The following operations use data of disk object: adding (disk_add);
  237. ;   deleting (disk_del); filesystem (fs_lfn which eventually calls
  238. ;   dyndisk_handler or dyndisk_enum_root).
  239. ; * The following operations use data of media object: adding/removing
  240. ;   (disk_media_changed); filesystem (fs_lfn which eventually calls
  241. ;   dyndisk_handler; dyndisk_enum_root doesn't work with media).
  242. ; * Notifications disk_add, disk_media_changed, disk_del are synchronized
  243. ;   between themselves, this is a requirement for the driver. However, file
  244. ;   system operations are asynchronous, can be issued at any time by any
  245. ;   thread.
  246. ; * We must prevent a situation when a filesystem operation thinks that the
  247. ;   object is still valid but in fact the notification has destroyed the
  248. ;   object. So we keep a reference counter for both disk and media and destroy
  249. ;   the object when this counter goes to zero.
  250. ; * The driver must know when it is safe to free driver-allocated resources.
  251. ;   The object can be alive even after death notification has completed.
  252. ;   We use special callbacks to satisfy both assertions: 'close' for the disk
  253. ;   and 'closemedia' for the media. The destruction of the object includes
  254. ;   calling the corresponding callback.
  255. ; * Each filesystem operation keeps one reference for the disk and one
  256. ;   reference for the media. Notification disk_del forces notification on the
  257. ;   media death, so the reference counter for the disk is always not less than
  258. ;   the reference counter for the media.
  259. ; * Two operations "get the object" and "increment the reference counter" can
  260. ;   not be done simultaneously. We use a mutex to guard the consistency here.
  261. ;   It must be a part of the container for the object, so that this mutex can
  262. ;   be acquired as a part of getting the object from the container. The
  263. ;   container for disk object is the global list, and this list is guarded by
  264. ;   'disk_list_mutex'. The container for media object is the disk object, and
  265. ;   the corresponding mutex is DISK.MediaLock.
  266. ; * Notifications do not change the data of objects, they can only remove
  267. ;   objects. Thus we don't need another synchronization at this level. If two
  268. ;   filesystem operations are referencing the same filesystem data, this is
  269. ;   better resolved at the level of the filesystem.
  270. endg
  271.  
  272. iglobal
  273. ; The function 'disk_scan_partitions' needs three 512-byte buffers for
  274. ; MBR, bootsector and fs-temporary sector data. It can not use the static
  275. ; buffers always, since it can be called for two or more disks in parallel.
  276. ; However, this case is not typical. We reserve three static 512-byte buffers
  277. ; and a flag that these buffers are currently used. If 'disk_scan_partitions'
  278. ; detects that the buffers are currently used, it allocates buffers from the
  279. ; heap.
  280. ; The flag is implemented as a global dword variable. When the static buffers
  281. ; are not used, the value is -1. When the static buffers are used, the value
  282. ; is normally 0 and temporarily can become greater. The function increments
  283. ; this value. If the resulting value is zero, it uses the buffers and
  284. ; decrements the value when the job is done. Otherwise, it immediately
  285. ; decrements the value and uses buffers from the heap, allocated in the
  286. ; beginning and freed in the end.
  287. partition_buffer_users  dd      -1
  288. endg
  289. uglobal
  290. ; The static buffers for MBR, bootsector and fs-temporary sector data.
  291. align 16
  292. mbr_buffer      rb      512
  293. bootsect_buffer rb      512
  294. fs_tmp_buffer   rb      512
  295. endg
  296.  
  297. iglobal
  298. ; This is the array of default implementations of driver callbacks.
  299. ; Same as DRIVERFUNC structure except for the first field; all functions must
  300. ; have the default implementations.
  301. align 4
  302. disk_default_callbacks:
  303.         dd      disk_default_close
  304.         dd      disk_default_closemedia
  305.         dd      disk_default_querymedia
  306.         dd      disk_default_read
  307.         dd      disk_default_write
  308.         dd      disk_default_flush
  309.         dd      disk_default_adjust_cache_size
  310. endg
  311.  
  312. ; =============================================================================
  313. ; ================================= Functions =================================
  314. ; =============================================================================
  315.  
  316. ; This function registers a disk device.
  317. ; This includes:
  318. ; - allocating an internal structure describing this device;
  319. ; - registering this structure in the global filesystem.
  320. ; The function initializes the disk as if there is no media. If a media is
  321. ; present, the function 'disk_media_changed' should be called after this
  322. ; function succeeds.
  323. ; Parameters:
  324. ; [esp+4] = pointer to DISKFUNC structure with the callbacks
  325. ; [esp+8] = pointer to name (ASCIIZ string)
  326. ; [esp+12] = userdata to be passed to the callbacks as is.
  327. ; [esp+16] = flags, bitfield. Currently only DISK_NO_INSERT_NOTIFICATION bit
  328. ;            is defined.
  329. ; Return value:
  330. ; NULL = operation has failed
  331. ; non-NULL = handle of the disk. This handle can be used
  332. ; in the operations with other Disk* functions.
  333. ; The handle is the pointer to the internal structure DISK.
  334. disk_add:
  335.         push    ebx esi         ; save used registers to be stdcall
  336. ; 1. Allocate the DISK structure.
  337. ; 1a. Call the heap manager.
  338.         movi    eax, sizeof.DISK
  339.         call    malloc
  340. ; 1b. Check the result. If allocation failed, return (go to 9) with eax = 0.
  341.         test    eax, eax
  342.         jz      .nothing
  343. ; 2. Copy the disk name to the DISK structure.
  344. ; 2a. Get length of the name, including the terminating zero.
  345.         mov     ebx, [esp+8+8]  ; ebx = pointer to name
  346.         push    eax             ; save allocated pointer to DISK
  347.         xor     eax, eax        ; the argument of malloc() is in eax
  348. @@:
  349.         inc     eax
  350.         cmp     byte [ebx+eax-1], 0
  351.         jnz     @b
  352. ; 2b. Call the heap manager.
  353.         call    malloc
  354. ; 2c. Check the result. If allocation failed, go to 7.
  355.         pop     esi             ; restore allocated pointer to DISK
  356.         test    eax, eax
  357.         jz      .free
  358. ; 2d. Store the allocated pointer to the DISK structure.
  359.         mov     [esi+DISK.Name], eax
  360. ; 2e. Copy the name.
  361. @@:
  362.         mov     dl, [ebx]
  363.         mov     [eax], dl
  364.         inc     ebx
  365.         inc     eax
  366.         test    dl, dl
  367.         jnz     @b
  368. ; 3. Copy other arguments of the function to the DISK structure.
  369.         mov     eax, [esp+4+8]
  370.         mov     [esi+DISK.Functions], eax
  371.         mov     eax, [esp+12+8]
  372.         mov     [esi+DISK.UserData], eax
  373.         mov     eax, [esp+16+8]
  374.         mov     [esi+DISK.DriverFlags], eax
  375. ; 4. Initialize other fields of the DISK structure.
  376. ; Media is not inserted, reference counter is 1.
  377.         lea     ecx, [esi+DISK.MediaLock]
  378.         call    mutex_init
  379.         xor     eax, eax
  380.         mov     dword [esi+DISK.MediaInserted], eax
  381.         mov     [esi+DISK.MediaRefCount], eax
  382.         inc     eax
  383.         mov     [esi+DISK.RefCount], eax
  384. ; The DISK structure is initialized.
  385. ; 5. Insert the new structure to the global list.
  386. ; 5a. Acquire the mutex.
  387.         mov     ecx, disk_list_mutex
  388.         call    mutex_lock
  389. ; 5b. Insert item to the tail of double-linked list.
  390.         mov     edx, disk_list
  391.         list_add_tail esi, edx     ;esi= new edx= list head
  392. ; 5c. Release the mutex.
  393.         call    mutex_unlock
  394. ; 6. Return with eax = pointer to DISK.
  395.         xchg    eax, esi
  396.         jmp     .nothing
  397. .free:
  398. ; Memory allocation for DISK structure succeeded, but for disk name failed.
  399. ; 7. Free the DISK structure.
  400.         xchg    eax, esi
  401.         call    free
  402. ; 8. Return with eax = 0.
  403.         xor     eax, eax
  404. .nothing:
  405. ; 9. Return.
  406.         pop     esi ebx         ; restore used registers to be stdcall
  407.         ret     16              ; purge 4 dword arguments to be stdcall
  408.  
  409. ; This function deletes a disk device from the global filesystem.
  410. ; This includes:
  411. ; - removing a media including all partitions;
  412. ; - deleting this structure from the global filesystem;
  413. ; - dereferencing the DISK structure and possibly destroying it.
  414. ; Parameters:
  415. ; [esp+4] = handle of the disk, i.e. the pointer to the DISK structure.
  416. ; Return value: none.
  417. disk_del:
  418.         push    esi         ; save used registers to be stdcall
  419. ; 1. Force media to be removed. If the media is already removed, the
  420. ; call does nothing.
  421.         mov     esi, [esp+4+4]  ; esi = handle of the disk
  422.         stdcall disk_media_changed, esi, 0
  423. ; 2. Delete the structure from the global list.
  424. ; 2a. Acquire the mutex.
  425.         mov     ecx, disk_list_mutex
  426.         call    mutex_lock
  427. ; 2b. Delete item from double-linked list.
  428.         mov     eax, [esi+DISK.Next]
  429.         mov     edx, [esi+DISK.Prev]
  430.         mov     [eax+DISK.Prev], edx
  431.         mov     [edx+DISK.Next], eax
  432. ; 2c. Release the mutex.
  433.         call    mutex_unlock
  434. ; 3. The structure still has one reference created in disk_add. Remove this
  435. ; reference. If there are no other references, disk_dereference will free the
  436. ; structure.
  437.         call    disk_dereference
  438. ; 4. Return.
  439.         pop     esi             ; restore used registers to be stdcall
  440.         ret     4               ; purge 1 dword argument to be stdcall
  441.  
  442. ; This is an internal function which removes a previously obtained reference
  443. ; to the disk. If this is the last reference, this function lets the driver
  444. ; finalize all associated data, and afterwards frees the DISK structure.
  445. ; esi = pointer to DISK structure
  446. disk_dereference:
  447. ; 1. Decrement reference counter. Use atomic operation to correctly handle
  448. ; possible simultaneous calls.
  449.         lock dec [esi+DISK.RefCount]
  450. ; 2. If the result is nonzero, there are other references, so nothing to do.
  451. ; In this case, return (go to 4).
  452.         jnz     .nothing
  453. ; 3. If we are here, we just removed the last reference and must destroy the
  454. ; disk object.
  455. ; 3a. Call the driver.
  456.         mov     al, DISKFUNC.close
  457.         stdcall disk_call_driver
  458. ; 3b. Free the structure.
  459.         xchg    eax, esi
  460.         push    ebx
  461.         call    free
  462.         pop     ebx
  463. ; 4. Return.
  464. .nothing:
  465.         ret
  466.  
  467. ; This is an internal function which removes a previously obtained reference
  468. ; to the media. If this is the last reference, this function calls 'closemedia'
  469. ; callback to signal the driver that the processing has finished and it is safe
  470. ; to inform about a new media.
  471. ; esi = pointer to DISK structure
  472. disk_media_dereference:
  473. ; 1. Decrement reference counter. Use atomic operation to correctly handle
  474. ; possible simultaneous calls.
  475.         lock dec [esi+DISK.MediaRefCount]
  476. ; 2. If the result is nonzero, there are other references, so nothing to do.
  477. ; In this case, return (go to 4).
  478.         jnz     .nothing
  479. ; 3. If we are here, we just removed the last reference and must destroy the
  480. ; media object.
  481. ; Note that the same place inside the DISK structure is reused for all media
  482. ; objects, so we must guarantee that reusing does not happen while freeing.
  483. ; Reusing is only possible when someone processes a new media. There are two
  484. ; mutually exclusive variants:
  485. ; * driver issues media insert notifications (DISK_NO_INSERT_NOTIFICATION bit
  486. ;   in DISK.DriverFlags is not set). In this case, we require from the driver
  487. ;   that such notification (except for the first one) can occur only after a
  488. ;   call to 'closemedia' callback.
  489. ; * driver does not issue media insert notifications. In this case, the kernel
  490. ;   itself must sometimes check whether media is inserted. We have the flag
  491. ;   DISK.MediaUsed, visible to the kernel. This flag signals to the other parts
  492. ;   of kernel that the way is free.
  493. ; In the first case other parts of the kernel do not use DISK.MediaUsed, so it
  494. ; does not matter when this flag is cleared. In the second case this flag must
  495. ; be cleared after all other actions, including call to 'closemedia'.
  496. ; 3a. Free all partitions.
  497.         push    esi edi
  498.         mov     edi, [esi+DISK.NumPartitions]
  499.         mov     esi, [esi+DISK.Partitions]
  500.         test    edi, edi
  501.         jz      .nofree
  502. .freeloop:
  503.         lodsd
  504.         mov     ecx, [eax+PARTITION.FSUserFunctions]
  505.         call    dword [ecx]
  506.         dec     edi
  507.         jnz     .freeloop
  508. .nofree:
  509.         pop     edi esi
  510. ; 3b. Free the cache.
  511.         call    disk_free_cache
  512. ; 3c. Call the driver.
  513.         mov     al, DISKFUNC.closemedia
  514.         stdcall disk_call_driver
  515. ; 3d. Clear the flag.
  516.         mov     [esi+DISK.MediaUsed], 0
  517. .nothing:
  518.         ret
  519.  
  520. ; This function is called by the driver and informs the kernel that the media
  521. ; has changed. If the media is non-removable, it is called exactly once
  522. ; immediately after 'disk_add' and once from 'disk_del'.
  523. ; Parameters:
  524. ; [esp+4] = handle of the disk, i.e. the pointer to the DISK structure.
  525. ; [esp+8] = new status of the media: zero = no media, nonzero = media inserted.
  526. disk_media_changed:
  527.         push    ebx esi edi             ; save used registers to be stdcall
  528. ; 1. Remove the existing media, if it is present.
  529.         mov     esi, [esp+4+12]         ; esi = pointer to DISK
  530. ; 1a. Check whether it is present. Since DISK.MediaInserted is changed only
  531. ; in this function and calls to this function are synchronized, no lock is
  532. ; required for checking.
  533.         cmp     [esi+DISK.MediaInserted], 0
  534.         jz      .noremove
  535. ; We really need to remove the media.
  536. ; 1b. Acquire mutex.
  537.         lea     ecx, [esi+DISK.MediaLock]
  538.         call    mutex_lock
  539. ; 1c. Clear the flag.
  540.         mov     [esi+DISK.MediaInserted], 0
  541. ; 1d. Release mutex.
  542.         call    mutex_unlock
  543. ; 1e. Remove the "lifetime" reference and possibly destroy the structure.
  544.         call    disk_media_dereference
  545. .noremove:
  546. ; 2. Test whether there is new media.
  547.         cmp     dword [esp+8+12], 0
  548.         jz      .noinsert
  549. ; Yep, there is.
  550. ; 3. Process the new media. We assume that all media fields are available to
  551. ; use, see comments in 'disk_media_dereference' (this covers using by previous
  552. ; media referencers) and note that calls to this function are synchronized
  553. ; (this covers using by new media referencers).
  554. ; 3a. Call the 'querymedia' callback.
  555. ; .Flags are set to zero for possible future extensions.
  556.         lea     edx, [esi+DISK.MediaInfo]
  557.         and     [edx+DISKMEDIAINFO.Flags], 0
  558.         mov     al, DISKFUNC.querymedia
  559.         stdcall disk_call_driver, edx
  560. ; 3b. Check the result of the callback. Abort if it failed.
  561.         test    eax, eax
  562.         jnz     .noinsert
  563. ; 3c. Allocate the cache unless disabled by the driver. Abort if failed.
  564.         call    disk_init_cache
  565.         test    al, al
  566.         jz      .noinsert
  567. ; 3d. Acquire the lifetime reference for the media object.
  568.         inc     [esi+DISK.MediaRefCount]
  569. ; 3e. Scan for partitions. Ignore result; the list of partitions is valid even
  570. ; on errors.
  571.         call    disk_scan_partitions
  572. ; 3f. Media is inserted and available for use.
  573.         inc     [esi+DISK.MediaInserted]
  574. .noinsert:
  575. ; 4. Return.
  576.         pop     edi esi ebx             ; restore used registers to be stdcall
  577.         ret     8                       ; purge 2 dword arguments to be stdcall
  578.  
  579. ; This function is a thunk for all functions of a disk driver.
  580. ; It checks whether the referenced function is implemented in the driver.
  581. ; If so, this function jumps to the function in the driver.
  582. ; Otherwise, it jumps to the default implementation.
  583. ; al = offset of function in the DISKFUNC structure;
  584. ; esi = pointer to the DISK structure;
  585. ; stack is the same as for the corresponding function except that the
  586. ; first parameter (void* userdata) is prepended automatically.
  587. disk_call_driver:
  588.         movzx   eax, al ; eax = offset of function in the DISKFUNC structure
  589. ; 1. Prepend the first argument to the stack.
  590.         pop     ecx     ; ecx = return address
  591.         push    [esi+DISK.UserData]     ; add argument
  592.         push    ecx     ; save return address
  593. ; 2. Check that the required function is inside the table. If not, go to 5.
  594.         mov     ecx, [esi+DISK.Functions]
  595.         cmp     eax, [ecx+DISKFUNC.strucsize]
  596.         jae     .default
  597. ; 3. Check that the required function is implemented. If not, go to 5.
  598.         mov     ecx, [ecx+eax]
  599.         test    ecx, ecx
  600.         jz      .default
  601. ; 4. Jump to the required function.
  602.         jmp     ecx
  603. .default:
  604. ; 5. Driver does not implement the required function; use default implementation.
  605.         jmp     dword [disk_default_callbacks+eax-4]
  606.  
  607. ; The default implementation of DISKFUNC.querymedia.
  608. disk_default_querymedia:
  609.         movi    eax, DISK_STATUS_INVALID_CALL
  610.         ret     8
  611.  
  612. ; The default implementation of DISKFUNC.read and DISKFUNC.write.
  613. disk_default_read:
  614. disk_default_write:
  615.         movi    eax, DISK_STATUS_INVALID_CALL
  616.         ret     20
  617.  
  618. ; The default implementation of DISKFUNC.close, DISKFUNC.closemedia and
  619. ; DISKFUNC.flush.
  620. disk_default_close:
  621. disk_default_closemedia:
  622. disk_default_flush:
  623.         xor     eax, eax
  624.         ret     4
  625.  
  626. ; The default implementation of DISKFUNC.adjust_cache_size.
  627. disk_default_adjust_cache_size:
  628.         mov     eax, [esp+8]
  629.         ret     8
  630.  
  631. ; This is an internal function called from 'disk_media_changed' when a new media
  632. ; is detected. It creates the list of partitions for the media.
  633. ; If media is not partitioned, then the list consists of one partition which
  634. ; covers all the media.
  635. ; esi = pointer to the DISK structure.
  636. disk_scan_partitions:
  637. ; 1. Initialize .NumPartitions and .Partitions fields as zeros: empty list.
  638.         and     [esi+DISK.NumPartitions], 0
  639.         and     [esi+DISK.Partitions], 0
  640. ; 2. Currently we can work only with 512-bytes sectors. Check this restriction.
  641. ; The only exception is 2048-bytes CD/DVD, but they are not supported yet by
  642. ; this code.
  643.         cmp     [esi+DISK.MediaInfo.SectorSize], 512
  644.         jz      .doscan
  645.         DEBUGF 1,'K : sector size is %d, only 512 is supported\n',[esi+DISK.MediaInfo.SectorSize]
  646.         ret
  647. .doscan:
  648. ; 3. Acquire the buffer for MBR and bootsector tests. See the comment before
  649. ; the 'partition_buffer_users' variable.
  650.         mov     ebx, mbr_buffer         ; assume the global buffer is free
  651.         lock inc [partition_buffer_users]
  652.         jz      .buffer_acquired        ; yes, it is free
  653.         lock dec [partition_buffer_users]       ; no, we must allocate
  654.         stdcall kernel_alloc, 512*3
  655.         test    eax, eax
  656.         jz      .nothing
  657.         xchg    eax, ebx
  658. .buffer_acquired:
  659. ; MBR/EBRs are organized in the chain. We use a loop over MBR/EBRs, but no
  660. ; more than MAX_NUM_PARTITION times.
  661. ; 4. Prepare things for the loop.
  662. ; ebp will hold the sector number for current MBR/EBR.
  663. ; [esp] will hold the sector number for current extended partition, if there
  664. ; is one.
  665. ; [esp+4] will hold the counter that prevents long loops.
  666.         push    ebp             ; save ebp
  667.         push    MAX_NUM_PARTITIONS      ; the counter of max MBRs to process
  668.         xor     ebp, ebp        ; start from sector zero
  669.         push    ebp             ; no extended partition yet
  670. .new_mbr:
  671. ; 5. Read the current sector.
  672. ; Note that 'read' callback operates with 64-bit sector numbers, so we must
  673. ; push additional zero as a high dword of sector number.
  674.         mov     al, DISKFUNC.read
  675.         push    1
  676.         stdcall disk_call_driver, ebx, ebp, 0, esp
  677.         pop     ecx
  678. ; 6. If the read has failed, abort the loop.
  679.         dec     ecx
  680.         jnz     .mbr_failed
  681. ; 7. Check the MBR/EBR signature. If it is wrong, abort the loop.
  682. ; Soon we will access the partition table which starts at ebx+0x1BE,
  683. ; so we can fill its address right now. If we do it now, then the addressing
  684. ; [ecx+0x40] is shorter than [ebx+0x1fe]: one-byte offset vs 4-bytes offset.
  685.         lea     ecx, [ebx+0x1be]        ; ecx -> partition table
  686.         cmp     word [ecx+0x40], 0xaa55
  687.         jnz     .mbr_failed
  688. ; 8. The MBR is treated differently from EBRs. For MBR we additionally need to
  689. ; execute step 9 and possibly step 10.
  690.         test    ebp, ebp
  691.         jnz     .mbr
  692. ; The partition table can be present or not present. In the first case, we just
  693. ; read the MBR. In the second case, we just read the bootsector for a
  694. ; filesystem.
  695. ; The following algorithm is used to distinguish between these cases.
  696. ; A. If at least one entry of the partition table is invalid, this is
  697. ;    a bootsector. See the description of 'is_partition_table_entry' for
  698. ;    definition of validity.
  699. ; B. If all entries are empty (filesystem type field is zero) and the first
  700. ;    byte is jmp opcode (0EBh or 0E9h), this is a bootsector which happens to
  701. ;    have zeros in the place of partition table.
  702. ; C. Otherwise, this is an MBR.
  703. ; 9. Test for MBR vs bootsector.
  704. ; 9a. Check entries. If any is invalid, go to 10 (rule A).
  705.         call    is_partition_table_entry
  706.         jc      .notmbr
  707.         add     ecx, 10h
  708.         call    is_partition_table_entry
  709.         jc      .notmbr
  710.         add     ecx, 10h
  711.         call    is_partition_table_entry
  712.         jc      .notmbr
  713.         add     ecx, 10h
  714.         call    is_partition_table_entry
  715.         jc      .notmbr
  716. ; 9b. Check types of the entries. If at least one is nonzero, go to 11 (rule C).
  717.         mov     al, [ecx-30h+PARTITION_TABLE_ENTRY.Type]
  718.         or      al, [ecx-20h+PARTITION_TABLE_ENTRY.Type]
  719.         or      al, [ecx-10h+PARTITION_TABLE_ENTRY.Type]
  720.         or      al, [ecx+PARTITION_TABLE_ENTRY.Type]
  721.         jnz     .mbr
  722. ; 9c. Empty partition table or bootsector with many zeroes? (rule B)
  723.         cmp     byte [ebx], 0EBh
  724.         jz      .notmbr
  725.         cmp     byte [ebx], 0E9h
  726.         jnz     .mbr
  727. .notmbr:
  728. ; 10. This is not an  MBR. The media is not partitioned. Create one partition
  729. ; which covers all the media and abort the loop.
  730.         stdcall disk_add_partition, 0, 0, \
  731.                 dword [esi+DISK.MediaInfo.Capacity], dword [esi+DISK.MediaInfo.Capacity+4], esi
  732.         jmp     .done
  733. .mbr:
  734. ; 11. Process all entries of the new MBR/EBR
  735.         lea     ecx, [ebx+0x1be]        ; ecx -> partition table
  736.         push    0       ; assume no extended partition
  737.         call    process_partition_table_entry
  738.         add     ecx, 10h
  739.         call    process_partition_table_entry
  740.         add     ecx, 10h
  741.         call    process_partition_table_entry
  742.         add     ecx, 10h
  743.         call    process_partition_table_entry
  744.         pop     ebp
  745. ; 12. Test whether we found a new EBR and should continue the loop.
  746. ; 12a. If there was no next EBR, return.
  747.         test    ebp, ebp
  748.         jz      .done
  749. ; Ok, we have EBR.
  750. ; 12b. EBRs addresses are relative to the start of extended partition.
  751. ; For simplicity, just abort if an 32-bit overflow occurs; large disks
  752. ; are most likely partitioned with GPT, not MBR scheme, since the precise
  753. ; calculation here would increase limit just twice at the price of big
  754. ; compatibility problems.
  755.         pop     eax     ; load extended partition
  756.         add     ebp, eax
  757.         jc      .mbr_failed
  758. ; 12c. If extended partition has not yet started, start it.
  759.         test    eax, eax
  760.         jnz     @f
  761.         mov     eax, ebp
  762. @@:
  763. ; 12c. If the limit is not exceeded, continue the loop.
  764.         dec     dword [esp]
  765.         push    eax     ; store extended partition
  766.         jnz     .new_mbr
  767. .mbr_failed:
  768. .done:
  769. ; 13. Cleanup after the loop.
  770.         pop     eax     ; not important anymore
  771.         pop     eax     ; not important anymore
  772.         pop     ebp     ; restore ebp
  773. ; 14. Release the buffer.
  774. ; 14a. Test whether it is the global buffer or we have allocated it.
  775.         cmp     ebx, mbr_buffer
  776.         jz      .release_partition_buffer
  777. ; 14b. If we have allocated it, free it.
  778.         xchg    eax, ebx
  779.         call    free
  780.         jmp     .nothing
  781. ; 14c. Otherwise, release reference.
  782. .release_partition_buffer:
  783.         lock dec [partition_buffer_users]
  784. .nothing:
  785. ; 15. Return.
  786.         ret
  787.  
  788. ; This is an internal function called from disk_scan_partitions. It checks
  789. ; whether the entry pointed to by ecx is a valid entry of partition table.
  790. ; The entry is valid if the first byte is 0 or 80h, the first sector plus the
  791. ; length is less than twice the size of media. Multiplication by two is
  792. ; required since the size mentioned in the partition table can be slightly
  793. ; greater than the real size.
  794. is_partition_table_entry:
  795. ; 1. Check .Bootable field.
  796.         mov     al, [ecx+PARTITION_TABLE_ENTRY.Bootable]
  797.         and     al, 7Fh
  798.         jnz     .invalid
  799. ; 3. Calculate first sector + length. Note that .FirstAbsSector is relative
  800. ; to the MBR/EBR, so the real sum is ebp + .FirstAbsSector + .Length.
  801.         mov     eax, ebp
  802.         xor     edx, edx
  803.         add     eax, [ecx+PARTITION_TABLE_ENTRY.FirstAbsSector]
  804.         adc     edx, 0
  805.         add     eax, [ecx+PARTITION_TABLE_ENTRY.Length]
  806.         adc     edx, 0
  807. ; 4. Divide by two.
  808.         shr     edx, 1
  809.         rcr     eax, 1
  810. ; 5. Compare with capacity. If the subtraction (edx:eax) - .Capacity does not
  811. ; overflow, this is bad.
  812.         sub     eax, dword [esi+DISK.MediaInfo.Capacity]
  813.         sbb     edx, dword [esi+DISK.MediaInfo.Capacity+4]
  814.         jnc     .invalid
  815. .valid:
  816. ; 5. Return success: CF is cleared.
  817.         clc
  818.         ret
  819. .invalid:
  820. ; 6. Return fail: CF is set.
  821.         stc
  822.         ret
  823.  
  824. ; This is an internal function called from disk_scan_partitions. It processes
  825. ; the entry pointed to by ecx.
  826. ; * If the entry is invalid, just ignore this entry.
  827. ; * If the type is zero, just ignore this entry.
  828. ; * If the type is one of types for extended partition, store the address
  829. ;   of this partition as the new MBR in [esp+4].
  830. ; * Otherwise, add the partition to the list of partitions for this disk.
  831. ;   We don't use the type from the entry to identify the file system;
  832. ;   fs-specific checks do this more reliably.
  833. process_partition_table_entry:
  834. ; 1. Check for valid entry. If invalid, return (go to 5).
  835.         call    is_partition_table_entry
  836.         jc      .nothing
  837. ; 2. Check for empty entry. If invalid, return (go to 5).
  838.         mov     al, [ecx+PARTITION_TABLE_ENTRY.Type]
  839.         test    al, al
  840.         jz      .nothing
  841. ; 3. Check for extended partition. If extended, go to 6.
  842. irp type,\
  843.     0x05,\                 ; DOS: extended partition
  844.     0x0f,\                 ; WIN95: extended partition, LBA-mapped
  845.     0xc5,\                 ; DRDOS/secured: extended partition
  846.     0xd5                   ; Old Multiuser DOS secured: extended partition
  847. {
  848.         cmp     al, type
  849.         jz      .extended
  850. }
  851. ; 4. If we are here, that is a normal partition. Add it to the list.
  852. ; Note that the first sector is relative to MBR/EBR.
  853.         mov     eax, ebp
  854.         xor     edx, edx
  855.         add     eax, [ecx+PARTITION_TABLE_ENTRY.FirstAbsSector]
  856.         adc     edx, 0
  857.         push    ecx
  858.         stdcall disk_add_partition, eax, edx, \
  859.                 [ecx+PARTITION_TABLE_ENTRY.Length], 0, esi
  860.         pop     ecx
  861. .nothing:
  862. ; 5. Return.
  863.         ret
  864. .extended:
  865. ; 6. If we are here, that is an extended partition. Store the address.
  866.         mov     eax, [ecx+PARTITION_TABLE_ENTRY.FirstAbsSector]
  867.         mov     [esp+4], eax
  868.         ret
  869.  
  870. ; This is an internal function called from disk_scan_partitions and
  871. ; process_partition_table_entry. It adds one partition to the list of
  872. ; partitions for the media.
  873. ; Important note: start, length, disk MUST be present and
  874. ; MUST be in the same order as in PARTITION structure.
  875. ; esi duplicates [disk].
  876. proc disk_add_partition stdcall uses ebx edi, start:qword, length:qword, disk:dword
  877. ; 1. Check that this partition will not exceed the limit on total number.
  878.         cmp     [esi+DISK.NumPartitions], MAX_NUM_PARTITIONS
  879.         jae     .nothing
  880. ; 2. Check that this partition does not overlap with any already registered
  881. ; partition. Since any file system assumes that the disk data will not change
  882. ; outside of its control, such overlap could be destructive.
  883. ; Since the number of partitions is usually very small and is guaranteed not
  884. ; to be large, the simple linear search is sufficient.
  885. ; 2a. Prepare the loop: edi will point to the current item of .Partitions
  886. ; array, ecx will be the current item, ebx will hold number of items left.
  887.         mov     edi, [esi+DISK.Partitions]
  888.         mov     ebx, [esi+DISK.NumPartitions]
  889.         test    ebx, ebx
  890.         jz      .partitionok
  891. .scan_existing:
  892. ; 2b. Get the next partition.
  893.         mov     ecx, [edi]
  894.         add     edi, 4
  895. ; The range [.FirstSector, .FirstSector+.Length) must be either entirely to
  896. ; the left of [start, start+length) or entirely to the right.
  897. ; 2c. Subtract .FirstSector - start. The possible overflow distinguish between
  898. ; cases "to the left" (2e) and "to the right" (2d).
  899.         mov     eax, dword [ecx+PARTITION.FirstSector]
  900.         mov     edx, dword [ecx+PARTITION.FirstSector+4]
  901.         sub     eax, dword [start]
  902.         sbb     edx, dword [start+4]
  903.         jb      .less
  904. ; 2d. .FirstSector is greater than or equal to start. Check that .FirstSector
  905. ; is greater than or equal to start+length; the subtraction
  906. ; (.FirstSector-start) - length must not cause overflow. Go to 2g if life is
  907. ; good or to 2f in the other case.
  908.         sub     eax, dword [length]
  909.         sbb     edx, dword [length+4]
  910.         jb      .overlap
  911.         jmp     .next_existing
  912. .less:
  913. ; 2e. .FirstSector is less than start. Check that .FirstSector+.Length is less
  914. ; than or equal to start. If the addition (.FirstSector-start) + .Length does
  915. ; not cause overflow, then .FirstSector + .Length is strictly less than start;
  916. ; since the equality is also valid, use decrement preliminarily. Go to 2g or
  917. ; 2f depending on the overflow.
  918.         sub     eax, 1
  919.         sbb     edx, 0
  920.         add     eax, dword [ecx+PARTITION.Length]
  921.         adc     edx, dword [ecx+PARTITION.Length+4]
  922.         jnc     .next_existing
  923. .overlap:
  924. ; 2f. The partition overlaps with previously registered partition. Say warning
  925. ; and return with nothing done.
  926.         dbgstr 'two partitions overlap, ignoring the last one'
  927.         jmp     .nothing
  928. .next_existing:
  929. ; 2g. The partition does not overlap with the current partition. Continue the
  930. ; loop.
  931.         dec     ebx
  932.         jnz     .scan_existing
  933. .partitionok:
  934. ; 3. The partition has passed tests. Reallocate the partitions array for a new
  935. ; entry.
  936. ; 3a. Call the allocator.
  937.         mov     eax, [esi+DISK.NumPartitions]
  938.         inc     eax     ; one more entry
  939.         shl     eax, 2  ; each entry is dword
  940.         call    malloc
  941. ; 3b. Test the result. If failed, return with nothing done.
  942.         test    eax, eax
  943.         jz      .nothing
  944. ; 3c. Copy the old array to the new array.
  945.         mov     edi, eax
  946.         push    esi
  947.         mov     ecx, [esi+DISK.NumPartitions]
  948.         mov     esi, [esi+DISK.Partitions]
  949.         rep movsd
  950.         pop     esi
  951. ; 3d. Set the field in the DISK structure to the new array.
  952.         xchg    [esi+DISK.Partitions], eax
  953. ; 3e. Free the old array.
  954.         call    free
  955. ; 4. Recognize the file system.
  956. ; 4a. Call the filesystem recognizer. It will allocate the PARTITION structure
  957. ; with possible filesystem-specific fields.
  958.         call    disk_detect_partition
  959. ; 4b. Check return value. If zero, return with list not changed; so far only
  960. ; the array was reallocated, this is ok for other code.
  961.         test    eax, eax
  962.         jz      .nothing
  963. ; 5. Insert the new partition to the list.
  964.         stosd
  965.         inc     [esi+DISK.NumPartitions]
  966. ; 6. Return.
  967. .nothing:
  968.         ret
  969. endp
  970.  
  971. ; This is an internal function called from disk_add_partition.
  972. ; It tries to recognize the file system on the partition and allocates the
  973. ; corresponding PARTITION structure with filesystem-specific fields.
  974. disk_detect_partition:
  975. ; This function inherits the stack frame from disk_add_partition. In stdcall
  976. ; with ebp-based frame arguments start from ebp+8, since [ebp]=saved ebp
  977. ; and [ebp+4]=return address.
  978. virtual at ebp+8
  979. .start  dq      ?
  980. .length dq      ?
  981. .disk   dd      ?
  982. end virtual
  983. ; 1. Read the bootsector to the buffer.
  984. ; When disk_add_partition is called, ebx contains a pointer to
  985. ; a three-sectors-sized buffer. This function saves ebx in the stack
  986. ; immediately before ebp.
  987.         mov     ebx, [ebp-4] ; get buffer
  988.         add     ebx, 512     ; advance over MBR data to bootsector data
  989.         add     ebp, 8       ; ebp points to part of PARTITION structure
  990.         xor     eax, eax     ; first sector of the partition
  991.         call    fs_read32_sys
  992.         push    eax
  993. ; 2. Run tests for all supported filesystems. If at least one test succeeded,
  994. ; go to 4.
  995. ; For tests:
  996. ; ebp -> first three fields of PARTITION structure, .start, .length, .disk;
  997. ; [esp] = error code after bootsector read: 0 = ok, otherwise = failed,
  998. ; ebx points to the buffer for bootsector,
  999. ; ebx+512 points to 512-bytes buffer that can be used for anything.
  1000.         call    fat_create_partition
  1001.         test    eax, eax
  1002.         jnz     .success
  1003.         call    ntfs_create_partition
  1004.         test    eax, eax
  1005.         jnz     .success
  1006.         call    ext2_create_partition
  1007.         test    eax, eax
  1008.         jnz     .success
  1009.         call    xfs_create_partition
  1010.         test    eax, eax
  1011.         jnz     .success
  1012. ; 3. No file system has recognized the volume, so just allocate the PARTITION
  1013. ; structure without extra fields.
  1014.         movi    eax, sizeof.PARTITION
  1015.         call    malloc
  1016.         test    eax, eax
  1017.         jz      .nothing
  1018.         mov     edx, dword [ebp+PARTITION.FirstSector]
  1019.         mov     dword [eax+PARTITION.FirstSector], edx
  1020.         mov     edx, dword [ebp+PARTITION.FirstSector+4]
  1021.         mov     dword [eax+PARTITION.FirstSector+4], edx
  1022.         mov     edx, dword [ebp+PARTITION.Length]
  1023.         mov     dword [eax+PARTITION.Length], edx
  1024.         mov     edx, dword [ebp+PARTITION.Length+4]
  1025.         mov     dword [eax+PARTITION.Length+4], edx
  1026.         mov     [eax+PARTITION.Disk], esi
  1027.         mov     [eax+PARTITION.FSUserFunctions], default_fs_functions
  1028. .success:
  1029. .nothing:
  1030.         sub     ebp, 8 ; restore ebp
  1031. ; 4. Return with eax = pointer to PARTITION or NULL.
  1032.         pop     ecx
  1033.         ret
  1034.  
  1035. iglobal
  1036. align 4
  1037. default_fs_functions:
  1038.         dd      free
  1039.         dd      0       ; no user functions
  1040. endg
  1041.  
  1042. ; This function is called from file_system_lfn.
  1043. ; This handler gets the control each time when fn 70 is called
  1044. ; with unknown item of root subdirectory.
  1045. ; in: esi -> name
  1046. ;     ebp = 0 or rest of name relative to esi
  1047. ; out: if the handler processes path, it must not return in file_system_lfn,
  1048. ;      but instead pop return address and return directly to the caller
  1049. ;      otherwise simply return
  1050. dyndisk_handler:
  1051.         push    ebx edi         ; save registers used in file_system_lfn
  1052. ; 1. Acquire the mutex.
  1053.         mov     ecx, disk_list_mutex
  1054.         call    mutex_lock
  1055. ; 2. Loop over the list of DISK structures.
  1056. ; 2a. Initialize.
  1057.         mov     ebx, disk_list
  1058. .scan:
  1059. ; 2b. Get the next item.
  1060.         mov     ebx, [ebx+DISK.Next]
  1061. ; 2c. Check whether the list is done. If so, go to 3.
  1062.         cmp     ebx, disk_list
  1063.         jz      .notfound
  1064. ; 2d. Compare names. If names match, go to 5.
  1065.         mov     edi, [ebx+DISK.Name]
  1066.         push    esi
  1067. @@:
  1068. ; esi points to the name from fs operation; it is terminated by zero or slash.
  1069.         lodsb
  1070.         test    al, al
  1071.         jz      .eoin_dec
  1072.         cmp     al, '/'
  1073.         jz      .eoin
  1074. ; edi points to the disk name.
  1075.         inc     edi
  1076. ; edi points to lowercase name, this is a requirement for the driver.
  1077. ; Characters at esi can have any register. Lowercase the current character.
  1078. ; This lowercasing works for latin letters and digits; since the disk name
  1079. ; should not contain other symbols, this is ok.
  1080.         or      al, 20h
  1081.         cmp     al, [edi-1]
  1082.         jz      @b
  1083. .wrongname:
  1084. ; 2f. Names don't match. Continue the loop.
  1085.         pop     esi
  1086.         jmp     .scan
  1087. .notfound:
  1088. ; The loop is done and no name matches.
  1089. ; 3. Release the mutex.
  1090.         call    mutex_unlock
  1091. ; 4. Return normally.
  1092.         pop     edi ebx         ; restore registers used in file_system_lfn
  1093.         ret
  1094. ; part of 2d: the name matches partially, but we must check that this is full
  1095. ; equality.
  1096. .eoin_dec:
  1097.         dec     esi
  1098. .eoin:
  1099.         cmp     byte [edi], 0
  1100.         jnz     .wrongname
  1101. ; We found the addressed DISK structure.
  1102. ; 5. Reference the disk.
  1103.         lock inc [ebx+DISK.RefCount]
  1104. ; 6. Now we are sure that the DISK structure is not going to die at least
  1105. ; while we are working with it, so release the global mutex.
  1106.         call    mutex_unlock
  1107.         pop     ecx             ; pop from the stack saved value of esi
  1108. ; 7. Acquire the mutex for media object.
  1109.         pop     edi             ; restore edi
  1110.         lea     ecx, [ebx+DISK.MediaLock]
  1111.         call    mutex_lock
  1112. ; 8. Get the media object. If it is not NULL, reference it.
  1113.         xor     edx, edx
  1114.         cmp     [ebx+DISK.MediaInserted], dl
  1115.         jz      @f
  1116.         mov     edx, ebx
  1117.         inc     [ebx+DISK.MediaRefCount]
  1118. @@:
  1119. ; 9. Now we are sure that the media object, if it exists, is not going to die
  1120. ; at least while we are working with it, so release the mutex for media object.
  1121.         call    mutex_unlock
  1122.         mov     ecx, ebx
  1123.         pop     ebx eax         ; restore ebx, pop return address
  1124. ; 10. Check whether the fs operation wants to enumerate partitions (go to 11)
  1125. ; or work with some concrete partition (go to 12).
  1126.         cmp     byte [esi], 0
  1127.         jnz     .haspartition
  1128. ; 11. The fs operation wants to enumerate partitions.
  1129. ; 11a. Only "list directory" operation is applicable to /<diskname> path. Check
  1130. ; the operation code. If wrong, go to 13.
  1131.         cmp     dword [ebx], 1
  1132.         jnz     .access_denied
  1133. ; 11b. If the media is inserted, use 'fs_dyndisk_next' as an enumeration
  1134. ; procedure. Otherwise, use 'fs_dyndisk_next_nomedia'.
  1135.         mov     esi, fs_dyndisk_next_nomedia
  1136.         test    edx, edx
  1137.         jz      @f
  1138.         mov     esi, fs_dyndisk_next
  1139. @@:
  1140. ; 11c. Let the procedure from fs_lfn.inc do the job.
  1141.         jmp     file_system_lfn.maindir_noesi
  1142. .haspartition:
  1143. ; 12. The fs operation has specified some partition.
  1144. ; 12a. Store parameters for callback functions.
  1145.         push    edx
  1146.         push    ecx
  1147. ; 12b. Store callback functions.
  1148.         push    dyndisk_cleanup
  1149.         push    fs_dyndisk
  1150.         mov     edi, esp
  1151. ; 12c. Let the procedure from fs_lfn.inc do the job.
  1152.         jmp     file_system_lfn.found2
  1153. .access_denied:
  1154. ; 13. Fail the operation with the appropriate code.
  1155.         mov     dword [esp+32], ERROR_ACCESS_DENIED
  1156. .cleanup:
  1157. ; 14. Cleanup.
  1158.         mov     esi, ecx        ; disk*dereference assume that esi points to DISK
  1159. .cleanup_esi:
  1160.         test    edx, edx        ; if there are no media, we didn't reference it
  1161.         jz      @f
  1162.         call    disk_media_dereference
  1163. @@:
  1164.         call    disk_dereference
  1165. ; 15. Return.
  1166.         ret
  1167.  
  1168. ; This is a callback for cleaning up things called from file_system_lfn.found2.
  1169. dyndisk_cleanup:
  1170.         mov     esi, [edi+8]
  1171.         mov     edx, [edi+12]
  1172.         jmp     dyndisk_handler.cleanup_esi
  1173.  
  1174. ; This is a callback for enumerating partitions called from
  1175. ; file_system_lfn.maindir in the case of inserted media.
  1176. ; It just increments eax until DISK.NumPartitions reached and then
  1177. ; cleans up.
  1178. fs_dyndisk_next:
  1179.         cmp     eax, [ecx+DISK.NumPartitions]
  1180.         jae     .nomore
  1181.         inc     eax
  1182.         clc
  1183.         ret
  1184. .nomore:
  1185.         pusha
  1186.         mov     esi, ecx
  1187.         call    disk_media_dereference
  1188.         call    disk_dereference
  1189.         popa
  1190.         stc
  1191.         ret
  1192.  
  1193. ; This is a callback for enumerating partitions called from
  1194. ; file_system_lfn.maindir in the case of missing media.
  1195. ; In this case we create one pseudo-partition.
  1196. fs_dyndisk_next_nomedia:
  1197.         cmp     eax, 1
  1198.         jae     .nomore
  1199.         inc     eax
  1200.         clc
  1201.         ret
  1202. .nomore:
  1203.         pusha
  1204.         mov     esi, ecx
  1205.         call    disk_dereference
  1206.         popa
  1207.         stc
  1208.         ret
  1209.  
  1210. ; This is a callback for doing real work with selected partition.
  1211. ; Currently this is just placeholder, since no file systems are supported.
  1212. ; edi = esp -> {dd fs_dyndisk, dd dyndisk_cleanup, dd pointer to DISK, dd media object}
  1213. ; ecx = partition number, esi+ebp = ASCIIZ name
  1214. fs_dyndisk:
  1215.         dec     ecx     ; convert to zero-based partition index
  1216.         pop     edx edx edx eax ; edx = pointer to DISK, eax = NULL or edx
  1217.         test    eax, eax
  1218.         jz      .nomedia
  1219. .main:
  1220.         cmp     ecx, [edx+DISK.NumPartitions]
  1221.         jae     .notfound
  1222.         mov     eax, [edx+DISK.Partitions]
  1223.         mov     eax, [eax+ecx*4]
  1224.         mov     edi, [eax+PARTITION.FSUserFunctions]
  1225.         mov     ecx, [ebx]
  1226.         cmp     [edi+4], ecx
  1227.         jbe     .unsupported
  1228.         push    edx
  1229.         push    ebp
  1230.         mov     ebp, eax
  1231.         call    dword [edi+8+ecx*4]
  1232.         pop     ebp
  1233.         pop     edx
  1234.         mov     dword [esp+32], eax
  1235.         mov     dword [esp+20], ebx
  1236. .cleanup:
  1237.         mov     esi, edx
  1238.         call    disk_media_dereference
  1239.         call    disk_dereference
  1240.         ret
  1241. .nofs:
  1242.         mov     dword [esp+32], ERROR_UNKNOWN_FS
  1243.         jmp     .cleanup
  1244. .notfound:
  1245.         mov     dword [esp+32], ERROR_FILE_NOT_FOUND
  1246.         jmp     .cleanup
  1247. .unsupported:
  1248.         cmp     edi, default_fs_functions
  1249.         jz      .nofs
  1250.         mov     dword [esp+32], ERROR_UNSUPPORTED_FS
  1251.         jmp     .cleanup
  1252. .nomedia:
  1253.         test    ecx, ecx
  1254.         jnz     .notfound
  1255.         test    byte [edx+DISK.DriverFlags], DISK_NO_INSERT_NOTIFICATION
  1256.         jz      .deverror
  1257. ; if the driver does not support insert notifications and we are the only fs
  1258. ; operation with this disk, issue the fake insert notification; if media is
  1259. ; still not inserted, 'disk_media_changed' will detect this and do nothing
  1260.         lea     ecx, [edx+DISK.MediaLock]
  1261.         call    mutex_lock
  1262.         cmp     [edx+DISK.MediaRefCount], 1
  1263.         jnz     .noluck
  1264.         call    mutex_unlock
  1265.         push    edx
  1266.         stdcall disk_media_changed, edx, 1
  1267.         pop     edx
  1268.         lea     ecx, [edx+DISK.MediaLock]
  1269.         call    mutex_lock
  1270.         cmp     [edx+DISK.MediaInserted], 0
  1271.         jz      .noluck
  1272.         lock inc [edx+DISK.MediaRefCount]
  1273.         call    mutex_unlock
  1274.         xor     ecx, ecx
  1275.         jmp     .main
  1276. .noluck:
  1277.         call    mutex_unlock
  1278. .deverror:
  1279.         mov     dword [esp+32], ERROR_DEVICE
  1280.         mov     esi, edx
  1281.         call    disk_dereference
  1282.         ret
  1283.  
  1284. ; This function is called from file_system_lfn.
  1285. ; This handler is called when virtual root is enumerated
  1286. ; and must return all items which can be handled by this.
  1287. ; It is called several times, first time with eax=0
  1288. ; in: eax = 0 for first call, previously returned value for subsequent calls
  1289. ; out: eax = 0 => no more items
  1290. ;      eax != 0 => buffer pointed to by edi contains name of item
  1291. dyndisk_enum_root:
  1292.         push    edx             ; save register used in file_system_lfn
  1293.         mov     ecx, disk_list_mutex    ; it will be useful
  1294. ; 1. If this is the first call, acquire the mutex and initialize.
  1295.         test    eax, eax
  1296.         jnz     .notfirst
  1297.         call    mutex_lock
  1298.         mov     eax, disk_list
  1299. .notfirst:
  1300. ; 2. Get next item.
  1301.         mov     eax, [eax+DISK.Next]
  1302. ; 3. If there are no more items, go to 6.
  1303.         cmp     eax, disk_list
  1304.         jz      .last
  1305. ; 4. Copy name from the DISK structure to edi.
  1306.         push    eax esi
  1307.         mov     esi, [eax+DISK.Name]
  1308. @@:
  1309.         lodsb
  1310.         stosb
  1311.         test    al, al
  1312.         jnz     @b
  1313.         pop     esi eax
  1314. ; 5. Return with eax = item.
  1315.         pop     edx             ; restore register used in file_system_lfn
  1316.         ret
  1317. .last:
  1318. ; 6. Release the mutex and return with eax = 0.
  1319.         call    mutex_unlock
  1320.         xor     eax, eax
  1321.         pop     edx             ; restore register used in file_system_lfn
  1322.         ret
  1323.