/*++

Copyright (c) 1995-2001 Microsoft Corporation

Module Name:

    efidrvent.c

Abstract:

    Contains the EFI driver entry abstraction implementation.

Author:

    Mandar Gokhale (MandarG@microsoft.com) 14-June-2002

Revision History:

    None.

--*/

#include <efidrvent.h>
#include <ntosp.h>
#include <efi.h>
#include <stdio.h>


static
NTSTATUS
EFIDEAddOrUpdateDriverEntry(
    IN PDRIVER_ENTRY    This,
    IN BOOLEAN          IsUpdate
    )
/*++
    Description:
        Modify or update a driver entry.

    Arguments:
        This - Driver entry .

        IsUpdate -  whether this is a driver entry update or add.

    Return Value:
        NTSTATUS or add/modify driver operation.
--*/
{
    //
    // Add this as new boot entry
    //                
    ULONG               FullPathLength = 0;
    ULONG               DevicePathLength = 0;
    ULONG               SrcPathLength = 0;
    ULONG               FriendlyNameOffset = 0;
    ULONG               FriendlyNameLength = 0;
    ULONG               FilePathLength     = 0;   
    ULONG               EntryLength        = 0;   
    ULONG               DriverEntryLength  = 0;
    PEFI_DRIVER_ENTRY   DriverEntry = NULL;
    PFILE_PATH          FilePath;
    NTSTATUS            Status;

    DevicePathLength = (wcslen((PCWSTR)This->NtDevicePath)+ 1) * sizeof(WCHAR);
    SrcPathLength = (wcslen((PCWSTR)This->DirPath) + 1) * sizeof(WCHAR);
    FullPathLength = DevicePathLength + SrcPathLength;
    
    FriendlyNameOffset = ALIGN_UP(sizeof(EFI_DRIVER_ENTRY), WCHAR);
    FriendlyNameLength = (wcslen(This->FriendlyName) + 1) * sizeof(WCHAR);
    FilePathLength = FIELD_OFFSET(FILE_PATH, FilePath) + FullPathLength;
    EntryLength = FriendlyNameOffset + ALIGN_UP(FriendlyNameLength, ULONG) + FilePathLength;
    DriverEntry = SBE_MALLOC(EntryLength);

    DriverEntry->Version = EFI_DRIVER_ENTRY_VERSION;
    DriverEntry->Length = EntryLength;
    DriverEntry->FriendlyNameOffset = FriendlyNameOffset;
    DriverEntry->DriverFilePathOffset = FriendlyNameOffset + 
                                        ALIGN_UP(FriendlyNameLength, ULONG);
    RtlCopyMemory((PCHAR) DriverEntry + DriverEntry->FriendlyNameOffset, 
                  This->FriendlyName, 
                  FriendlyNameLength);

    FilePath = (PFILE_PATH) ((PCHAR) DriverEntry + DriverEntry->DriverFilePathOffset);
    FilePath->Version = FILE_PATH_VERSION;
    FilePath->Length = FilePathLength;
    FilePath->Type = FILE_PATH_TYPE_NT;
    RtlCopyMemory(FilePath->FilePath, This->NtDevicePath, DevicePathLength );
    RtlCopyMemory(FilePath->FilePath + DevicePathLength, This->DirPath, SrcPathLength);

    if (IsUpdate){
        //
        // Update the driver.
        //
        DriverEntry->Id = This->Id;
        Status = NtModifyDriverEntry(DriverEntry);
    } else {
        //
        // Add a new driver entry.
        //
        Status = NtAddDriverEntry(DriverEntry, &(This->Id));          
    }

    //
    // Free allocated memory.
    //
    if(DriverEntry){
        
        SBE_FREE(DriverEntry);
    }
    return Status;
}
    

static
BOOLEAN
EFIDEFlushDriverEntry(
    IN  PDRIVER_ENTRY  This    // Points to the driver List.
    )
{

    BOOLEAN Result = FALSE;
    if (This) {
        NTSTATUS Status = STATUS_SUCCESS;
        if (DRIVERENT_IS_DIRTY(This)) {
            
            if (DRIVERENT_IS_DELETED(This)) {
                //
                // Delete this entry
                //
                Status = NtDeleteDriverEntry(This->Id);
            } else if (DRIVERENT_IS_NEW(This)) {
                //
                // Add new Entry.
                //
                Status = EFIDEAddOrUpdateDriverEntry(This, FALSE);                                      
                
            } else {
                //
                // Just update this boot entry
                //
                Status = EFIDEAddOrUpdateDriverEntry(This, TRUE);                           
            }

            if (NT_SUCCESS(Status)) {
                
                DRIVERENT_RESET_DIRTY(This);
                Result = TRUE;
            }     
            
        } else {
            Result = TRUE;  // nothing to flush
        }
    }

    return Result;
}

