2022年7月18日 星期一

PCI Enumeration - PCI Root Bridge IO Driver

PCI Enumeration是我做 BIOS這幾年還未跨過去的一道坎,也只是片段零碎地在 debug的過
程中摸索,看不清楚全貌。最近看了 "EDK II EFI Driver Writers Guide"的 "PCI Driver
Design Guidelines
"覺得獲益良多,許多觀念在讀這章的過程中重新整理了一次,所以藉由這
機會來重頭 study EDK II中關於整個 PCI Enumeration的程式碼,打鐵趁熱。

Pci Enumeration 在 EDK II主要由 PCI Root Bridge Io DriverPCI Bus Driver以及 PCI Driver
這三種 Driver來完成。這三者的主要工作與概念在"PCI Driver Design Guidelines"寫得很清
楚,就不著墨在這。而 PCI Driver又太多種,所以本篇目前只專注在 PCI Root Bridge Io Driver
及 PCI Bus Driver的程式碼上。

PCI Root Bridge Io Driver

從Entry Point InitializePciHostBridge開始看吧:

  RootBridges = PciHostBridgeGetRootBridges (&RootBridgeCount);
  if ((RootBridges == NULL) || (RootBridgeCount == 0)) {
    return EFI_UNSUPPORTED;
  }

好,我承認第一段就有點卡關,Host BridgeRoot Bridge分別到底是什麼? 先看一下 PCI_HOST_BRIDGE_INSTANCE的結構:

typedef struct {
  UINTN                                               Signature;
  EFI_HANDLE                                          Handle;
  LIST_ENTRY                                          RootBridges;
  BOOLEAN                                             CanRestarted;
  EFI_PCI_HOST_BRIDGE_RESOURCE_ALLOCATION_PROTOCOL    ResAlloc;
} PCI_HOST_BRIDGE_INSTANCE;

PCI_HOST_BRIDGE_INSTANCE的結構裡含有 RootBridges的 Linked List,代表一個 Host Bridge 可能有一個至多個 Root Bridge。又從後面的註解可以看到,EDK在 PCI Enumeration的情況只考慮系統只會有一個 Host Bridge。 可以理解成 Host Bridge是比 Root Bridge還上層的東西。

  //
  // Most systems in the world including complex servers have only one Host Bridge.
  //
  HostBridge = AllocateZeroPool (sizeof (PCI_HOST_BRIDGE_INSTANCE));
  ASSERT (HostBridge != NULL);

  HostBridge->Signature    = PCI_HOST_BRIDGE_SIGNATURE;
  HostBridge->CanRestarted = TRUE;
  InitializeListHead (&HostBridge->RootBridges);
  ResourceAssigned = FALSE;

翻開 PCI的 spec,可以看到 PCI Bus就是透過 Host Bridge來與系統上的 CPU及 Memory連接的。而到了 PCI Express, Host Bridge的功能就被包含在 Root Complex中。


Root Bridge的詳細描述可以在UEFI spec中找到,其是用以產生實體 PCI Bus的 chipset component。一個 Host Bridge可以有一個或多個 Root Bridge。下列兩個 UEFI spec中提到的比較常見的系統範例。

只有一個 Root Bridge:

多個 Root Bridge:

UEFI spce也有多個 Host Bridge的系統範例,這裡就不貼出來了。在我先前的經驗只有碰過一 個 Host Bridge和一個 Root Bridge的情況,所以這兩個東西才會讓我如此困惑,有時候也會混 在一起說。

另外其中還提到,PCI Segment與 Host Bridge和 Root Bridge是不同的概念。PCI Segment指的 是共用相同 PCI Configuration Space的 PCI Bus之集合,最多能有 256個 Bus。Root Bridge 可能包含整個或是部分的 PCI Segment。Host Bridge若是有多個 Root Bridge,則很有可能有 多個 PCI Segment。

再回到第一段 code,其用意就是要知道系統中有幾個 Root Bridge,其中的方法是去掃 Bus 0 - 255,如果 Bus上有 Device,就表示 Root Bridge存在,而在掃的過程中要去扣除掉 PCI to PCI Bridge所產生出來的 Bus。

當然,如果本來就是知道系統中有多少 Root Bridge,我也是看過直接 hard code的。

