← Back to list

Cadence Raw NAND Controller:在 Linux、U-Boot 與 OP-TEE 的一致化實作與效能診斷

本文記錄我將 Cadence Raw NAND Controller (HPNFC) 於 Linux、U-Boot、OP-TEE 三個軟體域進行一致化的驅動實作與修補。內容涵蓋:中斷(IRQ)與等待語意的標準化、CDMA/SDMA 命令路徑修正、R/B# 與時序處理、三層…

Allen Kuo (kwyshell) · 2025-10-03 23:27 · 1 claps · 16.9 min read
#nand #rawnand #optee #u-boot #linux
Open on Medium ↗
Wiki topics: 🔓 · Open Source

Cadence Raw NAND Controller:在 Linux、U-Boot 與 OP-TEE 的一致化實作與效能診斷

本文記錄我將 Cadence Raw NAND Controller (HPNFC)Linux、U-Boot、OP-TEE 三個軟體域進行一致化的驅動實作與修補。內容涵蓋:中斷(IRQ)與等待語意的標準化、CDMA/SDMA 命令路徑修正、R/B# 與時序處理、三層 ECC(HWECC / On-Die / SW)並存、U-Boot 的 raw 資料擷取能力(含 OOB)與 BBM(壞塊標記)策略。文末也討論 多處理器(SMP)下的 MMIO 次序化 — — 為何必須嚴謹區分 readl/writel*_relaxed,以及我加上的 barrier I/O 包裝

1. 動機

現成的開源 HPNFC 驅動(多起自 Cadence 舊版)在實務中存在幾個主要問題:

  • 命令與等待語意分歧:CDMA/SDMA/generic command 在不同路徑的等待條件不一致,R/B#(Ready/Busy)與超時處理缺乏系統化,造成效能抖動與偶發 timeout。
  • OOB/BBM 可觀測性不足:缺乏「真正 raw」的擷取手段,工程師常被 HWECC/控制器的自動處理遮蔽觀測視角,很難精準定位錯誤來源。
  • 跨域不一致:Linux 能動,但 U-Boot 或 OP-TEE 因時序或等待語意不同步而行為不一,導致 bring-up 與除錯成本高。

因此,本次工作的目標是:讓 Linux、U-Boot、OP-TEE 對 HPNFC 的操作語意一致、等待機制一致、可觀測性一致,並在此基礎上進行效能與可靠度的診斷與優化。

2. NAND Controller 與 NAND Chip Pins 背景知識簡易解說

2.1 NAND Controller 的角色

NAND Flash 不能像 DRAM 一樣直接被 CPU 隨機讀寫,因為 NAND 有特殊的 命令/地址/資料 (CMD/ADDR/DATA) 交握流程,以及 Ready/Busy 等待。 因此,系統必須依靠 NAND Controller 來完成下列任務:

  • 命令序列管理:例如 READ PAGE、PROGRAM PAGE、BLOCK ERASE。
  • 地址解析:將線性地址(offset)轉換成 Page/Block/Column。
  • 資料搬移:透過 DMA/CDMA/SDMA 方式搬運資料到記憶體。
  • ECC 檢查:控制器內建 HWECC 模組,幫助自動產生或驗證 ECC parity。
  • R/B# 等待:透過硬體或軟體方式監看 NAND 的 Ready/Busy 腳位。
  • OOB/Skip-bytes:控制是否跳過或保護壞塊標記區(BBM)。

在 Linux、U-Boot、OP-TEE 的驅動程式中,我們主要就是透過 NAND Controller 寄存器來設定這些功能。

2.2 NAND Chip 的 Pins

典型的 NAND Flash 顆粒會有下列主要引腳(pins):

Pin 名稱功能I/O[7:0] 或 I/O[15:0] — 資料匯流排。支援 8-bit 或 16-bit NAND。傳送命令、地址、資料。

