/*
 *  Worker thread functions
 *
 *
 */

#include "pch.h"

KSPIN_LOCK      ACPIWorkerSpinLock;
WORK_QUEUE_ITEM ACPIWorkItem;
LIST_ENTRY      ACPIDeviceWorkQueue;
BOOLEAN         ACPIWorkerBusy;

KEVENT          ACPIWorkToDoEvent;
KEVENT          ACPITerminateEvent;
LIST_ENTRY      ACPIWorkQueue;
HANDLE          ACPIThread;

VOID
ACPIWorkerThread (
    IN PVOID    Context
    );

VOID
ACPIWorker(
    IN PVOID StartContext
    );


#ifdef ALLOC_PRAGMA
#pragma alloc_text(INIT, ACPIInitializeWorker)
#endif

VOID
ACPIInitializeWorker (
    VOID
    )
{
    NTSTATUS Status;
    OBJECT_ATTRIBUTES ObjectAttributes;
    HANDLE ThreadHandle;
    PETHREAD *Thread;

    KeInitializeSpinLock (&ACPIWorkerSpinLock);
    ExInitializeWorkItem (&ACPIWorkItem, ACPIWorkerThread, NULL);
    InitializeListHead (&ACPIDeviceWorkQueue);

    //
    // Initialize the ACPI worker thread. This thread is for use by the AML
    // interpreter and must not page-fault or have its stack swapped.
    //
    KeInitializeEvent(&ACPIWorkToDoEvent, NotificationEvent, FALSE);
    KeInitializeEvent(&ACPITerminateEvent, NotificationEvent, FALSE);
    InitializeListHead(&ACPIWorkQueue);

    //
    // Create the worker thread
    //
    InitializeObjectAttributes(&ObjectAttributes, NULL, 0, NULL, NULL);
    Status = PsCreateSystemThread(&ThreadHandle,
                                  THREAD_ALL_ACCESS,
                                  &ObjectAttributes,
                                  0,
                                  NULL,
                                  ACPIWorker,
                                  NULL);
    if (Status != STATUS_SUCCESS) {

        ACPIInternalError( ACPI_WORKER );

    }

    Status = ObReferenceObjectByHandle (ThreadHandle,
                                        THREAD_ALL_ACCESS,
                                        NULL,
                                        KernelMode,
                                        (PVOID *)&Thread,
                                        NULL);

    if (Status != STATUS_SUCCESS) {

        ACPIInternalError( ACPI_WORKER );

    }
}


VOID
ACPISetDeviceWorker (
    IN PDEVICE_EXTENSION    DevExt,
    IN ULONG                Events
    )
{
    BOOLEAN         QueueWorker;
    KIRQL           OldIrql;

    //
    // Synchronize with worker thread
    //

    KeAcquireSpinLock (&ACPIWorkerSpinLock, &OldIrql);
    QueueWorker = FALSE;

    //
    // Set the devices pending events
    //

    DevExt->WorkQueue.PendingEvents |= Events;

    //
    // If this device is not being processed, start now
    //

    if (!DevExt->WorkQueue.Link.Flink) {
        //
        // Queue to worker thread
        //

        InsertTailList (&ACPIDeviceWorkQueue, &DevExt->WorkQueue.Link);
        QueueWorker = !ACPIWorkerBusy;
        ACPIWorkerBusy = TRUE;
    }

    //
    // Drop lock, and if needed get a worker thread
    //

    KeReleaseSpinLock (&ACPIWorkerSpinLock, OldIrql);
    if (QueueWorker) {
        ExQueueWorkItem (&ACPIWorkItem, DelayedWorkQueue);
    }
}

VOID
ACPIWorkerThread (
    IN PVOID    Context
    )
{
    KIRQL               OldIrql;
    PDEVICE_EXTENSION   DevExt;
    ULONG               Events;
    PLIST_ENTRY         Link;

    KeAcquireSpinLock (&ACPIWorkerSpinLock, &OldIrql);
    ACPIWorkerBusy = TRUE;

    //
    // Loop and handle each queue device
    //

    while (!IsListEmpty(&ACPIDeviceWorkQueue)) {
        Link = ACPIDeviceWorkQueue.Flink;
        RemoveEntryList (Link);
        Link->Flink = NULL;

        DevExt = CONTAINING_RECORD (Link, DEVICE_EXTENSION, WorkQueue.Link);

        //
        // Dispatch the pending events
        //

        Events = DevExt->WorkQueue.PendingEvents;
        DevExt->WorkQueue.PendingEvents = 0;

        KeReleaseSpinLock (&ACPIWorkerSpinLock, OldIrql);
        DevExt->DispatchTable->Worker (DevExt, Events);
        KeAcquireSpinLock (&ACPIWorkerSpinLock, &OldIrql);
    }

    ACPIWorkerBusy = FALSE;
    KeReleaseSpinLock (&ACPIWorkerSpinLock, OldIrql);
}

#if DBG