接下來 InitializePciHostBridge還會將每個 RB所用到的 MMIO及 IO透過 DXE service回報
給 GCD (Global Coherency Domain) ,讓系統來管理所能使用的 resource。

並在 Root Bridge Handle上安裝 PCI Root Bridge IO Protocol來讓之後的 PCI Bus Driver使用。

2022年7月13日 星期三

Git - 改變目錄名稱的大/小寫

話說有天心血來潮,想要把自己某個 side project的資料夾名稱從大寫的 Config改成小寫的 config。 但手動改成小寫後,git status指令的狀態不會有任何變化,於是查了一下,發現可以使用 git mv 指令來達到我要的效果。


$ git mv ./Config ./config1
$ git mv ./config1 ./config

透過一個過渡的目錄名稱 ./config1來達到我要的效果。還有另一個好處是 git mv之後的檔案都是 git add/rm之後的狀態,接下來直接 commit就行了。

由於 git mv本身就是移動或重新命名檔案或目錄的命令,直接使用:

$ git mv ./Config ./config
Rename from 'config' to 'Config/config' failed. Should I try again? (y/n)

git 以為是要把當前的 ./Config移動到 ./config下,會出現 fail。

而改變檔案名稱的大/小寫是可以直接使用 git mv的:

$ git mv a.txt A.txt

補充:
git的大小寫名稱行為似乎是跟 OS的檔案系統本身是不是 case sensitive有關。
透過 git config能改變 git case sensitive的行為。

2022年7月12日 星期二

Beyond BIOS Note - CH4 Protocols You Should Know


Common sense ain't common.
- Will Rogers

EFI OS Loaders

OS loader是一種特殊的 UEFI Application,用來讓系統從 firmware
環境轉換到 OS環境。

  1. 要確定是從哪被 load,這能讓其從相同位置取得其他檔案。
  2. 要確定 OS被存放在哪。OS可能會在硬碟中的某個 partition,所以 OS Loader
    需要實作或讀取檔案系統的驅動來存取OS partition的檔案。
  3. 需要建立實體記憶體的 memory map回報給 OS Kernel,有些 memory resource是
    OS不能碰的,所以 OS Loader也需要從UEFI API來獲得系統當前的 memory map。
  4. OS 能將 boot path及 boot options以環境變數的形式存在 nonvolatile storage
    中。OS Loader也會使用這些環境變數,並且也需要將某些傳遞給 OS Kernel。
  5. 呼叫 ExitBootService ()後,UEFI Boot Service已經無法再使用,控制權轉交
    OS Kernel,OS Kernel只能使用 UEFI Runtime Service。

2022年6月21日 星期二

Macro 進階用法 - X Macro


C/C++ 語言常使用 Macro 來預先定義一些常常用到的值或程式碼片段,讓整體的
程式看起來更簡潔。

X Macro是種 Macro更進階的使用「方法」,而不是另一種「功能」。所以只要有
支援定義 Macro功能的程式語言,也可以使用。

其透過 "#define"與 "#undef"來替換 "X"的定義可以達到各種神奇的效果。

以下是一種最簡單範例:

#include <stdio.h>

#define LIST_OF_NAMES \
  X(Alice) \
  X(Bob) \
  X(Charlie) \
  X(Dan) \
  X(Emily)

int main() {
#define X(name) printf ("Hello %s\n", #name);
  LIST_OF_NAMES
#undef X
  return 0;
}


Output:

Hello Alice
Hello Bob
Hello Charlie
Hello Dan
Hello Emily

我在 LIST_OF_NAMES宣告由 "X()"組成的函數,這時的 "X"還沒有被定義。
在 "main()"中,才把 "X()"定義成使用 printf函數印出 name參數並呼叫
LIST_OF_NAMES。

如果將 LIST_OF_NAMES的 X Macro展開來寫,就會是這個樣子:

#include <stdio.h>

#define LIST_OF_NAMES \
  printf ("Hello %s\n", Alice); \
  printf ("Hello %s\n", Bob); \
  printf ("Hello %s\n", Charlie); \
  printf ("Hello %s\n", Dan); \
  printf ("Hello %s\n", Emily);

int main() {
  LIST_OF_NAMES
  return 0;
}

而 X Macro最妙的用法還在於可以替換"X"的定義,使其重複利用:

