/*++

Copyright (c) 2014 Minoca Corp. All Rights Reserved

Module Name:

    dispatch.c

Abstract:

    This module implements the driver dispatcher.

Author:

    Evan Green 12-Mar-2014

Environment:

    Firmware

--*/

//
// ------------------------------------------------------------------- Includes
//

#include "ueficore.h"
#include "fwvolp.h"

//
// ---------------------------------------------------------------- Definitions
//

#define EFI_CORE_DRIVER_ENTRY_MAGIC 0x76697244 // 'virD'

//
// ------------------------------------------------------ Data Type Definitions
//

typedef struct _EFI_KNOWN_HANDLE {
    LIST_ENTRY ListEntry;
    EFI_HANDLE Handle;
    EFI_GUID NameGuid;
} EFI_KNOWN_HANDLE, *PEFI_KNOWN_HANDLE;

typedef struct _EFI_CORE_DRIVER_ENTRY {
    UINTN Magic;
    LIST_ENTRY DriverListEntry;
    LIST_ENTRY SchedulerListEntry;
    EFI_HANDLE VolumeHandle;
    EFI_GUID FileName;
    EFI_DEVICE_PATH_PROTOCOL *FileDevicePath;
    EFI_FIRMWARE_VOLUME2_PROTOCOL *Volume;
    EFI_HANDLE ImageHandle;
    BOOLEAN IsFirmwareVolumeImage;
    BOOLEAN Untrusted;
    BOOLEAN Initialized;
    BOOLEAN Scheduled;
    BOOLEAN Dependent;
} EFI_CORE_DRIVER_ENTRY, *PEFI_CORE_DRIVER_ENTRY;

typedef struct _EFI_FIRMWARE_VOLUME_FILE_DEVICE_PATH {
    MEDIA_FW_VOL_FILEPATH_DEVICE_PATH File;
    EFI_DEVICE_PATH_PROTOCOL End;
} EFI_FIRMWARE_VOLUME_FILE_DEVICE_PATH, *PEFI_FIRMWARE_VOLUME_FILE_DEVICE_PATH;

//
// ----------------------------------------------- Internal Function Prototypes
//

EFIAPI
VOID
EfipFirmwareVolumeEventProtocolNotify (
    EFI_EVENT Event,
    VOID *Context
    );

BOOLEAN
EfipFirmwareVolumeHasBeenProcessed (
    EFI_HANDLE Handle
    );

PEFI_KNOWN_HANDLE
EfipMarkFirmwareVolumeProcessed (
    EFI_HANDLE Handle
    );

EFI_STATUS
EfipCoreAddDriverToList (
    EFI_FIRMWARE_VOLUME2_PROTOCOL *Volume,
    EFI_HANDLE VolumeHandle,
    EFI_GUID *DriverName,
    EFI_FV_FILETYPE Type
    );

EFI_DEVICE_PATH_PROTOCOL *
EfipCoreConvertFirmwareVolumeFileToDevicePath (
    EFI_FIRMWARE_VOLUME2_PROTOCOL *Volume,
    EFI_HANDLE VolumeHandle,
    EFI_GUID *DriverName
    );

VOID
EfipCoreInsertOnScheduledQueue (
    PEFI_CORE_DRIVER_ENTRY DriverEntry
    );

//
// -------------------------------------------------------------------- Globals
//

//
// Store the list of known firmware volume handles.
//

LIST_ENTRY EfiFirmwareVolumeList;

//
// Define the variables used to register for firmware volume arrivals.
//

EFI_EVENT EfiFirmwareVolumeEvent;
VOID *EfiFirmwareVolumeEventRegistration;

//
// Define the list of file types supported by the dispatcher.
//

EFI_FV_FILETYPE EfiDispatcherFileTypes[] = {
    EFI_FV_FILETYPE_DRIVER,
    EFI_FV_FILETYPE_COMBINED_PEIM_DRIVER,
};

EFI_LOCK EfiDispatcherLock;
LIST_ENTRY EfiDiscoveredList;
LIST_ENTRY EfiScheduledQueue;

BOOLEAN EfiDispatcherRunning;

//
// ------------------------------------------------------------------ Functions
//

VOID
EfiCoreInitializeDispatcher (
    VOID
    )

/*++

Routine Description:

    This routine initializes the driver dispatcher.

Arguments:

    None.

Return Value:

    None.

--*/