EXCEPTION_DISPOSITION
ACPIWorkerThreadFilter(
    IN PWORKER_THREAD_ROUTINE WorkerRoutine,
    IN PVOID Parameter,
    IN PEXCEPTION_POINTERS ExceptionInfo
    )
{
    KdPrint(("ACPIWORKER: exception in worker routine %lx(%lx)\n", WorkerRoutine, Parameter));
    KdPrint(("  exception record at %lx\n", ExceptionInfo->ExceptionRecord));
    KdPrint(("  context record at %lx\n",ExceptionInfo->ContextRecord));

    try {
        DbgBreakPoint();

    } except (EXCEPTION_EXECUTE_HANDLER) {
        //
        // No kernel debugger attached, so let the system thread
        // exception handler call KeBugCheckEx.
        //
        return(EXCEPTION_CONTINUE_SEARCH);
    }

    return(EXCEPTION_EXECUTE_HANDLER);
}
#endif

typedef enum _ACPI_WORKER_OBJECT {
    ACPIWorkToDo,
    ACPITerminate,
    ACPIMaximumObject
} ACPI_WORKER_OBJECT;

VOID
ACPIWorker(
    IN PVOID StartContext
    )
{
    PLIST_ENTRY Entry;
    WORK_QUEUE_TYPE QueueType;
    PWORK_QUEUE_ITEM WorkItem;
    KIRQL OldIrql;
    NTSTATUS Status;
    static KWAIT_BLOCK WaitBlockArray[ACPIMaximumObject];
    PVOID WaitObjects[ACPIMaximumObject];

    ACPIThread = PsGetCurrentThread ();

    //
    // Wait for the modified page writer event AND the PFN mutex.
    //

    WaitObjects[ACPIWorkToDo] = (PVOID)&ACPIWorkToDoEvent;
    WaitObjects[ACPITerminate] = (PVOID)&ACPITerminateEvent;

    //
    // Loop forever waiting for a work queue item, calling the processing
    // routine, and then waiting for another work queue item.
    //

    do {

        //
        // Wait until something is put in the queue.
        //
        // By specifying a wait mode of KernelMode, the thread's kernel stack is
        // not swappable
        //


        Status = KeWaitForMultipleObjects(ACPIMaximumObject,
                                          &WaitObjects[0],
                                          WaitAny,
                                          Executive,
                                          KernelMode,
                                          FALSE,
                                          NULL,
                                          &WaitBlockArray[0]);

        //
        // Switch on the wait status.
        //

        switch (Status) {

        case ACPIWorkToDo:
                break;

        case ACPITerminate:
                // Stephane - you need to clear out any pending requests,
                // wake people up, etc.  here.
                //
                // Also make sure you free up any allocated pool, etc.

                PsTerminateSystemThread (STATUS_SUCCESS);
                break;

        default:
                break;
        }

        KeAcquireSpinLock(&ACPIWorkerSpinLock, &OldIrql);
        ASSERT(!IsListEmpty(&ACPIWorkQueue));
        Entry = RemoveHeadList(&ACPIWorkQueue);

        if (IsListEmpty(&ACPIWorkQueue)) {
            KeClearEvent(&ACPIWorkToDoEvent);
        }
        KeReleaseSpinLock(&ACPIWorkerSpinLock, OldIrql);

        WorkItem = CONTAINING_RECORD(Entry, WORK_QUEUE_ITEM, List);

        //
        // Execute the specified routine.
        //

#if DBG

        try {

            PVOID WorkerRoutine;
            PVOID Parameter;

            WorkerRoutine = WorkItem->WorkerRoutine;
            Parameter = WorkItem->Parameter;
            (WorkItem->WorkerRoutine)(WorkItem->Parameter);
            if (KeGetCurrentIrql() != 0) {

                ACPIPrint( (
                    ACPI_PRINT_CRITICAL,
                    "ACPIWORKER: worker exit at IRQL %d, worker routine %x, "
                    "parameter %x, item %x\n",
                    KeGetCurrentIrql(),
                    WorkerRoutine,
                    Parameter,
                    WorkItem
                    ) );
                DbgBreakPoint();

            }

        } except( ACPIWorkerThreadFilter(WorkItem->WorkerRoutine,
                                         WorkItem->Parameter,
                                         GetExceptionInformation() )) {
        }

#else

        (WorkItem->WorkerRoutine)(WorkItem->Parameter);
        if (KeGetCurrentIrql() != 0) {
            KeBugCheckEx(
                IRQL_NOT_LESS_OR_EQUAL,
                (ULONG_PTR)WorkItem->WorkerRoutine,
                (ULONG_PTR)KeGetCurrentIrql(),
                (ULONG_PTR)WorkItem->WorkerRoutine,
                (ULONG_PTR)WorkItem
                );
            }
#endif

    } while(TRUE);
}

VOID
OSQueueWorkItem(
    IN PWORK_QUEUE_ITEM WorkItem
    )

/*++

Routine Description:

    This function inserts a work item into a work queue that is processed
    by the ACPI worker thread

Arguments:

    WorkItem - Supplies a pointer to the work item to add the the queue.
        This structure must be located in NonPagedPool. The work item
        structure contains a doubly linked list entry, the address of a
        routine to call and a parameter to pass to that routine.

Return Value:

    None

--*/

{
    KIRQL OldIrql;

    ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);

    //
    // Insert the work item
    //
    KeAcquireSpinLock(&ACPIWorkerSpinLock, &OldIrql);
    if (IsListEmpty(&ACPIWorkQueue)) {
        KeSetEvent(&ACPIWorkToDoEvent, 0, FALSE);
    }
    InsertTailList(&ACPIWorkQueue, &WorkItem->List);
    KeReleaseSpinLock(&ACPIWorkerSpinLock, OldIrql);
    return;
}