__inline
BOOLEAN
EFIDEDriverMatch(
    IN PDRIVER_ENTRY    DriverEntry ,
    IN PCWSTR           SrcNtFullPath
    )
{
    BOOLEAN Result = FALSE;
    
    if (!_wcsicmp(DriverEntry->FileName, (wcsrchr(SrcNtFullPath,L'\\')+1))){
        
        Result = TRUE;
    }

    return(Result);
}

static
PDRIVER_ENTRY    
EFIDESearchForDriverEntry(
    IN POS_BOOT_OPTIONS  This,
    IN PCWSTR            SrcNtFullPath
    )
/*++
Description:
    Searches our internal list of driver entries for a match.
    It looks up the driver name (not including the path)
    for a match. so a\b\c\driver.sys and e\f\driver.sys would be a match.
--*/
{
    PDRIVER_ENTRY CurrentDriverEntry = NULL;   
    
    if (This && SrcNtFullPath){
        
        CurrentDriverEntry = This->DriverEntries;        
        while (CurrentDriverEntry){
            
            if (EFIDEDriverMatch(CurrentDriverEntry, 
                               SrcNtFullPath)){                               
                break;                               
            }
            CurrentDriverEntry = OSBOGetNextDriverEntry(This, CurrentDriverEntry);
        }
        
    }   
    return (CurrentDriverEntry);
}

PDRIVER_ENTRY
EFIDECreateNewDriverEntry(
    IN POS_BOOT_OPTIONS  This,
    IN PCWSTR            FriendlyName,
    IN PCWSTR            NtDevicePath,
    IN PCWSTR            DirPath    
    )
{
    PDRIVER_ENTRY DriverEntry = NULL;
    if (This && FriendlyName && DirPath && NtDevicePath){        
        PDRIVER_ENTRY CurrentDriverEntry = NULL;

        DriverEntry = (PDRIVER_ENTRY)SBE_MALLOC(sizeof(DRIVER_ENTRY));
        memset(DriverEntry, 0, sizeof(DRIVER_ENTRY));

        EFIDEDriverEntryInit(DriverEntry);
        DriverEntry->BootOptions = This;        
        //
        // Set information for the driver entry.
        //
        OSDriverSetFileName(DriverEntry, DirPath);
        OSDriverSetNtPath(DriverEntry, NtDevicePath);
        OSDriverSetDirPath(DriverEntry, DirPath);
        
        OSDriverSetFriendlyName(DriverEntry, FriendlyName);        

        
        //
        // Mark the driver entry new and dirty.
        //
        DRIVERENT_SET_NEW(DriverEntry);
        DRIVERENT_SET_DIRTY(DriverEntry);        
    }   
    return (DriverEntry);
}

PDRIVER_ENTRY
EFIOSBOInsertDriverListNewEntry(
    IN POS_BOOT_OPTIONS  This,
    IN PDRIVER_ENTRY     DriverEntry
    )

{

    if (This && DriverEntry){        
        PDRIVER_ENTRY CurrentDriverEntry = NULL;        
        //
        // Insert into the list.
        //        
        if (NULL == This->DriverEntries){
            //
            // No driver entries, this is the first one.
            //
            This->DriverEntries = DriverEntry;
        }else{
            //
            // Insert in the existing list.
            //
            DriverEntry->NextEntry = This->DriverEntries;
            This->DriverEntries = DriverEntry;            
        }        
    }   
    return (DriverEntry);

}

PDRIVER_ENTRY
EFIDEAddNewDriverEntry(
    IN POS_BOOT_OPTIONS  This,
    IN PCWSTR            FriendlyName,
    IN PCWSTR            NtDevicePath,
    IN PCWSTR            SrcNtFullPath
    )
