/*++

Copyright (c) 1990  Microsoft Corporation

Module Name:

    smbcsrv.c

Abstract:

    SMBus class driver service functions

Author:

    Ken Reneris

Environment:

Notes:


Revision History:

--*/

#include "smbc.h"

VOID
SmbCCheckAlarmDelete (
    IN PSMBDATA         Smb,
    IN PSMB_ALARM   SmbAlarm
    );



#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE,SmbCCheckAlarmDelete)
#pragma alloc_text(PAGE,SmbCRegisterAlarm)
#pragma alloc_text(PAGE,SmbCDeregisterAlarm)
#endif

UCHAR gHexDigits [] = "0123456789ABCDEF";


NTSTATUS
SmbCRunAlarmMethodCompletionRoutine (
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp,
    IN PVOID Context
    )
{
    SmbPrint(SMB_ALARMS, ("SmbCRunAlarmMethodCompletionRoutine: Done running Control Method.  Status=0x%08x\n", Irp->IoStatus.Status));
    
    ExFreePool (Irp->AssociatedIrp.SystemBuffer);
    IoFreeIrp(Irp);

    return STATUS_MORE_PROCESSING_REQUIRED;
}

VOID
SmbCRunAlarmMethod (
    IN PSMB_CLASS   SmbClass,
    IN UCHAR        Address,
    IN USHORT       Data
    )
/*++

Routine Description:

    Run _Rxx for the alarm

--*/
{
    PIRP irp;
    PIO_STACK_LOCATION irpSp;
    PACPI_EVAL_INPUT_BUFFER_SIMPLE_INTEGER inputBuffer;

    SmbPrint(SMB_ALARMS, ("SmbCRunAlarmMethod: Running Control method _R%02x\n", Address));
    
    inputBuffer = ExAllocatePoolWithTag (
        NonPagedPool,
        sizeof (ACPI_EVAL_INPUT_BUFFER_SIMPLE_INTEGER),
        'AbmS'
        );
    if (!inputBuffer) {
        return;
    }

    RtlZeroMemory( inputBuffer, sizeof(ACPI_EVAL_INPUT_BUFFER_SIMPLE_INTEGER) );
    inputBuffer->Signature = ACPI_EVAL_INPUT_BUFFER_SIMPLE_INTEGER_SIGNATURE;
    inputBuffer->MethodNameAsUlong = '00Q_';
    inputBuffer->MethodName[2] = gHexDigits[ Address / 16];
    inputBuffer->MethodName[3] = gHexDigits[ Address % 16];
    inputBuffer->IntegerArgument = Data;

    irp = IoAllocateIrp (SmbClass->LowerDeviceObject->StackSize, FALSE);
    if (!irp) {
        ExFreePool (inputBuffer);
        return;
    }
    
    irp->AssociatedIrp.SystemBuffer = inputBuffer;

    ASSERT ((IOCTL_ACPI_ASYNC_EVAL_METHOD & 0x3) == METHOD_BUFFERED);
    irp->Flags = IRP_BUFFERED_IO | IRP_INPUT_OPERATION;
    irp->IoStatus.Status = STATUS_NOT_SUPPORTED;

    irpSp = IoGetNextIrpStackLocation( irp );

    irpSp->MajorFunction = IRP_MJ_DEVICE_CONTROL;
    irpSp->Parameters.DeviceIoControl.OutputBufferLength = 0;
    irpSp->Parameters.DeviceIoControl.InputBufferLength = sizeof(ACPI_EVAL_INPUT_BUFFER_SIMPLE_INTEGER);
    irpSp->Parameters.DeviceIoControl.IoControlCode = IOCTL_ACPI_ASYNC_EVAL_METHOD;

    irp->UserBuffer = NULL;

    IoSetCompletionRoutine(
        irp,
        SmbCRunAlarmMethodCompletionRoutine,
        NULL, // No Context  This just frees the IRP
        TRUE,
        TRUE,
        TRUE
        );

    IoCallDriver(SmbClass->LowerDeviceObject, irp);

}


VOID
SmbClassAlarm (
    IN PSMB_CLASS   SmbClass,
    IN UCHAR        Address,
    IN USHORT       Data
    )
/*++

Routine Description:

    Miniport has an alarm input

--*/
{
    PSMBDATA            Smb;
    PSMB_ALARM      SmbAlarm;
    PLIST_ENTRY     Entry, NextEntry;
    BOOLEAN         AlarmRegistered = FALSE;

    Smb = CONTAINING_RECORD (SmbClass, SMBDATA, Class);
    ASSERT_DEVICE_LOCKED (Smb);

    Entry = Smb->Alarms.Flink;
    while (Entry != &Smb->Alarms) {
        SmbAlarm = CONTAINING_RECORD (Entry, SMB_ALARM, Link);

        //
        // If notification is for this address, issue it
        //

        if (Address >= SmbAlarm->MinAddress && Address <= SmbAlarm->MaxAddress) {

            //
            // A driver has registered for this notification.  Don't call the BIOS.
            //
            AlarmRegistered = TRUE;

            //
            // Raise reference count before calling notifcation function
            //

            SmbAlarm->Reference += 1;
            ASSERT (SmbAlarm->Reference != 0);
            SmbClassUnlockDevice (SmbClass);

            //
            // Issue notification
            //

            SmbAlarm->NotifyFunction (SmbAlarm->NotifyContext, Address, Data);

            //
            // Continue
            //

            SmbClassLockDevice (SmbClass);
            SmbAlarm->Reference -= 1;
        }

        //
        // Get next entry
        //

        NextEntry = Entry->Flink;

        //
        // If entry is pending delete, hand it to deleting thread
        //

        if (SmbAlarm->Flag & SMBC_ALARM_DELETE_PENDING) {
            SmbCCheckAlarmDelete (Smb, SmbAlarm);

        }

        //
        // Move on
        //

        Entry = NextEntry;
    }

    //
    // If no one registered for this alarm, call the _Rxx control method
    //
    if (!AlarmRegistered) {
        
        SmbCRunAlarmMethod (SmbClass, Address, Data);

    }
}

