mirror of
https://github.com/reactos/reactos.git
synced 2024-11-02 21:09:15 +00:00
4b34e44782
This should fix some virtual machines and real hardware machine with empty floopy drive not being able to boot ReactOS (stuck while initializing floppy.sys). This fixes a regression introduced in r70746. It could be generalized to other interrupts, floppy controllers not being reliable. For more information: http://wiki.osdev.org/Floppy_Disk_Controller CORE-7935 CORE-12908 CORE-13080
770 lines
30 KiB
C
770 lines
30 KiB
C
/*
|
|
* ReactOS Floppy Driver
|
|
* Copyright (C) 2004, Vizzini (vizzini@plasmic.com)
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* PROJECT: ReactOS Floppy Driver
|
|
* FILE: readwrite.c
|
|
* PURPOSE: Read/Write handler routines
|
|
* PROGRAMMER: Vizzini (vizzini@plasmic.com)
|
|
* REVISIONS:
|
|
* 15-Feb-2004 vizzini - Created
|
|
* NOTES:
|
|
*
|
|
* READ/WRITE PROCESS
|
|
*
|
|
* This process is extracted from the Intel datasheet for the floppy controller.
|
|
*
|
|
* - Turn on the motor and set turnoff time
|
|
* - Program the drive's data rate
|
|
* - Seek
|
|
* - Read ID
|
|
* - Set up DMA
|
|
* - Send read/write command to FDC
|
|
* - Read result bytes
|
|
*
|
|
* This is mostly implemented in one big function, which watis on the SynchEvent
|
|
* as many times as necessary to get through the process. See ReadWritePassive() for
|
|
* more details.
|
|
*
|
|
* NOTES:
|
|
* - Currently doesn't support partial-sector transfers, which is really just a failing
|
|
* of RWComputeCHS. I've never seen Windows send a partial-sector request, though, so
|
|
* this may not be a bad thing. Should be looked into, regardless.
|
|
*
|
|
* TODO: Break up ReadWritePassive and handle errors better
|
|
* TODO: Figure out data rate issues
|
|
* TODO: Media type detection
|
|
* TODO: Figure out perf issue - waiting after call to read/write for about a second each time
|
|
* TODO: Figure out specify timings
|
|
*/
|
|
|
|
#include "precomp.h"
|
|
|
|
#include <debug.h>
|
|
|
|
static IO_ALLOCATION_ACTION NTAPI
|
|
MapRegisterCallback(PDEVICE_OBJECT DeviceObject,
|
|
PIRP Irp,
|
|
PVOID MapRegisterBase,
|
|
PVOID Context)
|
|
/*
|
|
* FUNCTION: Acquire map registers in prep for DMA
|
|
* ARGUMENTS:
|
|
* DeviceObject: unused
|
|
* Irp: unused
|
|
* MapRegisterBase: returned to blocked thread via a member var
|
|
* Context: contains a pointer to the right ControllerInfo
|
|
* struct
|
|
* RETURNS:
|
|
* KeepObject, because that's what the DDK says to do
|
|
*/
|
|
{
|
|
PCONTROLLER_INFO ControllerInfo = (PCONTROLLER_INFO)Context;
|
|
UNREFERENCED_PARAMETER(DeviceObject);
|
|
UNREFERENCED_PARAMETER(Irp);
|
|
|
|
TRACE_(FLOPPY, "MapRegisterCallback Called\n");
|
|
|
|
ControllerInfo->MapRegisterBase = MapRegisterBase;
|
|
KeSetEvent(&ControllerInfo->SynchEvent, 0, FALSE);
|
|
|
|
return KeepObject;
|
|
}
|
|
|
|
|
|
NTSTATUS NTAPI
|
|
ReadWrite(PDEVICE_OBJECT DeviceObject, PIRP Irp)
|
|
/*
|
|
* FUNCTION: Dispatch routine called for read or write IRPs
|
|
* ARGUMENTS:
|
|
* RETURNS:
|
|
* STATUS_PENDING if the IRP is queued
|
|
* STATUS_INVALID_PARAMETER if IRP is set up wrong
|
|
* NOTES:
|
|
* - This function validates arguments to the IRP and then queues it
|
|
* - Note that this function is implicitly serialized by the queue logic. Only
|
|
* one of these at a time is active in the system, no matter how many processors
|
|
* and threads we have.
|
|
* - This function stores the DeviceObject in the IRP's context area before dropping
|
|
* it onto the irp queue
|
|
*/
|
|
{
|
|
TRACE_(FLOPPY, "ReadWrite called\n");
|
|
|
|
ASSERT(DeviceObject);
|
|
ASSERT(Irp);
|
|
|
|
if(!Irp->MdlAddress)
|
|
{
|
|
WARN_(FLOPPY, "ReadWrite(): MDL not found in IRP - Completing with STATUS_INVALID_PARAMETER\n");
|
|
Irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
|
|
Irp->IoStatus.Information = 0;
|
|
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
/*
|
|
* Queue the irp to the thread.
|
|
* The de-queue thread will look in DriverContext[0] for the Device Object.
|
|
*/
|
|
Irp->Tail.Overlay.DriverContext[0] = DeviceObject;
|
|
IoCsqInsertIrp(&Csq, Irp, NULL);
|
|
|
|
return STATUS_PENDING;
|
|
}
|
|
|
|
|
|
static VOID NTAPI
|
|
RWFreeAdapterChannel(PADAPTER_OBJECT AdapterObject)
|
|
/*
|
|
* FUNCTION: Free the adapter DMA channel that we allocated
|
|
* ARGUMENTS:
|
|
* AdapterObject: the object with the map registers to free
|
|
* NOTES:
|
|
* - This function is primarily needed because IoFreeAdapterChannel wants to
|
|
* be called at DISPATCH_LEVEL
|
|
*/
|
|
{
|
|
KIRQL Irql;
|
|
|
|
ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);
|
|
|
|
KeRaiseIrql(DISPATCH_LEVEL, &Irql);
|
|
IoFreeAdapterChannel(AdapterObject);
|
|
KeLowerIrql(Irql);
|
|
}
|
|
|
|
|
|
NTSTATUS NTAPI
|
|
RWDetermineMediaType(PDRIVE_INFO DriveInfo, BOOLEAN OneShot)
|
|
/*
|
|
* FUNCTION: Determine the media type of the disk in the drive and fill in the geometry
|
|
* ARGUMENTS:
|
|
* DriveInfo: drive to look at
|
|
* RETURNS:
|
|
* STATUS_SUCCESS if the media was recognized and the geometry struct was filled in
|
|
* STATUS_UNRECOGNIZED_MEDIA if not
|
|
* STATUS_UNSUCCESSFUL if the controller can't be talked to
|
|
* NOTES:
|
|
* - Expects the motor to already be running
|
|
* - Currently only supports 1.44MB 3.5" disks
|
|
* - PAGED_CODE because it waits
|
|
* TODO:
|
|
* - Support more disk types
|
|
*/
|
|
{
|
|
UCHAR HeadLoadTime;
|
|
UCHAR HeadUnloadTime;
|
|
UCHAR StepRateTime;
|
|
LARGE_INTEGER Timeout;
|
|
|
|
PAGED_CODE();
|
|
|
|
TRACE_(FLOPPY, "RWDetermineMediaType called\n");
|
|
|
|
/*
|
|
* This algorithm assumes that a 1.44MB floppy is in the drive. If it's not,
|
|
* it works backwards until the read works unless OneShot try is asked.
|
|
* Note that only 1.44 has been tested at all.
|
|
*/
|
|
|
|
Timeout.QuadPart = -10000000; /* 1 second. Is that enough? */
|
|
|
|
do
|
|
{
|
|
int i;
|
|
NTSTATUS Status;
|
|
|
|
/* Program data rate */
|
|
if(HwSetDataRate(DriveInfo->ControllerInfo, DRSR_DSEL_500KBPS) != STATUS_SUCCESS)
|
|
{
|
|
WARN_(FLOPPY, "RWDetermineMediaType(): unable to set data rate\n");
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
/* Specify */
|
|
HeadLoadTime = SPECIFY_HLT_500K;
|
|
HeadUnloadTime = SPECIFY_HUT_500K;
|
|
StepRateTime = SPECIFY_SRT_500K;
|
|
|
|
/* Don't disable DMA --> enable dma (dumb & confusing) */
|
|
if(HwSpecify(DriveInfo->ControllerInfo, HeadLoadTime, HeadUnloadTime, StepRateTime, FALSE) != STATUS_SUCCESS)
|
|
{
|
|
WARN_(FLOPPY, "RWDetermineMediaType(): specify failed\n");
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
/* clear any spurious interrupts in preparation for recalibrate */
|
|
KeClearEvent(&DriveInfo->ControllerInfo->SynchEvent);
|
|
|
|
/* Recalibrate --> head over first track */
|
|
for(i=0; i < 2; i++)
|
|
{
|
|
NTSTATUS RecalStatus;
|
|
|
|
if(HwRecalibrate(DriveInfo) != STATUS_SUCCESS)
|
|
{
|
|
WARN_(FLOPPY, "RWDetermineMediaType(): Recalibrate failed\n");
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
/* Wait for the recalibrate to finish */
|
|
WaitForControllerInterrupt(DriveInfo->ControllerInfo, NULL);
|
|
|
|
RecalStatus = HwRecalibrateResult(DriveInfo->ControllerInfo);
|
|
|
|
if(RecalStatus == STATUS_SUCCESS)
|
|
break;
|
|
|
|
if(i == 1) /* failed for 2nd time */
|
|
{
|
|
WARN_(FLOPPY, "RWDetermineMediaType(): RecalibrateResult failed\n");
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
}
|
|
|
|
/* clear any spurious interrupts */
|
|
KeClearEvent(&DriveInfo->ControllerInfo->SynchEvent);
|
|
|
|
/* Try to read an ID */
|
|
if(HwReadId(DriveInfo, 0) != STATUS_SUCCESS) /* read the first ID we find, from head 0 */
|
|
{
|
|
WARN_(FLOPPY, "RWDetermineMediaType(): ReadId failed\n");
|
|
return STATUS_UNSUCCESSFUL; /* if we can't even write to the controller, it's hopeless */
|
|
}
|
|
|
|
/* Wait for the ReadID to finish */
|
|
Status = WaitForControllerInterrupt(DriveInfo->ControllerInfo, &Timeout);
|
|
|
|
if(Status == STATUS_TIMEOUT || HwReadIdResult(DriveInfo->ControllerInfo, NULL, NULL) != STATUS_SUCCESS)
|
|
{
|
|
WARN_(FLOPPY, "RWDetermineMediaType(): ReadIdResult failed; continuing\n");
|
|
if (OneShot)
|
|
break;
|
|
else
|
|
continue;
|
|
}
|
|
|
|
/* Found the media; populate the geometry now */
|
|
WARN_(FLOPPY, "Hardcoded media type!\n");
|
|
INFO_(FLOPPY, "RWDetermineMediaType(): Found 1.44 media; returning success\n");
|
|
DriveInfo->DiskGeometry.MediaType = GEOMETRY_144_MEDIATYPE;
|
|
DriveInfo->DiskGeometry.Cylinders.QuadPart = GEOMETRY_144_CYLINDERS;
|
|
DriveInfo->DiskGeometry.TracksPerCylinder = GEOMETRY_144_TRACKSPERCYLINDER;
|
|
DriveInfo->DiskGeometry.SectorsPerTrack = GEOMETRY_144_SECTORSPERTRACK;
|
|
DriveInfo->DiskGeometry.BytesPerSector = GEOMETRY_144_BYTESPERSECTOR;
|
|
DriveInfo->BytesPerSectorCode = HW_512_BYTES_PER_SECTOR;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
while(TRUE);
|
|
|
|
TRACE_(FLOPPY, "RWDetermineMediaType(): failed to find media\n");
|
|
return STATUS_UNRECOGNIZED_MEDIA;
|
|
}
|
|
|
|
|
|
static NTSTATUS NTAPI
|
|
RWSeekToCylinder(PDRIVE_INFO DriveInfo, UCHAR Cylinder)
|
|
/*
|
|
* FUNCTION: Seek a particular drive to a particular track
|
|
* ARGUMENTS:
|
|
* DriveInfo: Drive to seek
|
|
* Cylinder: track to seek to
|
|
* RETURNS:
|
|
* STATUS_SUCCESS if the head was successfully seeked
|
|
* STATUS_UNSUCCESSFUL if not
|
|
* NOTES:
|
|
* - PAGED_CODE because it blocks
|
|
*/
|
|
{
|
|
UCHAR CurCylinder;
|
|
|
|
PAGED_CODE();
|
|
|
|
TRACE_(FLOPPY, "RWSeekToCylinder called drive 0x%p cylinder %d\n", DriveInfo, Cylinder);
|
|
|
|
/* Clear any spurious interrupts */
|
|
KeClearEvent(&DriveInfo->ControllerInfo->SynchEvent);
|
|
|
|
/* queue seek command */
|
|
if(HwSeek(DriveInfo, Cylinder) != STATUS_SUCCESS)
|
|
{
|
|
WARN_(FLOPPY, "RWSeekToTrack(): unable to seek\n");
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
WaitForControllerInterrupt(DriveInfo->ControllerInfo, NULL);
|
|
|
|
if(HwSenseInterruptStatus(DriveInfo->ControllerInfo) != STATUS_SUCCESS)
|
|
{
|
|
WARN_(FLOPPY, "RWSeekToTrack(): unable to get seek results\n");
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
/* read ID mark from head 0 to verify */
|
|
if(HwReadId(DriveInfo, 0) != STATUS_SUCCESS)
|
|
{
|
|
WARN_(FLOPPY, "RWSeekToTrack(): unable to queue ReadId\n");
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
WaitForControllerInterrupt(DriveInfo->ControllerInfo, NULL);
|
|
|
|
if(HwReadIdResult(DriveInfo->ControllerInfo, &CurCylinder, NULL) != STATUS_SUCCESS)
|
|
{
|
|
WARN_(FLOPPY, "RWSeekToTrack(): unable to get ReadId result\n");
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
if(CurCylinder != Cylinder)
|
|
{
|
|
WARN_(FLOPPY, "RWSeekToTrack(): Seek to track failed; current cylinder is 0x%x\n", CurCylinder);
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
INFO_(FLOPPY, "RWSeekToCylinder: returning successfully, now on cyl %d\n", Cylinder);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
static NTSTATUS NTAPI
|
|
RWComputeCHS(PDRIVE_INFO IN DriveInfo,
|
|
ULONG IN DiskByteOffset,
|
|
PUCHAR OUT Cylinder,
|
|
PUCHAR OUT Head,
|
|
PUCHAR OUT Sector)
|
|
/*
|
|
* FUNCTION: Compute the CHS from the absolute byte offset on disk
|
|
* ARGUMENTS:
|
|
* DriveInfo: Drive to compute on
|
|
* DiskByteOffset: Absolute offset on disk of the starting byte
|
|
* Cylinder: Cylinder that the byte is on
|
|
* Head: Head that the byte is on
|
|
* Sector: Sector that the byte is on
|
|
* RETURNS:
|
|
* STATUS_SUCCESS if CHS are determined correctly
|
|
* STATUS_UNSUCCESSFUL otherwise
|
|
* NOTES:
|
|
* - Lots of ugly typecasts here
|
|
* - Sectors are 1-based!
|
|
* - This is really crummy code. Please FIXME.
|
|
*/
|
|
{
|
|
ULONG AbsoluteSector;
|
|
UCHAR SectorsPerCylinder = (UCHAR)DriveInfo->DiskGeometry.SectorsPerTrack * (UCHAR)DriveInfo->DiskGeometry.TracksPerCylinder;
|
|
|
|
TRACE_(FLOPPY, "RWComputeCHS: Called with offset 0x%x\n", DiskByteOffset);
|
|
|
|
/* First calculate the 1-based "absolute sector" based on the byte offset */
|
|
ASSERT(!(DiskByteOffset % DriveInfo->DiskGeometry.BytesPerSector)); /* FIXME: Only handle full sector transfers atm */
|
|
|
|
/* AbsoluteSector is zero-based to make the math a little easier */
|
|
AbsoluteSector = DiskByteOffset / DriveInfo->DiskGeometry.BytesPerSector; /* Num full sectors */
|
|
|
|
/* Cylinder number is floor(AbsoluteSector / SectorsPerCylinder) */
|
|
*Cylinder = (CHAR)(AbsoluteSector / SectorsPerCylinder);
|
|
|
|
/* Head number is 0 if the sector within the cylinder < SectorsPerTrack; 1 otherwise */
|
|
*Head = AbsoluteSector % SectorsPerCylinder < DriveInfo->DiskGeometry.SectorsPerTrack ? 0 : 1;
|
|
|
|
/*
|
|
* Sector number is the sector within the cylinder if on head 0; that minus SectorsPerTrack if it's on head 1
|
|
* (lots of casts to placate msvc). 1-based!
|
|
*/
|
|
*Sector = ((UCHAR)(AbsoluteSector % SectorsPerCylinder) + 1) - ((*Head) * (UCHAR)DriveInfo->DiskGeometry.SectorsPerTrack);
|
|
|
|
INFO_(FLOPPY, "RWComputeCHS: offset 0x%x is c:0x%x h:0x%x s:0x%x\n", DiskByteOffset, *Cylinder, *Head, *Sector);
|
|
|
|
/* Sanity checking */
|
|
ASSERT(*Cylinder <= DriveInfo->DiskGeometry.Cylinders.QuadPart);
|
|
ASSERT(*Head <= DriveInfo->DiskGeometry.TracksPerCylinder);
|
|
ASSERT(*Sector <= DriveInfo->DiskGeometry.SectorsPerTrack);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
VOID NTAPI
|
|
ReadWritePassive(PDRIVE_INFO DriveInfo, PIRP Irp)
|
|
/*
|
|
* FUNCTION: Handle the first phase of a read or write IRP
|
|
* ARGUMENTS:
|
|
* DeviceObject: DeviceObject that is the target of the IRP
|
|
* Irp: IRP to process
|
|
* RETURNS:
|
|
* STATUS_VERIFY_REQUIRED if the media has changed and we need the filesystems to re-synch
|
|
* STATUS_SUCCESS otherwise
|
|
* NOTES:
|
|
* - Must be called at PASSIVE_LEVEL
|
|
* - This function is about 250 lines longer than I wanted it to be. Sorry.
|
|
*
|
|
* DETAILS:
|
|
* This routine manages the whole process of servicing a read or write request. It goes like this:
|
|
* 1) Check the DO_VERIFY_VOLUME flag and return if it's set
|
|
* 2) Check the disk change line and notify the OS if it's set and return
|
|
* 3) Detect the media if we haven't already
|
|
* 4) Set up DiskByteOffset, Length, and WriteToDevice parameters
|
|
* 5) Get DMA map registers
|
|
* 6) Then, in a loop for each track, until all bytes are transferred:
|
|
* a) Compute the current CHS to set the read/write head to
|
|
* b) Seek to that spot
|
|
* c) Compute the last sector to transfer on that track
|
|
* d) Map the transfer through DMA
|
|
* e) Send the read or write command to the controller
|
|
* f) Read the results of the command
|
|
*/
|
|
{
|
|
PDEVICE_OBJECT DeviceObject = DriveInfo->DeviceObject;
|
|
PIO_STACK_LOCATION Stack = IoGetCurrentIrpStackLocation(Irp);
|
|
BOOLEAN WriteToDevice;
|
|
ULONG Length;
|
|
ULONG DiskByteOffset;
|
|
KIRQL OldIrql;
|
|
NTSTATUS Status;
|
|
BOOLEAN DiskChanged;
|
|
ULONG_PTR TransferByteOffset;
|
|
UCHAR Gap;
|
|
|
|
PAGED_CODE();
|
|
|
|
TRACE_(FLOPPY, "ReadWritePassive called to %s 0x%x bytes from offset 0x%x\n",
|
|
(Stack->MajorFunction == IRP_MJ_READ ? "read" : "write"),
|
|
(Stack->MajorFunction == IRP_MJ_READ ? Stack->Parameters.Read.Length : Stack->Parameters.Write.Length),
|
|
(Stack->MajorFunction == IRP_MJ_READ ? Stack->Parameters.Read.ByteOffset.u.LowPart :
|
|
Stack->Parameters.Write.ByteOffset.u.LowPart));
|
|
|
|
/* Default return codes */
|
|
Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
|
|
Irp->IoStatus.Information = 0;
|
|
|
|
/*
|
|
* Check to see if the volume needs to be verified. If so,
|
|
* we can get out of here quickly.
|
|
*/
|
|
if(DeviceObject->Flags & DO_VERIFY_VOLUME && !(Stack->Flags & SL_OVERRIDE_VERIFY_VOLUME))
|
|
{
|
|
INFO_(FLOPPY, "ReadWritePassive(): DO_VERIFY_VOLUME set; Completing with STATUS_VERIFY_REQUIRED\n");
|
|
Irp->IoStatus.Status = STATUS_VERIFY_REQUIRED;
|
|
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Check the change line, and if it's set, return
|
|
*/
|
|
StartMotor(DriveInfo);
|
|
if(HwDiskChanged(DeviceObject->DeviceExtension, &DiskChanged) != STATUS_SUCCESS)
|
|
{
|
|
WARN_(FLOPPY, "ReadWritePassive(): unable to detect disk change; Completing with STATUS_UNSUCCESSFUL\n");
|
|
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
|
StopMotor(DriveInfo->ControllerInfo);
|
|
return;
|
|
}
|
|
|
|
if(DiskChanged)
|
|
{
|
|
INFO_(FLOPPY, "ReadWritePhase1(): signalling media changed; Completing with STATUS_MEDIA_CHANGED\n");
|
|
|
|
/* The following call sets IoStatus.Status and IoStatus.Information */
|
|
SignalMediaChanged(DeviceObject, Irp);
|
|
|
|
/*
|
|
* Guessing at something... see ioctl.c for more info
|
|
*/
|
|
if(ResetChangeFlag(DriveInfo) == STATUS_NO_MEDIA_IN_DEVICE)
|
|
Irp->IoStatus.Status = STATUS_NO_MEDIA_IN_DEVICE;
|
|
|
|
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
|
StopMotor(DriveInfo->ControllerInfo);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Figure out the media type, if we don't know it already
|
|
*/
|
|
if(DriveInfo->DiskGeometry.MediaType == Unknown)
|
|
{
|
|
if(RWDetermineMediaType(DriveInfo, FALSE) != STATUS_SUCCESS)
|
|
{
|
|
WARN_(FLOPPY, "ReadWritePassive(): unable to determine media type; completing with STATUS_UNSUCCESSFUL\n");
|
|
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
|
StopMotor(DriveInfo->ControllerInfo);
|
|
return;
|
|
}
|
|
|
|
if(DriveInfo->DiskGeometry.MediaType == Unknown)
|
|
{
|
|
WARN_(FLOPPY, "ReadWritePassive(): Unknown media in drive; completing with STATUS_UNRECOGNIZED_MEDIA\n");
|
|
Irp->IoStatus.Status = STATUS_UNRECOGNIZED_MEDIA;
|
|
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
|
StopMotor(DriveInfo->ControllerInfo);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Set up parameters for read or write */
|
|
if(Stack->MajorFunction == IRP_MJ_READ)
|
|
{
|
|
Length = Stack->Parameters.Read.Length;
|
|
DiskByteOffset = Stack->Parameters.Read.ByteOffset.u.LowPart;
|
|
WriteToDevice = FALSE;
|
|
}
|
|
else
|
|
{
|
|
Length = Stack->Parameters.Write.Length;
|
|
DiskByteOffset = Stack->Parameters.Write.ByteOffset.u.LowPart;
|
|
WriteToDevice = TRUE;
|
|
}
|
|
|
|
/*
|
|
* FIXME:
|
|
* FloppyDeviceData.ReadWriteGapLength specify the value for the physical drive.
|
|
* We should set this value depend on the format of the inserted disk and possible
|
|
* depend on the request (read or write). A value of 0 results in one rotation
|
|
* between the sectors (7.2sec for reading a track).
|
|
*/
|
|
Gap = DriveInfo->FloppyDeviceData.ReadWriteGapLength;
|
|
|
|
/*
|
|
* Set up DMA transfer
|
|
*
|
|
* This is as good of a place as any to document something that used to confuse me
|
|
* greatly (and I even wrote some of the kernel's DMA code, so if it confuses me, it
|
|
* probably confuses at least a couple of other people too).
|
|
*
|
|
* MmGetMdlVirtualAddress() returns the virtual address, as mapped in the buffer's original
|
|
* process context, of the MDL. In other words: say you start with a buffer at address X, then
|
|
* you build an MDL out of that buffer called Mdl. If you call MmGetMdlVirtualAddress(Mdl), it
|
|
* will return X.
|
|
*
|
|
* There are two parameters that the function looks at to produce X again, given the MDL: the
|
|
* first is the StartVa, which is the base virtual address of the page that the buffer starts
|
|
* in. If your buffer's virtual address is 0x12345678, StartVa will be 0x12345000, assuming 4K pages
|
|
* (which is (almost) always the case on x86). Note well: this address is only valid in the
|
|
* process context that you initially built the MDL from. The physical pages that make up
|
|
* the MDL might perhaps be mapped in other process contexts too (or even in the system space,
|
|
* above 0x80000000 (default; 0xc0000000 on current ReactOS or /3GB Windows)), but it will
|
|
* (possibly) be mapped at a different address.
|
|
*
|
|
* The second parameter is the ByteOffset. Given an original buffer address of 0x12345678,
|
|
* the ByteOffset would be 0x678. Because MDLs can only describe full pages (and therefore
|
|
* StartVa always points to the start address of a page), the ByteOffset must be used to
|
|
* find the real start of the buffer.
|
|
*
|
|
* In general, if you add the StartVa and ByteOffset together, you get back your original
|
|
* buffer pointer, which you are free to use if you're sure you're in the right process
|
|
* context. You could tell by accessing the (hidden and not-to-be-used) Process member of
|
|
* the MDL, but in general, if you have to ask whether or not you are in the right context,
|
|
* then you shouldn't be using this address for anything anyway. There are also security implications
|
|
* (big ones, really, I wouldn't kid about this) to directly accessing a user's buffer by VA, so
|
|
* Don't Do That.
|
|
*
|
|
* There is a somewhat weird but very common use of the virtual address associated with a MDL
|
|
* that pops up often in the context of DMA. DMA APIs (particularly MapTransfer()) need to
|
|
* know where the memory is that they should DMA into and out of. This memory is described
|
|
* by a MDL. The controller eventually needs to know a physical address on the host side,
|
|
* which is generally a 32-bit linear address (on x86), and not just a page address. Therefore,
|
|
* the DMA APIs look at the ByteOffset field of the MDL to reconstruct the real address that
|
|
* should be programmed into the DMA controller.
|
|
*
|
|
* It is often the case that a transfer needs to be broken down over more than one DMA operation,
|
|
* particularly when it is a big transfer and the HAL doesn't give you enough map registers
|
|
* to map the whole thing at once. Therefore, the APIs need a way to tell how far into the MDL
|
|
* they should look to transfer the next chunk of bytes. Now, Microsoft could have designed
|
|
* MapTransfer to take a "MDL offset" argument, starting with 0, for how far into the buffer to
|
|
* start, but it didn't. Instead, MapTransfer asks for the virtual address of the MDL as an "index" into
|
|
* the MDL. The way it computes how far into the page to start the transfer is by masking off all but
|
|
* the bottom 12 bits (on x86) of the number you supply as the CurrentVa and using *that* as the
|
|
* ByteOffset instead of the one in the MDL. (OK, this varies a bit by OS and version, but this
|
|
* is the effect).
|
|
*
|
|
* In other words, you get a number back from MmGetMdlVirtualAddress that represents the start of your
|
|
* buffer, and you pass it to the first MapTransfer call. Then, for each successive operation
|
|
* on the same buffer, you increment that address to point to the next spot in the MDL that
|
|
* you want to DMA to/from. The fact that the virtual address you're manipulating is probably not
|
|
* mapped into the process context that you're running in is irrelevant, since it's only being
|
|
* used to index into the MDL.
|
|
*/
|
|
|
|
/* Get map registers for DMA */
|
|
KeRaiseIrql(DISPATCH_LEVEL, &OldIrql);
|
|
Status = IoAllocateAdapterChannel(DriveInfo->ControllerInfo->AdapterObject, DeviceObject,
|
|
DriveInfo->ControllerInfo->MapRegisters, MapRegisterCallback, DriveInfo->ControllerInfo);
|
|
KeLowerIrql(OldIrql);
|
|
|
|
if(Status != STATUS_SUCCESS)
|
|
{
|
|
WARN_(FLOPPY, "ReadWritePassive(): unable allocate an adapter channel; completing with STATUS_UNSUCCESSFUL\n");
|
|
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
|
StopMotor(DriveInfo->ControllerInfo);
|
|
return ;
|
|
}
|
|
|
|
|
|
/*
|
|
* Read from (or write to) the device
|
|
*
|
|
* This has to be called in a loop, as you can only transfer data to/from a single track at
|
|
* a time.
|
|
*/
|
|
TransferByteOffset = 0;
|
|
while(TransferByteOffset < Length)
|
|
{
|
|
UCHAR Cylinder;
|
|
UCHAR Head;
|
|
UCHAR StartSector;
|
|
ULONG CurrentTransferBytes;
|
|
UCHAR CurrentTransferSectors;
|
|
|
|
INFO_(FLOPPY, "ReadWritePassive(): iterating in while (TransferByteOffset = 0x%x of 0x%x total) - allocating %d registers\n",
|
|
TransferByteOffset, Length, DriveInfo->ControllerInfo->MapRegisters);
|
|
|
|
KeClearEvent(&DriveInfo->ControllerInfo->SynchEvent);
|
|
|
|
/*
|
|
* Compute starting CHS
|
|
*/
|
|
if(RWComputeCHS(DriveInfo, DiskByteOffset+TransferByteOffset, &Cylinder, &Head, &StartSector) != STATUS_SUCCESS)
|
|
{
|
|
WARN_(FLOPPY, "ReadWritePassive(): unable to compute CHS; completing with STATUS_UNSUCCESSFUL\n");
|
|
RWFreeAdapterChannel(DriveInfo->ControllerInfo->AdapterObject);
|
|
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
|
StopMotor(DriveInfo->ControllerInfo);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Seek to the right track
|
|
*/
|
|
if(!DriveInfo->ControllerInfo->ImpliedSeeks)
|
|
{
|
|
if(RWSeekToCylinder(DriveInfo, Cylinder) != STATUS_SUCCESS)
|
|
{
|
|
WARN_(FLOPPY, "ReadWritePassive(): unable to seek; completing with STATUS_UNSUCCESSFUL\n");
|
|
RWFreeAdapterChannel(DriveInfo->ControllerInfo->AdapterObject);
|
|
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
|
StopMotor(DriveInfo->ControllerInfo);
|
|
return ;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Compute last sector
|
|
*
|
|
* We can only ask for a transfer up to the end of the track. Then we have to re-seek and do more.
|
|
* TODO: Support the MT bit
|
|
*/
|
|
INFO_(FLOPPY, "ReadWritePassive(): computing number of sectors to transfer (StartSector 0x%x): ", StartSector);
|
|
|
|
/* 1-based sector number */
|
|
if( (((DriveInfo->DiskGeometry.TracksPerCylinder - Head) * DriveInfo->DiskGeometry.SectorsPerTrack - StartSector) + 1 ) <
|
|
(Length - TransferByteOffset) / DriveInfo->DiskGeometry.BytesPerSector)
|
|
{
|
|
CurrentTransferSectors = (UCHAR)((DriveInfo->DiskGeometry.TracksPerCylinder - Head) * DriveInfo->DiskGeometry.SectorsPerTrack - StartSector) + 1;
|
|
}
|
|
else
|
|
{
|
|
CurrentTransferSectors = (UCHAR)((Length - TransferByteOffset) / DriveInfo->DiskGeometry.BytesPerSector);
|
|
}
|
|
|
|
INFO_(FLOPPY, "0x%x\n", CurrentTransferSectors);
|
|
|
|
CurrentTransferBytes = CurrentTransferSectors * DriveInfo->DiskGeometry.BytesPerSector;
|
|
|
|
/*
|
|
* Adjust to map registers
|
|
* BUG: Does this take into account page crossings?
|
|
*/
|
|
INFO_(FLOPPY, "ReadWritePassive(): Trying to transfer 0x%x bytes\n", CurrentTransferBytes);
|
|
|
|
ASSERT(CurrentTransferBytes);
|
|
|
|
if(BYTES_TO_PAGES(CurrentTransferBytes) > DriveInfo->ControllerInfo->MapRegisters)
|
|
{
|
|
CurrentTransferSectors = (UCHAR)((DriveInfo->ControllerInfo->MapRegisters * PAGE_SIZE) /
|
|
DriveInfo->DiskGeometry.BytesPerSector);
|
|
|
|
CurrentTransferBytes = CurrentTransferSectors * DriveInfo->DiskGeometry.BytesPerSector;
|
|
|
|
INFO_(FLOPPY, "ReadWritePassive: limiting transfer to 0x%x bytes (0x%x sectors) due to map registers\n",
|
|
CurrentTransferBytes, CurrentTransferSectors);
|
|
}
|
|
|
|
/* set up this round's dma operation */
|
|
/* param 2 is ReadOperation --> opposite of WriteToDevice that IoMapTransfer takes. BAD MS. */
|
|
KeFlushIoBuffers(Irp->MdlAddress, !WriteToDevice, TRUE);
|
|
|
|
IoMapTransfer(DriveInfo->ControllerInfo->AdapterObject, Irp->MdlAddress,
|
|
DriveInfo->ControllerInfo->MapRegisterBase,
|
|
(PVOID)((ULONG_PTR)MmGetMdlVirtualAddress(Irp->MdlAddress) + TransferByteOffset),
|
|
&CurrentTransferBytes, WriteToDevice);
|
|
|
|
/*
|
|
* Read or Write
|
|
*/
|
|
KeClearEvent(&DriveInfo->ControllerInfo->SynchEvent);
|
|
|
|
/* Issue the read/write command to the controller. Note that it expects the opposite of WriteToDevice. */
|
|
if(HwReadWriteData(DriveInfo->ControllerInfo, !WriteToDevice, DriveInfo->UnitNumber, Cylinder, Head, StartSector,
|
|
DriveInfo->BytesPerSectorCode, DriveInfo->DiskGeometry.SectorsPerTrack, Gap, 0xff) != STATUS_SUCCESS)
|
|
{
|
|
WARN_(FLOPPY, "ReadWritePassive(): HwReadWriteData returned failure; unable to read; completing with STATUS_UNSUCCESSFUL\n");
|
|
RWFreeAdapterChannel(DriveInfo->ControllerInfo->AdapterObject);
|
|
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
|
StopMotor(DriveInfo->ControllerInfo);
|
|
return ;
|
|
}
|
|
|
|
INFO_(FLOPPY, "ReadWritePassive(): HwReadWriteData returned -- waiting on event\n");
|
|
|
|
/*
|
|
* At this point, we block and wait for an interrupt
|
|
* FIXME: this seems to take too long
|
|
*/
|
|
WaitForControllerInterrupt(DriveInfo->ControllerInfo, NULL);
|
|
|
|
/* Read is complete; flush & free adapter channel */
|
|
IoFlushAdapterBuffers(DriveInfo->ControllerInfo->AdapterObject, Irp->MdlAddress,
|
|
DriveInfo->ControllerInfo->MapRegisterBase,
|
|
(PVOID)((ULONG_PTR)MmGetMdlVirtualAddress(Irp->MdlAddress) + TransferByteOffset),
|
|
CurrentTransferBytes, WriteToDevice);
|
|
|
|
/* Read the results from the drive */
|
|
if(HwReadWriteResult(DriveInfo->ControllerInfo) != STATUS_SUCCESS)
|
|
{
|
|
WARN_(FLOPPY, "ReadWritePassive(): HwReadWriteResult returned failure; unable to read; completing with STATUS_UNSUCCESSFUL\n");
|
|
HwDumpRegisters(DriveInfo->ControllerInfo);
|
|
RWFreeAdapterChannel(DriveInfo->ControllerInfo->AdapterObject);
|
|
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
|
StopMotor(DriveInfo->ControllerInfo);
|
|
return ;
|
|
}
|
|
|
|
TransferByteOffset += CurrentTransferBytes;
|
|
}
|
|
|
|
RWFreeAdapterChannel(DriveInfo->ControllerInfo->AdapterObject);
|
|
|
|
/* That's all folks! */
|
|
INFO_(FLOPPY, "ReadWritePassive(): success; Completing with STATUS_SUCCESS\n");
|
|
Irp->IoStatus.Status = STATUS_SUCCESS;
|
|
Irp->IoStatus.Information = Length;
|
|
IoCompleteRequest(Irp, IO_DISK_INCREMENT);
|
|
StopMotor(DriveInfo->ControllerInfo);
|
|
}
|