{

    INITIALIZE_LIST_HEAD(&EfiFirmwareVolumeList);
    INITIALIZE_LIST_HEAD(&EfiDiscoveredList);
    INITIALIZE_LIST_HEAD(&EfiScheduledQueue);
    EfiCoreInitializeLock(&EfiDispatcherLock, TPL_HIGH_LEVEL);
    EfiFirmwareVolumeEvent = EfiCoreCreateProtocolNotifyEvent(
                                         &EfiFirmwareVolume2ProtocolGuid,
                                         TPL_CALLBACK,
                                         EfipFirmwareVolumeEventProtocolNotify,
                                         NULL,
                                         &EfiFirmwareVolumeEventRegistration);

    ASSERT(EfiFirmwareVolumeEvent != NULL);

    return;
}

EFIAPI
EFI_STATUS
EfiCoreDispatcher (
    VOID
    )

/*++

Routine Description:

    This routine runs the driver dispatcher. It drains the scheduled queue
    loading and starting drivers until there are no more drivers to run.

Arguments:

    None.

Return Value:

    EFI_SUCCESS if one or more drivers were loaded.

    EFI_NOT_FOUND if no drivers were loaded.

    EFI_ALREADY_STARTED if the dispatcher is already running.

--*/

{

    PLIST_ENTRY CurrentEntry;
    PEFI_CORE_DRIVER_ENTRY DriverEntry;
    BOOLEAN ReadyToRun;
    EFI_STATUS ReturnStatus;
    EFI_STATUS Status;

    if (EfiDispatcherRunning != FALSE) {
        return EFI_ALREADY_STARTED;
    }

    EfiDispatcherRunning = TRUE;
    ReturnStatus = EFI_NOT_FOUND;
    do {

        //
        // Drain the scheduled queue.
        //

        while (LIST_EMPTY(&EfiScheduledQueue) == FALSE) {
            DriverEntry = LIST_VALUE(EfiScheduledQueue.Next,
                                     EFI_CORE_DRIVER_ENTRY,
                                     SchedulerListEntry);

            ASSERT(DriverEntry->Magic == EFI_CORE_DRIVER_ENTRY_MAGIC);

            //
            // Load the driver into memory if needed.
            //

            if ((DriverEntry->ImageHandle == NULL) &&
                (DriverEntry->IsFirmwareVolumeImage == FALSE)) {

                Status = EfiCoreLoadImage(FALSE,
                                          EfiFirmwareImageHandle,
                                          DriverEntry->FileDevicePath,
                                          NULL,
                                          0,
                                          &(DriverEntry->ImageHandle));

                if (EFI_ERROR(Status)) {
                    RtlDebugPrint("Warning: Driver failed load with status "
                                  "%x\n",
                                  Status);

                    EfiCoreAcquireLock(&EfiDispatcherLock);
                    if (Status == EFI_SECURITY_VIOLATION) {
                        DriverEntry->Untrusted = TRUE;

                    } else {
                        DriverEntry->Initialized = TRUE;
                    }

                    DriverEntry->Scheduled = FALSE;
                    LIST_REMOVE(&(DriverEntry->SchedulerListEntry));
                    EfiCoreReleaseLock(&EfiDispatcherLock);

                    //
                    // Don't try to start this image, it failed to load.
                    //

                    continue;
                }
            }

            EfiCoreAcquireLock(&EfiDispatcherLock);
            DriverEntry->Scheduled = FALSE;
            DriverEntry->Initialized = TRUE;
            LIST_REMOVE(&(DriverEntry->SchedulerListEntry));
            EfiCoreReleaseLock(&EfiDispatcherLock);
            if (DriverEntry->IsFirmwareVolumeImage == FALSE) {

                ASSERT(DriverEntry->ImageHandle != NULL);

                Status = EfiCoreStartImage(DriverEntry->ImageHandle,
                                           NULL,
                                           NULL);

                if (EFI_ERROR(Status)) {
                    RtlDebugPrint("Warning: Driver start failed with "
                                  "status %x\n",
                                  Status);
                }
            }

            ReturnStatus = EFI_SUCCESS;
        }

        //
        // Search the discovered list for items to place on the scheduled
        // queue.
        //

        ReadyToRun = FALSE;
        CurrentEntry = EfiDiscoveredList.Next;
        while (CurrentEntry != &EfiDiscoveredList) {
            DriverEntry = LIST_VALUE(CurrentEntry,
                                     EFI_CORE_DRIVER_ENTRY,
                                     DriverListEntry);

            CurrentEntry = CurrentEntry->Next;
            if (DriverEntry->Dependent != FALSE) {
                EfipCoreInsertOnScheduledQueue(DriverEntry);
                ReadyToRun = TRUE;
            }
        }

    } while (ReadyToRun != FALSE);

    EfiDispatcherRunning = FALSE;
    return ReturnStatus;
}

