Rev 4549 | Details | Compare with Previous | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
5051 | clevermous | 1 | ; standard driver stuff; version of driver model = 5 |
2 | format PE DLL native 0.05 |
||
3 | entry START |
||
3520 | clevermous | 4 | |
5 | DEBUG = 1 |
||
6 | DUMP_PACKETS = 0 |
||
7 | |||
8 | ; this is for DEBUGF macro from 'fdo.inc' |
||
9 | __DEBUG__ = 1 |
||
10 | __DEBUG_LEVEL__ = 1 |
||
11 | |||
5051 | clevermous | 12 | include '../struct.inc' |
3520 | clevermous | 13 | |
14 | ; USB constants |
||
15 | DEVICE_DESCR_TYPE = 1 |
||
16 | CONFIG_DESCR_TYPE = 2 |
||
17 | STRING_DESCR_TYPE = 3 |
||
18 | INTERFACE_DESCR_TYPE = 4 |
||
19 | ENDPOINT_DESCR_TYPE = 5 |
||
20 | DEVICE_QUALIFIER_DESCR_TYPE = 6 |
||
21 | |||
22 | CONTROL_PIPE = 0 |
||
23 | ISOCHRONOUS_PIPE = 1 |
||
24 | BULK_PIPE = 2 |
||
25 | INTERRUPT_PIPE = 3 |
||
26 | |||
27 | ; USB structures |
||
5051 | clevermous | 28 | struct config_descr |
29 | bLength db ? |
||
30 | bDescriptorType db ? |
||
31 | wTotalLength dw ? |
||
32 | bNumInterfaces db ? |
||
33 | bConfigurationValue db ? |
||
34 | iConfiguration db ? |
||
35 | bmAttributes db ? |
||
36 | bMaxPower db ? |
||
37 | ends |
||
3520 | clevermous | 38 | |
5051 | clevermous | 39 | struct interface_descr |
40 | bLength db ? |
||
41 | bDescriptorType db ? |
||
42 | bInterfaceNumber db ? |
||
43 | bAlternateSetting db ? |
||
44 | bNumEndpoints db ? |
||
45 | bInterfaceClass db ? |
||
46 | bInterfaceSubClass db ? |
||
47 | bInterfaceProtocol db ? |
||
48 | iInterface db ? |
||
49 | ends |
||
3520 | clevermous | 50 | |
5051 | clevermous | 51 | struct endpoint_descr |
52 | bLength db ? |
||
53 | bDescriptorType db ? |
||
54 | bEndpointAddress db ? |
||
55 | bmAttributes db ? |
||
56 | wMaxPacketSize dw ? |
||
57 | bInterval db ? |
||
58 | ends |
||
3520 | clevermous | 59 | |
60 | ; Mass storage protocol constants, USB layer |
||
61 | REQUEST_GETMAXLUN = 0xFE ; get max lun |
||
62 | REQUEST_BORESET = 0xFF ; bulk-only reset |
||
63 | |||
64 | ; Mass storage protocol structures, USB layer |
||
65 | ; Sent from host to device in the first stage of an operation. |
||
5051 | clevermous | 66 | struct command_block_wrapper |
67 | Signature dd ? ; the constant 'USBC' |
||
68 | Tag dd ? ; identifies response with request |
||
69 | Length dd ? ; length of data-transport phase |
||
70 | Flags db ? ; one of CBW_FLAG_* |
||
3520 | clevermous | 71 | CBW_FLAG_OUT = 0 |
72 | CBW_FLAG_IN = 80h |
||
5051 | clevermous | 73 | LUN db ? ; addressed unit |
74 | CommandLength db ? ; the length of the following field |
||
75 | Command rb 16 |
||
76 | ends |
||
3520 | clevermous | 77 | |
78 | ; Sent from device to host in the last stage of an operation. |
||
5051 | clevermous | 79 | struct command_status_wrapper |
80 | Signature dd ? ; the constant 'USBS' |
||
81 | Tag dd ? ; identifies response with request |
||
82 | LengthRest dd ? ; .Length - (size of data which were transferred) |
||
83 | Status db ? ; one of CSW_STATUS_* |
||
3520 | clevermous | 84 | CSW_STATUS_OK = 0 |
85 | CSW_STATUS_FAIL = 1 |
||
86 | CSW_STATUS_FATAL = 2 |
||
5051 | clevermous | 87 | ends |
3520 | clevermous | 88 | |
89 | ; Constants of SCSI layer |
||
90 | SCSI_REQUEST_SENSE = 3 |
||
91 | SCSI_INQUIRY = 12h |
||
92 | SCSI_READ_CAPACITY = 25h |
||
93 | SCSI_READ10 = 28h |
||
94 | SCSI_WRITE10 = 2Ah |
||
95 | |||
96 | ; Result of SCSI REQUEST SENSE command. |
||
97 | SENSE_UNKNOWN = 0 |
||
98 | SENSE_RECOVERED_ERROR = 1 |
||
99 | SENSE_NOT_READY = 2 |
||
100 | SENSE_MEDIUM_ERROR = 3 |
||
101 | SENSE_HARDWARE_ERROR = 4 |
||
102 | SENSE_ILLEGAL_REQUEST = 5 |
||
103 | SENSE_UNIT_ATTENTION = 6 |
||
104 | SENSE_DATA_PROTECT = 7 |
||
105 | SENSE_BLANK_CHECK = 8 |
||
106 | ; 9 is vendor-specific |
||
107 | SENSE_COPY_ABORTED = 10 |
||
108 | SENSE_ABORTED_COMMAND = 11 |
||
109 | SENSE_EQUAL = 12 |
||
110 | SENSE_VOLUME_OVERFLOW = 13 |
||
111 | SENSE_MISCOMPARE = 14 |
||
112 | ; 15 is reserved |
||
113 | |||
114 | ; Structures of SCSI layer |
||
115 | ; Result of SCSI INQUIRY request. |
||
5051 | clevermous | 116 | struct inquiry_data |
117 | PeripheralDevice db ? ; lower 5 bits are PeripheralDeviceType |
||
3520 | clevermous | 118 | ; upper 3 bits are PeripheralQualifier |
5051 | clevermous | 119 | RemovableMedium db ? ; upper bit is RemovableMedium |
3520 | clevermous | 120 | ; other bits are for compatibility |
5051 | clevermous | 121 | Version db ? ; lower 3 bits are ANSI-Approved version |
3520 | clevermous | 122 | ; next 3 bits are ECMA version |
123 | ; upper 2 bits are ISO version |
||
5051 | clevermous | 124 | ResponseDataFormat db ? ; lower 4 bits are ResponseDataFormat |
3520 | clevermous | 125 | ; bit 6 is TrmIOP |
126 | ; bit 7 is AENC |
||
5051 | clevermous | 127 | AdditionalLength db ? |
3520 | clevermous | 128 | dw ? ; reserved |
5051 | clevermous | 129 | Flags db ? |
130 | VendorID rb 8 ; vendor ID, big-endian |
||
131 | ProductID rb 16 ; product ID, big-endian |
||
132 | ProductRevBE dd ? ; product revision, big-endian |
||
133 | ends |
||
3520 | clevermous | 134 | |
5051 | clevermous | 135 | struct sense_data |
136 | ErrorCode db ? ; lower 7 bits are error code: |
||
3520 | clevermous | 137 | ; 70h = current error, |
138 | ; 71h = deferred error |
||
139 | ; upper bit is InformationValid |
||
5051 | clevermous | 140 | SegmentNumber db ? ; number of segment descriptor |
3520 | clevermous | 141 | ; for commands COPY [+VERIFY], COMPARE |
5051 | clevermous | 142 | SenseKey db ? ; bits 0-3 are one of SENSE_* |
3520 | clevermous | 143 | ; bit 4 is reserved |
144 | ; bit 5 is IncorrectLengthIndicator |
||
145 | ; bits 6 and 7 are used by |
||
146 | ; sequential-access devices |
||
5051 | clevermous | 147 | Information dd ? ; command-specific |
148 | AdditionalLength db ? ; length of data starting here |
||
149 | CommandInformation dd ? ; command-specific |
||
150 | AdditionalSenseCode db ? ; \ more detailed error code |
||
151 | AdditionalSenseQual db ? ; / standard has a large table of them |
||
152 | FRUCode db ? ; which part of device has failed |
||
3520 | clevermous | 153 | ; (device-specific, not regulated) |
5051 | clevermous | 154 | SenseKeySpecific rb 3 ; depends on SenseKey |
155 | ends |
||
3520 | clevermous | 156 | |
157 | ; Device data |
||
158 | ; USB Mass storage device has one or more logical units, identified by LUN, |
||
159 | ; logical unit number. The highest value of LUN, that is, number of units |
||
160 | ; minus 1, can be obtained via control request Get Max LUN. |
||
5051 | clevermous | 161 | struct usb_device_data |
162 | ConfigPipe dd ? ; configuration pipe |
||
163 | OutPipe dd ? ; pipe for OUT bulk endpoint |
||
164 | InPipe dd ? ; pipe for IN bulk endpoint |
||
165 | MaxLUN dd ? ; maximum Logical Unit Number |
||
166 | LogicalDevices dd ? ; pointer to array of usb_unit_data |
||
3520 | clevermous | 167 | ; 1 for a connected USB device, 1 for each disk device |
168 | ; the structure can be freed when .NumReferences decreases to zero |
||
5051 | clevermous | 169 | NumReferences dd ? ; number of references |
170 | ConfigRequest rb 8 ; buffer for configuration requests |
||
171 | LengthRest dd ? ; Length - (size of data which were transferred) |
||
3520 | clevermous | 172 | ; All requests to a given device are serialized, |
173 | ; only one request to a given device can be processed at a time. |
||
174 | ; The current request and all pending requests are organized in the following |
||
175 | ; queue, the head being the current request. |
||
176 | ; NB: the queue must be device-wide due to the protocol: |
||
177 | ; data stage is not tagged (unlike command_*_wrapper), so the only way to know |
||
178 | ; what request the data are associated with is to guarantee that only one |
||
179 | ; request is processing at the time. |
||
5051 | clevermous | 180 | RequestsQueue rd 2 |
181 | QueueLock rd 3 ; protects .RequestsQueue |
||
182 | InquiryData inquiry_data ; information about device |
||
3520 | clevermous | 183 | ; data for the current request |
5051 | clevermous | 184 | Command command_block_wrapper |
185 | DeviceDisconnected db ? |
||
186 | Status command_status_wrapper |
||
187 | Sense sense_data |
||
188 | ends |
||
3520 | clevermous | 189 | |
190 | ; Information about one logical device. |
||
5051 | clevermous | 191 | struct usb_unit_data |
192 | Parent dd ? ; pointer to parent usb_device_data |
||
193 | LUN db ? ; index in usb_device_data.LogicalDevices array |
||
194 | DiskIndex db ? ; for name "usbhd |
||
195 | MediaPresent db ? |
||
3520 | clevermous | 196 | db ? ; alignment |
5051 | clevermous | 197 | DiskDevice dd ? ; handle of disk device or NULL |
198 | SectorSize dd ? ; sector size |
||
3520 | clevermous | 199 | ; For some devices, the first request to the medium fails with 'unit not ready'. |
200 | ; When the code sees this status, it retries the command several times. |
||
201 | ; Two following variables track the retry count and total time for those; |
||
202 | ; total time is currently used only for debug output. |
||
5051 | clevermous | 203 | UnitReadyAttempts dd ? |
204 | TimerTicks dd ? |
||
205 | ends |
||
3520 | clevermous | 206 | |
207 | ; This is the structure for items in the queue usb_device_data.RequestsQueue. |
||
5051 | clevermous | 208 | struct request_queue_item |
209 | Next dd ? ; next item in the queue |
||
210 | Prev dd ? ; prev item in the queue |
||
211 | ReqBuilder dd ? ; procedure to fill command_block_wrapper |
||
212 | Buffer dd ? ; input or output data |
||
3520 | clevermous | 213 | ; (length is command_block_wrapper.Length) |
5051 | clevermous | 214 | Callback dd ? ; procedure to call in the end of transfer |
215 | UserData dd ? ; passed as-is to .Callback |
||
3520 | clevermous | 216 | ; There are 3 possible stages of any request, one of them optional: |
217 | ; command stage (host sends command_block_wrapper to device), |
||
218 | ; optional data stage, |
||
219 | ; status stage (device sends command_status_wrapper to host). |
||
220 | ; Also, if a request fails, the code queues additional request |
||
221 | ; SCSI_REQUEST_SENSE; sense_data from SCSI_REQUEST_SENSE |
||
222 | ; contains some information about the error. |
||
5051 | clevermous | 223 | Stage db ? |
224 | ends |
||
3520 | clevermous | 225 | |
5051 | clevermous | 226 | section '.flat' code readable writable executable |
227 | include '../proc32.inc' |
||
228 | include '../peimport.inc' |
||
229 | include '../fdo.inc' |
||
230 | include '../macros.inc' |
||
231 | |||
3520 | clevermous | 232 | ; The start procedure. |
233 | proc START |
||
234 | virtual at esp |
||
235 | dd ? ; return address |
||
236 | .reason dd ? ; DRV_ENTRY or DRV_EXIT |
||
5051 | clevermous | 237 | .cmdline dd ? |
3520 | clevermous | 238 | end virtual |
239 | ; 1. Test whether the procedure is called with the argument DRV_ENTRY. |
||
240 | ; If not, return 0. |
||
241 | xor eax, eax ; initialize return value |
||
242 | cmp [.reason], 1 ; compare the argument |
||
243 | jnz .nothing |
||
244 | ; 2. Initialize: we have one global mutex. |
||
245 | mov ecx, free_numbers_lock |
||
5051 | clevermous | 246 | invoke MutexInit |
3520 | clevermous | 247 | ; 3. Register self as a USB driver. |
248 | ; The name is my_driver = 'usbstor'; IOCTL interface is not supported; |
||
249 | ; usb_functions is an offset of a structure with callback functions. |
||
5051 | clevermous | 250 | invoke RegUSBDriver, my_driver, 0, usb_functions |
3520 | clevermous | 251 | ; 4. Return the returned value of RegUSBDriver. |
252 | .nothing: |
||
5051 | clevermous | 253 | ret |
3520 | clevermous | 254 | endp |
255 | |||
256 | ; Helper procedures to work with requests queue. |
||
257 | |||
258 | ; Add a request to the queue. Stdcall with 5 arguments. |
||
259 | proc queue_request |
||
260 | push ebx esi |
||
261 | virtual at esp |
||
262 | rd 2 ; saved registers |
||
263 | dd ? ; return address |
||
264 | .device dd ? ; pointer to usb_device_data |
||
265 | .ReqBuilder dd ? ; request_queue_item.ReqBuilder |
||
266 | .Buffer dd ? ; request_queue_item.Buffer |
||
267 | .Callback dd ? ; request_queue_item.Callback |
||
268 | .UserData dd ? ; request_queue_item.UserData |
||
269 | end virtual |
||
270 | ; 1. Allocate the memory for the request description. |
||
5051 | clevermous | 271 | movi eax, sizeof.request_queue_item |
272 | invoke Kmalloc |
||
3520 | clevermous | 273 | test eax, eax |
274 | jnz @f |
||
275 | mov esi, nomemory |
||
5051 | clevermous | 276 | invoke SysMsgBoardStr |
3520 | clevermous | 277 | pop esi ebx |
278 | ret 20 |
||
279 | @@: |
||
280 | ; 2. Fill user-provided parts of the request description. |
||
281 | push edi |
||
282 | xchg eax, ebx |
||
283 | lea esi, [.ReqBuilder+4] |
||
284 | lea edi, [ebx+request_queue_item.ReqBuilder] |
||
285 | movsd ; ReqBuilder |
||
286 | movsd ; Buffer |
||
287 | movsd ; Callback |
||
288 | movsd ; UserData |
||
289 | pop edi |
||
290 | ; 3. Set stage to zero: not started. |
||
291 | mov [ebx+request_queue_item.Stage], 0 |
||
292 | ; 4. Lock the queue. |
||
293 | mov esi, [.device] |
||
294 | lea ecx, [esi+usb_device_data.QueueLock] |
||
5051 | clevermous | 295 | invoke MutexLock |
3520 | clevermous | 296 | ; 5. Insert the request to the tail of the queue. |
297 | add esi, usb_device_data.RequestsQueue |
||
298 | mov edx, [esi+request_queue_item.Prev] |
||
299 | mov [ebx+request_queue_item.Next], esi |
||
300 | mov [ebx+request_queue_item.Prev], edx |
||
301 | mov [edx+request_queue_item.Next], ebx |
||
302 | mov [esi+request_queue_item.Prev], ebx |
||
303 | ; 6. Test whether the queue was empty |
||
304 | ; and the request should be started immediately. |
||
305 | cmp [esi+request_queue_item.Next], ebx |
||
306 | jnz .unlock |
||
307 | ; 8. If the step 6 shows that the request is the first in the queue, |
||
308 | ; start it. |
||
309 | sub esi, usb_device_data.RequestsQueue |
||
310 | call setup_request |
||
311 | jmp .nothing |
||
312 | .unlock: |
||
5051 | clevermous | 313 | invoke MutexUnlock |
3520 | clevermous | 314 | ; 9. Return. |
315 | .nothing: |
||
316 | pop esi ebx |
||
317 | ret 20 |
||
318 | endp |
||
319 | |||
320 | ; The current request is completed. Call the callback, |
||
321 | ; remove the request from the queue, start the next |
||
322 | ; request if there is one. |
||
323 | ; esi points to usb_device_data |
||
324 | proc complete_request |
||
325 | ; 1. Print common debug messages on fails. |
||
326 | if DEBUG |
||
327 | cmp [esi+usb_device_data.Status.Status], CSW_STATUS_FAIL |
||
328 | jb .normal |
||
329 | jz .fail |
||
330 | DEBUGF 1, 'K : Fatal error during execution of command %x\n', [esi+usb_device_data.Command.Command]:2 |
||
331 | jmp .normal |
||
332 | .fail: |
||
333 | DEBUGF 1, 'K : Command %x failed\n', [esi+usb_device_data.Command.Command]:2 |
||
334 | .normal: |
||
335 | end if |
||
336 | ; 2. Get the current request. |
||
337 | mov ebx, [esi+usb_device_data.RequestsQueue+request_queue_item.Next] |
||
338 | ; 3. Call the callback. |
||
339 | stdcall [ebx+request_queue_item.Callback], esi, [ebx+request_queue_item.UserData] |
||
340 | ; 4. Lock the queue. |
||
341 | lea ecx, [esi+usb_device_data.QueueLock] |
||
5051 | clevermous | 342 | invoke MutexLock |
3520 | clevermous | 343 | ; 5. Remove the request. |
344 | lea edx, [esi+usb_device_data.RequestsQueue] |
||
345 | mov eax, [ebx+request_queue_item.Next] |
||
346 | mov [eax+request_queue_item.Prev], edx |
||
347 | mov [edx+request_queue_item.Next], eax |
||
348 | ; 6. Free the request memory. |
||
349 | push eax edx |
||
350 | xchg eax, ebx |
||
5051 | clevermous | 351 | invoke Kfree |
3520 | clevermous | 352 | pop edx ebx |
353 | ; 7. If there is a next request, start processing. |
||
354 | cmp ebx, edx |
||
355 | jnz setup_request |
||
356 | ; 8. Unlock the queue and return. |
||
357 | lea ecx, [esi+usb_device_data.QueueLock] |
||
5051 | clevermous | 358 | invoke MutexUnlock |
3520 | clevermous | 359 | ret |
360 | endp |
||
361 | |||
362 | ; Start processing the request. Called either by queue_request |
||
363 | ; or when the previous request has been processed. |
||
364 | ; Do not call directly, use queue_request. |
||
365 | ; Must be called when queue is locked; unlocks the queue when returns. |
||
366 | proc setup_request |
||
367 | xor eax, eax |
||
368 | ; 1. If DeviceDisconnected has been run, then all handles of pipes |
||
369 | ; are invalid, so we must fail immediately. |
||
370 | ; (That is why this function needs the locked queue: this |
||
371 | ; guarantee that either DeviceDisconnected has been already run, or |
||
372 | ; DeviceDisconnected will not return before the queue is unlocked.) |
||
373 | cmp [esi+usb_device_data.DeviceDisconnected], al |
||
374 | jnz .fatal |
||
375 | ; 2. If the previous command has encountered a fatal error, |
||
376 | ; perform reset recovery. |
||
377 | cmp [esi+usb_device_data.Status.Status], CSW_STATUS_FATAL |
||
378 | jb .norecovery |
||
379 | ; 2a. Send Bulk-Only Mass Storage Reset command to config pipe. |
||
380 | lea edx, [esi+usb_device_data.ConfigRequest] |
||
381 | mov word [edx], (REQUEST_BORESET shl 8) + 21h ; class request |
||
382 | mov word [edx+6], ax ; length = 0 |
||
5051 | clevermous | 383 | invoke USBControlTransferAsync, [esi+usb_device_data.ConfigPipe], edx, eax, eax, recovery_callback1, esi, eax |
3520 | clevermous | 384 | ; 2b. Fail here = fatal error. |
385 | test eax, eax |
||
386 | jz .fatal |
||
387 | ; 2c. Otherwise, unlock the queue and return. recovery_callback1 will continue processing. |
||
388 | .unlock_return: |
||
389 | lea ecx, [esi+usb_device_data.QueueLock] |
||
5051 | clevermous | 390 | invoke MutexUnlock |
3520 | clevermous | 391 | ret |
392 | .norecovery: |
||
393 | ; 3. Send the command. Fail (no memory or device disconnected) = fatal error. |
||
394 | ; Otherwise, go to 2c. |
||
395 | call request_stage1 |
||
396 | test eax, eax |
||
397 | jnz .unlock_return |
||
398 | .fatal: |
||
399 | ; 4. Fatal error. Set status = FATAL, unlock the queue, complete the request. |
||
400 | mov [esi+usb_device_data.Status.Status], CSW_STATUS_FATAL |
||
401 | lea ecx, [esi+usb_device_data.QueueLock] |
||
5051 | clevermous | 402 | invoke MutexUnlock |
3520 | clevermous | 403 | jmp complete_request |
404 | endp |
||
405 | |||
406 | ; Initiate USB transfer for the first stage of a request (send command). |
||
407 | proc request_stage1 |
||
408 | mov ebx, [esi+usb_device_data.RequestsQueue+request_queue_item.Next] |
||
409 | ; 1. Set the stage to 1 = command stage. |
||
410 | inc [ebx+request_queue_item.Stage] |
||
411 | ; 2. Generate the command. Zero-initialize and use the caller-provided proc. |
||
412 | lea edx, [esi+usb_device_data.Command] |
||
413 | xor eax, eax |
||
414 | mov [edx+command_block_wrapper.CommandLength], 12 |
||
415 | mov dword [edx+command_block_wrapper.Command], eax |
||
416 | mov dword [edx+command_block_wrapper.Command+4], eax |
||
417 | mov dword [edx+command_block_wrapper.Command+8], eax |
||
418 | mov dword [edx+command_block_wrapper.Command+12], eax |
||
419 | inc [edx+command_block_wrapper.Tag] |
||
420 | stdcall [ebx+request_queue_item.ReqBuilder], edx, [ebx+request_queue_item.UserData] |
||
421 | ; 4. Initiate USB transfer. |
||
422 | lea edx, [esi+usb_device_data.Command] |
||
423 | if DUMP_PACKETS |
||
424 | DEBUGF 1,'K : USBSTOR out:' |
||
425 | mov eax, edx |
||
5051 | clevermous | 426 | mov ecx, sizeof.command_block_wrapper |
3520 | clevermous | 427 | call debug_dump |
428 | DEBUGF 1,'\n' |
||
429 | end if |
||
5051 | clevermous | 430 | invoke USBNormalTransferAsync, [esi+usb_device_data.OutPipe], edx, sizeof.command_block_wrapper, request_callback1, esi, 0 |
4346 | clevermous | 431 | test eax, eax |
432 | jz .nothing |
||
433 | ; 5. If the next stage is data stage in the same direction, enqueue it here. |
||
434 | cmp [esi+usb_device_data.Command.Flags], 0 |
||
435 | js .nothing |
||
436 | cmp [esi+usb_device_data.Command.Length], 0 |
||
437 | jz .nothing |
||
438 | mov edx, [esi+usb_device_data.RequestsQueue+request_queue_item.Next] |
||
439 | if DUMP_PACKETS |
||
440 | DEBUGF 1,'K : USBSTOR out:' |
||
441 | mov eax, [edx+request_queue_item.Buffer] |
||
442 | mov ecx, [esi+usb_device_data.Command.Length] |
||
443 | call debug_dump |
||
444 | DEBUGF 1,'\n' |
||
445 | end if |
||
5051 | clevermous | 446 | invoke USBNormalTransferAsync, [esi+usb_device_data.OutPipe], [edx+request_queue_item.Buffer], [esi+usb_device_data.Command.Length], request_callback2, esi, 0 |
4346 | clevermous | 447 | .nothing: |
3520 | clevermous | 448 | ret |
449 | endp |
||
450 | |||
451 | if DUMP_PACKETS |
||
452 | proc debug_dump |
||
453 | test ecx, ecx |
||
454 | jz .done |
||
455 | .loop: |
||
456 | test ecx, 0Fh |
||
457 | jnz @f |
||
458 | DEBUGF 1,'\nK :' |
||
459 | @@: |
||
460 | DEBUGF 1,' %x',[eax]:2 |
||
461 | inc eax |
||
462 | dec ecx |
||
463 | jnz .loop |
||
464 | .done: |
||
465 | ret |
||
466 | endp |
||
467 | end if |
||
468 | |||
469 | ; Called when the Reset command is completed, |
||
470 | ; either successfully or not. |
||
471 | proc recovery_callback1 |
||
472 | virtual at esp |
||
473 | dd ? ; return address |
||
474 | .pipe dd ? |
||
475 | .status dd ? |
||
476 | .buffer dd ? |
||
477 | .length dd ? |
||
478 | .calldata dd ? |
||
479 | end virtual |
||
480 | cmp [.status], 0 |
||
481 | jnz .error |
||
482 | ; todo: reset pipes |
||
483 | push ebx esi |
||
484 | mov esi, [.calldata+8] |
||
485 | call request_stage1 |
||
486 | pop esi ebx |
||
487 | test eax, eax |
||
488 | jz .error |
||
489 | ret 20 |
||
490 | .error: |
||
4549 | clevermous | 491 | DEBUGF 1, 'K : error %d while resetting', [.status+24h] |
3520 | clevermous | 492 | jmp request_callback1.common_error |
493 | endp |
||
494 | |||
495 | ; Called when the first stage of request is completed, |
||
496 | ; either successfully or not. |
||
497 | proc request_callback1 |
||
498 | virtual at esp |
||
499 | dd ? ; return address |
||
500 | .pipe dd ? |
||
501 | .status dd ? |
||
502 | .buffer dd ? |
||
503 | .length dd ? |
||
504 | .calldata dd ? |
||
505 | end virtual |
||
506 | ; 1. Initialize. |
||
507 | mov ecx, [.calldata] |
||
508 | mov eax, [.status] |
||
509 | ; 2. Test for error. |
||
510 | test eax, eax |
||
511 | jnz .error |
||
512 | ; No error. |
||
513 | ; 3. Increment the stage. |
||
514 | mov edx, [ecx+usb_device_data.RequestsQueue+request_queue_item.Next] |
||
515 | inc [edx+request_queue_item.Stage] |
||
4346 | clevermous | 516 | ; 4. Check whether we need to send the data. |
517 | ; 4a. If there is no data, skip this stage. |
||
3520 | clevermous | 518 | cmp [ecx+usb_device_data.Command.Length], 0 |
519 | jz ..request_get_status |
||
4346 | clevermous | 520 | ; 4b. If data were enqueued in the first stage, do nothing, wait for request_callback2. |
521 | cmp [ecx+usb_device_data.Command.Flags], 0 |
||
522 | jns .nothing |
||
3520 | clevermous | 523 | ; 5. Initiate USB transfer. If this fails, go to the error handler. |
5051 | clevermous | 524 | invoke USBNormalTransferAsync, [ecx+usb_device_data.InPipe], [edx+request_queue_item.Buffer], [ecx+usb_device_data.Command.Length], request_callback2, ecx, 0 |
3520 | clevermous | 525 | test eax, eax |
526 | jz .error |
||
4346 | clevermous | 527 | ; 6. The status stage goes to the same direction, enqueue it now. |
528 | mov ecx, [.calldata] |
||
529 | jmp ..enqueue_status |
||
530 | .nothing: |
||
3520 | clevermous | 531 | ret 20 |
532 | .error: |
||
533 | ; Error. |
||
534 | ; 7. Print debug message and complete the request as failed. |
||
4549 | clevermous | 535 | DEBUGF 1,'K : error %d after %d bytes in request stage\n',eax,[.length+24h] |
536 | ; If device is disconnected and data stage is enqueued, do nothing; |
||
537 | ; data stage callback will do everything. |
||
538 | cmp eax, 16 |
||
539 | jnz .common_error |
||
540 | cmp [ecx+usb_device_data.Command.Flags], 0 |
||
541 | js .common_error |
||
542 | cmp [ecx+usb_device_data.Command.Length], 0 |
||
543 | jz .common_error |
||
544 | ret 20 |
||
3520 | clevermous | 545 | .common_error: |
546 | ; TODO: add recovery after STALL |
||
547 | mov ecx, [.calldata] |
||
548 | mov [ecx+usb_device_data.Status.Status], CSW_STATUS_FATAL |
||
549 | push ebx esi |
||
550 | mov esi, ecx |
||
551 | call complete_request |
||
552 | pop esi ebx |
||
553 | ret 20 |
||
554 | endp |
||
555 | |||
556 | ; Called when the second stage of request is completed, |
||
557 | ; either successfully or not. |
||
558 | proc request_callback2 |
||
559 | virtual at esp |
||
560 | dd ? ; return address |
||
561 | .pipe dd ? |
||
562 | .status dd ? |
||
563 | .buffer dd ? |
||
564 | .length dd ? |
||
565 | .calldata dd ? |
||
566 | end virtual |
||
567 | if DUMP_PACKETS |
||
568 | mov eax, [.calldata] |
||
569 | mov eax, [eax+usb_device_data.InPipe] |
||
570 | cmp [.pipe], eax |
||
571 | jnz @f |
||
572 | DEBUGF 1,'K : USBSTOR in:' |
||
573 | push eax ecx |
||
574 | mov eax, [.buffer+8] |
||
575 | mov ecx, [.length+8] |
||
576 | call debug_dump |
||
577 | pop ecx eax |
||
578 | DEBUGF 1,'\n' |
||
579 | @@: |
||
580 | end if |
||
581 | ; 1. Initialize. |
||
582 | mov ecx, [.calldata] |
||
583 | mov eax, [.status] |
||
584 | ; 2. Test for error. |
||
585 | test eax, eax |
||
586 | jnz .error |
||
587 | ; No error. |
||
4346 | clevermous | 588 | ; If the previous stage was in same direction, do nothing; status request is already enqueued. |
589 | cmp [ecx+usb_device_data.Command.Flags], 0 |
||
590 | js .nothing |
||
3520 | clevermous | 591 | ..request_get_status: |
592 | ; 3. Increment the stage. |
||
593 | mov edx, [ecx+usb_device_data.RequestsQueue+request_queue_item.Next] |
||
594 | inc [edx+request_queue_item.Stage] |
||
595 | ; 4. Initiate USB transfer. If this fails, go to the error handler. |
||
4346 | clevermous | 596 | ..enqueue_status: |
3520 | clevermous | 597 | lea edx, [ecx+usb_device_data.Status] |
5051 | clevermous | 598 | invoke USBNormalTransferAsync, [ecx+usb_device_data.InPipe], edx, sizeof.command_status_wrapper, request_callback3, ecx, 0 |
3520 | clevermous | 599 | test eax, eax |
600 | jz .error |
||
4346 | clevermous | 601 | .nothing: |
3520 | clevermous | 602 | ret 20 |
603 | .error: |
||
604 | ; Error. |
||
4549 | clevermous | 605 | ; 5. Print debug message and complete the request as failed. |
606 | DEBUGF 1,'K : error %d after %d bytes in data stage\n',eax,[.length+24h] |
||
607 | ; If device is disconnected and data stage is enqueued, do nothing; |
||
608 | ; status stage callback will do everything. |
||
609 | cmp [ecx+usb_device_data.Command.Flags], 0 |
||
610 | js .nothing |
||
3520 | clevermous | 611 | jmp request_callback1.common_error |
612 | endp |
||
613 | |||
614 | ; Called when the third stage of request is completed, |
||
615 | ; either successfully or not. |
||
616 | proc request_callback3 |
||
617 | virtual at esp |
||
618 | dd ? ; return address |
||
619 | .pipe dd ? |
||
620 | .status dd ? |
||
621 | .buffer dd ? |
||
622 | .length dd ? |
||
623 | .calldata dd ? |
||
624 | end virtual |
||
625 | if DUMP_PACKETS |
||
626 | DEBUGF 1,'K : USBSTOR in:' |
||
627 | mov eax, [.buffer] |
||
628 | mov ecx, [.length] |
||
629 | call debug_dump |
||
630 | DEBUGF 1,'\n' |
||
631 | end if |
||
632 | ; 1. Initialize. |
||
633 | mov eax, [.status] |
||
634 | ; 2. Test for error. |
||
635 | test eax, eax |
||
636 | jnz .transfer_error |
||
637 | ; Transfer is OK. |
||
638 | ; 3. Validate the status. Invalid status = fatal error. |
||
639 | push ebx esi |
||
640 | mov esi, [.calldata+8] |
||
641 | mov ebx, [esi+usb_device_data.RequestsQueue+request_queue_item.Next] |
||
642 | cmp [esi+usb_device_data.Status.Signature], 'USBS' |
||
643 | jnz .invalid |
||
644 | mov eax, [esi+usb_device_data.Command.Tag] |
||
645 | cmp [esi+usb_device_data.Status.Tag], eax |
||
646 | jnz .invalid |
||
647 | cmp [esi+usb_device_data.Status.Status], CSW_STATUS_FATAL |
||
648 | ja .invalid |
||
649 | ; 4. The status block is valid. Check the status code. |
||
650 | jz .complete |
||
651 | ; 5. If this command was not REQUEST_SENSE, copy status data to safe place. |
||
652 | ; Otherwise, the original command has failed, so restore the fail status. |
||
653 | cmp byte [esi+usb_device_data.Command.Command], SCSI_REQUEST_SENSE |
||
654 | jz .request_sense |
||
655 | mov eax, [esi+usb_device_data.Status.LengthRest] |
||
656 | mov [esi+usb_device_data.LengthRest], eax |
||
657 | cmp [esi+usb_device_data.Status.Status], CSW_STATUS_FAIL |
||
658 | jz .fail |
||
659 | .complete: |
||
660 | call complete_request |
||
661 | .nothing: |
||
662 | pop esi ebx |
||
663 | ret 20 |
||
664 | .request_sense: |
||
665 | mov [esi+usb_device_data.Status.Status], CSW_STATUS_FAIL |
||
666 | jmp .complete |
||
667 | .invalid: |
||
668 | ; 6. Invalid status block. Say error, set status to fatal and complete request. |
||
669 | push esi |
||
670 | mov esi, invresponse |
||
5051 | clevermous | 671 | invoke SysMsgBoardStr |
3520 | clevermous | 672 | pop esi |
673 | mov [esi+usb_device_data.Status.Status], CSW_STATUS_FATAL |
||
674 | jmp .complete |
||
675 | .fail: |
||
676 | ; 7. The command has failed. |
||
677 | ; If this command was not REQUEST_SENSE, schedule the REQUEST_SENSE command |
||
678 | ; to determine the reason of fail. Otherwise, assume that there is no error data. |
||
679 | cmp [esi+usb_device_data.Command.Command], SCSI_REQUEST_SENSE |
||
680 | jz .fail_request_sense |
||
681 | mov [ebx+request_queue_item.ReqBuilder], request_sense_req |
||
682 | lea eax, [esi+usb_device_data.Sense] |
||
683 | mov [ebx+request_queue_item.Buffer], eax |
||
684 | call request_stage1 |
||
685 | test eax, eax |
||
686 | jnz .nothing |
||
687 | .fail_request_sense: |
||
688 | DEBUGF 1,'K : fail during REQUEST SENSE\n' |
||
689 | mov byte [esi+usb_device_data.Sense], 0 |
||
690 | jmp .complete |
||
691 | .transfer_error: |
||
692 | ; TODO: add recovery after STALL |
||
4549 | clevermous | 693 | DEBUGF 1,'K : error %d after %d bytes in status stage\n',eax,[.length+24h] |
3520 | clevermous | 694 | jmp request_callback1.common_error |
695 | endp |
||
696 | |||
697 | ; Builder for SCSI_REQUEST_SENSE request. |
||
698 | ; edx = first argument = pointer to usb_device_data.Command, |
||
699 | ; second argument = custom data given to queue_request (ignored). |
||
700 | proc request_sense_req |
||
5051 | clevermous | 701 | mov [edx+command_block_wrapper.Length], sizeof.sense_data |
3520 | clevermous | 702 | mov [edx+command_block_wrapper.Flags], CBW_FLAG_IN |
703 | mov byte [edx+command_block_wrapper.Command+0], SCSI_REQUEST_SENSE |
||
5051 | clevermous | 704 | mov byte [edx+command_block_wrapper.Command+4], sizeof.sense_data |
3520 | clevermous | 705 | ret 8 |
706 | endp |
||
707 | |||
708 | ; This procedure is called when new mass-storage device is detected. |
||
709 | ; It initializes the device. |
||
710 | ; Technically, initialization implies sending several USB queries, |
||
711 | ; so it is split in several procedures. The first is AddDevice, |
||
712 | ; other are callbacks which will be called at some time in the future, |
||
713 | ; when the device will respond. |
||
714 | ; The general scheme: |
||
715 | ; * AddDevice parses descriptors, opens pipes; if everything is ok, |
||
716 | ; AddDevice sends REQUEST_GETMAXLUN with callback known_lun_callback; |
||
717 | ; * known_lun_callback allocates memory for LogicalDevices and sends |
||
718 | ; SCSI_TEST_UNIT_READY to all logical devices with test_unit_ready_callback; |
||
719 | ; * test_unit_ready_callback checks whether the unit is ready; |
||
720 | ; if not, it repeats the same request several times; |
||
721 | ; if ok or there were too many attempts, it sends SCSI_INQUIRY with |
||
722 | ; callback inquiry_callback; |
||
723 | ; * inquiry_callback checks that a logical device is a block device |
||
724 | ; and the unit was ready; if so, it notifies the kernel about new disk device. |
||
725 | proc AddDevice |
||
726 | push ebx esi |
||
727 | virtual at esp |
||
728 | rd 2 ; saved registers ebx, esi |
||
729 | dd ? ; return address |
||
730 | .pipe0 dd ? ; handle of the config pipe |
||
731 | .config dd ? ; pointer to config_descr |
||
732 | .interface dd ? ; pointer to interface_descr |
||
733 | end virtual |
||
734 | ; 1. Check device type. Currently only SCSI-command-set Bulk-only devices |
||
735 | ; are supported. |
||
736 | ; 1a. Get the subclass and the protocol. Since bInterfaceSubClass and |
||
737 | ; bInterfaceProtocol are subsequent in interface_descr, just one |
||
738 | ; memory reference is used for both. |
||
739 | mov esi, [.interface] |
||
740 | xor ebx, ebx |
||
741 | mov cx, word [esi+interface_descr.bInterfaceSubClass] |
||
742 | ; 1b. For Mass-storage SCSI-command-set Bulk-only devices subclass must be 6 |
||
743 | ; and protocol must be 50h. Check. |
||
744 | cmp cx, 0x5006 |
||
745 | jz .known |
||
746 | ; There are devices with subclass 5 which use the same protocol 50h. |
||
747 | ; The difference is not important for the code except for this test, |
||
748 | ; so allow them to proceed also. |
||
749 | cmp cx, 0x5005 |
||
750 | jz .known |
||
751 | ; 1c. If the device is unknown, print a message and go to 11c. |
||
752 | mov esi, unkdevice |
||
5051 | clevermous | 753 | invoke SysMsgBoardStr |
3520 | clevermous | 754 | jmp .nothing |
755 | ; 1d. If the device uses known command set, print a message and continue |
||
756 | ; configuring. |
||
757 | .known: |
||
758 | push esi |
||
759 | mov esi, okdevice |
||
5051 | clevermous | 760 | invoke SysMsgBoardStr |
3520 | clevermous | 761 | pop esi |
762 | ; 2. Allocate memory for internal device data. |
||
763 | ; 2a. Call the kernel. |
||
5051 | clevermous | 764 | mov eax, sizeof.usb_device_data |
765 | invoke Kmalloc |
||
3520 | clevermous | 766 | ; 2b. Check return value. |
767 | test eax, eax |
||
768 | jnz @f |
||
769 | ; 2c. If failed, say a message and go to 11c. |
||
770 | mov esi, nomemory |
||
5051 | clevermous | 771 | invoke SysMsgBoardStr |
3520 | clevermous | 772 | jmp .nothing |
773 | @@: |
||
774 | ; 2d. If succeeded, zero the contents and continue configuring. |
||
775 | xchg ebx, eax ; ebx will point to usb_device_data |
||
776 | xor eax, eax |
||
777 | mov [ebx+usb_device_data.OutPipe], eax |
||
778 | mov [ebx+usb_device_data.InPipe], eax |
||
779 | mov [ebx+usb_device_data.MaxLUN], eax |
||
780 | mov [ebx+usb_device_data.LogicalDevices], eax |
||
781 | mov dword [ebx+usb_device_data.ConfigRequest], eax |
||
782 | mov dword [ebx+usb_device_data.ConfigRequest+4], eax |
||
783 | mov [ebx+usb_device_data.Status.Status], al |
||
784 | mov [ebx+usb_device_data.DeviceDisconnected], al |
||
785 | ; 2e. There is one reference: a connected USB device. |
||
786 | inc eax |
||
787 | mov [ebx+usb_device_data.NumReferences], eax |
||
788 | ; 2f. Save handle of configuration pipe for reset recovery. |
||
789 | mov eax, [.pipe0] |
||
790 | mov [ebx+usb_device_data.ConfigPipe], eax |
||
791 | ; 2g. Save the interface number for configuration requests. |
||
792 | mov al, [esi+interface_descr.bInterfaceNumber] |
||
793 | mov [ebx+usb_device_data.ConfigRequest+4], al |
||
794 | ; 2h. Initialize common fields in command wrapper. |
||
795 | mov [ebx+usb_device_data.Command.Signature], 'USBC' |
||
796 | mov [ebx+usb_device_data.Command.Tag], 'xxxx' |
||
797 | ; 2i. Initialize requests queue. |
||
798 | lea eax, [ebx+usb_device_data.RequestsQueue] |
||
799 | mov [eax+request_queue_item.Next], eax |
||
800 | mov [eax+request_queue_item.Prev], eax |
||
801 | lea ecx, [ebx+usb_device_data.QueueLock] |
||
5051 | clevermous | 802 | invoke MutexInit |
3520 | clevermous | 803 | ; Bulk-only mass storage devices use one OUT bulk endpoint for sending |
804 | ; command/data and one IN bulk endpoint for receiving data/status. |
||
805 | ; Look for those endpoints. |
||
806 | ; 3. Get the upper bound of all descriptors' data. |
||
807 | mov edx, [.config] ; configuration descriptor |
||
808 | movzx ecx, [edx+config_descr.wTotalLength] |
||
809 | add edx, ecx |
||
810 | ; 4. Loop over all descriptors until |
||
811 | ; either end-of-data reached - this is fail |
||
812 | ; or interface descriptor found - this is fail, all further data |
||
813 | ; correspond to that interface |
||
814 | ; or both endpoint descriptors found. |
||
815 | ; 4a. Loop start: esi points to the interface descriptor, |
||
816 | .lookep: |
||
817 | ; 4b. Get next descriptor. |
||
818 | movzx ecx, byte [esi] ; the first byte of all descriptors is length |
||
819 | add esi, ecx |
||
820 | ; 4c. Check that at least two bytes are readable. The opposite is an error. |
||
821 | inc esi |
||
822 | cmp esi, edx |
||
823 | jae .errorep |
||
824 | dec esi |
||
825 | ; 4d. Check that this descriptor is not interface descriptor. The opposite is |
||
826 | ; an error. |
||
827 | cmp byte [esi+endpoint_descr.bDescriptorType], INTERFACE_DESCR_TYPE |
||
828 | jz .errorep |
||
829 | ; 4e. Test whether this descriptor is an endpoint descriptor. If not, continue |
||
830 | ; the loop. |
||
831 | cmp byte [esi+endpoint_descr.bDescriptorType], ENDPOINT_DESCR_TYPE |
||
832 | jnz .lookep |
||
833 | ; 5. Check that the descriptor contains all required data and all data are |
||
834 | ; readable. The opposite is an error. |
||
5051 | clevermous | 835 | cmp byte [esi+endpoint_descr.bLength], sizeof.endpoint_descr |
3520 | clevermous | 836 | jb .errorep |
5051 | clevermous | 837 | lea ecx, [esi+sizeof.endpoint_descr] |
3520 | clevermous | 838 | cmp ecx, edx |
839 | ja .errorep |
||
840 | ; 6. Check that the endpoint is bulk endpoint. The opposite is an error. |
||
841 | mov cl, [esi+endpoint_descr.bmAttributes] |
||
842 | and cl, 3 |
||
843 | cmp cl, BULK_PIPE |
||
844 | jnz .errorep |
||
845 | ; 7. Get the direction of this endpoint. |
||
846 | movzx ecx, [esi+endpoint_descr.bEndpointAddress] |
||
847 | shr ecx, 7 |
||
848 | ; 8. Test whether a pipe for this direction is already opened. If so, continue |
||
849 | ; the loop. |
||
850 | cmp [ebx+usb_device_data.OutPipe+ecx*4], 0 |
||
851 | jnz .lookep |
||
852 | ; 9. Open pipe for this endpoint. |
||
853 | ; 9a. Save registers. |
||
854 | push ecx edx |
||
855 | ; 9b. Load parameters from the descriptor. |
||
856 | movzx ecx, [esi+endpoint_descr.bEndpointAddress] |
||
857 | movzx edx, [esi+endpoint_descr.wMaxPacketSize] |
||
858 | movzx eax, [esi+endpoint_descr.bInterval] ; not used for USB1, may be important for USB2 |
||
859 | ; 9c. Call the kernel. |
||
5051 | clevermous | 860 | invoke USBOpenPipe, [ebx+usb_device_data.ConfigPipe], ecx, edx, BULK_PIPE, eax |
3520 | clevermous | 861 | ; 9d. Restore registers. |
862 | pop edx ecx |
||
863 | ; 9e. Check result. If failed, go to 11b. |
||
864 | test eax, eax |
||
865 | jz .free |
||
866 | ; 9f. Save result. |
||
867 | mov [ebx+usb_device_data.OutPipe+ecx*4], eax |
||
868 | ; 10. Test whether the second pipe is already opened. If not, continue loop. |
||
869 | xor ecx, 1 |
||
870 | cmp [ebx+usb_device_data.OutPipe+ecx*4], 0 |
||
871 | jz .lookep |
||
872 | jmp .created |
||
873 | ; 11. An error occured during processing endpoint descriptor. |
||
874 | .errorep: |
||
875 | ; 11a. Print a message. |
||
876 | DEBUGF 1,'K : error: invalid endpoint descriptor\n' |
||
877 | .free: |
||
878 | ; 11b. Free the allocated usb_device_data. |
||
879 | xchg eax, ebx |
||
5051 | clevermous | 880 | invoke Kfree |
3520 | clevermous | 881 | .nothing: |
882 | ; 11c. Return an error. |
||
883 | xor eax, eax |
||
884 | jmp .return |
||
885 | .created: |
||
886 | ; 12. Pipes are opened. Send GetMaxLUN control request. |
||
887 | lea eax, [ebx+usb_device_data.ConfigRequest] |
||
888 | mov byte [eax], 0A1h ; class request from interface |
||
889 | mov byte [eax+1], REQUEST_GETMAXLUN |
||
890 | mov byte [eax+6], 1 ; transfer 1 byte |
||
891 | lea ecx, [ebx+usb_device_data.MaxLUN] |
||
892 | if DUMP_PACKETS |
||
893 | DEBUGF 1,'K : GETMAXLUN: %x %x %x %x %x %x %x %x\n',[eax]:2,[eax+1]:2,[eax+2]:2,[eax+3]:2,[eax+4]:2,[eax+5]:2,[eax+6]:2,[eax+7]:2 |
||
894 | end if |
||
5051 | clevermous | 895 | invoke USBControlTransferAsync, [ebx+usb_device_data.ConfigPipe], eax, ecx, 1, known_lun_callback, ebx, 0 |
3520 | clevermous | 896 | ; 13. Return with pointer to device data as returned value. |
897 | xchg eax, ebx |
||
898 | .return: |
||
899 | pop esi ebx |
||
900 | ret 12 |
||
901 | endp |
||
902 | |||
903 | ; This function is called when REQUEST_GETMAXLUN is done, |
||
904 | ; either successful or unsuccessful. |
||
905 | proc known_lun_callback |
||
906 | push ebx esi |
||
907 | virtual at esp |
||
908 | rd 2 ; saved registers |
||
909 | dd ? ; return address |
||
910 | .pipe dd ? |
||
911 | .status dd ? |
||
912 | .buffer dd ? |
||
913 | .length dd ? |
||
914 | .calldata dd ? |
||
915 | end virtual |
||
916 | ; 1. Check the status. If the request failed, assume that MaxLUN is zero. |
||
917 | mov ebx, [.calldata] |
||
918 | mov eax, [.status] |
||
919 | test eax, eax |
||
920 | jz @f |
||
921 | DEBUGF 1, 'K : GETMAXLUN failed with status %d, assuming zero\n', eax |
||
922 | mov [ebx+usb_device_data.MaxLUN], 0 |
||
923 | @@: |
||
924 | ; 2. Allocate the memory for logical devices. |
||
925 | mov eax, [ebx+usb_device_data.MaxLUN] |
||
926 | inc eax |
||
927 | DEBUGF 1,'K : %d logical unit(s)\n',eax |
||
5051 | clevermous | 928 | imul eax, sizeof.usb_unit_data |
3520 | clevermous | 929 | push ebx |
5051 | clevermous | 930 | invoke Kmalloc |
3520 | clevermous | 931 | pop ebx |
932 | ; If failed, print a message and do nothing. |
||
933 | test eax, eax |
||
934 | jnz @f |
||
935 | mov esi, nomemory |
||
5051 | clevermous | 936 | invoke SysMsgBoardStr |
3520 | clevermous | 937 | pop esi ebx |
938 | ret 20 |
||
939 | @@: |
||
940 | mov [ebx+usb_device_data.LogicalDevices], eax |
||
941 | ; 3. Initialize logical devices and initiate TEST_UNIT_READY request. |
||
942 | xchg esi, eax |
||
943 | xor ecx, ecx |
||
944 | .looplun: |
||
945 | mov [esi+usb_unit_data.Parent], ebx |
||
946 | mov [esi+usb_unit_data.LUN], cl |
||
947 | xor eax, eax |
||
948 | mov [esi+usb_unit_data.MediaPresent], al |
||
949 | mov [esi+usb_unit_data.DiskDevice], eax |
||
950 | mov [esi+usb_unit_data.SectorSize], eax |
||
951 | mov [esi+usb_unit_data.UnitReadyAttempts], eax |
||
952 | push ecx |
||
5051 | clevermous | 953 | invoke GetTimerTicks |
3520 | clevermous | 954 | mov [esi+usb_unit_data.TimerTicks], eax |
955 | stdcall queue_request, ebx, test_unit_ready_req, 0, test_unit_ready_callback, esi |
||
956 | pop ecx |
||
957 | inc ecx |
||
5051 | clevermous | 958 | add esi, sizeof.usb_unit_data |
3520 | clevermous | 959 | cmp ecx, [ebx+usb_device_data.MaxLUN] |
960 | jbe .looplun |
||
961 | ; 4. Return. |
||
962 | pop esi ebx |
||
963 | ret 20 |
||
964 | endp |
||
965 | |||
966 | ; Builder for SCSI INQUIRY request. |
||
967 | ; edx = first argument = pointer to usb_device_data.Command, |
||
968 | ; second argument = custom data given to queue_request. |
||
969 | proc inquiry_req |
||
970 | mov eax, [esp+8] |
||
971 | mov al, [eax+usb_unit_data.LUN] |
||
5051 | clevermous | 972 | mov [edx+command_block_wrapper.Length], sizeof.inquiry_data |
3520 | clevermous | 973 | mov [edx+command_block_wrapper.Flags], CBW_FLAG_IN |
974 | mov [edx+command_block_wrapper.LUN], al |
||
975 | mov byte [edx+command_block_wrapper.Command+0], SCSI_INQUIRY |
||
5051 | clevermous | 976 | mov byte [edx+command_block_wrapper.Command+4], sizeof.inquiry_data |
3520 | clevermous | 977 | ret 8 |
978 | endp |
||
979 | |||
980 | ; Called when SCSI INQUIRY request is completed. |
||
981 | proc inquiry_callback |
||
982 | ; 1. Check the status. |
||
983 | mov ecx, [esp+4] |
||
984 | cmp [ecx+usb_device_data.Status.Status], CSW_STATUS_OK |
||
985 | jnz .fail |
||
986 | ; 2. The command has completed successfully. |
||
987 | ; Print a message showing device type, ignore anything but block devices. |
||
988 | mov al, [ecx+usb_device_data.InquiryData.PeripheralDevice] |
||
989 | and al, 1Fh |
||
990 | DEBUGF 1,'K : peripheral device type is %x\n',al |
||
991 | test al, al |
||
992 | jnz .nothing |
||
993 | DEBUGF 1,'K : direct-access mass storage device detected\n' |
||
994 | ; 3. We have found a new disk device. Increment number of references. |
||
995 | lock inc [ecx+usb_device_data.NumReferences] |
||
996 | ; Unfortunately, we are now in the context of the USB thread, |
||
997 | ; so we can't notify the kernel immediately: it would try to do something |
||
998 | ; with a new disk, those actions would be synchronous and would require |
||
999 | ; waiting for results of USB requests, but we need to exit this callback |
||
1000 | ; to allow the USB thread to continue working and handling those requests. |
||
1001 | ; 4. Thus, create a temporary kernel thread which would do it. |
||
1002 | mov edx, [esp+8] |
||
4127 | clevermous | 1003 | push ebx ecx esi edi |
3598 | clevermous | 1004 | movi ebx, 1 |
3520 | clevermous | 1005 | mov ecx, new_disk_thread |
1006 | ; edx = parameter |
||
5051 | clevermous | 1007 | invoke CreateThread |
4127 | clevermous | 1008 | pop edi esi ecx ebx |
3520 | clevermous | 1009 | cmp eax, -1 |
1010 | jnz .nothing |
||
1011 | ; on error, reverse step 3 |
||
1012 | lock dec [ecx+usb_device_data.NumReferences] |
||
1013 | .nothing: |
||
1014 | ret 8 |
||
1015 | .fail: |
||
1016 | ; 4. The command has failed. Print a message and do nothing. |
||
1017 | push esi |
||
1018 | mov esi, inquiry_fail |
||
5051 | clevermous | 1019 | invoke SysMsgBoardStr |
3520 | clevermous | 1020 | pop esi |
1021 | ret 8 |
||
1022 | endp |
||
1023 | |||
1024 | ; Builder for SCSI TEST_UNIT_READY request. |
||
1025 | ; edx = first argument = pointer to usb_device_data.Command, |
||
1026 | ; second argument = custom data given to queue_request. |
||
1027 | proc test_unit_ready_req |
||
1028 | mov eax, [esp+8] |
||
1029 | mov al, [eax+usb_unit_data.LUN] |
||
1030 | mov [edx+command_block_wrapper.Length], 0 |
||
1031 | mov [edx+command_block_wrapper.Flags], CBW_FLAG_IN |
||
1032 | mov [edx+command_block_wrapper.LUN], al |
||
1033 | ret 8 |
||
1034 | endp |
||
1035 | |||
1036 | ; Called when SCSI TEST_UNIT_READY request is completed. |
||
1037 | proc test_unit_ready_callback |
||
1038 | virtual at esp |
||
1039 | dd ? ; return address |
||
1040 | .device dd ? |
||
1041 | .calldata dd ? |
||
1042 | end virtual |
||
1043 | ; 1. Check the status. |
||
1044 | mov ecx, [.device] |
||
1045 | mov edx, [.calldata] |
||
1046 | cmp [ecx+usb_device_data.Status.Status], CSW_STATUS_OK |
||
1047 | jnz .fail |
||
1048 | ; 2. The command has completed successfully, |
||
1049 | ; possibly after some repetitions. Print a debug message showing |
||
1050 | ; number and time of those. Remember that media is ready and go to 4. |
||
1051 | DEBUGF 1,'K : media is ready\n' |
||
5051 | clevermous | 1052 | invoke GetTimerTicks |
3520 | clevermous | 1053 | sub eax, [edx+usb_unit_data.TimerTicks] |
1054 | DEBUGF 1,'K : %d attempts, %d ticks\n',[edx+usb_unit_data.UnitReadyAttempts],eax |
||
1055 | inc [edx+usb_unit_data.MediaPresent] |
||
1056 | jmp .inquiry |
||
1057 | .fail: |
||
1058 | ; 3. The command has failed. |
||
1059 | ; Retry the same request up to 3 times with 10ms delay; |
||
1060 | ; if limit of retries is not reached, exit from the function. |
||
1061 | ; Otherwise, go to 4. |
||
1062 | inc [edx+usb_unit_data.UnitReadyAttempts] |
||
1063 | cmp [edx+usb_unit_data.UnitReadyAttempts], 3 |
||
1064 | jz @f |
||
1065 | push ecx edx esi |
||
3598 | clevermous | 1066 | movi esi, 10 |
5051 | clevermous | 1067 | invoke Sleep |
3520 | clevermous | 1068 | pop esi edx ecx |
1069 | stdcall queue_request, ecx, test_unit_ready_req, 0, test_unit_ready_callback, edx |
||
1070 | ret 8 |
||
1071 | @@: |
||
1072 | DEBUGF 1,'K : media not ready\n' |
||
1073 | .inquiry: |
||
1074 | ; 4. initiate INQUIRY request. |
||
1075 | lea eax, [ecx+usb_device_data.InquiryData] |
||
1076 | stdcall queue_request, ecx, inquiry_req, eax, inquiry_callback, edx |
||
1077 | ret 8 |
||
1078 | endp |
||
1079 | |||
1080 | ; Temporary thread for initial actions with a new disk device. |
||
1081 | proc new_disk_thread |
||
1082 | sub esp, 32 |
||
1083 | virtual at esp |
||
1084 | .name rb 32 ; device name |
||
1085 | .param dd ? ; contents of edx at the moment of int 0x40/eax=51 |
||
1086 | dd ? ; stack segment |
||
1087 | end virtual |
||
1088 | ; We are ready to notify the kernel about a new disk device. |
||
1089 | mov esi, [.param] |
||
1090 | ; 1. Generate name. |
||
1091 | ; 1a. Find a free index. |
||
1092 | mov ecx, free_numbers_lock |
||
5051 | clevermous | 1093 | invoke MutexLock |
3520 | clevermous | 1094 | xor eax, eax |
1095 | @@: |
||
1096 | bsf edx, [free_numbers+eax] |
||
1097 | jnz @f |
||
1098 | add eax, 4 |
||
1099 | cmp eax, 4*4 |
||
1100 | jnz @b |
||
5051 | clevermous | 1101 | invoke MutexUnlock |
3520 | clevermous | 1102 | push esi |
1103 | mov esi, noindex |
||
5051 | clevermous | 1104 | invoke SysMsgBoardStr |
3520 | clevermous | 1105 | pop esi |
1106 | jmp .drop_reference |
||
1107 | @@: |
||
1108 | ; 1b. Mark the index as busy. |
||
1109 | btr [free_numbers+eax], edx |
||
1110 | lea eax, [eax*8+edx] |
||
1111 | push eax |
||
5051 | clevermous | 1112 | invoke MutexUnlock |
3520 | clevermous | 1113 | pop eax |
1114 | ; 1c. Generate a name of the form "usbhd |
||
1115 | mov dword [esp], 'usbh' |
||
1116 | lea edi, [esp+5] |
||
1117 | mov byte [edi-1], 'd' |
||
1118 | push eax |
||
1119 | push -'0' |
||
3598 | clevermous | 1120 | movi ecx, 10 |
3520 | clevermous | 1121 | @@: |
1122 | cdq |
||
1123 | div ecx |
||
1124 | push edx |
||
1125 | test eax, eax |
||
1126 | jnz @b |
||
1127 | @@: |
||
1128 | pop eax |
||
1129 | add al, '0' |
||
1130 | stosb |
||
1131 | jnz @b |
||
1132 | pop ecx |
||
1133 | mov edx, esp |
||
1134 | ; 3d. Store the index in usb_unit_data to free it later. |
||
1135 | mov [esi+usb_unit_data.DiskIndex], cl |
||
1136 | ; 4. Notify the kernel about a new disk. |
||
1137 | ; 4a. Add a disk. |
||
1138 | ; stdcall queue_request, ecx, read_capacity_req, eax, read_capacity_callback, eax |
||
5051 | clevermous | 1139 | invoke DiskAdd, disk_functions, edx, esi, 0 |
3520 | clevermous | 1140 | mov ebx, eax |
1141 | ; 4b. If it failed, release the index and do nothing. |
||
1142 | test eax, eax |
||
1143 | jz .free_index |
||
1144 | ; 4c. Notify the kernel that a media is present. |
||
5051 | clevermous | 1145 | invoke DiskMediaChanged, eax, 1 |
3520 | clevermous | 1146 | ; 5. Lock the requests queue, check that device is not disconnected, |
1147 | ; store the disk handle, unlock the requests queue. |
||
1148 | mov ecx, [esi+usb_unit_data.Parent] |
||
1149 | add ecx, usb_device_data.QueueLock |
||
5051 | clevermous | 1150 | invoke MutexLock |
3520 | clevermous | 1151 | cmp byte [ecx+usb_device_data.DeviceDisconnected-usb_device_data.QueueLock], 0 |
1152 | jnz .disconnected |
||
1153 | mov [esi+usb_unit_data.DiskDevice], ebx |
||
5051 | clevermous | 1154 | invoke MutexUnlock |
3520 | clevermous | 1155 | jmp .exit |
1156 | .disconnected: |
||
5051 | clevermous | 1157 | invoke MutexUnlock |
3520 | clevermous | 1158 | stdcall disk_close, ebx |
1159 | jmp .exit |
||
1160 | .free_index: |
||
1161 | mov ecx, free_numbers_lock |
||
5051 | clevermous | 1162 | invoke MutexLock |
3520 | clevermous | 1163 | movzx eax, [esi+usb_unit_data.DiskIndex] |
1164 | bts [free_numbers], eax |
||
5051 | clevermous | 1165 | invoke MutexUnlock |
3520 | clevermous | 1166 | .drop_reference: |
1167 | mov esi, [esi+usb_unit_data.Parent] |
||
1168 | lock dec [esi+usb_device_data.NumReferences] |
||
1169 | jnz .exit |
||
1170 | mov eax, [esi+usb_device_data.LogicalDevices] |
||
5051 | clevermous | 1171 | invoke Kfree |
3520 | clevermous | 1172 | xchg eax, esi |
5051 | clevermous | 1173 | invoke Kfree |
3520 | clevermous | 1174 | .exit: |
1175 | or eax, -1 |
||
1176 | int 0x40 |
||
1177 | endp |
||
1178 | |||
1179 | ; This function is called when the device is disconnected. |
||
1180 | proc DeviceDisconnected |
||
1181 | push ebx esi |
||
1182 | virtual at esp |
||
1183 | rd 2 ; saved registers |
||
1184 | dd ? ; return address |
||
1185 | .device dd ? |
||
1186 | end virtual |
||
1187 | ; 1. Say a message. |
||
1188 | mov esi, disconnectmsg |
||
5051 | clevermous | 1189 | invoke SysMsgBoardStr |
3520 | clevermous | 1190 | ; 2. Lock the requests queue, set .DeviceDisconnected to 1, |
1191 | ; unlock the requests queue. |
||
1192 | ; Locking is required for synchronization with queue_request: |
||
1193 | ; all USB callbacks are executed in the same thread and are |
||
1194 | ; synchronized automatically, but queue_request can be running |
||
1195 | ; from any thread which wants to do something with a filesystem. |
||
1196 | ; Without locking, it would be possible that queue_request has |
||
1197 | ; been started, has checked that device is not yet disconnected, |
||
1198 | ; then DeviceDisconnected completes and all handles become invalid, |
||
1199 | ; then queue_request tries to use them. |
||
1200 | mov esi, [.device] |
||
1201 | lea ecx, [esi+usb_device_data.QueueLock] |
||
5051 | clevermous | 1202 | invoke MutexLock |
3520 | clevermous | 1203 | mov [esi+usb_device_data.DeviceDisconnected], 1 |
5051 | clevermous | 1204 | invoke MutexUnlock |
3520 | clevermous | 1205 | ; 3. Drop one reference to the structure and check whether |
1206 | ; that was the last reference. |
||
1207 | lock dec [esi+usb_device_data.NumReferences] |
||
1208 | jz .free |
||
1209 | ; 4. If not, there are some additional references due to disk devices; |
||
1210 | ; notify the kernel that those disks are deleted. |
||
1211 | ; Note that new disks cannot be added while we are looping here, |
||
1212 | ; because new_disk_thread checks for .DeviceDisconnected. |
||
1213 | mov ebx, [esi+usb_device_data.MaxLUN] |
||
1214 | mov esi, [esi+usb_device_data.LogicalDevices] |
||
1215 | inc ebx |
||
1216 | .diskdel: |
||
1217 | mov eax, [esi+usb_unit_data.DiskDevice] |
||
1218 | test eax, eax |
||
1219 | jz @f |
||
5051 | clevermous | 1220 | invoke DiskDel, eax |
3520 | clevermous | 1221 | @@: |
5051 | clevermous | 1222 | add esi, sizeof.usb_unit_data |
3520 | clevermous | 1223 | dec ebx |
1224 | jnz .diskdel |
||
1225 | ; In this case, some operations with those disks are still possible, |
||
1226 | ; so we can't do anything more now. disk_close will take care of the rest. |
||
1227 | .return: |
||
1228 | pop esi ebx |
||
1229 | ret 4 |
||
1230 | ; 5. If there are no disk devices, free all resources which were allocated. |
||
1231 | .free: |
||
1232 | mov eax, [esi+usb_device_data.LogicalDevices] |
||
1233 | test eax, eax |
||
1234 | jz @f |
||
5051 | clevermous | 1235 | invoke Kfree |
3520 | clevermous | 1236 | @@: |
1237 | xchg eax, esi |
||
5051 | clevermous | 1238 | invoke Kfree |
3520 | clevermous | 1239 | jmp .return |
1240 | endp |
||
1241 | |||
1242 | ; Disk functions. |
||
1243 | DISK_STATUS_OK = 0 ; success |
||
1244 | DISK_STATUS_GENERAL_ERROR = -1; if no other code is suitable |
||
1245 | DISK_STATUS_INVALID_CALL = 1 ; invalid input parameters |
||
1246 | DISK_STATUS_NO_MEDIA = 2 ; no media present |
||
1247 | DISK_STATUS_END_OF_MEDIA = 3 ; end of media while reading/writing data |
||
1248 | |||
1249 | ; Called when all operations with the given disk are done. |
||
1250 | proc disk_close |
||
1251 | push ebx esi |
||
1252 | virtual at esp |
||
1253 | rd 2 ; saved registers |
||
1254 | dd ? ; return address |
||
1255 | .userdata dd ? |
||
1256 | end virtual |
||
1257 | mov esi, [.userdata] |
||
1258 | mov ecx, free_numbers_lock |
||
5051 | clevermous | 1259 | invoke MutexLock |
3520 | clevermous | 1260 | movzx eax, [esi+usb_unit_data.DiskIndex] |
1261 | bts [free_numbers], eax |
||
5051 | clevermous | 1262 | invoke MutexUnlock |
3520 | clevermous | 1263 | mov esi, [esi+usb_unit_data.Parent] |
1264 | lock dec [esi+usb_device_data.NumReferences] |
||
1265 | jnz .nothing |
||
1266 | mov eax, [esi+usb_device_data.LogicalDevices] |
||
5051 | clevermous | 1267 | invoke Kfree |
3520 | clevermous | 1268 | xchg eax, esi |
5051 | clevermous | 1269 | invoke Kfree |
3520 | clevermous | 1270 | .nothing: |
1271 | pop esi ebx |
||
1272 | ret 4 |
||
1273 | endp |
||
1274 | |||
1275 | ; Returns sector size, capacity and flags of the media. |
||
1276 | proc disk_querymedia stdcall uses ebx esi edi, \ |
||
1277 | userdata:dword, mediainfo:dword |
||
1278 | ; 1. Create event for waiting. |
||
1279 | xor esi, esi |
||
1280 | xor ecx, ecx |
||
5051 | clevermous | 1281 | invoke CreateEvent |
3520 | clevermous | 1282 | test eax, eax |
1283 | jz .generic_fail |
||
1284 | push eax |
||
1285 | push edx |
||
1286 | push ecx |
||
1287 | push 0 |
||
1288 | push 0 |
||
1289 | virtual at ebp-.localsize |
||
1290 | .locals: |
||
1291 | ; two following dwords are the output of READ_CAPACITY |
||
1292 | .LastLBABE dd ? |
||
1293 | .SectorSizeBE dd ? |
||
1294 | .Status dd ? |
||
1295 | ; two following dwords identify an event |
||
1296 | .event_code dd ? |
||
1297 | .event dd ? |
||
1298 | rd 3 ; saved registers |
||
1299 | .localsize = $ - .locals |
||
1300 | dd ? ; saved ebp |
||
1301 | dd ? ; return address |
||
1302 | .userdata dd ? |
||
1303 | .mediainfo dd ? |
||
1304 | end virtual |
||
1305 | ; 2. Initiate SCSI READ_CAPACITY request. |
||
1306 | mov eax, [userdata] |
||
1307 | mov ecx, [eax+usb_unit_data.Parent] |
||
1308 | mov edx, esp |
||
1309 | stdcall queue_request, ecx, read_capacity_req, edx, read_capacity_callback, edx |
||
1310 | ; 3. Wait for event. This destroys it. |
||
1311 | mov eax, [.event] |
||
1312 | mov ebx, [.event_code] |
||
5051 | clevermous | 1313 | invoke WaitEvent |
3520 | clevermous | 1314 | ; 4. Get the status and results. |
1315 | pop ecx |
||
1316 | bswap ecx ; .LastLBA |
||
1317 | pop edx |
||
1318 | bswap edx ; .SectorSize |
||
1319 | pop eax ; .Status |
||
1320 | ; 5. If the request has completed successfully, store results. |
||
1321 | test eax, eax |
||
1322 | jnz @f |
||
1323 | DEBUGF 1,'K : sector size is %d, last sector is %d\n',edx,ecx |
||
1324 | mov ebx, [mediainfo] |
||
1325 | mov [ebx], eax ; flags = 0 |
||
1326 | mov [ebx+4], edx ; sectorsize |
||
1327 | add ecx, 1 |
||
1328 | adc eax, 0 |
||
1329 | mov [ebx+8], ecx |
||
1330 | mov [ebx+12], eax ; capacity |
||
1331 | mov eax, [userdata] |
||
1332 | mov [eax+usb_unit_data.SectorSize], edx |
||
1333 | xor eax, eax |
||
1334 | @@: |
||
1335 | ; 6. Restore the stack and return. |
||
1336 | pop ecx |
||
1337 | pop ecx |
||
1338 | ret |
||
1339 | .generic_fail: |
||
1340 | or eax, -1 |
||
1341 | ret |
||
1342 | endp |
||
1343 | |||
1344 | ; Builder for SCSI READ_CAPACITY request. |
||
1345 | ; edx = first argument = pointer to usb_device_data.Command, |
||
1346 | ; second argument = custom data given to queue_request, |
||
1347 | ; pointer to disk_querymedia.locals. |
||
1348 | proc read_capacity_req |
||
1349 | mov eax, [esp+8] |
||
1350 | mov eax, [eax+disk_querymedia.userdata-disk_querymedia.locals] |
||
1351 | mov al, [eax+usb_unit_data.LUN] |
||
1352 | mov [edx+command_block_wrapper.Length], 8 |
||
1353 | mov [edx+command_block_wrapper.Flags], CBW_FLAG_IN |
||
1354 | mov [edx+command_block_wrapper.LUN], al |
||
1355 | mov byte [edx+command_block_wrapper.Command+0], SCSI_READ_CAPACITY |
||
1356 | ret 8 |
||
1357 | endp |
||
1358 | |||
1359 | ; Called when SCSI READ_CAPACITY request is completed. |
||
1360 | proc read_capacity_callback |
||
1361 | ; Transform the status to return value of disk_querymedia |
||
1362 | ; and set the event. |
||
1363 | mov ecx, [esp+4] |
||
1364 | xor eax, eax |
||
1365 | cmp [ecx+usb_device_data.Status.Status], al |
||
1366 | jz @f |
||
1367 | or eax, -1 |
||
1368 | @@: |
||
1369 | mov ecx, [esp+8] |
||
1370 | mov [ecx+disk_querymedia.Status-disk_querymedia.locals], eax |
||
1371 | push ebx esi edi |
||
1372 | mov eax, [ecx+disk_querymedia.event-disk_querymedia.locals] |
||
1373 | mov ebx, [ecx+disk_querymedia.event_code-disk_querymedia.locals] |
||
1374 | xor edx, edx |
||
1375 | xor esi, esi |
||
5051 | clevermous | 1376 | invoke RaiseEvent |
3520 | clevermous | 1377 | pop edi esi ebx |
1378 | ret 8 |
||
1379 | endp |
||
1380 | |||
1381 | disk_write: |
||
1382 | mov al, SCSI_WRITE10 |
||
1383 | jmp disk_read_write |
||
1384 | |||
1385 | disk_read: |
||
1386 | mov al, SCSI_READ10 |
||
1387 | |||
1388 | ; Reads from the device or writes to the device. |
||
1389 | proc disk_read_write stdcall uses ebx esi edi, \ |
||
1390 | userdata:dword, buffer:dword, startsector:qword, numsectors:dword |
||
1391 | ; 1. Initialize. |
||
1392 | push eax ; .command |
||
1393 | mov eax, [userdata] |
||
1394 | mov eax, [eax+usb_unit_data.SectorSize] |
||
1395 | push eax ; .SectorSize |
||
1396 | push 0 ; .processed |
||
1397 | mov eax, [numsectors] |
||
1398 | mov eax, [eax] |
||
1399 | ; 2. The transfer length for SCSI_{READ,WRITE}10 commands can not be greater |
||
1400 | ; than 0xFFFF, so split the request to slices with <= 0xFFFF sectors. |
||
1401 | max_sectors_at_time = 0xFFFF |
||
1402 | .split: |
||
1403 | push eax ; .length_rest |
||
1404 | cmp eax, max_sectors_at_time |
||
1405 | jb @f |
||
1406 | mov eax, max_sectors_at_time |
||
1407 | @@: |
||
1408 | sub [esp], eax |
||
1409 | push eax ; .length_cur |
||
1410 | ; 3. startsector must fit in 32 bits, otherwise abort the request. |
||
1411 | cmp dword [startsector+4], 0 |
||
1412 | jnz .generic_fail |
||
1413 | ; 4. Create event for waiting. |
||
1414 | xor esi, esi |
||
1415 | xor ecx, ecx |
||
5051 | clevermous | 1416 | invoke CreateEvent |
3520 | clevermous | 1417 | test eax, eax |
1418 | jz .generic_fail |
||
1419 | push eax ; .event |
||
1420 | push edx ; .event_code |
||
1421 | push ecx ; .status |
||
1422 | virtual at ebp-.localsize |
||
1423 | .locals: |
||
1424 | .status dd ? |
||
1425 | .event_code dd ? |
||
1426 | .event dd ? |
||
1427 | .length_cur dd ? |
||
1428 | .length_rest dd ? |
||
1429 | .processed dd ? |
||
1430 | .SectorSize dd ? |
||
1431 | .command db ? |
||
1432 | rb 3 |
||
1433 | rd 3 ; saved registers |
||
1434 | .localsize = $ - .locals |
||
1435 | dd ? ; saved ebp |
||
1436 | dd ? ; return address |
||
1437 | .userdata dd ? |
||
1438 | .buffer dd ? |
||
1439 | .startsector dq ? |
||
1440 | .numsectors dd ? |
||
1441 | end virtual |
||
1442 | ; 5. Initiate SCSI READ10 or WRITE10 request. |
||
1443 | mov eax, [userdata] |
||
1444 | mov ecx, [eax+usb_unit_data.Parent] |
||
1445 | stdcall queue_request, ecx, read_write_req, [buffer], read_write_callback, esp |
||
1446 | ; 6. Wait for event. This destroys it. |
||
1447 | mov eax, [.event] |
||
1448 | mov ebx, [.event_code] |
||
5051 | clevermous | 1449 | invoke WaitEvent |
3520 | clevermous | 1450 | ; 7. Get the status. If the operation has failed, abort. |
1451 | pop eax ; .status |
||
1452 | pop ecx ecx ; cleanup .event_code, .event |
||
1453 | pop ecx ; .length_cur |
||
1454 | test eax, eax |
||
1455 | jnz .return |
||
1456 | ; 8. Otherwise, continue the loop started at step 2. |
||
1457 | add dword [startsector], ecx |
||
1458 | adc dword [startsector+4], eax |
||
1459 | imul ecx, [.SectorSize] |
||
1460 | add [buffer], ecx |
||
1461 | pop eax |
||
1462 | test eax, eax |
||
1463 | jnz .split |
||
1464 | push eax |
||
1465 | .return: |
||
1466 | ; 9. Restore the stack, store .processed to [numsectors], return. |
||
1467 | pop ecx ; .length_rest |
||
1468 | pop ecx ; .processed |
||
1469 | mov edx, [numsectors] |
||
1470 | mov [edx], ecx |
||
1471 | pop ecx ; .SectorSize |
||
1472 | pop ecx ; .command |
||
1473 | ret |
||
1474 | .generic_fail: |
||
1475 | or eax, -1 |
||
1476 | pop ecx ; .length_cur |
||
1477 | jmp .return |
||
1478 | endp |
||
1479 | |||
1480 | ; Builder for SCSI READ10 or WRITE10 request. |
||
1481 | ; edx = first argument = pointer to usb_device_data.Command, |
||
1482 | ; second argument = custom data given to queue_request, |
||
1483 | ; pointer to disk_read_write.locals. |
||
1484 | proc read_write_req |
||
1485 | mov eax, [esp+8] |
||
1486 | mov ecx, [eax+disk_read_write.userdata-disk_read_write.locals] |
||
1487 | mov cl, [ecx+usb_unit_data.LUN] |
||
1488 | mov [edx+command_block_wrapper.LUN], cl |
||
1489 | mov ecx, [eax+disk_read_write.length_cur-disk_read_write.locals] |
||
1490 | imul ecx, [eax+disk_read_write.SectorSize-disk_read_write.locals] |
||
1491 | mov [edx+command_block_wrapper.Length], ecx |
||
1492 | mov cl, [eax+disk_read_write.command-disk_read_write.locals] |
||
1493 | mov [edx+command_block_wrapper.Flags], CBW_FLAG_OUT |
||
1494 | cmp cl, SCSI_READ10 |
||
1495 | jnz @f |
||
1496 | mov [edx+command_block_wrapper.Flags], CBW_FLAG_IN |
||
1497 | @@: |
||
1498 | mov byte [edx+command_block_wrapper.Command], cl |
||
1499 | mov ecx, dword [eax+disk_read_write.startsector-disk_read_write.locals] |
||
1500 | bswap ecx |
||
1501 | mov dword [edx+command_block_wrapper.Command+2], ecx |
||
1502 | mov ecx, [eax+disk_read_write.length_cur-disk_read_write.locals] |
||
1503 | xchg cl, ch |
||
1504 | mov word [edx+command_block_wrapper.Command+7], cx |
||
1505 | ret 8 |
||
1506 | endp |
||
1507 | |||
1508 | ; Called when SCSI READ10 or WRITE10 request is completed. |
||
1509 | proc read_write_callback |
||
1510 | ; 1. Initialize. |
||
1511 | push ebx esi edi |
||
1512 | virtual at esp |
||
1513 | rd 3 ; saved registers |
||
1514 | dd ? ; return address |
||
1515 | .device dd ? |
||
1516 | .calldata dd ? |
||
1517 | end virtual |
||
1518 | mov ecx, [.device] |
||
1519 | mov esi, [.calldata] |
||
1520 | ; 2. Get the number of sectors which were read. |
||
1521 | ; If the status is OK or FAIL, the field .LengthRest is valid. |
||
1522 | ; Otherwise, it is invalid, so assume zero sectors. |
||
1523 | xor eax, eax |
||
1524 | cmp [ecx+usb_device_data.Status.Status], CSW_STATUS_FAIL |
||
1525 | ja .sectors_calculated |
||
1526 | mov eax, [ecx+usb_device_data.LengthRest] |
||
1527 | xor edx, edx |
||
1528 | div [esi+disk_read_write.SectorSize-disk_read_write.locals] |
||
1529 | test edx, edx |
||
1530 | jz @f |
||
1531 | inc eax |
||
1532 | @@: |
||
1533 | mov edx, eax |
||
1534 | mov eax, [esi+disk_read_write.length_cur-disk_read_write.locals] |
||
1535 | sub eax, edx |
||
1536 | jae .sectors_calculated |
||
1537 | xor eax, eax |
||
1538 | .sectors_calculated: |
||
1539 | ; 3. Increase the total number of processed sectors. |
||
1540 | add [esi+disk_read_write.processed-disk_read_write.locals], eax |
||
1541 | ; 4. Set status to OK if all sectors were read, to ERROR otherwise. |
||
1542 | cmp eax, [esi+disk_read_write.length_cur-disk_read_write.locals] |
||
1543 | setz al |
||
1544 | movzx eax, al |
||
1545 | dec eax |
||
1546 | mov [esi+disk_read_write.status-disk_read_write.locals], eax |
||
1547 | ; 5. Set the event. |
||
1548 | mov eax, [esi+disk_read_write.event-disk_read_write.locals] |
||
1549 | mov ebx, [esi+disk_read_write.event_code-disk_read_write.locals] |
||
1550 | xor edx, edx |
||
1551 | xor esi, esi |
||
5051 | clevermous | 1552 | invoke RaiseEvent |
3520 | clevermous | 1553 | ; 6. Return. |
1554 | pop edi esi ebx |
||
1555 | ret 8 |
||
1556 | endp |
||
1557 | |||
1558 | ; strings |
||
1559 | my_driver db 'usbstor',0 |
||
1560 | disconnectmsg db 'K : USB mass storage device disconnected',13,10,0 |
||
1561 | nomemory db 'K : no memory',13,10,0 |
||
1562 | unkdevice db 'K : unknown mass storage device',13,10,0 |
||
1563 | okdevice db 'K : USB mass storage device detected',13,10,0 |
||
1564 | transfererror db 'K : USB transfer error, disabling mass storage',13,10,0 |
||
1565 | invresponse db 'K : invalid response from mass storage device',13,10,0 |
||
1566 | fatalerr db 'K : mass storage device reports fatal error',13,10,0 |
||
1567 | inquiry_fail db 'K : INQUIRY command failed',13,10,0 |
||
1568 | ;read_capacity_fail db 'K : READ CAPACITY command failed',13,10,0 |
||
1569 | ;read_fail db 'K : READ command failed',13,10,0 |
||
1570 | noindex db 'K : failed to generate disk name',13,10,0 |
||
1571 | |||
1572 | align 4 |
||
1573 | ; Structure with callback functions. |
||
1574 | usb_functions: |
||
1575 | dd usb_functions_end - usb_functions |
||
1576 | dd AddDevice |
||
1577 | dd DeviceDisconnected |
||
1578 | usb_functions_end: |
||
1579 | |||
1580 | disk_functions: |
||
1581 | dd disk_functions_end - disk_functions |
||
1582 | dd disk_close |
||
1583 | dd 0 ; closemedia |
||
1584 | dd disk_querymedia |
||
1585 | dd disk_read |
||
1586 | dd disk_write |
||
1587 | dd 0 ; flush |
||
1588 | dd 0 ; adjust_cache_size: use default cache |
||
1589 | disk_functions_end: |
||
1590 | |||
5051 | clevermous | 1591 | data fixups |
1592 | end data |
||
1593 | |||
3520 | clevermous | 1594 | free_numbers_lock rd 3 |
1595 | ; 128 devices should be enough for everybody |
||
1596 | free_numbers dd -1, -1, -1, -1 |
||
1597 | |||
1598 | ; for DEBUGF macro |
||
1599 | include_debug_strings=> |