/*++

Copyright (c) 1990  Microsoft Corporation

Module Name:

    smbcpnp.c

Abstract:

    SMBus Class Driver Plug and Play support

Author:

    Bob Moore (Intel)

Environment:

Notes:


Revision History:

--*/

#include "smbc.h"
#include "oprghdlr.h"


#define SMBHC_DEVICE_NAME       L"\\Device\\SmbHc"
extern ULONG   SMBCDebug;

//
// Prototypes
//

NTSTATUS
SmbCPnpDispatch(
    IN PDEVICE_OBJECT   DeviceObject,
    IN PIRP             Irp
    );

NTSTATUS
SmbCStartDevice (
    IN PDEVICE_OBJECT   FDO,
    IN PIRP             Irp
    );

NTSTATUS
SmbCStopDevice (
    IN PDEVICE_OBJECT   FDO,
    IN PIRP             Irp
    );

NTSTATUS
SmbClassCreateFdo (
    IN PDRIVER_OBJECT           DriverObject,
    IN PDEVICE_OBJECT           PDO,
    IN ULONG                    MiniportExtensionSize,
    IN PSMB_INITIALIZE_MINIPORT MiniportInitialize,
    IN PVOID                    MiniportContext,
    OUT PDEVICE_OBJECT          *FDO
    );

#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE,SmbCPnpDispatch)
#pragma alloc_text(PAGE,SmbCStartDevice)
#pragma alloc_text(PAGE,SmbClassCreateFdo)
#endif


NTSTATUS
SmbCPnpDispatch(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )

/*++

Routine Description:

    This routine is the dispatcher for plug and play requests.

Arguments:

    DeviceObject    - Pointer to class device object.
    Irp             - Pointer to the request packet.

Return Value:

    Status is returned.

--*/

{
    PIO_STACK_LOCATION  irpStack;
    PSMBDATA            SmbData;
    KEVENT              syncEvent;
    NTSTATUS            status = STATUS_NOT_SUPPORTED;

    PAGED_CODE();

    //
    // Get a pointer to the current parameters for this request.  The
    // information is contained in the current stack location.
    //

    irpStack = IoGetCurrentIrpStackLocation(Irp);
    SmbData = (PSMBDATA) DeviceObject->DeviceExtension;

    SmbPrint (SMB_NOTE, ("SmbCPnpDispatch: PnP dispatch, minor = %d\n",
                        irpStack->MinorFunction));

    //
    // Dispatch minor function
    //

    switch (irpStack->MinorFunction) {

    case IRP_MN_START_DEVICE:
            IoCopyCurrentIrpStackLocationToNext (Irp);

            KeInitializeEvent(&syncEvent, SynchronizationEvent, FALSE);

            IoSetCompletionRoutine(Irp, SmbCSynchronousRequest, &syncEvent, TRUE, TRUE, TRUE);

            status = IoCallDriver(SmbData->Class.LowerDeviceObject, Irp);

            if (status == STATUS_PENDING) {
                KeWaitForSingleObject(&syncEvent, Executive, KernelMode, FALSE, NULL);
                status = Irp->IoStatus.Status;
            }
            
            status = SmbCStartDevice (DeviceObject, Irp);
            
            IoCompleteRequest(Irp, IO_NO_INCREMENT);
            
            return status;


    case IRP_MN_STOP_DEVICE:
            status = SmbCStopDevice(DeviceObject, Irp);
            break;


    case IRP_MN_QUERY_STOP_DEVICE:

            SmbPrint(SMB_LOW, ("SmbCPnp: IRP_MN_QUERY_STOP_DEVICE\n"));

            status = STATUS_SUCCESS;
            break;


    case IRP_MN_CANCEL_STOP_DEVICE:

            SmbPrint(SMB_LOW, ("SmbCPnp: IRP_MN_CANCEL_STOP_DEVICE\n"));

            status = STATUS_SUCCESS;
            break;


    default:
            SmbPrint(SMB_LOW, ("SmbCPnp: Unimplemented PNP minor code %d\n",
                    irpStack->MinorFunction));
    }

    if (status != STATUS_NOT_SUPPORTED) {

        Irp->IoStatus.Status = status;
    }

    if (NT_SUCCESS(status) || (status == STATUS_NOT_SUPPORTED)) {

        IoSkipCurrentIrpStackLocation (Irp);
        status = IoCallDriver(SmbData->Class.LowerDeviceObject, Irp) ;

    } else {

        IoCompleteRequest (Irp, IO_NO_INCREMENT);

    }

    return status;
}



NTSTATUS
SmbClassCreateFdo (
    IN PDRIVER_OBJECT           DriverObject,
    IN PDEVICE_OBJECT           PDO,
    IN ULONG                    MiniportExtensionSize,
    IN PSMB_INITIALIZE_MINIPORT MiniportInitialize,
    IN PVOID                    MiniportContext,
    OUT PDEVICE_OBJECT          *OutFDO
    )
/*++

Routine Description:

    This routine will create and initialize a functional device object to
    be attached to a SMBus Host controller PDO.  It is called from the miniport
    AddDevice routine.

Arguments:

    DriverObject            - a pointer to the driver object this is created under
    PDO                     - a pointer to the SMBus HC PDO
    MiniportExtensionSize   - Extension size required by the miniport
    MiniportInitialize      - a pointer to the miniport init routine
    MiniportContext         - Miniport-defined context info
    OutFDO                  - a location to store the pointer to the new device object

Return Value:

    STATUS_SUCCESS if everything was successful
    reason for failure otherwise

--*/