//
// --------------------------------------------------------- Internal Functions
//

EFIAPI
VOID
EfipFirmwareVolumeEventProtocolNotify (
    EFI_EVENT Event,
    VOID *Context
    )

/*++

Routine Description:

    This routine is called when a new firmware volume protocol appears in the
    system.

Arguments:

    Event - Supplies a pointer to the event that fired.

    Context - Supplies an unused context pointer.

Return Value:

    None.

--*/

{

    EFI_FV_FILE_ATTRIBUTES Attributes;
    UINTN BufferSize;
    EFI_DEVICE_PATH_PROTOCOL *DevicePath;
    UINTN FileTypeCount;
    EFI_STATUS GetNextFileStatus;
    UINTN Index;
    UINTN Key;
    PEFI_KNOWN_HANDLE KnownHandle;
    EFI_GUID NameGuid;
    UINTN Size;
    EFI_STATUS Status;
    EFI_FV_FILETYPE Type;
    EFI_FIRMWARE_VOLUME2_PROTOCOL *Volume;
    EFI_HANDLE VolumeHandle;

    //
    // Loop through all the new firmware volumes.
    //

    while (TRUE) {
        BufferSize = sizeof(EFI_HANDLE);
        Status = EfiCoreLocateHandle(ByRegisterNotify,
                                     NULL,
                                     EfiFirmwareVolumeEventRegistration,
                                     &BufferSize,
                                     &VolumeHandle);

        if (EFI_ERROR(Status)) {
            break;
        }

        if (EfipFirmwareVolumeHasBeenProcessed(VolumeHandle) != FALSE) {
            continue;
        }

        KnownHandle = EfipMarkFirmwareVolumeProcessed(VolumeHandle);
        if (KnownHandle == NULL) {
            continue;
        }

        Status = EfiCoreHandleProtocol(VolumeHandle,
                                       &EfiFirmwareVolume2ProtocolGuid,
                                       (VOID **)&Volume);

        if ((EFI_ERROR(Status)) || (Volume == NULL)) {

            ASSERT(FALSE);

            continue;
        }

        Status = EfiCoreHandleProtocol(VolumeHandle,
                                       &EfiDevicePathProtocolGuid,
                                       (VOID **)&DevicePath);

        if (EFI_ERROR(Status)) {
            continue;
        }

        //
        // Discover drivers in the firmware volume and add them to the
        // discovered driver list.
        //

        FileTypeCount = sizeof(EfiDispatcherFileTypes) /
                        sizeof(EfiDispatcherFileTypes[0]);

        for (Index = 0; Index < FileTypeCount; Index += 1) {
            Key = 0;
            while (TRUE) {
                Type = EfiDispatcherFileTypes[Index];
                GetNextFileStatus = Volume->GetNextFile(Volume,
                                                        &Key,
                                                        &Type,
                                                        &NameGuid,
                                                        &Attributes,
                                                        &Size);

                if (EFI_ERROR(GetNextFileStatus)) {
                    break;
                }

                EfipCoreAddDriverToList(Volume, VolumeHandle, &NameGuid, Type);
            }
        }
    }

    return;
}

BOOLEAN
EfipFirmwareVolumeHasBeenProcessed (
    EFI_HANDLE Handle
    )

/*++

Routine Description:

    This routine determines if the given firmware volume has been processed.

Arguments:

    Handle - Supplies the handle to the volume.

Return Value:

    TRUE if the handle has been processed.

    FALSE if the handle has not been processed.

--*/

{

    PLIST_ENTRY CurrentEntry;
    PEFI_KNOWN_HANDLE KnownHandle;

    CurrentEntry = EfiFirmwareVolumeList.Next;
    while (CurrentEntry != &EfiFirmwareVolumeList) {
        KnownHandle = LIST_VALUE(CurrentEntry, EFI_KNOWN_HANDLE, ListEntry);
        if (KnownHandle->Handle == Handle) {
            return TRUE;
        }

        CurrentEntry = CurrentEntry->Next;
    }

    return FALSE;
}

PEFI_KNOWN_HANDLE
EfipMarkFirmwareVolumeProcessed (
    EFI_HANDLE Handle
    )