VOID
SmbCCheckAlarmDelete (
    IN PSMBDATA         Smb,
    IN PSMB_ALARM   SmbAlarm
    )
{
    //
    // If alarm structure is referenced, wait somemore
    //

    if (SmbAlarm->Reference) {
        return ;
    }


    //
    // Time to free it.  Remove it from the notification list, clear
    // the pending flag and set the event to let waiting threads know
    // that some entry was removed
    //

    RemoveEntryList (&SmbAlarm->Link);
    SmbAlarm->Flag &= ~SMBC_ALARM_DELETE_PENDING;
    KeSetEvent (&Smb->AlarmEvent, 0, FALSE);
}

NTSTATUS
SmbCRegisterAlarm (
    PSMBDATA        Smb,
    PIRP        Irp
    )
/*++

Routine Description:

    Called to register for an alarm event

--*/
{
    PVOID               LockPtr;
    PSMB_ALARM          SmbAlarm, *Result;
    PSMB_REGISTER_ALARM RegAlarm;
    PIO_STACK_LOCATION  IrpSp;

    PAGED_CODE();

    IrpSp = IoGetCurrentIrpStackLocation(Irp);
    if (ExGetPreviousMode() != KernelMode ||
        IrpSp->Parameters.DeviceIoControl.InputBufferLength < sizeof(SMB_REGISTER_ALARM) ||
        IrpSp->Parameters.DeviceIoControl.OutputBufferLength < sizeof(PSMB_ALARM) ) {

        return STATUS_INVALID_PARAMETER;
    }

    RegAlarm = IrpSp->Parameters.DeviceIoControl.Type3InputBuffer;
    SmbAlarm = ExAllocatePoolWithTag (
                    NonPagedPool,
                    sizeof (SMB_ALARM),
                    'AbmS'
                    );

    if (!SmbAlarm) {
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    SmbAlarm->Flag = 0;
    SmbAlarm->Reference = 0;
    SmbAlarm->MinAddress     = RegAlarm->MinAddress;
    SmbAlarm->MaxAddress     = RegAlarm->MaxAddress;
    SmbAlarm->NotifyFunction = RegAlarm->NotifyFunction;
    SmbAlarm->NotifyContext  = RegAlarm->NotifyContext;


    //
    // Add it to the alarm notification list
    //

    LockPtr = MmLockPagableCodeSection(SmbCRegisterAlarm);
    SmbClassLockDevice (&Smb->Class);
    InsertTailList (&Smb->Alarms, &SmbAlarm->Link);
    SmbClassUnlockDevice (&Smb->Class);
    MmUnlockPagableImageSection(LockPtr);

    //
    // Return value caller needs to deregister with
    //

    Result  = (PSMB_ALARM *) Irp->UserBuffer;
    *Result = SmbAlarm;
    Irp->IoStatus.Information = sizeof(PSMB_ALARM);

    return STATUS_SUCCESS;
}

NTSTATUS
SmbCDeregisterAlarm (
    PSMBDATA        Smb,
    PIRP        Irp
    )
/*++

Routine Description:

    Called to register for an alarm event

--*/
{
    PVOID               LockPtr;
    PSMB_ALARM          SmbAlarm;
    PIO_STACK_LOCATION  IrpSp;

    PAGED_CODE();

    IrpSp = IoGetCurrentIrpStackLocation(Irp);
    if (ExGetPreviousMode() != KernelMode ||
        IrpSp->Parameters.DeviceIoControl.InputBufferLength < sizeof(PSMB_ALARM) ) {
        return STATUS_INVALID_PARAMETER;
    }

    SmbAlarm = * (PSMB_ALARM *) IrpSp->Parameters.DeviceIoControl.Type3InputBuffer;
    LockPtr = MmLockPagableCodeSection(SmbCDeregisterAlarm);
    SmbClassLockDevice (&Smb->Class);

    //
    // Flag alarm structure as delete pending
    //


    SmbAlarm->Flag |= SMBC_ALARM_DELETE_PENDING;

    //
    // While delete is pending wait
    //

    while (SmbAlarm->Flag & SMBC_ALARM_DELETE_PENDING) {

        //
        // Issue bogus alarm to generate freeing
        //

        KeResetEvent (&Smb->AlarmEvent);
        SmbClassAlarm (&Smb->Class, 0xFF, 0);

        //
        // Wait for alarm structure to get freed, then check if it
        // was ours
        //

        SmbClassUnlockDevice (&Smb->Class);
        KeWaitForSingleObject (
            &Smb->AlarmEvent,
            Executive,
            KernelMode,
            FALSE,
            NULL
            );

        SmbClassLockDevice (&Smb->Class);
    }

    //
    // It's been removed, free the memory
    //

    SmbClassUnlockDevice (&Smb->Class);
    MmUnlockPagableImageSection(LockPtr);

    ExFreePool (SmbAlarm);
    return STATUS_SUCCESS;
}