2010年10月5日 星期二
EFI中的CR (CONTAINING RECORD)
#define _CR(Record, TYPE, Field) ((TYPE *) ((CHAR8 *) (Record) - (CHAR8 *) &(((TYPE *) 0)->Field)))
Record:為該成員的位址
TYPE:為該結構的型態
Field:為在結構中指向的該成員
這個方法可以分開兩段來看:
(CHAR8 *) (Record):該成員的實際位址
(CHAR8 *) &(((TYPE *) 0)->Field):該成員在結構中的偏移量
兩個做相減之後便可取得結構頭的位址,最後再來個(TYPE *)作強制轉換。
2010年10月4日 星期一
PCI I/O Protocols
typedef struct _EFI_PCI_IO_PROTOCOL {
EFI_PCI_IO_PROTOCOL_POLL_IO_MEM PollMem;
EFI_PCI_IO_PROTOCOL_POLL_IO_MEM PollIo;
EFI_PCI_IO_PROTOCOL_ACCESS Mem;
EFI_PCI_IO_PROTOCOL_ACCESS Io;
EFI_PCI_IO_PROTOCOL_CONFIG_ACCESS Pci;
EFI_PCI_IO_PROTOCOL_COPY_MEM CopyMem;
EFI_PCI_IO_PROTOCOL_MAP Map;
EFI_PCI_IO_PROTOCOL_UNMAP Unmap;
EFI_PCI_IO_PROTOCOL_ALLOCATE_BUFFER AllocateBuffer;
EFI_PCI_IO_PROTOCOL_FREE_BUFFER FreeBuffer;
EFI_PCI_IO_PROTOCOL_FLUSH Flush;
EFI_PCI_IO_PROTOCOL_GET_LOCATION GetLocation;
EFI_PCI_IO_PROTOCOL_ATTRIBUTES Attributes;
EFI_PCI_IO_PROTOCOL_GET_BAR_ATTRIBUTES GetBarAttributes;
EFI_PCI_IO_PROTOCOL_SET_BAR_ATTRIBUTES SetBarAttributes;
UINT64 RomSize;
VOID *RomImage;
} EFI_PCI_IO_PROTOCOL;
// ++++++++++PCI Configuration Space++++++++++
首先來講如何取得PCI Configuration Space:
以上為PCI Configuration Space配置表Type 0。
先使用LocateHandleBuffer,將所有PCI Device的Handle全都找出來:
gBS->LocateHandleBuffer (ByProtocol, &gEfiPciIoProtocolGuid, NULL, &PciHandleCount, &PciHandleBuffer);
隨後在指定的Handle中將PCI IO Protocol找出來:
gBS->OpenProtocol (PciHandle, &gEfiPciIoProtocolGuid, &PciIo, ImageHandle, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
以下可取得PCI Device的四個參數:
UINTN Seg;
UINTN Bus;
UINTN Device;
UINTN Function;
PciIo->GetLocation (PciIo, &Seg, &Bus, &Device, &Function);
若要取得整張配置表可以宣告此union型別變數:
typedef union {
PCI_TYPE00 Device;
PCI_TYPE01 Bridge;
} PCI_TYPE_GENERIC;
可用其Header Type來判斷它是Device或Bridge,00h與01h...等。
typedef struct {
PCI_DEVICE_INDEPENDENT_REGION Hdr;
PCI_DEVICE_HEADER_TYPE_REGION Device;
} PCI_TYPE00;
typedef struct {
PCI_DEVICE_INDEPENDENT_REGION Hdr;
PCI_BRIDGE_CONTROL_REGISTER Bridge;
} PCI_TYPE01;
PCI_DEVICE_INDEPENDENT_REGION、PCI_DEVICE_HEADER_TYPE_REGION、PCI_BRIDGE_CONTROL_REGISTER
這三個結構定義可看Spec。
宣告一個PCI_TYPE_GENERIC變數
PCI_TYPE_GENERIC Pci;
PciIo->Pci.Read (PciIo, EfiPciWidthUint32, 0, sizeof (PCI_TYPE_GENERIC) / sizeof (UINT32), &Pci);
第二個參數可以設定每次所要取得的大小,詳情可看其定義。
第三個參數為起始位址,由於要抓整張表所以便填入0。
第四個參數為總共要抓取的次數。
成功後可一一取值,如:
Pci.Device.Hdr.VendorId;
Pci.Device.Device.SubsystemVendorID;
若要取得配置表的其中一個值,可先算出所在位址,如Status位址在06h而大小是16bits,先宣告一個UINT16的變數Status,代入Pci.Read()。
PciIo->Pci.Read (PciIo, EfiPciWidthUint16, 0, 1, &Status);
如果要取得ClassCode的話,而它是3個8bits整數,所以宣告一個UINT8 ClassCode[3]陣列,再呼叫Pci.Read()。
PciIo->Pci.Read (PciIo, EfiPciWidthUint8, 0, 3, ClassCode);
以下可印出整張表的內容:
UINT8 Content;
for (i = 0x00; i <= 0x0F; ++i) { for (j = 0x00; j <= 0x0F; ++j) { Status = PciIo->Pci.Read (PciIo, EfiPciIoWidthUint8, ((i * 0x10) + j), sizeof (Content), &Content);
PrintAt (j * 3, i, L"%02x", Content);
}
}
// ++++++++++PCI Base Address Register++++++++++
接下來是Base Address Register的部分,一般PCI Device共有六個BAR,而PCI/PCI Bridge只有兩個BAR,可看Spec。
而BAR又分了IO Space與Memory Space兩種方式,可看其第一個bit決定,0是Memory Space而1是IO Space,也就是基偶之分,EFI也提供了一個結構儲存該BAR的屬性。
typedef struct {
UINT8 Desc;
UINT16 Len;
UINT8 ResType;
UINT8 GenFlag;
UINT8 SpecificFlag;
UINT64 AddrSpaceGranularity;
UINT64 AddrRangeMin;
UINT64 AddrRangeMax;
UINT64 AddrTranslationOffset;
UINT64 AddrLen;
} EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR;
此時宣告其指標:
EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR *Resources;
PciIo->GetBarAttributes (PciIo, PciBarIndex, NULL, &Resources);
注意用完Resources後要釋放掉。
Resources->ResType也可判斷BAR的屬性(IO/Memory)。
if (Resources->ResType == 0) {
PciIo->Mem.Read(PciIo, EfiPciIoWidthUint8, PciBarIndex,0, sizeof (Content), &Content);
} else {
PciIo->Io.Read(PciIo, EfiPciIoWidthUint8, PciBarIndex, 0, sizeof (Content), &Content);
}
BarIndex按自己所需的BAR代入0~5(P2P Bridge: 0~1)的整數。
至於寫入也是相同方式。
2010年10月2日 星期六
Variable Service
GetNextVariableName(
IN OUT UINTN *VariableNameSize,
IN OUT CHAR16 *VariableName,
IN OUT EFI_GUID *VendorGuid
);
可以透過傳進去的Name與GUID來得到它下一個Name與GUID,並傳出Name的實際Size,如果您不知道任何的Name與GUID的話,此時可填入空的Name與GUID,便會將第一組傳回。
GetVariable(
IN CHAR16 *VariableName,
IN EFI_GUID *VendorGuid,
OUT UINT32 *Attributes OPTIONAL,
IN OUT UINTN *DataSize,
OUT VOID *Data
);
可傳回對應Name與GUID的Data,要傳出的Data要先為它配置好一個空間(動態或靜態皆可),如果不知道Data的Size,此時可先傳入一個非0的Size,它會將實際的Size傳出,並傳回EFI_BUFFER_TOO_SMALL的錯誤訊息,接下來再動態配置空間即可。
SetVariable(
IN CHAR16 *VariableName,
IN EFI_GUID *VendorGuid,
IN UINT32 Attributes,
IN UINTN DataSize,
IN VOID *Data
);
將傳入的Data寫入至對應的Name與GUID。
Attributes:
#define EFI_VARIABLE_NON_VOLATILE 0x00000001
#define EFI_VARIABLE_BOOTSERVICE_ACCESS 0x00000002
#define EFI_VARIABLE_RUNTIME_ACCESS 0x00000004
#define EFI_VARIABLE_HARDWARE_ERROR_RECORD 0x00000008
#define EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS 0x00000010
2010年9月29日 星期三
淺談EFI Protocol
一個Protocol基本上是由數個變數與函式指標所組成的結構,也會跟隨著一個唯一識別的GUID(Globally Unique Identifier),這部分與PEI階段的PEIM中的PPI滿像的,各個不同程式間可利用方法將所需的功能找出來使用。
安裝一個Protocol時會與自己對應的GUID配在一起,放置於Handle Database中,Handle Database是一個Linked List,每個Handle被串在一起,而Handle底下又與屬於它的所有GUID | Protocol相連。
EFI DRIVER BINDING PROTOCOL原型:
typedef struct _EFI_DRIVER_BINDING_PROTOCOL {
EFI_DRIVER_BINDING_PROTOCOL_SUPPORTED Supported;
EFI_DRIVER_BINDING_PROTOCOL_START Start;
EFI_DRIVER_BINDING_PROTOCOL_STOP Stop;
UINT32 Version;
EFI_HANDLE ImageHandle;
EFI_HANDLE DriverBindingHandle;
} EFI_DRIVER_BINDING_PROTOCOL;
其GUID:
#define EFI_DRIVER_BINDING_PROTOCOL_GUID \
{0x18A031AB,0xB443,0x4D1A,0xA5,0xC0,0x0C,0x09,0x26,0x1E,0x9F,0x71}
extern EFI_GUID gEfiDriverBindingProtocolGuid = EFI_DRIVER_BINDING_PROTOCOL_GUID;
基本上是使用gEfiDriverBindingProtocolGuid這個變數來與EFI DRIVER BINDING PROTOCOL做關聯。
與Protocol常用的相關服務如下:
InstallMultipleProtocolInterfaces (
IN OUT EFI_HANDLE *Handle,
...
);
安裝數個Protocol與搭配的GUID至指定的Handle底下。
UninstallMultipleProtocolInterfaces (
IN EFI_HANDLE Handle,
...
);
移除數個Handle底下的Protocol與所搭配的GUID。
LocateHandleBuffer (
IN EFI_LOCATE_SEARCH_TYPE SearchType,
IN EFI_GUID *Protocol OPTIONAL,
IN VOID *SearchKey OPTIONAL,
IN OUT UINTN *NoHandles,
OUT EFI_HANDLE **Buffer
);
可將全部或以特定的條件將Handle取出。
ProtocolsPerHandle (
IN EFI_HANDLE Handle,
OUT EFI_GUID ***ProtocolBuffer,
OUT UINTN *ProtocolBufferCount
);
取得指定的Handle底下所有的Protocol GUID陣列,並傳出數量。
OpenProtocolInformation (
IN EFI_HANDLE Handle,
IN EFI_GUID *Protocol,
OUT EFI_OPEN_PROTOCOL_INFORMATION_ENTRY **EntryBuffer,
OUT UINTN *EntryCount
);
取得Handle底下指定的GUID的Information Entry陣列,並回傳數量。
LocateProtocol (
IN EFI_GUID *Protocol,
IN VOID *Registration OPTIONAL,
OUT VOID **Interface
);
以指定的GUID取出第一個Protocol。
HandleProtocol (
IN EFI_HANDLE Handle,
IN EFI_GUID *Protocol,
OUT VOID **Interface
);
取出Handle中指定的Protocol,以GUID為識別。
OpenProtocol (
IN EFI_HANDLE Handle,
IN EFI_GUID *Protocol,
OUT VOID **Interface OPTIONAL,
IN EFI_HANDLE AgentHandle,
IN EFI_HANDLE ControllerHandle,
IN UINT32 Attributes
);
類似HandleProtocol(),但提供了更安全的做法,綁訂指定的Handle,避免使用中的Protocol被Uninstall。
此為最後一個參數的定義:
#define EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL 0x00000001
#define EFI_OPEN_PROTOCOL_GET_PROTOCOL 0x00000002
#define EFI_OPEN_PROTOCOL_TEST_PROTOCOL 0x00000004
#define EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER 0x00000008
#define EFI_OPEN_PROTOCOL_BY_DRIVER 0x00000010
#define EFI_OPEN_PROTOCOL_EXCLUSIVE 0x00000020
依需要使用,有些定義可不用CloseProtocol()。
CloseProtocol (
IN EFI_HANDLE Handle,
IN EFI_GUID *Protocol,
IN EFI_HANDLE AgentHandle,
IN EFI_HANDLE ControllerHandle
);
為以OpenProtocol()開啟的Protocol解除占用。
2010年8月31日 星期二
於EDK中加入DXE Driver
依序建立以下檔案:
TestDriver.h
========================================
#ifndef _TEST_DRIVER_H_
#define _TEST_DRIVER_H_
#include "Tiano.h"
#include "EfiDriverLib.h"
//
// Function Prototypes
//
EFI_STATUS
EFIAPI
TestDriverEntry (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
);
#endif
========================================
TestDriver.c
========================================
#include "TestDriver.h"
EFI_DRIVER_ENTRY_POINT (TestDriverEntry)
EFI_STATUS
EFIAPI
TestDriverEntry (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
Status = EfiInitializeDriverLib(ImageHandle, SystemTable);
DEBUG_CODE (
DEBUG ((EFI_D_INFO | EFI_D_LOAD, "Test = %s\n", L"TestDriverEntry"));
)
return Status;
}
========================================
TestDriver.inf
========================================
[defines]
BASE_NAME = TestDriver
FILE_GUID = 7B257F2E-7177-408a-BA46-567C07301F69
COMPONENT_TYPE = BS_DRIVER
[sources.common]
TestDriver.c
[libraries.common]
EdkGuidLib
EfiGuidLib
EfiProtocolLib
EfiDriverLib
[includes.common]
$(EDK_SOURCE)\Foundation
$(EDK_SOURCE)\Foundation\Efi
$(EDK_SOURCE)\Foundation\Framework
.
$(EDK_SOURCE)\Foundation\Include
$(EDK_SOURCE)\Foundation\Efi\Include
$(EDK_SOURCE)\Foundation\Framework\Include
$(EDK_SOURCE)\Foundation\Include\IndustryStandard
$(EDK_SOURCE)\Foundation\Core\Dxe
$(EDK_SOURCE)\Foundation\Library\Dxe\Include
$(EDK_SOURCE)\Sample\Include
[nmake.common]
IMAGE_ENTRY_POINT=TestDriverEntry
========================================
其中.inf的FILE_GUID可由Visual Studio中的Tools->建立GUID這個工具建立。
加入以上檔案後開啟Sample\Platform\Nt32\Build\Nt32.dsc
找一個地方加入這一行:
Sample\Platform\Generic\Test\TestDriver\TestDriver.inf
範例:
#
# Fat File System driver. Default is binary only
#
Other\Maintained\Universal\Disk\FileSystem\EnhancedFat\Dxe\Fat.inf
# By Yourself
Sample\Platform\Generic\Test\TestDriver\TestDriver.inf
存檔後即可build,此時在Console即可印出剛剛程式中的debug訊息:
EventTest = EventTestDriverEntry
Finish!
2010年8月12日 星期四
建立EDK模擬環境與範例HelloWorld編譯
一、解壓縮EDK至C:\根目錄底下。
二、開啟剛下載的EFI Shell,將Shell目錄底下的檔案全部解壓縮至C:\EDK\Other\Maintained\Application\UefiShell\中。
三、再來一樣把HelloWorld解壓縮至此。
四、開啟Sample\Platform\Nt32\Build\Nt32.dsc,找到[Libraries.Platform]這個區塊,
在下面加入Other\Maintained\Application\UefiShell\Library\EfiShellLib.inf;再來找到[Components]區塊,在DEFINE FV=FvRecovery後面添加Other\Maintained\Application\UefiShell\HelloWorld\HelloWorld.inf FV=NULL,之後存檔即可。
編譯前依照您的Visual Studio版本修改Sample\Platform\Nt32\Build\Config.env,找到USE_VC8,只要您是2005(含以上版本)就改為YES。
打開【Visual Studio 命令提示字元】,進入到Sample\Platform\Nt32目錄底下,鍵入SET EDK_SOURCE=C:\EDK,完成後再輸入build編譯即可。
編譯完成後,先執行uefi底下的System.cmd設定好環境變數,最後可到IA32底下執行secmain.exe,這個就是Shell的模擬環境,相同的地方底下也有個剛剛一同建置好的HelloWorld.efi,可在Shell中執行他,預設是放在fsnt0(f9)裝置。
======================================================================
而起始Loading部分的Logo圖檔更換方式有兩種:
一、直接到Sample\Platform\Generic\Logo底下將logo.bmp換掉,再重新編譯一次。
二、開啟Sample\Platform\Nt32\Build\Nt32.dsc到Sample\Platform\Generic\Logo\Logo.inf這一行,修改為Sample\Platform\Generic\Logo\Logo.inf SOURCE_OVERRIDE_PATH=您自己的logo.bmp目錄(不含檔名)。
2010年8月3日 星期二
建立EDK II模擬環境
我的系統為Win7 X64,Visual Studio版本為2010,欲編譯的架構為IA32
假設EDK II的存放地點在C:\EDK2\,執行Visual Studio 命令提示字元 (2010)(依您所安裝的VS版本開啟)
目錄切換到C:\EDK2\,執行edksetup.bat佈署環境。
接下來開啟Conf\target.txt,進行編譯前的設置,有以下參數:
ACTIVE_PLATFORM = Nt32Pkg/Nt32Pkg.dsc
TARGET = DEBUG
TARGET_ARCH = IA32
TOOL_CHAIN_CONF = Conf/tools_def.txt
TOOL_CHAIN_TAG = MYTOOLS
MAX_CONCURRENT_THREAD_NUMBER = 1
MULTIPLE_THREAD = Disable
BUILD_RULE_CONF = Conf/build_rule.txt
我只修改MAX_CONCURRENT_THREAD_NUMBER與MULTIPLE_THREAD,若您的CPU為多核心,則可做多執行續的程式編譯,其餘參數可依照自己所需的平台與架構做修改。
另外由於我的系統為X64,VS 2010安裝的路徑為C:\Program Files (x86)\Microsoft Visual Studio 10.0,而Conf/tools_def.txt中並沒有定義VS 2010的變數,此時如要編譯程式將會有錯誤!
接下來開啟tools_def.txt這個文字檔,內有各項工具的定義,唯獨少了VS 2010,此時仿照VS 2008的方式為VS 2010定義一組新變數(或者直接修改VS 2008的變數),接下來搜尋到第二個MYTOOLS(約3083行),底下有各架構的定義,找到IA32 definitions,將他底下括號中的變數修改成剛為VS 2010所定義的變數即可。
其餘方法,自己的編譯器已有定義在內的話:
1. build -t VS2005 (直接指定)
2. 修改tools_def.txt中的MYTOOLS對應的變數。
3. 修改target.txt中的TOOL_CHAIN_TAG對應的變數。如:TOOL_CHAIN_TAG = VS2005
修改完成後,繼續回到Visual Studio 命令提示字元 (2010),在C:\EDK2\底下鍵入build執行,接下來就等待建置完成。
完成後,輸入cd Build\NT32\DEBUG_MYTOOLS\IA32後,資料夾底下有secmain.exe,這個就是Shell的模擬環境,長得滿像DOS的,基本操作方式也很相似,fsnt0:底下有一些簡單的Apps可執行,如HelloWorld、Timer...等。