#include <stdio.h>

#define LIST_OF_NAMES \
  X(Alice) \
  X(Bob) \
  X(Charlie) \
  X(Dan) \
  X(Emily)

int main() {
#define X(name) printf ("Hello %s\n", #name);
  LIST_OF_NAMES
#undef X

#define X(name) printf ("Hi, I am %s\n", #name);
  LIST_OF_NAMES
#undef X
  return 0;
}

Output:

Hello Alice
Hello Bob
Hello Charlie
Hello Dan
Hello Emily
Hi, I am Alice
Hi, I am Bob
Hi, I am Charlie
Hi, I am Dan
Hi, I am Emily

2022年4月17日 星期日

Chocolatey - Windows 軟體套件管理工具


官方網址
https://chocolatey.org

Chocolatey 可以讓 Windows以 command line的形式來安裝及管理軟體套件,類似
Linux使用的 APT。

使用Powershell 安裝


1. 以系統管理員權限執行 Powershell。

2. 在 PowerShell執行以下命令
Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient). DownloadString('https://community.chocolatey.org/install.ps1'))

3. 重新開機。

4. 在 Powershell執行 choco,確認是否安裝成功。
choco

Chocolatey v1.1.0

Chocolately 操作


安裝套件,以 jq為例:
choco install jq

2022年4月15日 星期五

Legacy PCI IRQ Routing (PIC)

PCI Configuration Space Header

PCI Local Bus spec中定義了兩個 register,Interrupt Line (0x3C)及 Interrupt Pin (0x3D)

Interrupt Line (0x3C)

BIOS會負責填入此 PCI device的 interrupt pin在系統上所使用的 IRQ number。
這個 register不會被 device本身使用,而是給 driver或 OS看的。

Interrupt Pin (0x3D)