/*++

Description:
    Used to add a new driver entry in NVRAM.

--*/
{   
    PEFI_DRIVER_ENTRY_LIST DriverList = NULL;       // list of driver entries
    PDRIVER_ENTRY  DriverEntry = NULL;
    if (This && FriendlyName && SrcNtFullPath && NtDevicePath){
        
        DriverEntry = EFIDECreateNewDriverEntry(This,
                                                FriendlyName,
                                                NtDevicePath,
                                                SrcNtFullPath);
        //
        // Mark it as new
        //
        DRIVERENT_IS_NEW(DriverEntry);
       
        //
        // flush the entry, 
        // see status, if successful put it in the list (only if new) 
        // otherwise free it
        //
        if (!OSDriverEntryFlush(DriverEntry)){
            
            SBE_FREE(DriverEntry);
            DriverEntry = NULL;
        } else {
        
            ULONG   OrderCount;
            PULONG  NewOrder;
            //
            // If the driver was newly added one then insert it in the driver list.
            //
            if (DRIVERENT_IS_NEW(DriverEntry)){
                EFIOSBOInsertDriverListNewEntry(This,
                                                DriverEntry);                                         

                //
                // Increment the count of the number of driver entries in the list.
                //
                This->DriverEntryCount++;
                
                //
                // Put the new entry at the end of the boot order
                //
                OrderCount = OSBOGetOrderedDriverEntryCount(This);

                NewOrder = (PULONG)SBE_MALLOC((OrderCount + 1) * sizeof(ULONG));

                if (NewOrder) {
                    
                    memset(NewOrder, 0, sizeof(ULONG) * (OrderCount + 1));

                    //
                    // copy over the old ordered list
                    //
                    memcpy(NewOrder, This->DriverEntryOrder, sizeof(ULONG) * OrderCount);
                    NewOrder[OrderCount] = OSDriverGetId((PDRIVER_ENTRY)DriverEntry);
                    SBE_FREE(This->DriverEntryOrder);
                    This->DriverEntryOrder = NewOrder;
                    This->DriverEntryOrderCount = OrderCount + 1;
                } else {
                    //
                    // Remove the driver entry out of the link list.
                    // Just freeing it will cause memory leaks.
                    // TBD: decide if we want to delete this driver entry too
                    //
                    This->DriverEntries = DriverEntry->NextEntry;
                    SBE_FREE(DriverEntry);
                    DriverEntry = NULL;
                }  
            }
        }        
     }           
    return DriverEntry;
}

__inline
ULONG
EFIDESetDriverId(
    IN PDRIVER_ENTRY This,
    IN ULONG DriverId
    )
{
    if (This){        
        This->Id = DriverId;
    }
    return (DriverId);
}

PDRIVER_ENTRY
EFIDECreateDriverEntry(PEFI_DRIVER_ENTRY_LIST Entry, 
                       POS_BOOT_OPTIONS This )
/*++
    Description:
        Used to interpret a driver entry returned by NtEnumerateDriverEntries(..)
        into our format.

    Arguments:
        Entry - EFI format driver entry returned to us by NT.
        
        This - container for the driver entry list that we generate.

    Return:
        PDRIVER_ENTRY ( driver entry in our format).
--*/