CLE (Command Latch Enable) — 當 CLE=1,寫入 I/O 線會被視為「命令」。 ALE (Address Latch Enable) — 當 ALE=1,寫入 I/O 線會被視為「地址」。 CE# (Chip Enable) — 選擇 NAND 晶片,低電位有效。多顆 NAND 時可用來切換。 WE# (Write Enable) — 負脈衝觸發,將 I/O 匯流排的值寫入(命令、地址或資料)。 RE# (Read Enable) — 負脈衝觸發,從 NAND 輸出一個 byte/word 到 I/O 匯流排。 R/B# (Ready/Busy) — 狀態指示腳。低表示 NAND 忙碌,無法接受新命令;高表示 Ready。 WP# (Write Protect) — 寫保護,低電位時禁止程式或擦除操作。 VCC / VSS — 電源/接地。

2.3 主機與 NAND 的交握範例

READ PAGE 為例,主機(NAND Controller)會進行下列步驟:

  1. 命令 0x00 → CLE=1,WE# 拉低,送命令。
  2. 地址循序 → ALE=1,分別送 Column、Row 地址。
  3. 命令 0x30 → CLE=1,送執行命令。
  4. 等待 R/B# → R/B# 拉低(Busy),直到拉高(Ready)。
  5. 讀取資料 → RE# 觸發,NAND 將資料送到 I/O bus。

這就是文章裡提到的「R/B# 等待」場景。

2.4 與驅動程式的對應

  • Controller 寄存器裡,CLE/ALE/WE#/RE# 等動作被包裝成「Generic Command」或「Descriptor」模式。
  • R/B# pin 通常會被 Controller 接管,並透過狀態暫存器或中斷(IRQ)回報給驅動程式。
  • skip-bytes/BBM 則是 Controller 在「讀/寫 pipeline」裡動手腳,保護或覆寫某些 OOB 區域。

3. 整體改善概述

3.1 Linux:中斷聚合與等待語意標準化

  • ISR(中斷服務例程) 改為「先清除硬體狀態,再聚合、最後完成通知」,避免殘留狀態導致上層誤判。
  • 等待端使用 completion + timeout 的單一路徑,所有命令(CDMA/SDMA/Generic)統一走相同等待語意,失敗時回拋一致化的狀態快照。
  • R/B#(RBN_SETINGS) 與各 timing 的讀寫在同一個層級檢查,避免命令序列與 Ready 判斷脫鉤。

示意(節錄):ISR 聚合與完成通知

static irqreturn_t cadence_nand_isr(int irq, void *dev_id)
{
 struct cadence_nand_ctrl *c = dev_id;
 struct cadence_nand_irq_status st = {0};

 /* 讀取 + 清除硬體狀態 */
 cadence_nand_get_and_clear_irq(c, &st);

 /* 聚合到軟體狀態(避免多中斷分次遺失) */
 c->irq_status.status     |= st.status;
 c->irq_status.trd_status |= st.trd_status;
 c->irq_status.trd_error  |= st.trd_error;

 /* 發出完成通知,喚醒等待端 */
 complete(&c->complete);
 return IRQ_HANDLED;
};

示意(節錄):通用等待(CDMA/SDMA/Generic 共用語意)

static int cadence_nand_wait_for_irq(struct cadence_nand_ctrl *c,
         const struct cadence_nand_irq_status *mask,
         struct cadence_nand_irq_status *out)
{
 unsigned long tmo = msecs_to_jiffies(TIMEOUT_MS);

 reinit_completion(&c->complete);
 cadence_nand_set_irq_mask(c, mask);

 if (!wait_for_completion_timeout(&c->complete, tmo))
  return -ETIMEDOUT;

 /* 讀聚合後的軟體狀態快照 */
 *out = c->irq_status;
 return 0;
}

3.2 U-Boot:改良等待模型 + 提供 真正 raw 的 dump

  • 在 U-Boot 缺少完整 IRQ 框架的前提下,我以 polling + get_timer/udelay 模擬與 Linux 等價的等待語意(含 timeout),確保跨域一致。
  • 新增 nand dump.raw 指令,用於直接讀取 page + OOB 的原始位元組
  • 不經 HWECC、控制器 skip-bytes 或驅動改寫
  • 可直接檢視 BBM 與各家 NAND 的 OOB 佈局(metadata/ECC 區塊分佈)。