{
    NTSTATUS            Status;
    UNICODE_STRING      UnicodeString;
    PDEVICE_OBJECT      FDO;
    PDEVICE_OBJECT      lowerDevice = NULL;
    PSMBDATA                SmbData;

    //
    // Allocate a device object for this miniport
    //

    RtlInitUnicodeString(&UnicodeString, SMBHC_DEVICE_NAME);

    Status = IoCreateDevice(
                DriverObject,
                sizeof (SMBDATA) + MiniportExtensionSize,
                &UnicodeString,
                FILE_DEVICE_UNKNOWN,    // DeviceType
                0,
                FALSE,
                &FDO
                );

    if (Status != STATUS_SUCCESS) {
        SmbPrint(SMB_LOW, ("SmbC: unable to create device object: %X\n", Status ));
        return(Status);
    }

    //
    // Initialize class data
    //

    FDO->Flags |= DO_BUFFERED_IO;

    //
    // Layer our FDO on top of the PDO
    //

    lowerDevice = IoAttachDeviceToDeviceStack(FDO,PDO);

    //
    // No status. Do the best we can.
    //
    ASSERT(lowerDevice);

    //
    // Fill out class data
    //

    SmbData = (PSMBDATA) FDO->DeviceExtension;
    SmbData->Class.MajorVersion         = SMB_CLASS_MAJOR_VERSION;
    SmbData->Class.MinorVersion         = SMB_CLASS_MINOR_VERSION;
    SmbData->Class.Miniport             = SmbData + 1;
    SmbData->Class.DeviceObject         = FDO;
    SmbData->Class.LowerDeviceObject    = lowerDevice;
    SmbData->Class.PDO                  = PDO;
    SmbData->Class.CurrentIrp           = NULL;
    SmbData->Class.CurrentSmb           = NULL;

    KeInitializeEvent (&SmbData->AlarmEvent, NotificationEvent, FALSE);
    KeInitializeSpinLock (&SmbData->SpinLock);
    InitializeListHead (&SmbData->WorkQueue);
    InitializeListHead (&SmbData->Alarms);

    KeInitializeTimer (&SmbData->RetryTimer);
    KeInitializeDpc (&SmbData->RetryDpc, SmbCRetry, SmbData);

    //
    // Miniport initialization
    //

    Status = MiniportInitialize (&SmbData->Class, SmbData->Class.Miniport, MiniportContext);
    FDO->Flags |= DO_POWER_PAGABLE;
    FDO->Flags &= ~DO_DEVICE_INITIALIZING;

    if (!NT_SUCCESS(Status)) {
        IoDeleteDevice (FDO);
        return Status;
    }

    *OutFDO = FDO;
    return Status;
}


NTSTATUS
SmbCStartDevice (
    IN PDEVICE_OBJECT   FDO,
    IN PIRP             Irp
    )
{
    NTSTATUS            Status;
    PSMBDATA            SmbData;


    SmbPrint(SMB_LOW, ("SmbCStartDevice Entered with fdo %x\n", FDO));

    SmbData = (PSMBDATA) FDO->DeviceExtension;
    
    //
    // Initialize the Miniclass driver.
    //
    SmbData->Class.CurrentIrp = Irp;

    Status = SmbData->Class.ResetDevice (
                    &SmbData->Class,
                    SmbData->Class.Miniport
                    );

    SmbData->Class.CurrentIrp = NULL;
    
    if (!NT_SUCCESS(Status)) {

        SmbPrint(SMB_ERROR,
            ("SmbCStartDevice: Class.ResetDevice failed. = %Lx\n",
            Status));

        return Status;
    }
    
    //
    // Install the Operation Region handlers
    //

    Status = RegisterOpRegionHandler (SmbData->Class.LowerDeviceObject,
                                      ACPI_OPREGION_ACCESS_AS_RAW,
                                      ACPI_OPREGION_REGION_SPACE_SMB,
                                      (PACPI_OP_REGION_HANDLER)SmbCRawOpRegionHandler,
                                      SmbData,
                                      0,
                                      &SmbData->RawOperationRegionObject);
    if (!NT_SUCCESS(Status)) {

        SmbPrint(SMB_ERROR,
            ("SmbCStartDevice: Could not install raw Op region handler, status = %Lx\n",
            Status));
        
        //
        // Failure to register opregion handler is not critical.  It just reduces functionality
        //
        SmbData->RawOperationRegionObject = NULL;
        Status = STATUS_SUCCESS;
    }

    return Status;
}


NTSTATUS
SmbCStopDevice (
    IN PDEVICE_OBJECT   FDO,
    IN PIRP             Irp
    )
{
    NTSTATUS            Status;
    PSMBDATA                SmbData;


    SmbPrint(SMB_LOW, ("SmbCStopDevice Entered with fdo %x\n", FDO));


    SmbData = (PSMBDATA) FDO->DeviceExtension;

    //
    // Stop handling operation regions before turning off driver.
    //
    if (SmbData->RawOperationRegionObject) {
        DeRegisterOpRegionHandler (SmbData->Class.LowerDeviceObject,
                                   SmbData->RawOperationRegionObject);
    }

    //
    // Stop the device
    //

    SmbData->Class.CurrentIrp = Irp;
    
    Status = SmbData->Class.StopDevice (
                    &SmbData->Class,
                    SmbData->Class.Miniport
                    );

    SmbData->Class.CurrentIrp = NULL;

    return Status;
}



NTSTATUS
SmbCSynchronousRequest (
    IN PDEVICE_OBJECT   DeviceObject,
    IN PIRP             Irp,
    IN PKEVENT          IoCompletionEvent
    )
/*++

Routine Description:

    Completion function for synchronous IRPs sent to this driver.
    No event.

--*/
{
    KeSetEvent(IoCompletionEvent, IO_NO_INCREMENT, FALSE);

    return STATUS_MORE_PROCESSING_REQUIRED;
}