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。

2021年9月16日 星期四

[X86] Memory Paging


參考來源:
Intel 64 and IA-32 Architectures Software Developer Manuals
AMD64 Architecture Programmer's Manual
The Intel Microprocessors by Barry B.Brey

Paging Registers


Paging行為主要由 control register所控制。


若CR0的 PG(Paging)=0,程式所使用的 linear address就是 physical address。

PG=1,則 linear address會透過 Paging的機制轉換成 physical address。


CR3的PCD(Page-level Cache Disable)與 PWT(Page-level Write-Through) bit控制Page

Directory 的 cache type。

PCD=0為cachable,PCD=1為not cachable。PWT=0 cache type為 writeback,PCD=1 為

writethrough。

Page-Directory Base指向 Page Directory 的 physical address。


linear addresspage directory entrypage table entrymemory page offset

address組成。page directory entry(10 bits)為 1024(2^10) 個 page directory的 index。

每個 page directory entry可表示 4M的記憶體(1024個page table * 4k)。


而page table entry(10 bits)為 1024(2^10)個 page table的 index。page table會指向某段4K 記憶體。

memory page offset address(12 bits) 為該段 4K記憶體的offset。


2021年9月10日 星期五

Windows Server 延長使用授權 - slmgr

參考網站
https://docs.microsoft.com/en-us/windows-server/get-started/activation-slmgr-vbs-options

Windows Server 評估版在過了使用期限後,還是可以透過 slmgr來延長。


透過 Win + R快捷鍵執行:

查看使用期限及剩餘可延長次數

slmgr.vbs /dlv

延長使用授權

slmgr.vbs /rearm

2021年5月17日 星期一

Beyond BIOS Note - CH3 UEFI Driver Model


Things should be made as simple as possible - but not simpler
- Albert Einstein

Driver initialization

Load Image

Driver image的檔案必須儲存於ROM、硬碟或網路等媒體裝置,當系統找到driver image時,
就可以透過 gBS->LoadImage() 將image加載至記憶體,而此Image必須符合PE/COFF 格式。

當gBS->LoadImage()執行後,系統就會為driver建立一個handle,這個handle被稱為
Image Handle,並且將一個 EFI_LOADED_IMAGE_PROTOCOL實體放在這handle下。
這時的driver還沒有執行(start),只存在於記憶體之中。

Image Handle
   └ ─ ─ ─ EFI_LOADED_IMAGE_PROTOCOL

Start Image

UEFI Driver Model 的driver 不能直接touch hardware,只能在本身的 Image Handle上
Install Protocol,且還必須安裝EFI_DRIVER_BINDING_PROTOCOL,有
EFI_DRIVER_BINDING_PROTOCOL 的Image Handle 則被稱為Driver Image Handle

若driver要能被unload,則必須實作EFI_LOADED_IMAGE_PROTOCOL 中的Unload() function。

Driver Image Handle
   └ ─ ─ ─ EFI_LOADED_IMAGE_PROTOCOL
   └ ─ ─ ─ EFI_DRIVER_BINDING_PROTOCOL
   └ ─ ─ ─ EFI_DRIVER_CONFIGURATION_PROTOCOL (optional)
   └ ─ ─ ─ EFI_DRIVER_DIAGNOSTICS_PROTOCOL (optional)
   └ ─ ─ ─ EFI_DRIVER_COMPONENT_NAME2_PROTOCOL (optional)

Host Bus Controllers

UEFI Driver Model driver 通常用來操作一至多個controller,而在driver與controller連接之
前,需要操作某些controller,這些controller稱為Host Bus Controller。

每個host bridge都表示成一個device handle,device handle中有 Device Path Protocol及
其IO抽象化操作的Protocol。以PCI Host Bus Controller為例,其提供PCI Host Bridge IO
Protocol。

PCI Host Bridge Device Handle
   └ ─ ─ ─ EFI_DEVICE_PATH_PROTOCOL
   └ ─ ─ ─ EFI_PCI_HOST_BRIDGE_IO_PROTOCOL