示意(節錄):U-Boot 模擬等待

static int cadence_nand_wait_for_irq_poll(struct cadence_nand_ctrl *c, u32 ms)
{
 ulong start = get_timer(0);
 for (;;) {
  if (cadence_nand_isr(c->irq, c) == IRQ_HANDLED)
   return 0;
  if (get_timer(start) > ms)
   return -ETIMEDOUT;
  udelay(1);
 }
}

示意(節錄)nand dump.raw <off> 讀 page+OOB(真 raw)

off &= ~(mtd->writesize - 1);
page   = (int)(off >> chip->page_shift);
chipnr = (int)(off >> chip->chip_shift);

chip->select_chip(mtd, chipnr);
ret = nand_read_page_op(chip, page, 0, buf, mtd->writesize + mtd->oobsize);
chip->select_chip(mtd, -1);

hexdump_page_and_oob(buf, mtd->writesize, mtd->oobsize);

3.3 OP-TEE:與 Linux 對齊的命令與等待語意

  • 在 secure world 維持與 Linux 相同的命令輸出與等待時序,避免 normal/secure 的行為差異造成診斷與維護困難。

4. Raw NAND 的命令路徑與等待條件

  • Generic Command:設定 mini-controller 命令欄位 → 送出 → 等待 IRQ/Ready。
  • CDMA/SDMA:描述子準備 → 啟動 → 以 IRQ/timeout 判斷完成與錯誤。
  • R/B#(READY/BUSY):對需要 Ready 的命令,必須在對應路徑上加入 R/B# 判斷或 tWB/tADL 的等待,否則將出現頁面讀寫間歇性錯誤或效能下滑。

實務上,「四連讀 94us」與「單次 CDMA 慢許多」常源自命令路徑不同等待條件不同 控制器延伸讀寫模式(extended mode) 設定差異。統一等待語意後,兩條路徑的瓶頸可明確辨識(是 tR 的裝置時間、還是傳輸/軟體路徑造成的間隙)。

5. 三層 ECC:HWECC、On-Die、SW 的並存設計

  • HWECC(控制器):速度佳,需正確管理 sector 切分與 syndrome。
  • On-Die ECC(顆粒):以廠商定義命令(如 ECC STATUS READ)讀回狀態。
  • SW ECC(軟體檢核):在懷疑前兩者報告不一致時,以 raw dump 的位元組為準進行校驗,釐清錯誤來源(顆粒 vs. 控制器 vs. 驅動)。

此三層並存,提供了量測與歸因的「基準座標系」。沒有 raw 視角,工程判斷容易偏誤。

6. U-Boot 的 raw dump 與 OOB/BBM 可觀測性

我新增的 **dump.raw** 可直接觀察:

  • OOB 真實內容:未被硬體或驅動改寫的 metadata/ECC 區塊;
  • BBM(Bad Block Marker):通常位於 OOB 的前兩個位元組;多數顆粒以 0xFF 0xFF 為好塊,任一非 0xFF 代表壞塊。
  • 控制器 skip-bytes 機制:可用來「保留」BBM(以指定 marker 覆寫),但在分析時必須先釐清你看到的是「原始值」或「被覆寫後的值」。

注意只有 raw dump 才能保證觀測值可被(資料取證/量測)信任。一般 API 若經過 HWECC/skip-bytes,輸出值未必等同晶片實際內容。

diff --git a/cmd/nand.c b/cmd/nand.c

--- a/cmd/nand.c
+++ b/cmd/nand.c
@@ -32,6 +32,7 @@
 #include <asm/byteorder.h>
 #include <jffs2/jffs2.h>
 #include <nand.h>
+#include <linux/mtd/rawnand.h>

 #include "legacy-mtd-utils.h"

