mirror of
https://github.com/reactos/reactos.git
synced 2024-11-18 21:13:52 +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
1214 lines
45 KiB
C
1214 lines
45 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: 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
|
|
*/
|
|
|
|
#include "precomp.h"
|
|
|
|
#include <ntddk.h>
|
|
#include <debug.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);
|
|
|
|
TRACE_(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 completely done running, or else you'll race
|
|
* with the turner-offer
|
|
* - PAGED_CODE because we wait
|
|
*/
|
|
{
|
|
PAGED_CODE();
|
|
ASSERT(DriveInfo);
|
|
|
|
TRACE_(FLOPPY, "StartMotor called\n");
|
|
|
|
if(DriveInfo->ControllerInfo->StopDpcQueued && !KeCancelTimer(&DriveInfo->ControllerInfo->MotorTimer))
|
|
{
|
|
/* Motor turner-offer is already running; wait for it to finish */
|
|
INFO_(FLOPPY, "StartMotor: motor turner-offer is already running; waiting for it\n");
|
|
KeWaitForSingleObject(&DriveInfo->ControllerInfo->MotorStoppedEvent, Executive, KernelMode, FALSE, NULL);
|
|
INFO_(FLOPPY, "StartMotor: wait satisfied\n");
|
|
}
|
|
|
|
DriveInfo->ControllerInfo->StopDpcQueued = FALSE;
|
|
|
|
if(HwTurnOnMotor(DriveInfo) != STATUS_SUCCESS)
|
|
{
|
|
WARN_(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);
|
|
|
|
TRACE_(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;
|
|
}
|
|
|
|
|
|
NTSTATUS NTAPI
|
|
WaitForControllerInterrupt(PCONTROLLER_INFO ControllerInfo, PLARGE_INTEGER Timeout)
|
|
/*
|
|
* FUNCTION: Wait for the controller to interrupt, and then clear the event
|
|
* ARGUMENTS:
|
|
* ControllerInfo: Controller to wait for
|
|
* Timeout: How long 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
|
|
*/
|
|
{
|
|
NTSTATUS Status;
|
|
|
|
PAGED_CODE();
|
|
ASSERT(ControllerInfo);
|
|
|
|
Status = KeWaitForSingleObject(&ControllerInfo->SynchEvent, Executive, KernelMode, FALSE, Timeout);
|
|
KeClearEvent(&ControllerInfo->SynchEvent);
|
|
|
|
return Status;
|
|
}
|
|
|
|
static DRIVER_DISPATCH CreateClose;
|
|
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);
|
|
|
|
TRACE_(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 */
|
|
WARN_(FLOPPY, "FIXME: UN-HARDCODE DATA RATE\n");
|
|
if(HwSetDataRate(DriveInfo->ControllerInfo, 0) != STATUS_SUCCESS)
|
|
{
|
|
WARN_(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)
|
|
{
|
|
WARN_(FLOPPY, "Recalibrate: HwRecalibrate returned error\n");
|
|
continue;
|
|
}
|
|
|
|
WaitForControllerInterrupt(DriveInfo->ControllerInfo, NULL);
|
|
|
|
/* Get the results */
|
|
Status = HwRecalibrateResult(DriveInfo->ControllerInfo);
|
|
if(Status != STATUS_SUCCESS)
|
|
{
|
|
WARN_(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);
|
|
|
|
TRACE_(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)
|
|
{
|
|
WARN_(FLOPPY, "ResetChangeFlag(): HwSeek failed; returning STATUS_IO_DEVICE_ERROR\n");
|
|
StopMotor(DriveInfo->ControllerInfo);
|
|
return STATUS_IO_DEVICE_ERROR;
|
|
}
|
|
|
|
WaitForControllerInterrupt(DriveInfo->ControllerInfo, NULL);
|
|
|
|
if(HwSenseInterruptStatus(DriveInfo->ControllerInfo) != STATUS_SUCCESS)
|
|
{
|
|
WARN_(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)
|
|
{
|
|
WARN_(FLOPPY, "ResetChangeFlag(): HwSeek failed; returning STATUS_IO_DEVICE_ERROR\n");
|
|
StopMotor(DriveInfo->ControllerInfo);
|
|
return STATUS_IO_DEVICE_ERROR;
|
|
}
|
|
|
|
WaitForControllerInterrupt(DriveInfo->ControllerInfo, NULL);
|
|
|
|
if(HwSenseInterruptStatus(DriveInfo->ControllerInfo) != STATUS_SUCCESS)
|
|
{
|
|
WARN_(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)
|
|
{
|
|
WARN_(FLOPPY, "ResetChangeFlag(): HwDiskChanged 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);
|
|
|
|
TRACE_(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)
|
|
continue;
|
|
|
|
if(gControllerInfo[i].DriveInfo[j].DeviceObject)
|
|
{
|
|
UNICODE_STRING Link;
|
|
|
|
RtlInitUnicodeString(&Link, gControllerInfo[i].DriveInfo[j].SymLinkBuffer);
|
|
IoDeleteSymbolicLink(&Link);
|
|
|
|
RtlInitUnicodeString(&Link, gControllerInfo[i].DriveInfo[j].ArcPathBuffer);
|
|
IoDeassignArcName(&Link);
|
|
|
|
IoDeleteDevice(gControllerInfo[i].DriveInfo[j].DeviceObject);
|
|
}
|
|
}
|
|
|
|
IoDisconnectInterrupt(gControllerInfo[i].InterruptObject);
|
|
|
|
/* Power down the controller */
|
|
if(HwPowerOff(&gControllerInfo[i]) != STATUS_SUCCESS)
|
|
{
|
|
WARN_(FLOPPY, "unload: warning: HwPowerOff failed\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static NTSTATUS NTAPI
|
|
ConfigCallback(PVOID Context,
|
|
PUNICODE_STRING PathName,
|
|
INTERFACE_TYPE BusType,
|
|
ULONG BusNumber,
|
|
PKEY_VALUE_FULL_INFORMATION *BusInformation,
|
|
CONFIGURATION_TYPE ControllerType,
|
|
ULONG ControllerNumber,
|
|
PKEY_VALUE_FULL_INFORMATION *ControllerInformation,
|
|
CONFIGURATION_TYPE PeripheralType,
|
|
ULONG PeripheralNumber,
|
|
PKEY_VALUE_FULL_INFORMATION *PeripheralInformation)
|
|
/*
|
|
* FUNCTION: Callback to IoQueryDeviceDescription, which tells us about our controllers
|
|
* ARGUMENTS:
|
|
* Context: Unused
|
|
* PathName: Unused
|
|
* BusType: Type of the bus that our controller is on
|
|
* BusNumber: Number of the bus that our controller is on
|
|
* BusInformation: Unused
|
|
* ControllerType: Unused
|
|
* ControllerNumber: Number of the controller that we're adding
|
|
* ControllerInformation: Full configuration information for our controller
|
|
* PeripheralType: Unused
|
|
* PeripheralNumber: Unused
|
|
* PeripheralInformation: Full configuration information for each drive on our controller
|
|
* RETURNS:
|
|
* STATUS_SUCCESS in all cases
|
|
* NOTES:
|
|
* - The only documentation I've found about the contents of these structures is
|
|
* from the various Microsoft floppy samples and from the DDK headers. They're
|
|
* very vague, though, so I'm only mostly sure that this stuff is correct, as
|
|
* the MS samples do things completely differently than I have done them. Seems
|
|
* to work in my VMWare, though.
|
|
* - Basically, the function gets all of the information (port, dma, irq) about the
|
|
* controller, and then loops through all of the drives presented in PeripheralInformation.
|
|
* - Each controller has a CONTROLLER_INFO created for it, and each drive has a DRIVE_INFO.
|
|
* - Device objects are created for each drive (not controller), as that's the targeted
|
|
* device in the eyes of the rest of the OS. Each DRIVE_INFO points to a single CONTROLLER_INFO.
|
|
* - We only support up to four controllers in the whole system, each of which supports up to four
|
|
* drives.
|
|
*/
|
|
{
|
|
PKEY_VALUE_FULL_INFORMATION ControllerFullDescriptor = ControllerInformation[IoQueryDeviceConfigurationData];
|
|
PCM_FULL_RESOURCE_DESCRIPTOR ControllerResourceDescriptor = (PCM_FULL_RESOURCE_DESCRIPTOR)((PCHAR)ControllerFullDescriptor +
|
|
ControllerFullDescriptor->DataOffset);
|
|
|
|
PKEY_VALUE_FULL_INFORMATION PeripheralFullDescriptor = PeripheralInformation[IoQueryDeviceConfigurationData];
|
|
PCM_FULL_RESOURCE_DESCRIPTOR PeripheralResourceDescriptor = (PCM_FULL_RESOURCE_DESCRIPTOR)((PCHAR)PeripheralFullDescriptor +
|
|
PeripheralFullDescriptor->DataOffset);
|
|
|
|
PCM_PARTIAL_RESOURCE_DESCRIPTOR PartialDescriptor;
|
|
PCM_FLOPPY_DEVICE_DATA FloppyDeviceData;
|
|
UCHAR i;
|
|
|
|
PAGED_CODE();
|
|
UNREFERENCED_PARAMETER(PeripheralType);
|
|
UNREFERENCED_PARAMETER(PeripheralNumber);
|
|
UNREFERENCED_PARAMETER(BusInformation);
|
|
UNREFERENCED_PARAMETER(Context);
|
|
UNREFERENCED_PARAMETER(ControllerType);
|
|
UNREFERENCED_PARAMETER(PathName);
|
|
|
|
|
|
TRACE_(FLOPPY, "ConfigCallback called with ControllerNumber %d\n", ControllerNumber);
|
|
|
|
gControllerInfo[gNumberOfControllers].ControllerNumber = ControllerNumber;
|
|
gControllerInfo[gNumberOfControllers].InterfaceType = BusType;
|
|
gControllerInfo[gNumberOfControllers].BusNumber = BusNumber;
|
|
|
|
/* Get controller interrupt level/vector, dma channel, and port base */
|
|
for(i = 0; i < ControllerResourceDescriptor->PartialResourceList.Count; i++)
|
|
{
|
|
KeInitializeEvent(&gControllerInfo[gNumberOfControllers].SynchEvent, NotificationEvent, FALSE);
|
|
|
|
PartialDescriptor = &ControllerResourceDescriptor->PartialResourceList.PartialDescriptors[i];
|
|
|
|
if(PartialDescriptor->Type == CmResourceTypeInterrupt)
|
|
{
|
|
gControllerInfo[gNumberOfControllers].Level = PartialDescriptor->u.Interrupt.Level;
|
|
gControllerInfo[gNumberOfControllers].Vector = PartialDescriptor->u.Interrupt.Vector;
|
|
|
|
if(PartialDescriptor->Flags & CM_RESOURCE_INTERRUPT_LATCHED)
|
|
gControllerInfo[gNumberOfControllers].InterruptMode = Latched;
|
|
else
|
|
gControllerInfo[gNumberOfControllers].InterruptMode = LevelSensitive;
|
|
}
|
|
|
|
else if(PartialDescriptor->Type == CmResourceTypePort)
|
|
{
|
|
PHYSICAL_ADDRESS TranslatedAddress;
|
|
ULONG AddressSpace = 0x1; /* I/O Port Range */
|
|
|
|
if(!HalTranslateBusAddress(BusType, BusNumber, PartialDescriptor->u.Port.Start, &AddressSpace, &TranslatedAddress))
|
|
{
|
|
WARN_(FLOPPY, "HalTranslateBusAddress failed; returning\n");
|
|
return STATUS_IO_DEVICE_ERROR;
|
|
}
|
|
|
|
if(AddressSpace == 0)
|
|
gControllerInfo[gNumberOfControllers].BaseAddress = MmMapIoSpace(TranslatedAddress, FDC_PORT_BYTES, MmNonCached);
|
|
else
|
|
gControllerInfo[gNumberOfControllers].BaseAddress = (PUCHAR)(ULONG_PTR)TranslatedAddress.QuadPart;
|
|
}
|
|
|
|
else if(PartialDescriptor->Type == CmResourceTypeDma)
|
|
gControllerInfo[gNumberOfControllers].Dma = PartialDescriptor->u.Dma.Channel;
|
|
}
|
|
|
|
/* Start with 0 drives, then go looking */
|
|
gControllerInfo[gNumberOfControllers].NumberOfDrives = 0;
|
|
|
|
/* learn about drives attached to controller */
|
|
for(i = 0; i < PeripheralResourceDescriptor->PartialResourceList.Count; i++)
|
|
{
|
|
PDRIVE_INFO DriveInfo = &gControllerInfo[gNumberOfControllers].DriveInfo[i];
|
|
|
|
PartialDescriptor = &PeripheralResourceDescriptor->PartialResourceList.PartialDescriptors[i];
|
|
|
|
if(PartialDescriptor->Type != CmResourceTypeDeviceSpecific)
|
|
continue;
|
|
|
|
FloppyDeviceData = (PCM_FLOPPY_DEVICE_DATA)(PartialDescriptor + 1);
|
|
|
|
DriveInfo->ControllerInfo = &gControllerInfo[gNumberOfControllers];
|
|
DriveInfo->UnitNumber = i;
|
|
|
|
DriveInfo->FloppyDeviceData.MaxDensity = FloppyDeviceData->MaxDensity;
|
|
DriveInfo->FloppyDeviceData.MountDensity = FloppyDeviceData->MountDensity;
|
|
DriveInfo->FloppyDeviceData.StepRateHeadUnloadTime = FloppyDeviceData->StepRateHeadUnloadTime;
|
|
DriveInfo->FloppyDeviceData.HeadLoadTime = FloppyDeviceData->HeadLoadTime;
|
|
DriveInfo->FloppyDeviceData.MotorOffTime = FloppyDeviceData->MotorOffTime;
|
|
DriveInfo->FloppyDeviceData.SectorLengthCode = FloppyDeviceData->SectorLengthCode;
|
|
DriveInfo->FloppyDeviceData.SectorPerTrack = FloppyDeviceData->SectorPerTrack;
|
|
DriveInfo->FloppyDeviceData.ReadWriteGapLength = FloppyDeviceData->ReadWriteGapLength;
|
|
DriveInfo->FloppyDeviceData.FormatGapLength = FloppyDeviceData->FormatGapLength;
|
|
DriveInfo->FloppyDeviceData.FormatFillCharacter = FloppyDeviceData->FormatFillCharacter;
|
|
DriveInfo->FloppyDeviceData.HeadSettleTime = FloppyDeviceData->HeadSettleTime;
|
|
DriveInfo->FloppyDeviceData.MotorSettleTime = FloppyDeviceData->MotorSettleTime;
|
|
DriveInfo->FloppyDeviceData.MaximumTrackValue = FloppyDeviceData->MaximumTrackValue;
|
|
DriveInfo->FloppyDeviceData.DataTransferLength = FloppyDeviceData->DataTransferLength;
|
|
|
|
/* Once it's all set up, acknowledge its existence in the controller info object */
|
|
gControllerInfo[gNumberOfControllers].NumberOfDrives++;
|
|
}
|
|
|
|
gControllerInfo[gNumberOfControllers].Populated = TRUE;
|
|
gNumberOfControllers++;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
static BOOLEAN NTAPI
|
|
Isr(PKINTERRUPT Interrupt, PVOID ServiceContext)
|
|
/*
|
|
* FUNCTION: Interrupt service routine for the controllers
|
|
* ARGUMENTS:
|
|
* Interrupt: Interrupt object representing the interrupt that occured
|
|
* ServiceContext: Pointer to the ControllerInfo object that caused the interrupt
|
|
* RETURNS:
|
|
* TRUE in all cases (see notes)
|
|
* NOTES:
|
|
* - We should always be the target of the interrupt, being an edge-triggered ISA interrupt, but
|
|
* this won't be the case with a level-sensitive system like PCI
|
|
* - Note that it probably doesn't matter if the interrupt isn't dismissed, as it's edge-triggered.
|
|
* It probably won't keep re-interrupting.
|
|
* - There are two different ways to dismiss a floppy interrupt. If the command has a result phase
|
|
* (see intel datasheet), you dismiss the interrupt by reading the first data byte. If it does
|
|
* not, you dismiss the interrupt by doing a Sense Interrupt command. Again, because it's edge-
|
|
* triggered, this is safe to not do here, as we can just wait for the DPC.
|
|
* - Either way, we don't want to do this here. The controller shouldn't interrupt again, so we'll
|
|
* schedule a DPC to take care of it.
|
|
* - This driver really cannot share interrupts, as I don't know how to conclusively say
|
|
* whether it was our controller that interrupted or not. I just have to assume that any time
|
|
* my ISR gets called, it was my board that called it. Dumb design, yes, but it goes back to
|
|
* the semantics of ISA buses. That, and I don't know much about ISA drivers. :-)
|
|
* UPDATE: The high bit of Status Register A seems to work on non-AT controllers.
|
|
* - Called at DIRQL
|
|
*/
|
|
{
|
|
PCONTROLLER_INFO ControllerInfo = (PCONTROLLER_INFO)ServiceContext;
|
|
|
|
UNREFERENCED_PARAMETER(Interrupt);
|
|
|
|
ASSERT(ControllerInfo);
|
|
|
|
TRACE_(FLOPPY, "ISR called\n");
|
|
|
|
/*
|
|
* Due to the stupidity of the drive/controller relationship on the floppy drive, only one device object
|
|
* can have an active interrupt pending. Due to the nature of these IRPs, though, there will only ever
|
|
* be one thread expecting an interrupt at a time, and furthermore, Interrupts (outside of spurious ones)
|
|
* won't ever happen unless a thread is expecting them. Therefore, all we have to do is signal an event
|
|
* and we're done. Queue a DPC and leave.
|
|
*/
|
|
KeInsertQueueDpc(&ControllerInfo->Dpc, NULL, NULL);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
VOID NTAPI
|
|
DpcForIsr(PKDPC UnusedDpc, PVOID Context, PVOID SystemArgument1, PVOID SystemArgument2)
|
|
/*
|
|
* FUNCTION: This DPC gets queued by every ISR. Does the real per-interrupt work.
|
|
* ARGUMENTS:
|
|
* UnusedDpc: Pointer to the DPC object that represents our function
|
|
* DeviceObject: Device that this DPC is running for
|
|
* Irp: Unused
|
|
* Context: Pointer to our ControllerInfo struct
|
|
* NOTES:
|
|
* - This function just kicks off whatever the SynchEvent is and returns. We depend on
|
|
* the thing that caused the drive to interrupt to handle the work of clearing the interrupt.
|
|
* This enables us to get back to PASSIVE_LEVEL and not hog system time on a really stupid,
|
|
* slow, screwed-up piece of hardware.
|
|
* - If nothing is waiting for us to set the event, the interrupt is effectively lost and will
|
|
* never be dismissed. I wonder if this will become a problem.
|
|
* - Called at DISPATCH_LEVEL
|
|
*/
|
|
{
|
|
PCONTROLLER_INFO ControllerInfo = (PCONTROLLER_INFO)Context;
|
|
|
|
UNREFERENCED_PARAMETER(UnusedDpc);
|
|
UNREFERENCED_PARAMETER(SystemArgument1);
|
|
UNREFERENCED_PARAMETER(SystemArgument2);
|
|
|
|
ASSERT(ControllerInfo);
|
|
|
|
TRACE_(FLOPPY, "DpcForIsr called\n");
|
|
|
|
KeSetEvent(&ControllerInfo->SynchEvent, EVENT_INCREMENT, FALSE);
|
|
}
|
|
|
|
|
|
static NTSTATUS NTAPI
|
|
InitController(PCONTROLLER_INFO ControllerInfo)
|
|
/*
|
|
* FUNCTION: Initialize a newly-found controller
|
|
* ARGUMENTS:
|
|
* ControllerInfo: pointer to the controller to be initialized
|
|
* RETURNS:
|
|
* STATUS_SUCCESS if the controller is successfully initialized
|
|
* STATUS_IO_DEVICE_ERROR otherwise
|
|
*/
|
|
{
|
|
int i;
|
|
UCHAR HeadLoadTime;
|
|
UCHAR HeadUnloadTime;
|
|
UCHAR StepRateTime;
|
|
UCHAR ControllerVersion;
|
|
|
|
PAGED_CODE();
|
|
ASSERT(ControllerInfo);
|
|
|
|
TRACE_(FLOPPY, "InitController called with Controller 0x%p\n", ControllerInfo);
|
|
|
|
/* Get controller in a known state */
|
|
if(HwConfigure(ControllerInfo, FALSE, TRUE, TRUE, 0, 0) != STATUS_SUCCESS)
|
|
{
|
|
WARN_(FLOPPY, "InitController: unable to configure controller\n");
|
|
return STATUS_IO_DEVICE_ERROR;
|
|
}
|
|
|
|
/* Get the controller version */
|
|
ControllerVersion = HwGetVersion(ControllerInfo);
|
|
|
|
KeClearEvent(&ControllerInfo->SynchEvent);
|
|
|
|
/* Reset the controller */
|
|
if(HwReset(ControllerInfo) != STATUS_SUCCESS)
|
|
{
|
|
WARN_(FLOPPY, "InitController: unable to reset controller\n");
|
|
return STATUS_IO_DEVICE_ERROR;
|
|
}
|
|
|
|
INFO_(FLOPPY, "InitController: waiting for initial interrupt\n");
|
|
|
|
/* Wait for an interrupt */
|
|
WaitForControllerInterrupt(ControllerInfo, NULL);
|
|
|
|
/* Reset means you have to clear each of the four interrupts (one per drive) */
|
|
for(i = 0; i < MAX_DRIVES_PER_CONTROLLER; i++)
|
|
{
|
|
INFO_(FLOPPY, "InitController: Sensing interrupt %d\n", i);
|
|
|
|
if(HwSenseInterruptStatus(ControllerInfo) != STATUS_SUCCESS)
|
|
{
|
|
WARN_(FLOPPY, "InitController: Unable to clear interrupt 0x%x\n", i);
|
|
return STATUS_IO_DEVICE_ERROR;
|
|
}
|
|
}
|
|
|
|
INFO_(FLOPPY, "InitController: done sensing interrupts\n");
|
|
|
|
/* Next, see if we have the right version to do implied seek */
|
|
if(ControllerVersion == VERSION_ENHANCED)
|
|
{
|
|
/* If so, set that up -- all defaults below except first TRUE for EIS */
|
|
if(HwConfigure(ControllerInfo, TRUE, TRUE, TRUE, 0, 0) != STATUS_SUCCESS)
|
|
{
|
|
WARN_(FLOPPY, "InitController: unable to set up implied seek\n");
|
|
ControllerInfo->ImpliedSeeks = FALSE;
|
|
}
|
|
else
|
|
{
|
|
INFO_(FLOPPY, "InitController: implied seeks set!\n");
|
|
ControllerInfo->ImpliedSeeks = TRUE;
|
|
}
|
|
|
|
/*
|
|
* FIXME: Figure out the answer to the below
|
|
*
|
|
* I must admit that I'm really confused about the Model 30 issue. At least one
|
|
* important bit (the disk change bit in the DIR) is flipped if this is a Model 30
|
|
* controller. However, at least one other floppy driver believes that there are only
|
|
* two computers that are guaranteed to have a Model 30 controller:
|
|
* - IBM Thinkpad 750
|
|
* - IBM PS2e
|
|
*
|
|
* ...and another driver only lists a config option for "thinkpad", that flips
|
|
* the change line. A third driver doesn't mention the Model 30 issue at all.
|
|
*
|
|
* What I can't tell is whether or not the average, run-of-the-mill computer now has
|
|
* a Model 30 controller. For the time being, I'm going to wire this to FALSE,
|
|
* and just not support the computers mentioned above, while I try to figure out
|
|
* how ubiquitous these newfangled 30 thingies are.
|
|
*/
|
|
//ControllerInfo->Model30 = TRUE;
|
|
ControllerInfo->Model30 = FALSE;
|
|
}
|
|
else
|
|
{
|
|
INFO_(FLOPPY, "InitController: enhanced version not supported; disabling implied seeks\n");
|
|
ControllerInfo->ImpliedSeeks = FALSE;
|
|
ControllerInfo->Model30 = FALSE;
|
|
}
|
|
|
|
/* Specify */
|
|
WARN_(FLOPPY, "FIXME: Figure out speed\n");
|
|
HeadLoadTime = SPECIFY_HLT_500K;
|
|
HeadUnloadTime = SPECIFY_HUT_500K;
|
|
StepRateTime = SPECIFY_SRT_500K;
|
|
|
|
INFO_(FLOPPY, "InitController: setting data rate\n");
|
|
|
|
/* Set data rate */
|
|
if(HwSetDataRate(ControllerInfo, DRSR_DSEL_500KBPS) != STATUS_SUCCESS)
|
|
{
|
|
WARN_(FLOPPY, "InitController: unable to set data rate\n");
|
|
return STATUS_IO_DEVICE_ERROR;
|
|
}
|
|
|
|
INFO_(FLOPPY, "InitController: issuing specify command to controller\n");
|
|
|
|
/* Don't disable DMA --> enable dma (dumb & confusing) */
|
|
if(HwSpecify(ControllerInfo, HeadLoadTime, HeadUnloadTime, StepRateTime, FALSE) != STATUS_SUCCESS)
|
|
{
|
|
WARN_(FLOPPY, "InitController: unable to specify options\n");
|
|
return STATUS_IO_DEVICE_ERROR;
|
|
}
|
|
|
|
/* Init the stop stuff */
|
|
KeInitializeDpc(&ControllerInfo->MotorStopDpc, MotorStopDpcFunc, ControllerInfo);
|
|
KeInitializeTimer(&ControllerInfo->MotorTimer);
|
|
KeInitializeEvent(&ControllerInfo->MotorStoppedEvent, NotificationEvent, FALSE);
|
|
ControllerInfo->StopDpcQueued = FALSE;
|
|
|
|
/*
|
|
* Recalibrate each drive on the controller (depends on StartMotor, which depends on the timer stuff above)
|
|
* We don't even know if there is a disk in the drive, so this may not work, but that's OK.
|
|
*/
|
|
for(i = 0; i < ControllerInfo->NumberOfDrives; i++)
|
|
{
|
|
INFO_(FLOPPY, "InitController: recalibrating drive 0x%x on controller 0x%p\n", i, ControllerInfo);
|
|
Recalibrate(&ControllerInfo->DriveInfo[i]);
|
|
}
|
|
|
|
INFO_(FLOPPY, "InitController: done initializing; returning STATUS_SUCCESS\n");
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
static BOOLEAN NTAPI
|
|
AddControllers(PDRIVER_OBJECT DriverObject)
|
|
/*
|
|
* FUNCTION: Called on initialization to find our controllers and build device and controller objects for them
|
|
* ARGUMENTS:
|
|
* DriverObject: Our driver's DriverObject (so we can create devices against it)
|
|
* RETURNS:
|
|
* FALSE if we can't allocate a device, adapter, or interrupt object, or if we fail to find any controllers
|
|
* TRUE otherwise (i.e. we have at least one fully-configured controller)
|
|
* NOTES:
|
|
* - Currently we only support ISA buses.
|
|
* - BUG: Windows 2000 seems to clobber the response from the IoQueryDeviceDescription callback, so now we
|
|
* just test a boolean value in the first object to see if it was completely populated. The same value
|
|
* is tested for each controller before we build device objects for it.
|
|
* TODO:
|
|
* - Report resource usage to the HAL
|
|
*/
|
|
{
|
|
INTERFACE_TYPE InterfaceType = Isa;
|
|
CONFIGURATION_TYPE ControllerType = DiskController;
|
|
CONFIGURATION_TYPE PeripheralType = FloppyDiskPeripheral;
|
|
KAFFINITY Affinity;
|
|
DEVICE_DESCRIPTION DeviceDescription;
|
|
UCHAR i;
|
|
UCHAR j;
|
|
|
|
PAGED_CODE();
|
|
|
|
/* Find our controllers on all ISA buses */
|
|
IoQueryDeviceDescription(&InterfaceType, 0, &ControllerType, 0, &PeripheralType, 0, ConfigCallback, 0);
|
|
|
|
/*
|
|
* w2k breaks the return val from ConfigCallback, so we have to hack around it, rather than just
|
|
* looking for a return value from ConfigCallback. We expect at least one controller.
|
|
*/
|
|
if(!gControllerInfo[0].Populated)
|
|
{
|
|
WARN_(FLOPPY, "AddControllers: failed to get controller info from registry\n");
|
|
return FALSE;
|
|
}
|
|
|
|
/* Now that we have a controller, set it up with the system */
|
|
for(i = 0; i < gNumberOfControllers && gControllerInfo[i].NumberOfDrives > 0; i++)
|
|
{
|
|
/* 0: Report resource usage to the kernel, to make sure they aren't assigned to anyone else */
|
|
/* FIXME: Implement me. */
|
|
|
|
/* 1: Set up interrupt */
|
|
gControllerInfo[i].MappedVector = HalGetInterruptVector(gControllerInfo[i].InterfaceType, gControllerInfo[i].BusNumber,
|
|
gControllerInfo[i].Level, gControllerInfo[i].Vector,
|
|
&gControllerInfo[i].MappedLevel, &Affinity);
|
|
|
|
/* Must set up the DPC before we connect the interrupt */
|
|
KeInitializeDpc(&gControllerInfo[i].Dpc, DpcForIsr, &gControllerInfo[i]);
|
|
|
|
INFO_(FLOPPY, "Connecting interrupt %d to controller%d (object 0x%p)\n", gControllerInfo[i].MappedVector,
|
|
i, &gControllerInfo[i]);
|
|
|
|
/* NOTE: We cannot share our interrupt, even on level-triggered buses. See Isr() for details. */
|
|
if(IoConnectInterrupt(&gControllerInfo[i].InterruptObject, Isr, &gControllerInfo[i], 0, gControllerInfo[i].MappedVector,
|
|
gControllerInfo[i].MappedLevel, gControllerInfo[i].MappedLevel, gControllerInfo[i].InterruptMode,
|
|
FALSE, Affinity, 0) != STATUS_SUCCESS)
|
|
{
|
|
WARN_(FLOPPY, "AddControllers: unable to connect interrupt\n");
|
|
continue;
|
|
}
|
|
|
|
/* 2: Set up DMA */
|
|
memset(&DeviceDescription, 0, sizeof(DeviceDescription));
|
|
DeviceDescription.Version = DEVICE_DESCRIPTION_VERSION;
|
|
DeviceDescription.DmaChannel = gControllerInfo[i].Dma;
|
|
DeviceDescription.InterfaceType = gControllerInfo[i].InterfaceType;
|
|
DeviceDescription.BusNumber = gControllerInfo[i].BusNumber;
|
|
DeviceDescription.MaximumLength = 2*18*512; /* based on a 1.44MB floppy */
|
|
|
|
/* DMA 0,1,2,3 are 8-bit; 4,5,6,7 are 16-bit (4 is chain i think) */
|
|
DeviceDescription.DmaWidth = gControllerInfo[i].Dma > 3 ? Width16Bits: Width8Bits;
|
|
|
|
gControllerInfo[i].AdapterObject = HalGetAdapter(&DeviceDescription, &gControllerInfo[i].MapRegisters);
|
|
|
|
if(!gControllerInfo[i].AdapterObject)
|
|
{
|
|
WARN_(FLOPPY, "AddControllers: unable to allocate an adapter object\n");
|
|
IoDisconnectInterrupt(gControllerInfo[i].InterruptObject);
|
|
continue;
|
|
}
|
|
|
|
/* 2b: Initialize the new controller */
|
|
if(InitController(&gControllerInfo[i]) != STATUS_SUCCESS)
|
|
{
|
|
WARN_(FLOPPY, "AddControllers(): Unable to set up controller %d - initialization failed\n", i);
|
|
IoDisconnectInterrupt(gControllerInfo[i].InterruptObject);
|
|
continue;
|
|
}
|
|
|
|
/* 2c: Set the controller's initialized flag so we know to release stuff in Unload */
|
|
gControllerInfo[i].Initialized = TRUE;
|
|
|
|
/* 3: per-drive setup */
|
|
for(j = 0; j < gControllerInfo[i].NumberOfDrives; j++)
|
|
{
|
|
WCHAR DeviceNameBuf[MAX_DEVICE_NAME];
|
|
UNICODE_STRING DeviceName;
|
|
UNICODE_STRING LinkName;
|
|
UNICODE_STRING ArcPath;
|
|
UCHAR DriveNumber;
|
|
|
|
INFO_(FLOPPY, "AddControllers(): Configuring drive %d on controller %d\n", i, j);
|
|
|
|
/*
|
|
* 3a: create a device object for the drive
|
|
* Controllers and drives are 0-based, so the combos are:
|
|
* 0: 0,0
|
|
* 1: 0,1
|
|
* 2: 0,2
|
|
* 3: 0,3
|
|
* 4: 1,0
|
|
* 5: 1,1
|
|
* ...
|
|
* 14: 3,2
|
|
* 15: 3,3
|
|
*/
|
|
|
|
DriveNumber = (UCHAR)(i*4 + j); /* loss of precision is OK; there are only 16 of 'em */
|
|
|
|
RtlZeroMemory(&DeviceNameBuf, MAX_DEVICE_NAME * sizeof(WCHAR));
|
|
swprintf(DeviceNameBuf, L"\\Device\\Floppy%d", DriveNumber);
|
|
RtlInitUnicodeString(&DeviceName, DeviceNameBuf);
|
|
|
|
if(IoCreateDevice(DriverObject, sizeof(PVOID), &DeviceName,
|
|
FILE_DEVICE_DISK, FILE_REMOVABLE_MEDIA | FILE_FLOPPY_DISKETTE, FALSE,
|
|
&gControllerInfo[i].DriveInfo[j].DeviceObject) != STATUS_SUCCESS)
|
|
{
|
|
WARN_(FLOPPY, "AddControllers: unable to register a Device object\n");
|
|
IoDisconnectInterrupt(gControllerInfo[i].InterruptObject);
|
|
continue; /* continue on to next drive */
|
|
}
|
|
|
|
INFO_(FLOPPY, "AddControllers: New device: %S (0x%p)\n", DeviceNameBuf, gControllerInfo[i].DriveInfo[j].DeviceObject);
|
|
|
|
/* 3b.5: Create an ARC path in case we're booting from this drive */
|
|
swprintf(gControllerInfo[i].DriveInfo[j].ArcPathBuffer,
|
|
L"\\ArcName\\multi(%d)disk(%d)fdisk(%d)", gControllerInfo[i].BusNumber, i, DriveNumber);
|
|
|
|
RtlInitUnicodeString(&ArcPath, gControllerInfo[i].DriveInfo[j].ArcPathBuffer);
|
|
IoAssignArcName(&ArcPath, &DeviceName);
|
|
|
|
/* 3c: Set flags up */
|
|
gControllerInfo[i].DriveInfo[j].DeviceObject->Flags |= DO_DIRECT_IO;
|
|
|
|
/* 3d: Create a symlink */
|
|
swprintf(gControllerInfo[i].DriveInfo[j].SymLinkBuffer, L"\\DosDevices\\%c:", DriveNumber + 'A');
|
|
RtlInitUnicodeString(&LinkName, gControllerInfo[i].DriveInfo[j].SymLinkBuffer);
|
|
if(IoCreateSymbolicLink(&LinkName, &DeviceName) != STATUS_SUCCESS)
|
|
{
|
|
WARN_(FLOPPY, "AddControllers: Unable to create a symlink for drive %d\n", DriveNumber);
|
|
IoDisconnectInterrupt(gControllerInfo[i].InterruptObject);
|
|
IoDeassignArcName(&ArcPath);
|
|
continue; /* continue to next drive */
|
|
}
|
|
|
|
/* 3e: Increase global floppy drives count */
|
|
IoGetConfigurationInformation()->FloppyCount++;
|
|
|
|
/* 3f: Set up the DPC */
|
|
IoInitializeDpcRequest(gControllerInfo[i].DriveInfo[j].DeviceObject, (PIO_DPC_ROUTINE)DpcForIsr);
|
|
|
|
/* 3g: Point the device extension at our DriveInfo struct */
|
|
gControllerInfo[i].DriveInfo[j].DeviceObject->DeviceExtension = &gControllerInfo[i].DriveInfo[j];
|
|
|
|
/* 3h: neat comic strip */
|
|
|
|
/* 3i: set the initial media type to unknown */
|
|
memset(&gControllerInfo[i].DriveInfo[j].DiskGeometry, 0, sizeof(DISK_GEOMETRY));
|
|
gControllerInfo[i].DriveInfo[j].DiskGeometry.MediaType = Unknown;
|
|
|
|
/* 3j: Now that we're done, set the Initialized flag so we know to free this in Unload */
|
|
gControllerInfo[i].DriveInfo[j].Initialized = TRUE;
|
|
|
|
/* 3k: Clear the DO_DEVICE_INITIALIZING flag */
|
|
gControllerInfo[i].DriveInfo[j].DeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;
|
|
|
|
/* 3l: Attempt to get drive info - if a floppy is already present */
|
|
StartMotor(&gControllerInfo[i].DriveInfo[j]);
|
|
RWDetermineMediaType(&gControllerInfo[i].DriveInfo[j], TRUE);
|
|
StopMotor(gControllerInfo[i].DriveInfo[j].ControllerInfo);
|
|
}
|
|
}
|
|
|
|
INFO_(FLOPPY, "AddControllers: --------------------------------------------> finished adding controllers\n");
|
|
|
|
return (IoGetConfigurationInformation()->FloppyCount != 0);
|
|
}
|
|
|
|
|
|
VOID NTAPI
|
|
SignalMediaChanged(PDEVICE_OBJECT DeviceObject, PIRP Irp)
|
|
/*
|
|
* FUNCTION: Process an IRP when the media has changed, and possibly notify the user
|
|
* ARGUMENTS:
|
|
* DeviceObject: DeviceObject associated with the IRP
|
|
* Irp: IRP that we're failing due to change
|
|
* NOTES:
|
|
* - This procedure is documented in the DDK by "Notifying the File System of Possible Media Changes",
|
|
* "IoSetHardErrorOrVerifyDevice", and by "Responding to Check-Verify Requests from the File System".
|
|
* - Callable at <= DISPATCH_LEVEL
|
|
*/
|
|
{
|
|
PDRIVE_INFO DriveInfo = DeviceObject->DeviceExtension;
|
|
|
|
TRACE_(FLOPPY, "SignalMediaChanged called\n");
|
|
|
|
DriveInfo->DiskChangeCount++;
|
|
|
|
/* If volume is not mounted, do NOT set verify and return STATUS_IO_DEVICE_ERROR */
|
|
if(!(DeviceObject->Vpb->Flags & VPB_MOUNTED))
|
|
{
|
|
Irp->IoStatus.Status = STATUS_IO_DEVICE_ERROR;
|
|
Irp->IoStatus.Information = 0;
|
|
return;
|
|
}
|
|
|
|
/* Notify the filesystem that it will need to verify the volume */
|
|
DeviceObject->Flags |= DO_VERIFY_VOLUME;
|
|
Irp->IoStatus.Status = STATUS_VERIFY_REQUIRED;
|
|
Irp->IoStatus.Information = 0;
|
|
|
|
/*
|
|
* If this is a user-based, threaded request, let the IO manager know to pop up a box asking
|
|
* the user to supply the correct media, but only if the error (which we just picked out above)
|
|
* is deemed by the IO manager to be "user induced". The reason we don't just unconditionally
|
|
* call IoSetHardError... is because MS might change the definition of "user induced" some day,
|
|
* and we don't want to have to remember to re-code this.
|
|
*/
|
|
if(Irp->Tail.Overlay.Thread && IoIsErrorUserInduced(Irp->IoStatus.Status))
|
|
IoSetHardErrorOrVerifyDevice(Irp, DeviceObject);
|
|
}
|
|
|
|
|
|
static VOID NTAPI
|
|
QueueThread(PVOID Context)
|
|
/*
|
|
* FUNCTION: Thread that manages the queue and dispatches any queued requests
|
|
* ARGUMENTS:
|
|
* Context: unused
|
|
*/
|
|
{
|
|
PIRP Irp;
|
|
PIO_STACK_LOCATION Stack;
|
|
PDEVICE_OBJECT DeviceObject;
|
|
PVOID Objects[2];
|
|
|
|
PAGED_CODE();
|
|
UNREFERENCED_PARAMETER(Context);
|
|
|
|
Objects[0] = &QueueSemaphore;
|
|
Objects[1] = &QueueThreadTerminate;
|
|
|
|
for(;;)
|
|
{
|
|
KeWaitForMultipleObjects(2, Objects, WaitAny, Executive, KernelMode, FALSE, NULL, NULL);
|
|
|
|
if(KeReadStateEvent(&QueueThreadTerminate))
|
|
{
|
|
INFO_(FLOPPY, "QueueThread terminating\n");
|
|
return;
|
|
}
|
|
|
|
INFO_(FLOPPY, "QueueThread: servicing an IRP\n");
|
|
|
|
Irp = IoCsqRemoveNextIrp(&Csq, 0);
|
|
|
|
/* we won't get an irp if it was canceled */
|
|
if(!Irp)
|
|
{
|
|
INFO_(FLOPPY, "QueueThread: IRP queue empty\n");
|
|
continue;
|
|
}
|
|
|
|
DeviceObject = (PDEVICE_OBJECT)Irp->Tail.Overlay.DriverContext[0];
|
|
|
|
ASSERT(DeviceObject);
|
|
|
|
Stack = IoGetCurrentIrpStackLocation(Irp);
|
|
|
|
/* Decide what to do with the IRP */
|
|
switch(Stack->MajorFunction)
|
|
{
|
|
case IRP_MJ_READ:
|
|
case IRP_MJ_WRITE:
|
|
ReadWritePassive(DeviceObject->DeviceExtension, Irp);
|
|
break;
|
|
|
|
case IRP_MJ_DEVICE_CONTROL:
|
|
DeviceIoctlPassive(DeviceObject->DeviceExtension, Irp);
|
|
break;
|
|
|
|
default:
|
|
WARN_(FLOPPY, "QueueThread(): Unrecognized irp: mj: 0x%x\n", Stack->MajorFunction);
|
|
Irp->IoStatus.Status = STATUS_NOT_SUPPORTED;
|
|
Irp->IoStatus.Information = 0;
|
|
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
NTSTATUS NTAPI
|
|
DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
|
|
/*
|
|
* FUNCTION: Entry-point for the driver
|
|
* ARGUMENTS:
|
|
* DriverObject: Our driver object
|
|
* RegistryPath: Unused
|
|
* RETURNS:
|
|
* STATUS_SUCCESS on successful initialization of at least one drive
|
|
* STATUS_NO_SUCH_DEVICE if we didn't find even one drive
|
|
* STATUS_UNSUCCESSFUL otherwise
|
|
*/
|
|
{
|
|
HANDLE ThreadHandle;
|
|
|
|
UNREFERENCED_PARAMETER(RegistryPath);
|
|
|
|
/*
|
|
* Set up dispatch routines
|
|
*/
|
|
DriverObject->MajorFunction[IRP_MJ_CREATE] = (PDRIVER_DISPATCH)CreateClose;
|
|
DriverObject->MajorFunction[IRP_MJ_CLOSE] = (PDRIVER_DISPATCH)CreateClose;
|
|
DriverObject->MajorFunction[IRP_MJ_READ] = (PDRIVER_DISPATCH)ReadWrite;
|
|
DriverObject->MajorFunction[IRP_MJ_WRITE] = (PDRIVER_DISPATCH)ReadWrite;
|
|
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = (PDRIVER_DISPATCH)DeviceIoctl;
|
|
|
|
DriverObject->DriverUnload = Unload;
|
|
|
|
/*
|
|
* We depend on some zeroes in these structures. I know this is supposed to be
|
|
* initialized to 0 by the complier but this makes me feel beter.
|
|
*/
|
|
memset(&gControllerInfo, 0, sizeof(gControllerInfo));
|
|
|
|
/*
|
|
* Set up queue. This routine cannot fail (trust me, I wrote it).
|
|
*/
|
|
IoCsqInitialize(&Csq, CsqInsertIrp, CsqRemoveIrp, CsqPeekNextIrp,
|
|
CsqAcquireLock, CsqReleaseLock, CsqCompleteCanceledIrp);
|
|
|
|
/*
|
|
* ...and its lock
|
|
*/
|
|
KeInitializeSpinLock(&IrpQueueLock);
|
|
|
|
/*
|
|
* ...and the queue list itself
|
|
*/
|
|
InitializeListHead(&IrpQueue);
|
|
|
|
/*
|
|
* The queue is counted by a semaphore. The queue management thread
|
|
* blocks on this semaphore, so if requests come in faster than the queue
|
|
* thread can handle them, the semaphore count goes up.
|
|
*/
|
|
KeInitializeSemaphore(&QueueSemaphore, 0, 0x7fffffff);
|
|
|
|
/*
|
|
* Event to terminate that thread
|
|
*/
|
|
KeInitializeEvent(&QueueThreadTerminate, NotificationEvent, FALSE);
|
|
|
|
/*
|
|
* Create the queue processing thread. Save its handle in the global variable
|
|
* ThreadHandle so we can wait on its termination during Unload.
|
|
*/
|
|
if(PsCreateSystemThread(&ThreadHandle, THREAD_ALL_ACCESS, 0, 0, 0, QueueThread, 0) != STATUS_SUCCESS)
|
|
{
|
|
WARN_(FLOPPY, "Unable to create system thread; failing init\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
if(ObReferenceObjectByHandle(ThreadHandle, STANDARD_RIGHTS_ALL, *PsThreadType, KernelMode, &QueueThreadObject, NULL) != STATUS_SUCCESS)
|
|
{
|
|
WARN_(FLOPPY, "Unable to reference returned thread handle; failing init\n");
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
/*
|
|
* Close the handle, now that we have the object pointer and a reference of our own.
|
|
* The handle will certainly not be valid in the context of the caller next time we
|
|
* need it, as handles are process-specific.
|
|
*/
|
|
ZwClose(ThreadHandle);
|
|
|
|
/*
|
|
* Start the device discovery process. Returns STATUS_SUCCESS if
|
|
* it finds even one drive attached to one controller.
|
|
*/
|
|
if(!AddControllers(DriverObject))
|
|
return STATUS_NO_SUCH_DEVICE;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|