{
    PDRIVER_ENTRY ResultDriverEntry = NULL;

    if (Entry && This){
        PFILE_PATH      FilePath = (PFILE_PATH) ((PCHAR) &Entry->DriverEntry + 
                                    Entry->DriverEntry.DriverFilePathOffset);
        PWCHAR          FriendlyName = (PWCHAR)((PCHAR)&Entry->DriverEntry + 
                                        Entry->DriverEntry.FriendlyNameOffset);
        ULONG           NtDevicePathLength = 0;
        PDRIVER_ENTRY   DriverEntry = NULL;    
        PFILE_PATH      DriverOptionPath = NULL;
        NTSTATUS   Status = STATUS_UNSUCCESSFUL;

        if(FilePath->Type != FILE_PATH_TYPE_NT) {                    
            PVOID Buffer;
            ULONG PathLength = 0;
            
            Status = NtTranslateFilePath(FilePath, FILE_PATH_TYPE_NT, NULL, &PathLength);

            if(NT_SUCCESS(Status)) {
                
                Status = STATUS_UNSUCCESSFUL;
            }

            if(STATUS_BUFFER_TOO_SMALL == Status) {
                
                ASSERT(PathLength != 0); 
                
                DriverOptionPath = (PFILE_PATH)SBE_MALLOC(PathLength);
                memset(DriverOptionPath, 0, sizeof(PathLength));
                Status = NtTranslateFilePath(FilePath, 
                                            FILE_PATH_TYPE_NT, 
                                            DriverOptionPath, 
                                            &PathLength);
            }

            if(!NT_SUCCESS(Status)) {                
                if(STATUS_OBJECT_PATH_NOT_FOUND == Status || STATUS_OBJECT_NAME_NOT_FOUND == Status) {
                    //
                    // This entry is stale; remove it
                    //
                    NtDeleteDriverEntry(Entry->DriverEntry.Id);
                }

                //
                // Free the DriverOptionPath memory
                //
                if(DriverOptionPath != NULL) {                    
                    SBE_FREE(DriverOptionPath);
                    DriverOptionPath = NULL;
                }
            }
            
        }
        if (DriverOptionPath){
            
            ULONG FilePathLength = 0;
            DriverEntry = (PDRIVER_ENTRY)SBE_MALLOC(sizeof(DRIVER_ENTRY));
            memset(DriverEntry, 0, sizeof(DRIVER_ENTRY));
            
            //
            // Set pointer back to boot options (container).
            //
            DriverEntry->BootOptions = This;

            EFIDEDriverEntryInit(DriverEntry);
        
            //
            // Set driver ID.
            //
            EFIDESetDriverId(DriverEntry, Entry->DriverEntry.Id);

            //
            // Set File Name.
            //
            NtDevicePathLength = wcslen((PCWSTR) DriverOptionPath->FilePath) + 1;
            OSDriverSetFileName(DriverEntry, 
                              (PCWSTR) DriverOptionPath->FilePath + NtDevicePathLength);                

            //
            // Set NT path and Driver dir.  
            //            
            OSDriverSetNtPath(DriverEntry, (PCWSTR)DriverOptionPath->FilePath);
            OSDriverSetDirPath(DriverEntry, (PCWSTR)(DriverOptionPath->FilePath) + NtDevicePathLength);

            //
            // Set Friendly Name.
            //
            OSDriverSetFriendlyName(DriverEntry, FriendlyName);   

            //
            // Free the DriverOptionPath memory
            //
            if(DriverOptionPath != NULL) {                    
                SBE_FREE(DriverOptionPath);
            }

            ResultDriverEntry = DriverEntry;
        }
    }
    return ResultDriverEntry;
}


NTSTATUS
EFIDEInterpretDriverEntries(
    IN POS_BOOT_OPTIONS         This,
    IN PEFI_DRIVER_ENTRY_LIST   DriverList
)
/*++
    Description:
        Used to interpret the driver entries returned by NtEnumerateDriverEntries(..)
        into our format.

    Arguments:
        This - container for the driver entry list that we generate.

        DriverList - Driver list returned by NtEnumerateDriverEntries(..).

    Return:
        NTSTATUS code.
--*/
{       
    NTSTATUS                Status = STATUS_UNSUCCESSFUL;
    
    if (This && DriverList){
        
        PEFI_DRIVER_ENTRY_LIST  Entry;   
        PDRIVER_ENTRY           DriverEntry = NULL;
        BOOLEAN                 Continue = TRUE;


        Status = STATUS_SUCCESS;
        for(Entry = DriverList; 
            Continue; 
            Entry = (PEFI_DRIVER_ENTRY_LIST) ((PCHAR) Entry + 
                                              Entry->NextEntryOffset)) {                                              

            Continue = (Entry->NextEntryOffset != 0);            
            DriverEntry = EFIDECreateDriverEntry(Entry, This);

            if (DriverEntry){
                //
                // Insert into the list of drivers in the OSBO structure.
                //
                if (NULL == This->DriverEntries){
                    This->DriverEntries = DriverEntry;                
                } else{
                    DriverEntry->NextEntry = This->DriverEntries;
                    This->DriverEntries = DriverEntry;
                }
                
                This->DriverEntryCount++;
            }
        }            
    }

    return Status;
}

static
VOID
EFIDEDriverEntryInit(
    IN PDRIVER_ENTRY This
    )
{ 
    This->Flush  = EFIDEFlushDriverEntry;
}