PCI Bus Driver可以連接在此PCI Host Bridge,並建立其child handle給每個系統中的PCI
device。而PCI Device Driver 則必須連接這些child handle並提供其抽象化IO操作給系統使用。

Device Drivers

Device Driver不允許建立新的device handle,只在現有的device handle上添加protocol。
其最常見的行為是在Bus driver所建立的handle上提供IO抽象化操作,例如Simple Text
Output、Simple Input、Block I/O及Simple Network Protocol。

Device Handle
   └ ─ ─ ─ EFI_DEVICE_PATH_PROTOCOL
   └ ─ ─ ─ EFI_XYZ_IO_PROTOCOL

    ↓ Start()   ↑ Stop()

Device Handle
   └ ─ ─ ─ EFI_DEVICE_PATH_PROTOCOL
   └ ─ ─ ─ EFI_XYZ_IO_PROTOCOL
   └ ─ ─ ─ EFI_BLOCK_IO_PROTOCOL

連接Device handle的Device Driver必須有Driver Binding Protocol在其本身的image handle上。

Driver Binding Protocol包含Supported ()、Start ()及Stop ()三個functions。
  • Supported ()
  • 功用為測試此driver是否支援特定controller。以上面的Device handle為例,driver
    可以檢查此device handle是否支援 Device Path Protocol及EFI_XYZ_IO_PROTOCOL。
    若 Supported()通過,driver就能透過 Start()來連接controller。

  • Start ()
  • driver透過Start() 在device handle上添加額外的IO protocol。以上面例子來看
    ,Block IO protocol就被建立在device handle上。

  • Stop ()
  • 相對於Start(),Stop() 用來終止driver對device handle的操作,並要負責將原來
    driver安裝在device handle 上的任何protocol移除。

Support ()、Start ()及Stop ()需呼叫OpenProtocol ()來取得protocol和CloseProtocol ()
來釋出Protocol。OpenProtocol ()及CloseProtocol ()會更新handle database,讓系統能追蹤
哪些protocol正在被使用。可透過OpenProtocolInformation ()來獲取component正在使用
protocol的相關列表。

Bus Drivers

Bus driver 負責對其 bus上的 child controller建立 device handle。
A、B、C、D和E代表此Bus controller 的child controller。其上的箭頭代表其parent controller
,如果此Bus controller 為 Host Bus Controller的話,則沒有parent controller。

    ↙
 Bus Controller

    ↓ Start()   ↑ Stop()

    ↙
 Bus Controller
    └ ─ A
    └ ─ B
    └ ─ C
    └ ─ D
    └ ─ E

Bus driver至少必須在其child handle上安裝IO抽象化操作的protocol(EFI_XYZ_IO_PROTOCOL),
若child handle代表physical device,則還需安裝DEVICE_PATH_PROTOCOL。Bus Specific
Driver Protocol為optional,在driver connect child controller時會使用到(Boot Service 的
ConnectController ())。

Child Device Handle
   └ ─ ─ ─ EFI_DEVICE_PATH_PROTOCOL
   └ ─ ─ ─ EFI_XYZ_IO_PROTOCOL
   └ ─ ─ ─ EFI_BUS_SPECIAL_DRIVER_OVERRIDE_PROTOCOL (optional)

Platform Components

driver 的connect與disconnect controller由platform firmware透過Boot Service
ConnectController()和DisconnectController()來決定,通常為UEFI Boot Manager
的一部分。

若platform想要執行系統檢測或安裝作業系統,則其會connect driver到所有可能的 boot
device。若platform想開機到預安裝好的作業系統,則其只需connect 該作業系統需要的
device及其所需的driver。

platform 也可以選擇安裝optional的 protocol Platform Driver Override Protocol,其作用與
Bus Specific Driver Protocol相同,但擁有更高的priority。

Hot Plug Event

當 Hot Plug Event因新增 device觸發時,Bus driver需要負責:
  1. 建立 device 的child handle
  2. 呼叫 ConnectController()