/*++

Routine Description:

    This routine marks a firmware volume handle as having been processed. This
    function adds entries on the firmware volume list if the new entry is
    different from the one in the handle list by checking the firmware volume
    image GUID. Items are never removed/free from the firmware volume list.

Arguments:

    Handle - Supplies the handle to the volume.

Return Value:

    Returns a pointer to the new known handle structure.

    NULL if a firmware volume with the same GUID was already processed.

--*/

{

    EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL *BlockIo;
    EFI_FV_BLOCK_MAP_ENTRY *BlockMap;
    PLIST_ENTRY CurrentEntry;
    UINT32 ExtHeaderOffset;
    UINTN Index;
    PEFI_KNOWN_HANDLE KnownHandle;
    EFI_LBA LbaIndex;
    UINTN LbaOffset;
    EFI_GUID NameGuid;
    BOOLEAN NameGuidFound;
    EFI_STATUS Status;
    EFI_FIRMWARE_VOLUME_HEADER *VolumeHeader;

    NameGuidFound = FALSE;

    //
    // Get the firmware volume block protocol on the handle. Going rogue.
    //

    Status = EfiCoreHandleProtocol(Handle,
                                   &EfiFirmwareVolumeBlockProtocolGuid,
                                   (VOID **)&BlockIo);

    if (!EFI_ERROR(Status)) {

        //
        // Get the full volume header using the block I/O protocol.
        //

        ASSERT(BlockIo != NULL);

        Status = EfiFvGetVolumeHeader(BlockIo, &VolumeHeader);
        if (!EFI_ERROR(Status)) {

            ASSERT(VolumeHeader != NULL);

            if ((EfiFvVerifyHeaderChecksum(VolumeHeader) != FALSE) &&
                (VolumeHeader->ExtHeaderOffset != 0)) {

                ExtHeaderOffset = VolumeHeader->ExtHeaderOffset;
                BlockMap = VolumeHeader->BlockMap;
                LbaIndex = 0;
                LbaOffset = 0;

                //
                // Find the LBA index and offset for the volume extension
                // header using the block map.
                //

                while ((BlockMap->BlockCount != 0) ||
                       (BlockMap->BlockLength != 0)) {

                    for (Index = 0;
                         Index < BlockMap->BlockCount;
                         Index += 1) {

                        if (ExtHeaderOffset < BlockMap->BlockLength) {
                            break;
                        }

                        ExtHeaderOffset -= BlockMap->BlockLength;
                        LbaIndex += 1;
                    }

                    if (Index < BlockMap->BlockCount) {
                        LbaOffset = ExtHeaderOffset;
                        break;
                    }

                    BlockMap += 1;
                }

                Status = EfiFvReadData(BlockIo,
                                       &LbaIndex,
                                       &LbaOffset,
                                       sizeof(NameGuid),
                                       (UINT8 *)&NameGuid);

                if (!EFI_ERROR(Status)) {
                    NameGuidFound = TRUE;
                }
            }

            EfiCoreFreePool(VolumeHeader);
        }
    }

    //
    // If a name GUID for this volume was found, compare it with all the other
    // known volumes.
    //

    if (NameGuidFound != FALSE) {
        CurrentEntry = EfiFirmwareVolumeList.Next;
        while (CurrentEntry != &EfiFirmwareVolumeList) {
            KnownHandle = LIST_VALUE(CurrentEntry, EFI_KNOWN_HANDLE, ListEntry);
            if (EfiCoreCompareGuids(&NameGuid, &(KnownHandle->NameGuid)) !=
                FALSE) {

                RtlDebugPrint("Found two firmware volumes with the same GUID. "
                              "Skipping one!\n");

                return NULL;
            }
        }
    }

    KnownHandle = EfiCoreAllocateBootPool(sizeof(EFI_KNOWN_HANDLE));
    if (KnownHandle == NULL) {

        ASSERT(FALSE);

        return NULL;
    }

    EfiCoreSetMemory(KnownHandle, sizeof(EFI_KNOWN_HANDLE), 0);
    KnownHandle->Handle = Handle;
    if (NameGuidFound != FALSE) {
        EfiCoreCopyMemory(&(KnownHandle->NameGuid),
                          &NameGuid,
                          sizeof(EFI_GUID));
    }

    INSERT_BEFORE(&(KnownHandle->ListEntry), &EfiFirmwareVolumeList);
    return KnownHandle;
}

