› 論壇總覽 › BeagleV-beta RISC-V 開源單板電腦 › OpenSBI uart8250.c 程式講解
- This topic has 0 則回覆, 1 個參與人, and was last updated 7 months, 2 weeks ago by johnson.
-
作者文章
-
2021 年 7 月 28 日 下午 4:23 #688
一、前言
繼上一篇UART8250與NS16550深入剖析裡,介紹NS16550的硬體腳位與對應OpenSBI程式碼的大致架構後,現在要來講解OpenSBI原始碼中,被BeagleV採用的uart8250核心的驅動程式碼。
首先是在Github上的uart8250.c 筆者建議可以將整個OpenSBI專案用git clone下來,再用自己熟悉的IDE或編輯器來觀看會比用網頁看方便許多。筆者使用Visual Studio Code(Visual Studio的開源免費版) 來看,因為它可以直接選取引用的函式並直接看到其內部程式碼。這個檔案很簡單,只分成三個部分:
引用、宣告與子程式。引用檔案
這個檔案引用的檔案有三個,分別是sbi.riscv_io.h,sbi.sbi_console.h以及sbi_utils/serial/uart8250.h。
riscv_io.h
這個除了引用最基本的sbi_types.h之外,也引用riscv_barrier.h。
前者是open sbi會定義到的資料型態,後者則是為了避免指令因為最佳化而亂序的記憶體屏障(Memory Barrier)保護。
關於記憶體屏障的說明可以參考wikipedia
接下來就是一連串的函式,利用組合語言的方式,直接寫入/讀取記憶體字元,如__raw_writeb(寫入原始數據1 bit),_raw_writew(寫入原始數據1 word),或者__raw_writel(寫入原始數據1 long )。
由於要使用UART通訊必須要存取VIC7100或任何系統晶片的內部記憶體,因此我們務必引用此函式庫才能對記憶體進行讀寫。
sbi_console.h
這個程式碼與上一個相同,引用資料型態定義,而在這個程式裡,宣告了名為sbi_console_device的結構,其中結構包含name名稱,console_putc以及console_getc兩個子程式。
在這裡的console_putc是要以指標的形式來寫,因為C語言與C++不同,沒辦法直接在struct內部宣告子程式,因此要讓系統直接執行子程式的記憶體位置,除此之外,由於我們可能會選用不同的UART device,每個不同device會對應到不同的記憶體偏移(offset),所以這種寫法可以提高更廣泛的泛用性。
如在uart8250.c中,我們宣告一個名為uart8250的sbi_console_device資料型態,而裡面就宣告.console_putc=uart8250_putc。但在sifive_uart.c中,我們宣告.console_putc=sifive_uart_putc。
上方原始碼告訴我們這兩個不同.console_putc做的事情與宿入的記憶體位址是完全不同的,一個是get_reg(UART_LSR_OFFSET),另一個則是get_reg(UART_REG_TXFIFO)。
此外,我們可以比對uart8250.c以及sifive-uart.c兩個程式各自宣告自己硬體適用的記憶體位址與字元放置副程式。
這個例子就可以很明顯看出sbi_console.h使用指標方式定義sbi_console_device的泛用性。
而console_putc這個變數子程式會在未來被lib/sbi/sbi_console.c被呼叫,也就是sbi用來與外界溝通的最重要的部分。
接著就是其他基本的sbi_printf,sbi_sprinf等等函式宣告。
子程式的實作則是放在sbi_console.c中,請各位有興趣的朋友務必看看。
uart8250.h
這個程式只宣告了uart8250初始化所需要的程式碼,但未來依然保留可擴充性。
三、宣告變數與副程式
這裡定義所有會用到的參數數值以及副程式型態,這裡的記憶體名稱以及記憶體移量都是遵循NS16550資料手冊中的定義來設定的,如RBR暫存器、IER暫存器等等。
下方部分則是UART暫存器的基本設定、鮑率設定、記憶體寬度以及記憶體位移長度設定。
副程式宣告
這裡一共有六個副程式被宣告,分別是
- get_reg
- set_reg
- uart8250_putc
- uart8250_getc
- uart8250_init
- 以及初始化sbi_console_device
get_reg
這裡利用riscv_io.h中宣告的副程式readb與readw以及readl將特定UART晶片的暫存器數值讀取出來。
首先判斷get_reg傳入的數值,並將暫存器位移後,直接讀取暫存器數值。
當width=1時表示資料型態是位元組,因此使用readb。
當width=2時,資料型態為word,使用readw。
這個程式可以讀取UART晶片的中斷狀態、晶片狀況以及是否溢位等等,詳細暫存器說明要看Datasheet。
set_reg
與上get_reg相對的,是寫入暫存器。利用這個指令還寫入UART晶片的設定暫存器。設定的暫存器名稱與功能要詳閱Datasheet。
這裡使用writeb,writew,以及writel作為寫入的指令。
uart8250_putc與uart8250_getc
這兩個程式分別寫入/讀取UART的傳送暫存器以及接收暫存器,UAR傳送暫存器(THR)只要一被寫入,就會立即傳送資料。
而當UART暫存器一接收到資料,就會傳送中斷訊號給CPU,讓CPU得以處理接收到的資料。
uart8250_init
這裡比較多程式碼,但做的事情一樣很簡單,首先先設定uart8250的暫存器基本位址,位移長度,系統頻率,鮑率以及除頻器參數,再將UART中斷關閉避免設定過程被中斷。
然後設定IER、LCR、FCR、MCR等等暫存器的基本位址,最後將這些參數寫入uart8250_console中。
sbi_console_device
這個部分上面有提過,就是整合不同UART晶片讓OpenSBI可以使用sbi_console_device這個structure的部分。
結論
OpenSBI是一個Binary Interface,設計初期就要考慮到未來的擴充性與相容性。
因此就算只是UART介面,也要在初期程式規劃的時就將變數名稱與驅動程式的名稱利用指標的型態編寫。
這麼做的好處不僅可以提高程式碼的利用率,同時也讓使用者(如我們)在分析程式碼的同時能夠更輕易快速的理解。
-
作者文章
- 需要以回覆此篇主題...