@@ -43,6 +44,73 @@ int find_dev_and_part(const char *id, struct mtd_device **dev,
         u8 *part_num, struct part_info **part);
 #endif

+static int nand_dump_raw(struct mtd_info *mtd, ulong off,
+       int repeat)
+{
+ int page, realpage, chipnr;
+ struct nand_chip *chip = mtd_to_nand(mtd);
+ int i;
+ u_char *datbuf, *p;
+ static loff_t last;
+ int ret = 0;
+
+ if (repeat)
+  off = last + mtd->writesize;
+
+ last = off;
+
+ datbuf = memalign(ARCH_DMA_MINALIGN, mtd->writesize + mtd->oobsize);
+ if (!datbuf) {
+  puts("No memory for raw page buffer\n");
+  return 1;
+ }
+
+ // align writesize
+ off &= ~(mtd->writesize - 1);
+
+ // off to page number
+ realpage = (int)(off >> chip->page_shift);
+ page = realpage & chip->pagemask;
+
+ // read page
+ chipnr = (int)(off >> chip->chip_shift);
+ chip->select_chip(mtd, chipnr);
+ ret = nand_read_page_op(chip, page, 0, datbuf, mtd->writesize + mtd->oobsize);
+ chip->select_chip(mtd, -1);
+ if (ret) {
+  printf("Error (%d) reading raw page %08lx\n", i, off);
+  ret = 1;
+  goto free_dat;
+ }
+
+ printf("RAW page addr:%08lx / page:%08x dump:\n", off, page);
+ i = mtd->writesize >> 4;
+ p = datbuf;
+ while (i--) {
+  printf("\t%02x %02x %02x %02x %02x %02x %02x %02x"
+       "  %02x %02x %02x %02x %02x %02x %02x %02x\n",
+       p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7],
+       p[8], p[9], p[10], p[11], p[12], p[13], p[14],
+       p[15]);
+  p += 16;
+ }
+
+ printf("\n");
+ printf("RAW OOB addr:%08lx / page:%08x:\n", off, page);
+ i = mtd->oobsize >> 3;
+ p = datbuf + mtd->writesize;
+ while (i--) {
+  printf("\t%02x %02x %02x %02x %02x %02x %02x %02x\n",
+         p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7]);
+  p += 8;
+ }
+
+free_dat:
+ free(datbuf);
+
+ return ret;
+}
+

7. BBM 與 skip-bytes 的實作要點

控制器提供兩個寄存器來保護壞塊標記(避免被上層寫入覆蓋):

  • SKIP_BYTES_CONF(含 marker 值skip bytes 數;需對齊到 8/16/32-bit,視 SDR/DDR 與 extended mode 而定)。
  • SKIP_BYTES_OFFSET(從「本次傳輸資料包的起點」算起的偏移;以 column address 為基準)。

當 skip 有效時,控制器會在該區段以 marker 取代原始資料,使 BBM 能被「保留為好」。這對量產系統避免誤判壞塊有實際價值;但在偵錯還原場景,需要以 dump.raw 取得「未覆寫」的位元組佐證。

8. 關鍵:SMP/MP 系統中的 MMIO 次序化(barrier I/O)

在多核心(SMP)或外設深度排程的系統上,MMIO 存取的次序化是穩定性的根本。 我加入以下包裝巨集,強制在需要時使用具備屏障語意的 I/O,避免 *_relaxed 在關鍵路徑造成 out-of-order 或 write-posting 導致的難解問題。

#if HPNFC_ENABLE_BARRIER_IO
#define cdns_readl                     readl
#define cdns_writel                    writel
#define cdns_readl_poll_timeout        readl_poll_timeout
#else
#define cdns_readl                     readl_relaxed
#define cdns_writel                    writel_relaxed
#define cdns_readl_poll_timeout        readl_relaxed_poll_timeout
#endif

#define cdns_readl_barrier             readl
#define cdns_writel_barrier            writel
#define cdns_readl_barrier_poll_timeout readl_poll_timeout