EFI_STATUS
EfipCoreAddDriverToList (
    EFI_FIRMWARE_VOLUME2_PROTOCOL *Volume,
    EFI_HANDLE VolumeHandle,
    EFI_GUID *DriverName,
    EFI_FV_FILETYPE Type
    )

/*++

Routine Description:

    This routine adds a driver entry to the discovered list.

Arguments:

    Volume - Supplies a pointer to the firmware volume.

    VolumeHandle - Supplies the firmware volume handle.

    DriverName - Supplies a pointer to the GUID of the driver's name.

    Type - Supplies the file type.

Return Value:

    EFI_SUCCESS on success.

    EFI_ALREADY_STARTED if the driver has already been started.

--*/

{

    EFI_CORE_DRIVER_ENTRY *DriverEntry;

    DriverEntry = EfiCoreAllocateBootPool(sizeof(EFI_CORE_DRIVER_ENTRY));
    if (DriverEntry == NULL) {
        return EFI_OUT_OF_RESOURCES;
    }

    EfiCoreSetMemory(DriverEntry, sizeof(EFI_CORE_DRIVER_ENTRY), 0);
    if (Type == EFI_FV_FILETYPE_FIRMWARE_VOLUME_IMAGE) {
        DriverEntry->IsFirmwareVolumeImage = TRUE;
    }

    DriverEntry->Magic = EFI_CORE_DRIVER_ENTRY_MAGIC;
    EfiCoreCopyMemory(&(DriverEntry->FileName), DriverName, sizeof(EFI_GUID));
    DriverEntry->VolumeHandle = VolumeHandle;
    DriverEntry->Volume = Volume;
    DriverEntry->FileDevicePath =
                    EfipCoreConvertFirmwareVolumeFileToDevicePath(Volume,
                                                                  VolumeHandle,
                                                                  DriverName);

    DriverEntry->Dependent = TRUE;
    EfiCoreAcquireLock(&EfiDispatcherLock);
    INSERT_BEFORE(&(DriverEntry->DriverListEntry), &EfiDiscoveredList);
    EfiCoreReleaseLock(&EfiDispatcherLock);
    return EFI_SUCCESS;
}

EFI_DEVICE_PATH_PROTOCOL *
EfipCoreConvertFirmwareVolumeFileToDevicePath (
    EFI_FIRMWARE_VOLUME2_PROTOCOL *Volume,
    EFI_HANDLE VolumeHandle,
    EFI_GUID *DriverName
    )

/*++

Routine Description:

    This routine converts a firmware volume and driver name into an EFI
    device path.

Arguments:

    Volume - Supplies a pointer to the firmware volume.

    VolumeHandle - Supplies the firmware volume handle.

    DriverName - Supplies a pointer to the GUID of the driver's name.

Return Value:

    Returns a pointer to the file device path.

--*/

{

    EFI_DEVICE_PATH_PROTOCOL *FileDevicePath;
    EFI_FIRMWARE_VOLUME_FILE_DEVICE_PATH FileNameDevicePath;
    EFI_STATUS Status;
    EFI_DEVICE_PATH_PROTOCOL *VolumeDevicePath;

    Status = EfiCoreHandleProtocol(VolumeHandle,
                                   &EfiDevicePathProtocolGuid,
                                   (VOID **)&VolumeDevicePath);

    if (EFI_ERROR(Status)) {
        return NULL;
    }

    //
    // Build a device path to the file in the volume.
    //

    EfiCoreInitializeFirmwareVolumeDevicePathNode(&(FileNameDevicePath.File),
                                                  DriverName);

    EfiCoreSetDevicePathEndNode(&(FileNameDevicePath.End));
    FileDevicePath = EfiCoreAppendDevicePath(
                              VolumeDevicePath,
                              (EFI_DEVICE_PATH_PROTOCOL *)&FileNameDevicePath);

    return FileDevicePath;
}

VOID
EfipCoreInsertOnScheduledQueue (
    PEFI_CORE_DRIVER_ENTRY DriverEntry
    )

/*++

Routine Description:

    This routine inserts a driver entry onto the scheduled queue.

Arguments:

    DriverEntry - Supplies a pointer to the driver entry to schedule.

Return Value:

    None.

--*/

{

    EfiCoreAcquireLock(&EfiDispatcherLock);
    DriverEntry->Dependent = FALSE;
    DriverEntry->Scheduled = TRUE;
    INSERT_BEFORE(&(DriverEntry->SchedulerListEntry), &EfiScheduledQueue);
    EfiCoreReleaseLock(&EfiDispatcherLock);
    return;
}

