1. 程式人生 > >記一次 spinor flash 讀速度優化

記一次 spinor flash 讀速度優化

## 背景 某個專案使用的介質是 `spinor`, 其 `bootloader` 需要從 `flash` 中載入 `os`。 啟動速度是一個關鍵指標,需要深入優化。其他部分的優化暫且略過,此篇主要記錄對 `nor` 讀速度的優化過程。 ## 瞭解現狀 接到啟動速度優化的任務之後, 首先是瞭解情況。 當前的 `bootloader` 實測讀速度只有約 `4M/s`。 為了加快速度已經嘗試過 * `spinor` 驅動改為使用四線讀命令讀取資料。速度並沒有明顯改善。待確認改動是否生效。 * `spinor` 驅動改為使用 `dma` 搬運資料。尚未修改成功。 ## 計算上限 既然是要深入優化,那知道終點在哪還是很有必要的。 整個讀取過程,資料主要是從 `spinor` 到達 `soc` 的 `spi` 控制器,再由 `cpu` 或 `dma` 搬運到 `dram` 中的目標位置。 ``` spinor --> spi控制器 --> cpu/dma --> dram ``` 先來考慮第一段的速度,這裡比較好計算。針對當前的 `soc` 和 `flash` 的組合,從規格書可得到最高的 `spi` 時鐘頻率為 `100M = 100 * 10^6`,且讀資料可使用 `4` 線讀取,即 `soc` 和 `flash` 之間有 `4` 根資料線在並行傳輸資料。那麼簡單算下 `100 * 10^6 * 4bit = 400 * 10^6 bit/s = 47.68 MB/s` , 可知極限速度為 `47.68 MB/s`。 當然較真一點,傳送讀命令給 `flash` 也需要時間,讓我們來算下。 一般一個讀命令需要 `5 bytes`, 即 `cmd + addr[3] + dummy`,所以實際的極限速度要考慮每發一次讀命令後讀取多少資料。讀命令是單線傳輸的,資料是四線傳輸。假設發一次命令讀 `nbytes` 資料,則命令和資料所佔時間的比例為 `5:(n/4)`, 那麼實際用於傳輸資料的 `clk` 就只有 `(n/4) / (5 + n/4) * 100M`。`4` 線傳輸的情況下每個 `clk` 可傳輸 `4bit`,從 `bit` 換算成 `byte` 再除以 `8`,於是速度公式為 `(n/4) / (5 + n/4) * 100M * 4 / 8 `, 這裡要注意對於大小 `1MB = 1024*1024 Byte`, 對於時鐘 `100M = 10^6`。 代入一些具體資料可得 每次讀取bytes | 讀速度 | -|- 64 | 36.33 MB/s 256 | 44.23 MB/s 1024 | 46.77 MB/s 64k | 47.67 MB/s 1M | 47.68 MB/s 可以看出,如果每次讀取資料量較小,那麼傳送讀命令消耗的時間就不可忽視。每次讀取的資料量越大,則讀命令對速度造成的整體影響就越小。 後面的部分理論速度暫時沒有很明確的計算方式,那暫時先知道完整的資料是這麼流動的就可以了。 ## 確認瓶頸 看了下驅動打印出來的確實是 `80M` 的 `clk` 和 `4` 線的讀命令,雖然還沒調到最高時鐘 `100M`,但當前 `4M/s` 也完全對不上,到底是誰出了問題呢?時鐘不對?四線沒配置成功?驅動存在 `bug`? `nor flash` 物料的問題? 思考一下,要確認到底是誰的鍋,最簡單明瞭的方式還是量下波形,不管軟體驅動上怎麼寫,控制器的暫存器怎麼配,最終還是得反映在波形上才是最真實的傳輸效果。接上示波器或邏輯分析儀,看看 `spi` 線上的情況,是誰的問題就一目瞭然了。 首先量一下 `spi clk` 線,可以發現讀資料的過程中,`clk` 的訊號不是連續的,在有訊號時其頻率是正常的,但大部分時間 `clk` 線上卻是沒有訊號的。再量量資料線,可以確認到確實使用了 `4` 線讀。 問題很明顯,`spi` 控制器是在間歇性讀資料,所以雖然讀 `nor` 的時候是 `80M` 的時鐘頻率進行讀取,但把 `spi` 的空閒時間計算進去,均攤下來的總的速度就只有 `4M/s` 了。 那為什麼 `spi` 控制器會間歇性讀取而不是一直在讀取呢? 這就涉及到剛剛所說的資料流了,`spi` 控制器本身的 `fifo` 是有限的,當從 `spinor` 讀取的資料填滿 `fifo` 之後,就必須等著 `cpu/dma` 把資料取走,騰出 `fifo` 空間來,才能繼續傳送指令從 `nor` 取資料。那麼這段空閒時間,應該就是在等 `cpu/dma` 取資料了。 ## 驗證 CPU 有了懷疑方向,那就得看下程式碼了。目前驅動中使用的是 `cpu` 來搬運資料,正常讀取過程中,`cpu` 在執行以下程式碼 ``` while 待讀取資料計數值大於0 if (查詢spi暫存器,判斷到fifo中存在資料) 讀取spi fifo暫存器資料,寫到dram的buffer中 待讀取資料計數值減1 ``` 如果是這裡成為了瓶頸,那就有兩個地方比較有嫌疑,一是讀取 `spi` 暫存器,而是寫 `dram`。 做點實驗確認下 實驗一,嘗試下把寫 `dram` 的操作去掉,使用如下操作,並由讀取前後的 `log` 時間戳來判斷耗時。 ``` while 待讀取資料計數值大於0 if (查詢spi暫存器,判斷到fifo中存在資料) 讀取spi fifo暫存器資料 待讀取資料計數值減1 ``` 重新測試下,發現速度沒有明顯變化。 實驗二,嘗試下減少讀 `spi` 暫存器的操作 ``` while 待讀取資料計數值大於0 讀取spi fifo暫存器資料 待讀取資料計數值減1 ``` 重新測試下,發現讀速度翻倍了,達到了 `8M/s`,看來果然是這裡成為了瓶頸。沒想到 `cpu` 讀個 `spi` 暫存器竟然這麼耗時。 ## 改用 DMA `cpu` 太慢,那就指望 `dma` 了。 先來解決 `dma` 驅動異常問題,瞭解下情況,原來這個 `dma` 驅動的支援是從另一個分支上移植過來的,原本工作正常,到了這個分支就翻車了。 這就是一個找不同的問題了,先比較下兩個分支的差異,再將可疑的地方 `checkout/cherry-pick` 到另一個分支來驗證。很快找到了關鍵因素 `dcache`。 這個分支上是預設打開了 `dcache` 的,可見舊文 [記一個bootloader的cache問題](https://sourl.cn/gGUjie),而這就導致了 `dma` 驅動工作異常。 簡單點,關掉 `dcache` 試試,果然 `dma` 就正常了。測下速度,達到了 `21M/s`。 再測試下不關 `dcache`,在配置了 `dma` 描述符之後,刷一次 `cache` 再啟動 `dma` 傳輸,也是正常的了。 ## 優化配置 `21M/s` 的速度,看來瓶頸還是在 `dma` 這裡。此時可以嘗試將 `spi clk` 從 `80M` 提高到 `100M`,可以發現整體讀速度沒有變化,這也可以佐證當前瓶頸仍然不在 `nor` 的讀取速度上面。 測個波形看看,果然 `clk` 線上還是間歇性的,不過空閒時間比之前少了很多。 `dma` 的速度能不能改進呢? 這就涉及到具體的晶片了,需要深究下 `dma` 控制器和 `spi` 控制器的配置。 優化 `dma` 和 `spi` 控制器的配置後,`dma` 從 `spi` 控制器取資料的速度,終於超過了 `80M` 時鐘下的 `spinor` 讀取速度,將 `spi clk` 修改為 `100M`,測得讀速度約 `36M/s`。 ## 優化驅動 前面說到,傳送讀命令給 `flash` 也需要時間,在 `os` 中受限於 `buffer` 大小等,可能會限制每次讀取和處理的資料量,但對於 `bootloader` 來說則完全可以一口氣將所需的資料讀入,無需分段。 另外可檢視驅動中是否有無用的清空 `buffer` 之類的操作,一併優化掉。 ## 提高時鐘 驅動邏輯和暫存器上無法優化之後,還想提速,那麼可以試試提高時鐘。 提高 `cpu` 時鐘和 `dma` 時鐘,提高後測得速度約 `47M/s`,基本就是理論極限了。 但具體能否提高時鐘還是得謹慎評估,單個板子可以正常執行,不意味能夠穩定量產。 ## 壓縮映象 在讀取速度無法進一步優化的情況下,要提高啟動速度,那就得減少讀入的資料量了。 可以評估下使用壓縮的映象來減少讀入的資料量,只要多出的解壓時間不長於節省掉的讀取 `flash` 時間,那就是划算的。 是否壓縮,選擇哪種壓縮演算法,就跟 `io` 速度,`cpu` 解壓速度直接相關了,最好經過實測確認,綜合啟動速度和 `flash` 佔用來選擇。 ## 其他 如果是帶壓縮的映象,那啟動速度就是讀取時間+解壓時間。 讀取時主要是 `io` 操作,解壓則主要是 `cpu` 操作。那麼是否有可能實現邊讀取邊解壓,使得總的啟動時間進一步縮短呢?這個待研究 blog: [https://www.cnblogs.com/zqb-all/p/12824908.html](https://www.cnblogs.com/zqb-all/p/12824908.html) 公眾號:[https://sourl.cn/4X3jE7](https://sourl.cn/4X3