#define cdns_readl_relaxed             readl_relaxed
#define cdns_writel_relaxed            writel_relaxed
#define cdns_readl_relaxed_poll_timeout readl_relaxed_poll_timeout

為何重要

  • readl/writel(非 relaxed)在多數架構上具備 I/O 屏障,可確保對同一外設寄存器的讀寫順序不被 CPU 或匯流排重新排序。
  • *_relaxed 允許更多硬體/微架構最佳化(如 write-combine / posting),在時序敏感或「寫後立即讀狀態」的控制序列上容易出錯
  • 透過條件編譯 HPNFC_ENABLE_BARRIER_IO,可在量測/驗證期間強制嚴格次序化,問題定位完成後再視情況切回 relaxed 以爭取極限效能。
  • 特別是在 SMP(多處理器) 場景,若不同核心交錯存取控制器暫存器,缺乏屏障將引發間歇性不可重現的錯誤。這類錯誤往往只在壓力測試或角落時序(如高頻切換、DMA 連動)出現,延宕專案風險極高。

實務準則

  • 命令發送序列、狀態輪詢、IRQ 使能/遮罩 等關鍵點,*使用 `cdns_`(barrier 版)**;
  • 高頻讀資料路徑(確定無時序依賴)可選擇 *_relaxed
  • 任何「寫入控制暫存器 → 立即讀狀態判定」都要用 barrier 版本,否則會撞上 out-of-order 或 posted write。

9. 效能診斷方法論(簡述)

  • 逐頁面時間模型:以(tR + 傳輸 + 軟體開銷)為基本單位估算可達 MB/s;若 R/B# 等待(如 40–70 µs)佔比高,實測吞吐必然低於介面峰值。
  • 命令路徑差異:連續讀(預取/流水)與單發 CDMA 的空檔差異,常來自等待語意或控制器擴展模式設定不同。
  • 跨域一致性:Linux 與 U-Boot 的等待模型/次序化一致後,才能公平比較與歸因。
  • raw 佐證:所有 ECC 狀態與 OOB 佈局爭議,以 dump.raw 取樣為準確依據。

10. 結論

本次工作將 Cadence Raw NAND ControllerLinux、U-Boot、OP-TEE 的操作語意與等待機制完成一致化,並引入:

  • U-Boot dump.raw:可檢視真實 page + OOB,為 ECC/BBM/metadata 診斷提供可信觀測;
  • 三層 ECC(HWECC/On-Die/SW):建立完整的錯誤歸因體系;
  • skip-bytes 與 BBM 策略:在量產與偵錯兩種場景間具備可交換的保護與觀測;
  • barrier I/O 封裝:於 SMP/MP 環境下明確管控 MMIO 次序化,避免非決定性錯誤。

在實作 NAND 子系統時,建議先建立上述「等待語意標準化 + raw 可觀測性 + I/O 次序化」三個基石。它們不直接等於效能參數,卻是所有效能優化與可靠度工程的必要前提。


메타데이터
post_id
4b9fb03356a5
slug
cadence-raw-nand-controller-在-linux-u-boot-與-op-tee-的一致化實作與效能診斷-4b9fb03356a5
url
https://medium.com/@allenkuo/cadence-raw-nand-controller-%E5%9C%A8-linux-u-boot-%E8%88%87-op-tee-%E7%9A%84%E4%B8%80%E8%87%B4%E5%8C%96%E5%AF%A6%E4%BD%9C%E8%88%87%E6%95%88%E8%83%BD%E8%A8%BA%E6%96%B7-4b9fb03356a5
canonical_url
https://medium.com/@allenkuo/cadence-raw-nand-controller-%E5%9C%A8-linux-u-boot-%E8%88%87-op-tee-%E7%9A%84%E4%B8%80%E8%87%B4%E5%8C%96%E5%AF%A6%E4%BD%9C%E8%88%87%E6%95%88%E8%83%BD%E8%A8%BA%E6%96%B7-4b9fb03356a5
author_url
https://medium.com/@allenkuo
status
ok
fetched_at
2026-06-20 20:29:01