---
# System prepended metadata

title: Yocto Project (3)：在 Yocto 上建立 OpenBMC
tags: [Yocto Project]

---

##    Yocto Project (3)：在 Yocto 上建立 OpenBMC
前情提要－[安裝 Yocto Project 在 Ubuntu 24.04 上並建立 Poky 系統](https://hackmd.io/@zhonwei/ryqxAHXnWx)

現在的學習路徑是要跟著 [OpenBMC Developer Documentation](https://github.com/openbmc/docs/blob/master/development/README.md) 學習 OpenBMC 的開發

1. Development Environment Setup
Start here. This shows how to setup an OpenBMC development environment using its bitbake build process and how to start the software emulator, QEMU.

2. Hello World
This shows how to use the yocto tool, devtool, to extract an OpenBMC source repository, build, and test your changes within QEMU.

3. Web UI Development
This shows how to modify the phosphor-webui web application and test your changes within QEMU.

4. Code Reviews Using Gerrit
This shows how to setup your environment to utilize Gerrit for submitting your changes for code review.

---

##    Building OpenBMC
現在依照 [OpenBMC Development Environment](https://github.com/openbmc/openbmc/blob/master/README.md#1-prerequisite) 上面的步驟，建立一個針對 Romulus 主機板設置的 image。

更新套件
```b
$ sudo apt-get install build-essential chrpath cpio debianutils diffstat file gawk gcc git iputils-ping libacl1 locales python3 python3-git python3-jinja2 python3-pexpect python3-pip python3-subunit socat texinfo unzip wget xz-utils zstd
```

如果 Linux 系統是 Ubuntu 24.04 的話還需要先輸入這個指令
```bash
echo 'kernel.apparmor_restrict_unprivileged_userns = 0' | \
    sudo tee /etc/sysctl.d/99-bitbake.conf
sudo sysctl -p /etc/sysctl.d/99-bitbake.conf
```

Clone OpenBMC 的儲存庫（Clone OpenBMC Repository）

```bash
$ git clone https://github.com/openbmc/openbmc.git
$ cd openbmc
```

針對你的硬體進行設定（Target your hardware）

```bash
$ . setup romulus
```

開始使用 bitbake 編譯
```bash
$ bitbake obmc-phosphor-image
```

這樣就生成了一個專為 romulus 主機板設定的 image 檔。它的路徑是　`~/openbmc/build/romulus/tmp/deploy/images/romulus`，名稱叫做 `obmc-phosphor-image-romulus.static.mtd`

---

##    Download and Start QEMU Session

1. 下載最新的 openbmc/qemu fork
```bash
$ wget https://jenkins.openbmc.org/job/latest-qemu-x86/lastSuccessfulBuild/artifact/qemu/build/qemu-system-arm

$ chmod u+x qemu-system-arm
```

:::info
QEMU 在這裡的角色是硬體模擬器，讓您不需要實際的實體伺服器就能執行 OpenBMC 韌體。
:::
:::info
Fork 指的是從原始專案的分支或衍生版本
:::

2. 複製生成好的 image 到跟 qemu-system-arm 同一層（在 /build/romulus 底下）
```bash
$ cp ~/openbmc/build/romulus/tmp/deploy/images/romulus/obmc-phosphor-image-romulus.static.mtd ./
```

3. Romulus image 開始進行 QEMU
```bash
$ ./qemu-system-arm -m 256 -machine romulus-bmc -nographic \
 -drive file=./obmc-phosphor-image-romulus.static.mtd,format=raw,if=mtd \
 -net nic \
 -net user,hostfwd=:127.0.0.1:2222-:22,hostfwd=:127.0.0.1:2443-:443,hostfwd=tcp:127.0.0.1:2080-:80,hostfwd=tcp:127.0.0.1:2200-:2200,hostfwd=udp:127.0.0.1:2623-:623,hostfwd=udp:127.0.0.1:2664-:664,hostname=qemu
```

4. 等待 QEMU 模擬的 BMC 開機，登入使用預設的使用者 root 和密碼 0penBmc (0 是數字零)
```bash
romulus login: root
Password: 
```
成功之後就會看到
```bash
root@romulus:~#
```
5. 檢察系統的狀態

使用 obmcutil tool 來確認 OpenBMC 的服務狀態
```bash    
root@romulus:~# obmcutil state
CurrentBMCState     : xyz.openbmc_project.State.BMC.BMCState.NotReady
CurrentPowerState   : xyz.openbmc_project.State.Chassis.PowerState.Off
CurrentHostState    : xyz.openbmc_project.State.Host.HostState.Off
BootProgress        : xyz.openbmc_project.State.Boot.Progress.ProgressStages.Unspecified
OperatingSystemState: xyz.openbmc_project.State.OperatingSystem.Status.OSStatus.Inactive
```
:::info
BMCState 是 BMC 本身的狀態，也就是管理控制器（現在跑的這個系統）有沒有準備好對外提供服務，例如 REST API、SSH、IPMI 這些介面是否可用。Ready 才表示可以正常管理。
:::
:::info
PowerState 是 Chassis（機箱）的電源狀態，代表整台伺服器的物理電源有沒有接通。Off 就是機箱電源是關的，On 表示機箱有通電。這對應到真實伺服器上就是電源供應器有沒有供電給主機板。
:::
:::info
HostState 是 Host 的狀態，也就是跑在伺服器上的主作業系統（例如 Linux 或 Windows）有沒有在運作。即使機箱通電了，Host 也可能還在開機中或是關機狀態。
:::

CurrentBMCState 這欄是 NotReady，有可能是剛開機要給它啟動的時間，但如果等了一陣子都是 Not Ready，就需要來 debug 一下。
:::warning
systemctl list-jobs --no-pager 可以用來確認哪個 service 執行完畢、哪個 service 卡住
:::

```bash
root@romulus:~# systemctl list-jobs --no-pager
JOB    UNIT                                                TYPE  STATE
------------------------------------------------------------------------
179    phosphor-discover-system-state@0.service            start waiting
173    obmc-led-group-start@bmc_booted.service             start waiting
1      multi-user.target                                   start waiting
94     obmc-fru-fault-monitor.service                      start waiting
250821 xyz.openbmc_project.LED.GroupManager.service        start running
95     mapper-wait@-xyz-openbmc_project-led-groups.service start running
```
running 代表正在執行
:::warning
輸入 journalctl --no-pager 可以確認 LED.GroupManager.service 發生什麼事情
:::
```bash    
root@romulus:~# journalctl -u xyz.openbmc_project.LED.GroupManager.service -n 50 --no-pager
Apr 20 06:31:10 romulus systemd[1]: xyz.openbmc_project.LED.GroupManager.service: Failed with result 'timeout'.
Apr 20 06:31:10 romulus systemd[1]: Failed to start Phosphor LED Group Management Daemon.
Apr 20 06:31:11 romulus systemd[1]: xyz.openbmc_project.LED.GroupManager.service: Scheduled restart job, restart counter is at 3700.
```
LED GroupManager 重啟了 3700 次，每次都是啟動 90 秒後 timeout，代表它沒辦法成功初始化，這是因為 phosphor-ledmanager 啟動時會去讀取 LED 的硬體設定檔（JSON config），這個設定檔描述了這塊板子上有哪些 LED。但在 QEMU 模擬環境下，它找不到或讀不到這個設定檔，導致程式無法初始化，也就無法在 D-Bus 上完成 xyz.openbmc_project.LED.GroupManager 的註冊。

解決方法：參考 https://github.com/openbmc/phosphor-led-manager/issues/14
給 phosphor-ledmanager 一個空的 JSON config {}，讓它認為「這塊板子上沒有任何 LED」，程式就能正常初始化並在 D-Bus 上完成註冊，systemd 也就認為它啟動成功了

首先，建立一個空的 json 檔

```bash    
root@romulus:~# echo '{}' > /tmp/led-empty.json
```
:::info
echo '{}'：在終端機印出 {}（代表一個空的 JSON 物件）。

`>`：是重新導向符號，會將左邊輸出的內容寫入右邊的檔案。如果檔案已存在，內容會被覆蓋；如果不存在，則會建立新檔。

/tmp/led-empty.json：目標檔案的路徑。它在 /tmp 目錄（暫存資料夾）下建立一個叫做 led-empty.json 的檔案。
:::

在背景啟動 OpenBMC 的 LED 管理服務，並強制它使用剛才建立的那個空設定檔。

```bash
root@romulus:~# /usr/libexec/phosphor-led-manager/phosphor-ledmanager -c /tmp/led-empty.json &
```

:::info
/usr/libexec/phosphor-led-manager/phosphor-ledmanager：這是程式的完整路徑，執行 OpenBMC 中負責控制 LED 燈光（如告警燈、定位燈）的主程式。

-c /tmp/led-empty.json：-c 代表 config。這是在告訴程式：「不要用預設的設定，請讀取 /tmp/led-empty.json 這個檔案當作你的設定檔」。

&：它代表在背景執行（Background）。這樣指令送出後，程式會持續執行，但終端機不會被佔據，可以繼續輸入其他指令。
:::

樹狀結構列出 LED 管理服務在 D-Bus 系統匯流排上註冊的所有物件路徑．確認有沒有成功註冊到 D-Bus

```bash
root@romulus:~# busctl tree xyz.openbmc_project.LED.GroupManager
`- /xyz
  `- /xyz/openbmc_project
    `- /xyz/openbmc_project/led
      `- /xyz/openbmc_project/led/groups
```

:::info
busctl：這是 Systemd 提供的工具，用於檢查與操作 D-Bus（Linux 程序間通訊的機制）。OpenBMC 極度依賴 D-Bus 來讓不同服務互相溝通。

tree：指令參數，要求以「樹狀圖」呈現，讓你一眼看出物件的層級關係。

xyz.openbmc_project.LED.GroupManager：這是服務的名稱（Service Name）。它代表負責管理 LED 群組（例如系統警告燈、定位燈等）的那個特定程式。
:::

如果路徑出現成功出現就代表註冊成功，那我們就可以停掉剛剛放到背景執行的 phosphor-ledmanager，重新啟動系統來管理這個 service

先把手動跑的那個 process 關掉，否則會有兩個 phosphor-ledmanager 同時在跑，搶著註冊同一個 bus name xyz.openbmc_project.LED.GroupManager
```bash
root@romulus:~# kill %1
```

:::info
kill %1：強制停止（結束）你剛才放在背景執行的第一個工作。

%1：在 Linux 終端機中，當執行指令並在結尾加上 &（例如你剛剛執行的 phosphor-ledmanager ... &），系統會將它放入背景，並分配一個編號（通常第一個就是 [1]）。
:::

建立一個特定的目錄

因為 systemd 規定，若要針對 xyz.openbmc_project.LED.GroupManager.service 進行「覆蓋（override）」而不修改原始設定的話，必須在 /etc/systemd/system/ 下建立一個以 .service.d 結尾的資料夾

```bash
root@romulus:~# mkdir -p /etc/systemd/system/xyz.openbmc_project.LED.GroupManager.service.d/
```

在剛剛建立的目錄底下，建立一個名為 override.conf 的設定檔，並寫入內容給 systemd 看

```bash
root@romulus:~# cat > /etc/systemd/system/xyz.openbmc_project.LED.GroupManager.service.d/override.conf << 'EOF'
[Service]
ExecStart=
ExecStart=/usr/libexec/phosphor-led-manager/phosphor-ledmanager -c /tmp/led-empty.json
EOF
```
:::info
cat > [檔案路徑]：將接下來輸入的內容寫入指定的 override.conf。

<< 'EOF'：告訴系統：「開始讀取內容，直到我輸入 EOF 為止」。

'EOF' 加單引號：防止內容中的變數（如 $) 被當前的 Shell 提前解析。
:::
:::info
[Service]：這是 systemd 設定檔的分段標籤，告訴系統接下來的設定是關於「服務如何執行」的參數。
:::
:::info
ExecStart= 這兩行要連在一起看：

第一行 ExecStart=：在 systemd 中，如果你要更換一個 Type 不是 oneshot 的服務指令，必須先給一個空值來清除原有的指令。

第二行 ExecStart=/usr/... -c /tmp/led-empty.json：設定新的啟動指令。這裡加入了 -c /tmp/led-empty.json，強制服務去讀取那個空的 JSON 設定檔。
:::

之後就讓 systemd 重新讀取所有 unit 檔和 override，改了設定之後一定要跑這個才會生效。
```bash
root@romulus:~# systemctl daemon-reload
```

用新的設定重新啟動這個 service。
```bash
root@romulus:~# systemctl restart xyz.openbmc_project.LED.GroupManager.service
```

重新確認
```bash
root@romulus:~# systemctl list-jobs --no-pager
No jobs running.

root@romulus:~# obmcutil state
CurrentBMCState     : xyz.openbmc_project.State.BMC.BMCState.Ready
CurrentPowerState   : xyz.openbmc_project.State.Chassis.PowerState.Off
CurrentHostState    : xyz.openbmc_project.State.Host.HostState.Off
```

這樣 BMC 就成功啟動了！！！

最後再確認這台模擬的 BMC 可以透過網路被管理，從外面透過 SSH 連進 QEMU 模擬的 BMC，測試網路有沒有通

流程是：
```
Ubuntu 虛擬機 → SSH port 2222 → QEMU 的 user-mode network → BMC 內部 port 22
```
```bash
$ ssh root@localhost -p 2222
root@localhost's password:
root@romulus:~#
```

輸入密碼登入後，會進入一個新的 shell，但這次是透過網路連進去的，不是直接在 QEMU console 上操作。



---

##    Sharing Downloads and Build Cache


BitBake 的快取會同時 fetched sources (DL_DIR) and reusable build outputs (SSTATE_DIR)，我們可以設定 BitBake 的共用快取，讓多個 build 可以共享已經下載和編譯過的東西。

:::info
DL_DIR（Downloads）
存放 BitBake 從網路抓下來的原始碼，設定共用目錄後，換一個 machine（例如從 romulus 改 build 其他板子）就不用重新下載同樣的原始碼。
:::

:::info
SSTATE_DIR（Shared State）
存放已經編譯好的中間產物，例如某個 library 編譯完的結果。下次 build 時如果這個 library 沒有變動，直接從 sstate 拿，不用重新編譯。
:::

建立共用目錄
```bash
$ mkdir -p "${XDG_CACHE_HOME}/bitbake/downloads" "${XDG_CACHE_HOME}/bitbake/sstate"
```

在每個 MACHINE 的 build/MACHINE_NAME/conf/local.conf 加上這兩條設定
```bash
DL_DIR = "${XDG_CACHE_HOME}/bitbake/downloads"
SSTATE_DIR = "${XDG_CACHE_HOME}/bitbake/sstate"
```

---

##    OpenBMC 環境建置總結
整體流程
```
Build 環境 → 編譯 Image → QEMU 模擬 → 驗證網路
```

Build 階段重點
:::warning
Ubuntu 24.04 需要關閉 AppArmor 的 unprivileged user namespace 限制，否則 BitBake worker 會失敗
:::
:::warning
git clone 大型 repo 失敗時，強制改用 HTTP/1.1 可解決 HTTP/2 傳輸中斷問題
:::

OpenBMC 狀態層次，三個狀態代表不同層級，有依賴關係
:::info
BMCState    → BMC 本身是否 Ready（管理介面是否可用）
PowerState  → Chassis 電源是否接通
HostState   → Host OS 是否在運作
BMC 是獨立運作的，不依賴 Host，開機後 BMCState 會從 NotReady 變成 Ready。
:::

D-Bus 與 systemd 的關係（核心概念：OpenBMC 的所有服務之間透過 D-Bus 溝通）：
:::info
systemd 的 Type=dbus 表示服務啟動完成的條件是在 D-Bus 上成功註冊 bus name
:::
:::info
mapper-wait@<path>.service 是專門等待某個 D-Bus 路徑出現的機制，路徑沒出現就會卡住，導致後續所有依賴它的服務都 waiting
:::
:::info
busctl tree <service> 可以查看某個 D-Bus service 註冊了哪些物件路徑
:::

QEMU 模擬環境的限制
:::warning

部分服務依賴實體硬體（例如 LED、感測器），在 QEMU 裡會因為找不到硬體設定而無法啟動
解法是提供空的 config（{}）讓服務認為硬體不存在但仍能正常初始化
:::
:::warning
修改服務設定應使用 systemd override（.service.d/override.conf），不直接改原始 unit 檔，upgrade 時才不會被覆蓋
:::

網路架構

| Host port | BMC port | 用途       |
| --------- | -------- | ---------- |
| 2222      | 22       | SSH        |
| 2443      | 443      | HTTPS/REST |
| 2080      | 80       | HTTP       |
| 2623      | 623      | IPMI       |

小於 1024 的 port（< 1024）需要 root 權限才能綁定，所以在一般使用者下執行 QEMU 時要改用高於 1024 的 port。