PCI device透過 Interrupt Pin來發出中斷的信號,分別為INTA#、INTB#、INTC#及 INTD#。
可從此 register的值來判斷這 device或 device function所使用的 Interrupt Pin是哪一個,
在出廠時就固定了。(1=INTA# 2=INTB# 3=INTC# 4=INTD#)

PCI IRQ Router

Intel 南橋提供8個 PIRQn#來讓 BIOS決定PCI device的 interrupt pin要 route
到哪個 IRQ。

透過 LPC register 0x60-0x63 (PIRQA#-PIRQD#)及 0x68-0x6B (PIRQ#E-PIRQ#H)
來設定所使用的 IRQ number。

PCI IRQ Routing Specification

BIOS要負責提供 PCI routing的資訊,例如PCI Slot的 interrupt pin是接到Pci IRQ Router 的哪
個pin,藉此讓 OS能夠判斷 IRQ是由哪個 PCI device所觸發的。

Legacy BIOS時代便是由 Microsoft所規範的 PCI IRQ Routing Specification中的 PCI IRQ
Table來實現。

PCI IRQ table會以 16-byte boundary形式存放在系統記憶體 F0000h - FFFFFh(F segment)中,
其開頭為 $PIR。

Byte OffsetSize in BytesName
04Signature
42Version
62Table Size
81PCI Interrupt Router's Bus
91PCI Interrupt Router's DevFunc
102PCI Exclusive IRQs
124Compatible PCI Interrupt Router
164Miniport Data
2011Reserved (Zero)
311Checksum
3216First Slot Entry
4816Second Slot Entry
N+1*1616Nth Slot Entry

Slot Entry
Byte OffsetSize in BytesName
0BytePCI Bus Number
1BytePCI Device Number (in upper five bits)
2ByteLink Value for INTA#
3WordIRQ Bitmap for INTA#
5ByteLink Value for INTB#
6WordIRQ Bitmap for INTB#
8ByteLink Value for INTC#
9WordIRQ Bitmap for INTC#
11ByteLink Value for INTD#
12WordIRQ Bitmap for INTD#
14ByteSlot Number
15ByteReserved

Link Value for INTn#

代表此 INTn#所連接到的Interrupt Router's Pin (PIRQm#),若為 0則代表沒有連到
任何 PIRQm#。

IRQ Bitmap for INTn#

代表此 INTn#能使用的 IRQ。Bit0代表 IRQ0,Bit1代表 IRQ1以此類推。

2022年4月6日 星期三

Hello CMake


Source

CMake
https://cmake.org/

Visual Studio Community 2019
https://visualstudio.microsoft.com

以建立一個名為HelloCMake的專案為例,在 HelloCMake的目錄新增 CMakeLists.txt
檔案及放入專案程式碼 HelloCMake.cpp,如下:

HelloCmake
├ ─ ─ ─ CMakeLists.txt
└ ─ ─ ─ HelloCMake.cpp


CMakeLists.txt
cmake_minimum_required(VERSION 3.23)

# set the project name
project(HelloCMake)

# add the executable
add_executable(HelloCMake HelloCMake.cpp)

cmake_minimum_required

指定 CMake最低版本需求。

project

設定專案名稱。

add_executable

從指定的 source來產生執行檔。

Build CMake


在 HelloCMake的目錄下建立名為 build的目錄。
mkdir build


切換到 build,執行 cmake命令並指定 CMakeLists.txt所在的目錄。
cd build
cmake ../

在產生完 CMake檔案的 build下開始編譯
cmake --build .

2021年11月23日 星期二

[X86] Real Mode to Protected Mode


參考來源:
Intel 64 and IA-32 Architectures Software Developer Manuals
AMD64 Architecture Programmer's Manual

System Reset後,BIOS在 SEC階段一開始就會從 Real Mode切換到 Protected Mode。
切換 Protected Mode前,BIOS最少要設定 GDTR及 Control Registers。

GDTR
透過 lgdt命令將 ROM中寫好的 table載入 GDT中,

Control Register
Enable CR0的 PE flag


2021年11月8日 星期一

[WHLK] Directed FX System Verification Test

參考網站
introduction-to-the-directed-power-management-framework
pwrtest-directedfx-scenario

此測項可用 pwrtest.exe來替代做簡單的測試: pwrtest.exe /directedfx /device:path path可從 device manager中的 device instance path查詢

2021年10月5日 星期二

[X86] Reset Vector


參考來源:
Intel 64 and IA-32 Architectures Software Developer Manuals
AMD64 Architecture Programmer's Manual

當前 x86架構下,CPU第一個指令執行位置是從 0xFFFF_FFF0開始。但 CPU啟動時是 Real

Mode,照理說只能定址到 1MB位置。所以其是透過 segment register(CS)中 invisible register

的機制來達成。

一般情況下 CS的 Base值會是 Selector左移 4 bits的值,但 CPU初始時會將 CS的 Selector及

Base設成 0xF000和 0xFFFF_0000。由於 EIP初始的值為 0xFFF0,如此將初始位置指向

0xFFFF_FFF0 (0xFFFF_0000 + 0xFFF0)。


0xFFFF_FFF0 是 ROM映射的位置,所以 BIOS會將 Reset Vector程式碼放在與之相應的位置。

以16 MB(0x100_0000)的BIOS來說,其會映射在0xFF00_0000 - 0xFFFF_FFFF


用 RWEverything實際看機器上 0xFFFF_FFF0位置的值

其 machine code為0x90 0x90 0xE9,後面則是 16 bits jmp address(0xC39B)。

可與EDK2中 Reset Vector程式碼對照 UefiCpuPkg\SecCore\Ia32\ResetVec.nasmb

;
; For IA32, the reset vector must be at 0xFFFFFFF0, i.e., 4G-16 byte
; Execution starts here upon power-on/platform-reset.
;
ResetHandler:
  nop
  nop
ApStartup:
  ;
  ; Jmp Rel16 instruction
  ; Use machine code directly in case of the assembler optimization
  ; SEC entry point relative address will be fixed up by some build tool.
  ;
  ; Typically, SEC entry point is the function _ModuleEntryPoint() defined in
  ; SecEntry.asm
  ;
  DB 0e9h
  DW -3

在兩個 nop後,接 near jump 0xE9,來確保 CS selector的值不會被改變。後面預留 DW

會由 build tool來填入 SEC進入點的位址。

上圖RW看到的位址是 0xC39B,此值與當前的 IP值 0xFFF5 (0xFFFF_FFF5)相加,就能找到

SEC的進入點在 0xFFFF_C390的位置。

開始的指令為0xDB 0xE3,是 FNINIT的 machine code。