[FLOPPY]
[reactos.git] / reactos / drivers / storage / floppy / readwrite.c
1 /*
2 * ReactOS Floppy Driver
3 * Copyright (C) 2004, Vizzini (vizzini@plasmic.com)
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * PROJECT: ReactOS Floppy Driver
20 * FILE: readwrite.c
21 * PURPOSE: Read/Write handler routines
22 * PROGRAMMER: Vizzini (vizzini@plasmic.com)
23 * REVISIONS:
24 * 15-Feb-2004 vizzini - Created
25 * NOTES:
26 *
27 * READ/WRITE PROCESS
28 *
29 * This process is extracted from the Intel datasheet for the floppy controller.
30 *
31 * - Turn on the motor and set turnoff time
32 * - Program the drive's data rate
33 * - Seek
34 * - Read ID
35 * - Set up DMA
36 * - Send read/write command to FDC
37 * - Read result bytes
38 *
39 * This is mostly implemented in one big function, which watis on the SynchEvent
40 * as many times as necessary to get through the process. See ReadWritePassive() for
41 * more details.
42 *
43 * NOTES:
44 * - Currently doesn't support partial-sector transfers, which is really just a failing
45 * of RWComputeCHS. I've never seen Windows send a partial-sector request, though, so
46 * this may not be a bad thing. Should be looked into, regardless.
47 *
48 * TODO: Break up ReadWritePassive and handle errors better
49 * TODO: Figure out data rate issues
50 * TODO: Media type detection
51 * TODO: Figure out perf issue - waiting after call to read/write for about a second each time
52 * TODO: Figure out specify timings
53 */
54
55 #include "precomp.h"
56
57 #include <debug.h>
58
59 static IO_ALLOCATION_ACTION NTAPI
60 MapRegisterCallback(PDEVICE_OBJECT DeviceObject,
61 PIRP Irp,
62 PVOID MapRegisterBase,
63 PVOID Context)
64 /*
65 * FUNCTION: Acquire map registers in prep for DMA
66 * ARGUMENTS:
67 * DeviceObject: unused
68 * Irp: unused
69 * MapRegisterBase: returned to blocked thread via a member var
70 * Context: contains a pointer to the right ControllerInfo
71 * struct
72 * RETURNS:
73 * KeepObject, because that's what the DDK says to do
74 */
75 {
76 PCONTROLLER_INFO ControllerInfo = (PCONTROLLER_INFO)Context;
77 UNREFERENCED_PARAMETER(DeviceObject);
78 UNREFERENCED_PARAMETER(Irp);
79
80 TRACE_(FLOPPY, "MapRegisterCallback Called\n");
81
82 ControllerInfo->MapRegisterBase = MapRegisterBase;
83 KeSetEvent(&ControllerInfo->SynchEvent, 0, FALSE);
84
85 return KeepObject;
86 }
87
88
89 NTSTATUS NTAPI
90 ReadWrite(PDEVICE_OBJECT DeviceObject, PIRP Irp)
91 /*
92 * FUNCTION: Dispatch routine called for read or write IRPs
93 * ARGUMENTS:
94 * RETURNS:
95 * STATUS_PENDING if the IRP is queued
96 * STATUS_INVALID_PARAMETER if IRP is set up wrong
97 * NOTES:
98 * - This function validates arguments to the IRP and then queues it
99 * - Note that this function is implicitly serialized by the queue logic. Only
100 * one of these at a time is active in the system, no matter how many processors
101 * and threads we have.
102 * - This function stores the DeviceObject in the IRP's context area before dropping
103 * it onto the irp queue
104 */
105 {
106 TRACE_(FLOPPY, "ReadWrite called\n");
107
108 ASSERT(DeviceObject);
109 ASSERT(Irp);
110
111 if(!Irp->MdlAddress)
112 {
113 WARN_(FLOPPY, "ReadWrite(): MDL not found in IRP - Completing with STATUS_INVALID_PARAMETER\n");
114 Irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
115 Irp->IoStatus.Information = 0;
116 IoCompleteRequest(Irp, IO_NO_INCREMENT);
117 return STATUS_INVALID_PARAMETER;
118 }
119
120 /*
121 * Queue the irp to the thread.
122 * The de-queue thread will look in DriverContext[0] for the Device Object.
123 */
124 Irp->Tail.Overlay.DriverContext[0] = DeviceObject;
125 IoCsqInsertIrp(&Csq, Irp, NULL);
126
127 return STATUS_PENDING;
128 }
129
130
131 static VOID NTAPI
132 RWFreeAdapterChannel(PADAPTER_OBJECT AdapterObject)
133 /*
134 * FUNCTION: Free the adapter DMA channel that we allocated
135 * ARGUMENTS:
136 * AdapterObject: the object with the map registers to free
137 * NOTES:
138 * - This function is primarily needed because IoFreeAdapterChannel wants to
139 * be called at DISPATCH_LEVEL
140 */
141 {
142 KIRQL Irql;
143
144 ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);
145
146 KeRaiseIrql(DISPATCH_LEVEL, &Irql);
147 IoFreeAdapterChannel(AdapterObject);
148 KeLowerIrql(Irql);
149 }
150
151
152 NTSTATUS NTAPI
153 RWDetermineMediaType(PDRIVE_INFO DriveInfo, BOOLEAN OneShot)
154 /*
155 * FUNCTION: Determine the media type of the disk in the drive and fill in the geometry
156 * ARGUMENTS:
157 * DriveInfo: drive to look at
158 * RETURNS:
159 * STATUS_SUCCESS if the media was recognized and the geometry struct was filled in
160 * STATUS_UNRECOGNIZED_MEDIA if not
161 * STATUS_UNSUCCESSFUL if the controller can't be talked to
162 * NOTES:
163 * - Expects the motor to already be running
164 * - Currently only supports 1.44MB 3.5" disks
165 * - PAGED_CODE because it waits
166 * TODO:
167 * - Support more disk types
168 */
169 {
170 UCHAR HeadLoadTime;
171 UCHAR HeadUnloadTime;
172 UCHAR StepRateTime;
173
174 PAGED_CODE();
175
176 TRACE_(FLOPPY, "RWDetermineMediaType called\n");
177
178 /*
179 * This algorithm assumes that a 1.44MB floppy is in the drive. If it's not,
180 * it works backwards until the read works unless OneShot try is asked.
181 * Note that only 1.44 has been tested at all.
182 */
183
184 do
185 {
186 int i;
187
188 /* Program data rate */
189 if(HwSetDataRate(DriveInfo->ControllerInfo, DRSR_DSEL_500KBPS) != STATUS_SUCCESS)
190 {
191 WARN_(FLOPPY, "RWDetermineMediaType(): unable to set data rate\n");
192 return STATUS_UNSUCCESSFUL;
193 }
194
195 /* Specify */
196 HeadLoadTime = SPECIFY_HLT_500K;
197 HeadUnloadTime = SPECIFY_HUT_500K;
198 StepRateTime = SPECIFY_SRT_500K;
199
200 /* Don't disable DMA --> enable dma (dumb & confusing) */
201 if(HwSpecify(DriveInfo->ControllerInfo, HeadLoadTime, HeadUnloadTime, StepRateTime, FALSE) != STATUS_SUCCESS)
202 {
203 WARN_(FLOPPY, "RWDetermineMediaType(): specify failed\n");
204 return STATUS_UNSUCCESSFUL;
205 }
206
207 /* clear any spurious interrupts in preparation for recalibrate */
208 KeClearEvent(&DriveInfo->ControllerInfo->SynchEvent);
209
210 /* Recalibrate --> head over first track */
211 for(i=0; i < 2; i++)
212 {
213 NTSTATUS RecalStatus;
214
215 if(HwRecalibrate(DriveInfo) != STATUS_SUCCESS)
216 {
217 WARN_(FLOPPY, "RWDetermineMediaType(): Recalibrate failed\n");
218 return STATUS_UNSUCCESSFUL;
219 }
220
221 /* Wait for the recalibrate to finish */
222 WaitForControllerInterrupt(DriveInfo->ControllerInfo);
223
224 RecalStatus = HwRecalibrateResult(DriveInfo->ControllerInfo);
225
226 if(RecalStatus == STATUS_SUCCESS)
227 break;
228
229 if(i == 1) /* failed for 2nd time */
230 {
231 WARN_(FLOPPY, "RWDetermineMediaType(): RecalibrateResult failed\n");
232 return STATUS_UNSUCCESSFUL;
233 }
234 }
235
236 /* clear any spurious interrupts */
237 KeClearEvent(&DriveInfo->ControllerInfo->SynchEvent);
238
239 /* Try to read an ID */
240 if(HwReadId(DriveInfo, 0) != STATUS_SUCCESS) /* read the first ID we find, from head 0 */
241 {
242 WARN_(FLOPPY, "RWDetermineMediaType(): ReadId failed\n");
243 return STATUS_UNSUCCESSFUL; /* if we can't even write to the controller, it's hopeless */
244 }
245
246 /* Wait for the ReadID to finish */
247 WaitForControllerInterrupt(DriveInfo->ControllerInfo);
248
249 if(HwReadIdResult(DriveInfo->ControllerInfo, NULL, NULL) != STATUS_SUCCESS)
250 {
251 WARN_(FLOPPY, "RWDetermineMediaType(): ReadIdResult failed; continuing\n");
252 if (OneShot)
253 break;
254 else
255 continue;
256 }
257
258 /* Found the media; populate the geometry now */
259 WARN_(FLOPPY, "Hardcoded media type!\n");
260 INFO_(FLOPPY, "RWDetermineMediaType(): Found 1.44 media; returning success\n");
261 DriveInfo->DiskGeometry.MediaType = GEOMETRY_144_MEDIATYPE;
262 DriveInfo->DiskGeometry.Cylinders.QuadPart = GEOMETRY_144_CYLINDERS;
263 DriveInfo->DiskGeometry.TracksPerCylinder = GEOMETRY_144_TRACKSPERCYLINDER;
264 DriveInfo->DiskGeometry.SectorsPerTrack = GEOMETRY_144_SECTORSPERTRACK;
265 DriveInfo->DiskGeometry.BytesPerSector = GEOMETRY_144_BYTESPERSECTOR;
266 DriveInfo->BytesPerSectorCode = HW_512_BYTES_PER_SECTOR;
267 return STATUS_SUCCESS;
268 }
269 while(FALSE);
270
271 TRACE_(FLOPPY, "RWDetermineMediaType(): failed to find media\n");
272 return STATUS_UNRECOGNIZED_MEDIA;
273 }
274
275
276 static NTSTATUS NTAPI
277 RWSeekToCylinder(PDRIVE_INFO DriveInfo, UCHAR Cylinder)
278 /*
279 * FUNCTION: Seek a particular drive to a particular track
280 * ARGUMENTS:
281 * DriveInfo: Drive to seek
282 * Cylinder: track to seek to
283 * RETURNS:
284 * STATUS_SUCCESS if the head was successfully seeked
285 * STATUS_UNSUCCESSFUL if not
286 * NOTES:
287 * - PAGED_CODE because it blocks
288 */
289 {
290 UCHAR CurCylinder;
291
292 PAGED_CODE();
293
294 TRACE_(FLOPPY, "RWSeekToCylinder called drive 0x%p cylinder %d\n", DriveInfo, Cylinder);
295
296 /* Clear any spurious interrupts */
297 KeClearEvent(&DriveInfo->ControllerInfo->SynchEvent);
298
299 /* queue seek command */
300 if(HwSeek(DriveInfo, Cylinder) != STATUS_SUCCESS)
301 {
302 WARN_(FLOPPY, "RWSeekToTrack(): unable to seek\n");
303 return STATUS_UNSUCCESSFUL;
304 }
305
306 WaitForControllerInterrupt(DriveInfo->ControllerInfo);
307
308 if(HwSenseInterruptStatus(DriveInfo->ControllerInfo) != STATUS_SUCCESS)
309 {
310 WARN_(FLOPPY, "RWSeekToTrack(): unable to get seek results\n");
311 return STATUS_UNSUCCESSFUL;
312 }
313
314 /* read ID mark from head 0 to verify */
315 if(HwReadId(DriveInfo, 0) != STATUS_SUCCESS)
316 {
317 WARN_(FLOPPY, "RWSeekToTrack(): unable to queue ReadId\n");
318 return STATUS_UNSUCCESSFUL;
319 }
320
321 WaitForControllerInterrupt(DriveInfo->ControllerInfo);
322
323 if(HwReadIdResult(DriveInfo->ControllerInfo, &CurCylinder, NULL) != STATUS_SUCCESS)
324 {
325 WARN_(FLOPPY, "RWSeekToTrack(): unable to get ReadId result\n");
326 return STATUS_UNSUCCESSFUL;
327 }
328
329 if(CurCylinder != Cylinder)
330 {
331 WARN_(FLOPPY, "RWSeekToTrack(): Seeek to track failed; current cylinder is 0x%x\n", CurCylinder);
332 return STATUS_UNSUCCESSFUL;
333 }
334
335 INFO_(FLOPPY, "RWSeekToCylinder: returning successfully, now on cyl %d\n", Cylinder);
336
337 return STATUS_SUCCESS;
338 }
339
340
341 static NTSTATUS NTAPI
342 RWComputeCHS(PDRIVE_INFO IN DriveInfo,
343 ULONG IN DiskByteOffset,
344 PUCHAR OUT Cylinder,
345 PUCHAR OUT Head,
346 PUCHAR OUT Sector)
347 /*
348 * FUNCTION: Compute the CHS from the absolute byte offset on disk
349 * ARGUMENTS:
350 * DriveInfo: Drive to compute on
351 * DiskByteOffset: Absolute offset on disk of the starting byte
352 * Cylinder: Cylinder that the byte is on
353 * Head: Head that the byte is on
354 * Sector: Sector that the byte is on
355 * RETURNS:
356 * STATUS_SUCCESS if CHS are determined correctly
357 * STATUS_UNSUCCESSFUL otherwise
358 * NOTES:
359 * - Lots of ugly typecasts here
360 * - Sectors are 1-based!
361 * - This is really crummy code. Please FIXME.
362 */
363 {
364 ULONG AbsoluteSector;
365 UCHAR SectorsPerCylinder = (UCHAR)DriveInfo->DiskGeometry.SectorsPerTrack * (UCHAR)DriveInfo->DiskGeometry.TracksPerCylinder;
366
367 TRACE_(FLOPPY, "RWComputeCHS: Called with offset 0x%x\n", DiskByteOffset);
368
369 /* First calculate the 1-based "absolute sector" based on the byte offset */
370 ASSERT(!(DiskByteOffset % DriveInfo->DiskGeometry.BytesPerSector)); /* FIXME: Only handle full sector transfers atm */
371
372 /* AbsoluteSector is zero-based to make the math a little easier */
373 AbsoluteSector = DiskByteOffset / DriveInfo->DiskGeometry.BytesPerSector; /* Num full sectors */
374
375 /* Cylinder number is floor(AbsoluteSector / SectorsPerCylinder) */
376 *Cylinder = (CHAR)(AbsoluteSector / SectorsPerCylinder);
377
378 /* Head number is 0 if the sector within the cylinder < SectorsPerTrack; 1 otherwise */
379 *Head = AbsoluteSector % SectorsPerCylinder < DriveInfo->DiskGeometry.SectorsPerTrack ? 0 : 1;
380
381 /*
382 * Sector number is the sector within the cylinder if on head 0; that minus SectorsPerTrack if it's on head 1
383 * (lots of casts to placate msvc). 1-based!
384 */
385 *Sector = ((UCHAR)(AbsoluteSector % SectorsPerCylinder) + 1) - ((*Head) * (UCHAR)DriveInfo->DiskGeometry.SectorsPerTrack);
386
387 INFO_(FLOPPY, "RWComputeCHS: offset 0x%x is c:0x%x h:0x%x s:0x%x\n", DiskByteOffset, *Cylinder, *Head, *Sector);
388
389 /* Sanity checking */
390 ASSERT(*Cylinder <= DriveInfo->DiskGeometry.Cylinders.QuadPart);
391 ASSERT(*Head <= DriveInfo->DiskGeometry.TracksPerCylinder);
392 ASSERT(*Sector <= DriveInfo->DiskGeometry.SectorsPerTrack);
393
394 return STATUS_SUCCESS;
395 }
396
397
398 VOID NTAPI
399 ReadWritePassive(PDRIVE_INFO DriveInfo, PIRP Irp)
400 /*
401 * FUNCTION: Handle the first phase of a read or write IRP
402 * ARGUMENTS:
403 * DeviceObject: DeviceObject that is the target of the IRP
404 * Irp: IRP to process
405 * RETURNS:
406 * STATUS_VERIFY_REQUIRED if the media has changed and we need the filesystems to re-synch
407 * STATUS_SUCCESS otherwise
408 * NOTES:
409 * - Must be called at PASSIVE_LEVEL
410 * - This function is about 250 lines longer than I wanted it to be. Sorry.
411 *
412 * DETAILS:
413 * This routine manages the whole process of servicing a read or write request. It goes like this:
414 * 1) Check the DO_VERIFY_VOLUME flag and return if it's set
415 * 2) Check the disk change line and notify the OS if it's set and return
416 * 3) Detect the media if we haven't already
417 * 4) Set up DiskByteOffset, Length, and WriteToDevice parameters
418 * 5) Get DMA map registers
419 * 6) Then, in a loop for each track, until all bytes are transferred:
420 * a) Compute the current CHS to set the read/write head to
421 * b) Seek to that spot
422 * c) Compute the last sector to transfer on that track
423 * d) Map the transfer through DMA
424 * e) Send the read or write command to the controller
425 * f) Read the results of the command
426 */
427 {
428 PDEVICE_OBJECT DeviceObject = DriveInfo->DeviceObject;
429 PIO_STACK_LOCATION Stack = IoGetCurrentIrpStackLocation(Irp);
430 BOOLEAN WriteToDevice;
431 ULONG Length;
432 ULONG DiskByteOffset;
433 KIRQL OldIrql;
434 NTSTATUS Status;
435 BOOLEAN DiskChanged;
436 ULONG_PTR TransferByteOffset;
437 UCHAR Gap;
438
439 PAGED_CODE();
440
441 TRACE_(FLOPPY, "ReadWritePassive called to %s 0x%x bytes from offset 0x%x\n",
442 (Stack->MajorFunction == IRP_MJ_READ ? "read" : "write"),
443 (Stack->MajorFunction == IRP_MJ_READ ? Stack->Parameters.Read.Length : Stack->Parameters.Write.Length),
444 (Stack->MajorFunction == IRP_MJ_READ ? Stack->Parameters.Read.ByteOffset.u.LowPart :
445 Stack->Parameters.Write.ByteOffset.u.LowPart));
446
447 /* Default return codes */
448 Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
449 Irp->IoStatus.Information = 0;
450
451 /*
452 * Check to see if the volume needs to be verified. If so,
453 * we can get out of here quickly.
454 */
455 if(DeviceObject->Flags & DO_VERIFY_VOLUME && !(Stack->Flags & SL_OVERRIDE_VERIFY_VOLUME))
456 {
457 INFO_(FLOPPY, "ReadWritePassive(): DO_VERIFY_VOLUME set; Completing with STATUS_VERIFY_REQUIRED\n");
458 Irp->IoStatus.Status = STATUS_VERIFY_REQUIRED;
459 IoCompleteRequest(Irp, IO_NO_INCREMENT);
460 return;
461 }
462
463 /*
464 * Check the change line, and if it's set, return
465 */
466 StartMotor(DriveInfo);
467 if(HwDiskChanged(DeviceObject->DeviceExtension, &DiskChanged) != STATUS_SUCCESS)
468 {
469 WARN_(FLOPPY, "ReadWritePassive(): unable to detect disk change; Completing with STATUS_UNSUCCESSFUL\n");
470 IoCompleteRequest(Irp, IO_NO_INCREMENT);
471 StopMotor(DriveInfo->ControllerInfo);
472 return;
473 }
474
475 if(DiskChanged)
476 {
477 INFO_(FLOPPY, "ReadWritePhase1(): signalling media changed; Completing with STATUS_MEDIA_CHANGED\n");
478
479 /* The following call sets IoStatus.Status and IoStatus.Information */
480 SignalMediaChanged(DeviceObject, Irp);
481
482 /*
483 * Guessing at something... see ioctl.c for more info
484 */
485 if(ResetChangeFlag(DriveInfo) == STATUS_NO_MEDIA_IN_DEVICE)
486 Irp->IoStatus.Status = STATUS_NO_MEDIA_IN_DEVICE;
487
488 IoCompleteRequest(Irp, IO_NO_INCREMENT);
489 StopMotor(DriveInfo->ControllerInfo);
490 return;
491 }
492
493 /*
494 * Figure out the media type, if we don't know it already
495 */
496 if(DriveInfo->DiskGeometry.MediaType == Unknown)
497 {
498 if(RWDetermineMediaType(DriveInfo, FALSE) != STATUS_SUCCESS)
499 {
500 WARN_(FLOPPY, "ReadWritePassive(): unable to determine media type; completing with STATUS_UNSUCCESSFUL\n");
501 IoCompleteRequest(Irp, IO_NO_INCREMENT);
502 StopMotor(DriveInfo->ControllerInfo);
503 return;
504 }
505
506 if(DriveInfo->DiskGeometry.MediaType == Unknown)
507 {
508 WARN_(FLOPPY, "ReadWritePassive(): Unknown media in drive; completing with STATUS_UNRECOGNIZED_MEDIA\n");
509 Irp->IoStatus.Status = STATUS_UNRECOGNIZED_MEDIA;
510 IoCompleteRequest(Irp, IO_NO_INCREMENT);
511 StopMotor(DriveInfo->ControllerInfo);
512 return;
513 }
514 }
515
516 /* Set up parameters for read or write */
517 if(Stack->MajorFunction == IRP_MJ_READ)
518 {
519 Length = Stack->Parameters.Read.Length;
520 DiskByteOffset = Stack->Parameters.Read.ByteOffset.u.LowPart;
521 WriteToDevice = FALSE;
522 }
523 else
524 {
525 Length = Stack->Parameters.Write.Length;
526 DiskByteOffset = Stack->Parameters.Write.ByteOffset.u.LowPart;
527 WriteToDevice = TRUE;
528 }
529
530 /*
531 * FIXME:
532 * FloppyDeviceData.ReadWriteGapLength specify the value for the physical drive.
533 * We should set this value depend on the format of the inserted disk and possible
534 * depend on the request (read or write). A value of 0 results in one rotation
535 * between the sectors (7.2sec for reading a track).
536 */
537 Gap = DriveInfo->FloppyDeviceData.ReadWriteGapLength;
538
539 /*
540 * Set up DMA transfer
541 *
542 * This is as good of a place as any to document something that used to confuse me
543 * greatly (and I even wrote some of the kernel's DMA code, so if it confuses me, it
544 * probably confuses at least a couple of other people too).
545 *
546 * MmGetMdlVirtualAddress() returns the virtal address, as mapped in the buffer's original
547 * process context, of the MDL. In other words: say you start with a buffer at address X, then
548 * you build an MDL out of that buffer called Mdl. If you call MmGetMdlVirtualAddress(Mdl), it
549 * will return X.
550 *
551 * There are two parameters that the function looks at to produce X again, given the MDL: the
552 * first is the StartVa, which is the base virtual address of the page that the buffer starts
553 * in. If your buffer's virtual address is 0x12345678, StartVa will be 0x12345000, assuming 4K pages
554 * (which is (almost) always the case on x86). Note well: this address is only valid in the
555 * process context that you initially built the MDL from. The physical pages that make up
556 * the MDL might perhaps be mapped in other process contexts too (or even in the system space,
557 * above 0x80000000 (default; 0xc0000000 on current ReactOS or /3GB Windows)), but it will
558 * (possibly) be mapped at a different address.
559 *
560 * The second parameter is the ByteOffset. Given an original buffer address of 0x12345678,
561 * the ByteOffset would be 0x678. Because MDLs can only describe full pages (and therefore
562 * StartVa always points to the start address of a page), the ByteOffset must be used to
563 * find the real start of the buffer.
564 *
565 * In general, if you add the StartVa and ByteOffset together, you get back your original
566 * buffer pointer, which you are free to use if you're sure you're in the right process
567 * context. You could tell by accessing the (hidden and not-to-be-used) Process member of
568 * the MDL, but in general, if you have to ask whether or not you are in the right context,
569 * then you shouldn't be using this address for anything anyway. There are also security implications
570 * (big ones, really, I wouldn't kid about this) to directly accessing a user's buffer by VA, so
571 * Don't Do That.
572 *
573 * There is a somewhat weird but very common use of the virtual address associated with a MDL
574 * that pops up often in the context of DMA. DMA APIs (particularly MapTransfer()) need to
575 * know where the memory is that they should DMA into and out of. This memory is described
576 * by a MDL. The controller eventually needs to know a physical address on the host side,
577 * which is generally a 32-bit linear address (on x86), and not just a page address. Therefore,
578 * the DMA APIs look at the ByteOffset field of the MDL to reconstruct the real address that
579 * should be programmed into the DMA controller.
580 *
581 * It is often the case that a transfer needs to be broken down over more than one DMA operation,
582 * particularly when it is a big transfer and the HAL doesn't give you enough map registers
583 * to map the whole thing at once. Therefore, the APIs need a way to tell how far into the MDL
584 * they should look to transfer the next chunk of bytes. Now, Microsoft could have designed
585 * MapTransfer to take a "MDL offset" argument, starting with 0, for how far into the buffer to
586 * start, but it didn't. Instead, MapTransfer asks for the virtual address of the MDL as an "index" into
587 * the MDL. The way it computes how far into the page to start the transfer is by masking off all but
588 * the bottom 12 bits (on x86) of the number you supply as the CurrentVa and using *that* as the
589 * ByteOffset instead of the one in the MDL. (OK, this varies a bit by OS and version, but this
590 * is the effect).
591 *
592 * In other words, you get a number back from MmGetMdlVirtualAddress that represents the start of your
593 * buffer, and you pass it to the first MapTransfer call. Then, for each successive operation
594 * on the same buffer, you increment that address to point to the next spot in the MDL that
595 * you want to DMA to/from. The fact that the virtual address you're manipulating is probably not
596 * mapped into the process context that you're running in is irrelevant, since it's only being
597 * used to index into the MDL.
598 */
599
600 /* Get map registers for DMA */
601 KeRaiseIrql(DISPATCH_LEVEL, &OldIrql);
602 Status = IoAllocateAdapterChannel(DriveInfo->ControllerInfo->AdapterObject, DeviceObject,
603 DriveInfo->ControllerInfo->MapRegisters, MapRegisterCallback, DriveInfo->ControllerInfo);
604 KeLowerIrql(OldIrql);
605
606 if(Status != STATUS_SUCCESS)
607 {
608 WARN_(FLOPPY, "ReadWritePassive(): unable allocate an adapter channel; completing with STATUS_UNSUCCESSFUL\n");
609 IoCompleteRequest(Irp, IO_NO_INCREMENT);
610 StopMotor(DriveInfo->ControllerInfo);
611 return ;
612 }
613
614
615 /*
616 * Read from (or write to) the device
617 *
618 * This has to be called in a loop, as you can only transfer data to/from a single track at
619 * a time.
620 */
621 TransferByteOffset = 0;
622 while(TransferByteOffset < Length)
623 {
624 UCHAR Cylinder;
625 UCHAR Head;
626 UCHAR StartSector;
627 ULONG CurrentTransferBytes;
628 UCHAR CurrentTransferSectors;
629
630 INFO_(FLOPPY, "ReadWritePassive(): iterating in while (TransferByteOffset = 0x%x of 0x%x total) - allocating %d registers\n",
631 TransferByteOffset, Length, DriveInfo->ControllerInfo->MapRegisters);
632
633 KeClearEvent(&DriveInfo->ControllerInfo->SynchEvent);
634
635 /*
636 * Compute starting CHS
637 */
638 if(RWComputeCHS(DriveInfo, DiskByteOffset+TransferByteOffset, &Cylinder, &Head, &StartSector) != STATUS_SUCCESS)
639 {
640 WARN_(FLOPPY, "ReadWritePassive(): unable to compute CHS; completing with STATUS_UNSUCCESSFUL\n");
641 RWFreeAdapterChannel(DriveInfo->ControllerInfo->AdapterObject);
642 IoCompleteRequest(Irp, IO_NO_INCREMENT);
643 StopMotor(DriveInfo->ControllerInfo);
644 return;
645 }
646
647 /*
648 * Seek to the right track
649 */
650 if(!DriveInfo->ControllerInfo->ImpliedSeeks)
651 {
652 if(RWSeekToCylinder(DriveInfo, Cylinder) != STATUS_SUCCESS)
653 {
654 WARN_(FLOPPY, "ReadWritePassive(): unable to seek; completing with STATUS_UNSUCCESSFUL\n");
655 RWFreeAdapterChannel(DriveInfo->ControllerInfo->AdapterObject);
656 IoCompleteRequest(Irp, IO_NO_INCREMENT);
657 StopMotor(DriveInfo->ControllerInfo);
658 return ;
659 }
660 }
661
662 /*
663 * Compute last sector
664 *
665 * We can only ask for a transfer up to the end of the track. Then we have to re-seek and do more.
666 * TODO: Support the MT bit
667 */
668 INFO_(FLOPPY, "ReadWritePassive(): computing number of sectors to transfer (StartSector 0x%x): ", StartSector);
669
670 /* 1-based sector number */
671 if( (((DriveInfo->DiskGeometry.TracksPerCylinder - Head) * DriveInfo->DiskGeometry.SectorsPerTrack - StartSector) + 1 ) <
672 (Length - TransferByteOffset) / DriveInfo->DiskGeometry.BytesPerSector)
673 {
674 CurrentTransferSectors = (UCHAR)((DriveInfo->DiskGeometry.TracksPerCylinder - Head) * DriveInfo->DiskGeometry.SectorsPerTrack - StartSector) + 1;
675 }
676 else
677 {
678 CurrentTransferSectors = (UCHAR)((Length - TransferByteOffset) / DriveInfo->DiskGeometry.BytesPerSector);
679 }
680
681 INFO_(FLOPPY, "0x%x\n", CurrentTransferSectors);
682
683 CurrentTransferBytes = CurrentTransferSectors * DriveInfo->DiskGeometry.BytesPerSector;
684
685 /*
686 * Adjust to map registers
687 * BUG: Does this take into account page crossings?
688 */
689 INFO_(FLOPPY, "ReadWritePassive(): Trying to transfer 0x%x bytes\n", CurrentTransferBytes);
690
691 ASSERT(CurrentTransferBytes);
692
693 if(BYTES_TO_PAGES(CurrentTransferBytes) > DriveInfo->ControllerInfo->MapRegisters)
694 {
695 CurrentTransferSectors = (UCHAR)((DriveInfo->ControllerInfo->MapRegisters * PAGE_SIZE) /
696 DriveInfo->DiskGeometry.BytesPerSector);
697
698 CurrentTransferBytes = CurrentTransferSectors * DriveInfo->DiskGeometry.BytesPerSector;
699
700 INFO_(FLOPPY, "ReadWritePassive: limiting transfer to 0x%x bytes (0x%x sectors) due to map registers\n",
701 CurrentTransferBytes, CurrentTransferSectors);
702 }
703
704 /* set up this round's dma operation */
705 /* param 2 is ReadOperation --> opposite of WriteToDevice that IoMapTransfer takes. BAD MS. */
706 KeFlushIoBuffers(Irp->MdlAddress, !WriteToDevice, TRUE);
707
708 IoMapTransfer(DriveInfo->ControllerInfo->AdapterObject, Irp->MdlAddress,
709 DriveInfo->ControllerInfo->MapRegisterBase,
710 (PVOID)((ULONG_PTR)MmGetMdlVirtualAddress(Irp->MdlAddress) + TransferByteOffset),
711 &CurrentTransferBytes, WriteToDevice);
712
713 /*
714 * Read or Write
715 */
716 KeClearEvent(&DriveInfo->ControllerInfo->SynchEvent);
717
718 /* Issue the read/write command to the controller. Note that it expects the opposite of WriteToDevice. */
719 if(HwReadWriteData(DriveInfo->ControllerInfo, !WriteToDevice, DriveInfo->UnitNumber, Cylinder, Head, StartSector,
720 DriveInfo->BytesPerSectorCode, DriveInfo->DiskGeometry.SectorsPerTrack, Gap, 0xff) != STATUS_SUCCESS)
721 {
722 WARN_(FLOPPY, "ReadWritePassive(): HwReadWriteData returned failure; unable to read; completing with STATUS_UNSUCCESSFUL\n");
723 RWFreeAdapterChannel(DriveInfo->ControllerInfo->AdapterObject);
724 IoCompleteRequest(Irp, IO_NO_INCREMENT);
725 StopMotor(DriveInfo->ControllerInfo);
726 return ;
727 }
728
729 INFO_(FLOPPY, "ReadWritePassive(): HwReadWriteData returned -- waiting on event\n");
730
731 /*
732 * At this point, we block and wait for an interrupt
733 * FIXME: this seems to take too long
734 */
735 WaitForControllerInterrupt(DriveInfo->ControllerInfo);
736
737 /* Read is complete; flush & free adapter channel */
738 IoFlushAdapterBuffers(DriveInfo->ControllerInfo->AdapterObject, Irp->MdlAddress,
739 DriveInfo->ControllerInfo->MapRegisterBase,
740 (PVOID)((ULONG_PTR)MmGetMdlVirtualAddress(Irp->MdlAddress) + TransferByteOffset),
741 CurrentTransferBytes, WriteToDevice);
742
743 /* Read the results from the drive */
744 if(HwReadWriteResult(DriveInfo->ControllerInfo) != STATUS_SUCCESS)
745 {
746 WARN_(FLOPPY, "ReadWritePassive(): HwReadWriteResult returned failure; unable to read; completing with STATUS_UNSUCCESSFUL\n");
747 HwDumpRegisters(DriveInfo->ControllerInfo);
748 RWFreeAdapterChannel(DriveInfo->ControllerInfo->AdapterObject);
749 IoCompleteRequest(Irp, IO_NO_INCREMENT);
750 StopMotor(DriveInfo->ControllerInfo);
751 return ;
752 }
753
754 TransferByteOffset += CurrentTransferBytes;
755 }
756
757 RWFreeAdapterChannel(DriveInfo->ControllerInfo->AdapterObject);
758
759 /* That's all folks! */
760 INFO_(FLOPPY, "ReadWritePassive(): success; Completing with STATUS_SUCCESS\n");
761 Irp->IoStatus.Status = STATUS_SUCCESS;
762 Irp->IoStatus.Information = Length;
763 IoCompleteRequest(Irp, IO_DISK_INCREMENT);
764 StopMotor(DriveInfo->ControllerInfo);
765 }