📄 floppy.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., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* PROJECT: ReactOS Floppy Driver
* FILE: floppy.c
* PURPOSE: Main floppy driver routines
* PROGRAMMER: Vizzini (vizzini@plasmic.com)
* REVISIONS:
* 15-Feb-2004 vizzini - Created
* NOTES:
* - This driver is only designed to work with ISA-bus floppy controllers. This
* won't work on PCI-based controllers or on anything else with level-sensitive
* interrupts without modification. I don't think these controllers exist.
*
* ---- General to-do items ----
* TODO: Figure out why CreateClose isn't called any more. Seems to correspond
* with the driver not being unloadable.
* TODO: Think about StopDpcQueued -- could be a race; too tired atm to tell
* TODO: Clean up drive start/stop responsibilities (currently a mess...)
*
* ---- Support for proper media detection ----
* TODO: Handle MFM flag
* TODO: Un-hardcode the data rate from various places
* TODO: Proper media detection (right now we're hardcoded to 1.44)
* TODO: Media detection based on sector 1
*/
#define NDEBUG
#include <debug.h>
#include <ntddk.h>
#include "floppy.h"
#include "hardware.h"
#include "csqrtns.h"
#include "ioctl.h"
#include "readwrite.h"
/*
* Global controller info structures. Each controller gets one. Since the system
* will probably have only one, with four being a very unlikely maximum, a static
* global array is easiest to deal with.
*/
static CONTROLLER_INFO gControllerInfo[MAX_CONTROLLERS];
static ULONG gNumberOfControllers = 0;
/* Queue thread management */
static KEVENT QueueThreadTerminate;
static PVOID QueueThreadObject;
static VOID NTAPI MotorStopDpcFunc(PKDPC UnusedDpc,
PVOID DeferredContext,
PVOID SystemArgument1,
PVOID SystemArgument2)
/*
* FUNCTION: Stop the floppy motor
* ARGUMENTS:
* UnusedDpc: DPC object that's going off
* DeferredContext: called with DRIVE_INFO for drive to turn off
* SystemArgument1: unused
* SystemArgument2: unused
* NOTES:
* - Must set an event to let other threads know we're done turning off the motor
* - Called back at DISPATCH_LEVEL
*/
{
PCONTROLLER_INFO ControllerInfo = (PCONTROLLER_INFO)DeferredContext;
UNREFERENCED_PARAMETER(SystemArgument1);
UNREFERENCED_PARAMETER(SystemArgument2);
UNREFERENCED_PARAMETER(UnusedDpc);
ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);
ASSERT(ControllerInfo);
DPRINT("floppy: MotorStopDpcFunc called\n");
HwTurnOffMotor(ControllerInfo);
ControllerInfo->StopDpcQueued = FALSE;
KeSetEvent(&ControllerInfo->MotorStoppedEvent, EVENT_INCREMENT, FALSE);
}
VOID NTAPI StartMotor(PDRIVE_INFO DriveInfo)
/*
* FUNCTION: Start the motor, taking into account proper handling of the timer race
* ARGUMENTS:
* DriveInfo: drive to start
* NOTES:
* - Never call HwTurnOnMotor() directly
* - This protocol manages a race between the cancel timer and the requesting thread.
* You wouldn't want to turn on the motor and then cancel the timer, because the
* cancel dpc might fire in the meantime, and that'd un-do what you just did. If you
* cancel the timer first, but KeCancelTimer returns false, the dpc is already running,
* so you have to wait until the dpc is completly done running, or else you'll race
* with the turner-offer
* - PAGED_CODE because we wait
*/
{
PAGED_CODE();
ASSERT(DriveInfo);
DPRINT("floppy: StartMotor called\n");
if(DriveInfo->ControllerInfo->StopDpcQueued && !KeCancelTimer(&DriveInfo->ControllerInfo->MotorTimer))
{
/* Motor turner-offer is already running; wait for it to finish */
DPRINT("floppy: StartMotor: motor turner-offer is already running; waiting for it\n");
KeWaitForSingleObject(&DriveInfo->ControllerInfo->MotorStoppedEvent, Executive, KernelMode, FALSE, NULL);
DPRINT("floppy: StartMotor: wait satisfied\n");
}
DriveInfo->ControllerInfo->StopDpcQueued = FALSE;
if(HwTurnOnMotor(DriveInfo) != STATUS_SUCCESS)
{
DPRINT("floppy: StartMotor(): warning: HwTurnOnMotor failed\n");
}
}
VOID NTAPI StopMotor(PCONTROLLER_INFO ControllerInfo)
/*
* FUNCTION: Stop all motors on the controller
* ARGUMENTS:
* DriveInfo: Drive to stop
* NOTES:
* - Never call HwTurnOffMotor() directly
* - This manages the timer cancelation race (see StartMotor for details).
* All we have to do is set up a timer.
*/
{
LARGE_INTEGER StopTime;
ASSERT(ControllerInfo);
DPRINT("floppy: StopMotor called\n");
/* one relative second, in 100-ns units */
StopTime.QuadPart = 10000000;
StopTime.QuadPart *= -1;
KeClearEvent(&ControllerInfo->MotorStoppedEvent);
KeSetTimer(&ControllerInfo->MotorTimer, StopTime, &ControllerInfo->MotorStopDpc);
ControllerInfo->StopDpcQueued = TRUE;
}
VOID NTAPI WaitForControllerInterrupt(PCONTROLLER_INFO ControllerInfo)
/*
* FUNCTION: Wait for the controller to interrupt, and then clear the event
* ARGUMENTS:
* ControllerInfo: Controller to wait for
* NOTES:
* - There is a small chance that an unexpected or spurious interrupt could
* be lost with this clear/wait/clear scheme used in this driver. This is
* deemed to be an acceptable risk due to the unlikeliness of the scenario,
* and the fact that it'll probably work fine next time.
* - PAGED_CODE because it waits
*/
{
PAGED_CODE();
ASSERT(ControllerInfo);
KeWaitForSingleObject(&ControllerInfo->SynchEvent, Executive, KernelMode, FALSE, NULL);
KeClearEvent(&ControllerInfo->SynchEvent);
}
static NTSTATUS NTAPI CreateClose(PDEVICE_OBJECT DeviceObject,
PIRP Irp)
/*
* FUNCTION: Dispatch function called for Create and Close IRPs
* ARGUMENTS:
* DeviceObject: DeviceObject that is the target of the IRP
* Irp: IRP to process
* RETURNS:
* STATUS_SUCCESS in all cases
* NOTES:
* - The Microsoft sample drivers tend to return FILE_OPENED in Information, so I do too.
* - No reason to fail the device open
* - No state to track, so this routine is easy
* - Can be called <= DISPATCH_LEVEL
*
* TODO: Figure out why this isn't getting called
*/
{
UNREFERENCED_PARAMETER(DeviceObject);
DPRINT("floppy: CreateClose called\n");
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = FILE_OPENED;
IoCompleteRequest(Irp, IO_DISK_INCREMENT);
return STATUS_SUCCESS;
}
static NTSTATUS NTAPI Recalibrate(PDRIVE_INFO DriveInfo)
/*
* FUNCTION: Start the recalibration process
* ARGUMENTS:
* DriveInfo: Pointer to the driveinfo struct associated with the targeted drive
* RETURNS:
* STATUS_SUCCESS on successful starting of the process
* STATUS_IO_DEVICE_ERROR if it fails
* NOTES:
* - Sometimes you have to do two recalibrations, particularly if the disk has <80 tracks.
* - PAGED_CODE because we wait
*/
{
NTSTATUS Status;
ULONG i;
PAGED_CODE();
ASSERT(DriveInfo);
/* first turn on the motor */
/* Must stop after every start, prior to return */
StartMotor(DriveInfo);
/* set the data rate */
DPRINT("floppy: FIXME: UN-HARDCODE DATA RATE\n");
if(HwSetDataRate(DriveInfo->ControllerInfo, 0) != STATUS_SUCCESS)
{
DPRINT("floppy: Recalibrate: HwSetDataRate failed\n");
StopMotor(DriveInfo->ControllerInfo);
return STATUS_IO_DEVICE_ERROR;
}
/* clear the event just in case the last call forgot */
KeClearEvent(&DriveInfo->ControllerInfo->SynchEvent);
/* sometimes you have to do this twice; we'll just do it twice all the time since
* we don't know if the people calling this Recalibrate routine expect a disk to
* even be in the drive, and if so, if that disk is formatted.
*/
for(i = 0; i < 2; i++)
{
/* Send the command */
Status = HwRecalibrate(DriveInfo);
if(Status != STATUS_SUCCESS)
{
DPRINT("floppy: Recalibrate: HwRecalibrate returned error\n");
continue;
}
WaitForControllerInterrupt(DriveInfo->ControllerInfo);
/* Get the results */
Status = HwRecalibrateResult(DriveInfo->ControllerInfo);
if(Status != STATUS_SUCCESS)
{
DPRINT("floppy: Recalibrate: HwRecalibrateResult returned error\n");
break;
}
}
KeClearEvent(&DriveInfo->ControllerInfo->SynchEvent);
/* Must stop after every start, prior to return */
StopMotor(DriveInfo->ControllerInfo);
return Status;
}
NTSTATUS NTAPI ResetChangeFlag(PDRIVE_INFO DriveInfo)
/*
* FUNCTION: Reset the drive's change flag (as reflected in the DIR)
* ARGUMENTS:
* DriveInfo: the drive to reset
* RETURNS:
* STATUS_SUCCESS if the changeline is cleared
* STATUS_NO_MEDIA_IN_DEVICE if the changeline cannot be cleared
* STATUS_IO_DEVICE_ERROR if the controller cannot be communicated with
* NOTES:
* - Change reset procedure: recalibrate, seek 1, seek 0
* - If the line is still set after that, there's clearly no disk in the
* drive, so we return STATUS_NO_MEDIA_IN_DEVICE
* - PAGED_CODE because we wait
*/
{
BOOLEAN DiskChanged;
PAGED_CODE();
ASSERT(DriveInfo);
DPRINT("floppy: ResetChangeFlag called\n");
/* Try to recalibrate. We don't care if it works. */
Recalibrate(DriveInfo);
/* clear spurious interrupts in prep for seeks */
KeClearEvent(&DriveInfo->ControllerInfo->SynchEvent);
/* must re-start the drive because Recalibrate() stops it */
StartMotor(DriveInfo);
/* Seek to 1 */
if(HwSeek(DriveInfo, 1) != STATUS_SUCCESS)
{
DPRINT("floppy: ResetChangeFlag(): HwSeek failed; returning STATUS_IO_DEVICE_ERROR\n");
StopMotor(DriveInfo->ControllerInfo);
return STATUS_IO_DEVICE_ERROR;
}
WaitForControllerInterrupt(DriveInfo->ControllerInfo);
if(HwSenseInterruptStatus(DriveInfo->ControllerInfo) != STATUS_SUCCESS)
{
DPRINT("floppy: ResetChangeFlag(): HwSenseInterruptStatus failed; bailing out\n");
StopMotor(DriveInfo->ControllerInfo);
return STATUS_IO_DEVICE_ERROR;
}
/* Seek back to 0 */
if(HwSeek(DriveInfo, 0) != STATUS_SUCCESS)
{
DPRINT("floppy: ResetChangeFlag(): HwSeek failed; returning STATUS_IO_DEVICE_ERROR\n");
StopMotor(DriveInfo->ControllerInfo);
return STATUS_IO_DEVICE_ERROR;
}
WaitForControllerInterrupt(DriveInfo->ControllerInfo);
if(HwSenseInterruptStatus(DriveInfo->ControllerInfo) != STATUS_SUCCESS)
{
DPRINT("floppy: ResetChangeFlag(): HwSenseInterruptStatus #2 failed; bailing\n");
StopMotor(DriveInfo->ControllerInfo);
return STATUS_IO_DEVICE_ERROR;
}
/* Check the change bit */
if(HwDiskChanged(DriveInfo, &DiskChanged) != STATUS_SUCCESS)
{
DPRINT("floppy: ResetChangeFlag(): HwDiskChagned failed; returning STATUS_IO_DEVICE_ERROR\n");
StopMotor(DriveInfo->ControllerInfo);
return STATUS_IO_DEVICE_ERROR;
}
StopMotor(DriveInfo->ControllerInfo);
/* if the change flag is still set, there's probably no media in the drive. */
if(DiskChanged)
return STATUS_NO_MEDIA_IN_DEVICE;
/* else we're done! */
return STATUS_SUCCESS;
}
static VOID NTAPI Unload(PDRIVER_OBJECT DriverObject)
/*
* FUNCTION: Unload the driver from memory
* ARGUMENTS:
* DriverObject - The driver that is being unloaded
*/
{
ULONG i,j;
PAGED_CODE();
UNREFERENCED_PARAMETER(DriverObject);
DPRINT("floppy: unloading\n");
KeSetEvent(&QueueThreadTerminate, 0, FALSE);
KeWaitForSingleObject(QueueThreadObject, Executive, KernelMode, FALSE, 0);
ObDereferenceObject(QueueThreadObject);
for(i = 0; i < gNumberOfControllers; i++)
{
if(!gControllerInfo[i].Initialized)
continue;
for(j = 0; j < gControllerInfo[i].NumberOfDrives; j++)
{
if(!gControllerInfo[i].DriveInfo[j].Initialized)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -