1. 程式人生 > >linux-uart

linux-uart

uart是Universal Asynchronous Receiver and Transmitter的縮寫.翻譯成中文即為”通用非同步收發器”.它是串列埠裝置驅動的封裝層。它是linux在tty的基礎上又做了一層封裝,通過該封裝層,可以比較容易的編寫新的串列埠驅動程式。

一、uart資料結構

uart建立在tty之上,它是真實的驅動和tty之間的橋樑,其涉及到的關鍵資料結構及其相互關係如下圖所示:


從上圖可以看出,一個uart_driver就對應一個tty_driver,而一個uart_driver可以包括多個uart_state,每個uart_state對應一個uart_device,每個uart_device都有自己的tty_port、uart_port以及收發快取。

circ_buf是一個傳送快取,在寫資料時,當tty層呼叫驅動提供的寫函式時,資料會首先進入circ_buf的環形快取區,然後由uart_port從快取區中取資料,將其寫入到串列埠裝置中。

當uart_port從串列埠裝置接收到資料時,它會直接將資料放入tty的快取中(tty快取屬於tty_port),進而放入對應的line discipline的快取區中。

二、和其它模組的關係

2.1 和上層即tty的關係

從設計的角度上,uart依賴於tty,但是tty並不依賴於uart層存在,所以uart需要提供給tty的介面都以函式指標的形式儲存在某種資料結構中,並且會通過tty提供的通用介面註冊到tty中,以供tty隨後呼叫,這也是一種很常見的設計方式。uart提供給tty的介面都儲存在資料結構tty_operations中,並通過tty_set_operations註冊到了tty中。

因此uart需要上層提供一些介面給它,比較重要的幾個為

  1. alloc_tty_driver:申請一個tty驅動的資料結構
  2. tty_set_operations:註冊uart的操作函式到tty中。
  3. tty_port_init、tty_register_driver:初始化tty_port資料結構、釋放tty_port關聯的快取
  4. tty_register_driver:註冊tty驅動到tty中
  5. tty_port_register_device_attr:將一個tty設備註冊到系統中
  6. tty_unregister_device:將一個tty裝置從系統中刪除
  7. tty_insert_flip_char和tty_insert_flip_string:將字元插入到tty快取中

標為黑體的為最關鍵的i幾個,在uart的驅動中,所有的驅動都會被設定TTY_DRIVER_DYNAMIC_DEV標記,根據tty中的學習,我們知道這意味著在呼叫tty_register_driver時,裝置不會被註冊到系統中,也就是在uart註冊驅動時不會將裝置新增到系統中,將裝置新增到系統中的工作是由tty_port_register_device_attr完成的。一個uart_driver支援的裝置數目會被儲存在uart_driver資料結構的nr中,並且會儲存在相關聯的tty_driver的num中,在呼叫tty_register_driver時,系統會為該裝置分配相應數目的裝置號(起始裝置號取決於驅動是否指定了起始裝置號,如果沒有指定就由系統分配,否則取驅動設定的)。

2.2 和下層即真實驅動的關係

類似於uart層和tty層的關係,uart層被設計為不依賴於真實的驅動,因而它不會直接呼叫驅動的函式,所有它需要驅動提供的功能都儲存在uart_port的ops欄位所指向的資料結構中,驅動需要通過呼叫uart_register_driver將uart_driver註冊到uart框架中,uart_port中的資訊由驅動提供並負責填充。 uart框架向驅動提供的幾個最主要的介面包括:
  1. uart_register_driver:將一個實際的串列埠驅動uart driver註冊到uart框架中,這一步會
    • 分配並初始化相關聯的tty_driver
    • 分配與每個uart_state關聯的tty_port的資料結構並初始化
    • 將tty驅動註冊到tty框架
  2. uart_add_one_port:這一步在驅動檢測到了實際的物理裝置後執行(利用probe或者是手動),根據uart_port的資訊配置串列埠,並將其註冊到系統中,利用tty_port_register_device_attr完成。在完成者一步後(在這一步中會先將裝置新增到sysfs中,然後傳送udev訊息),udev即可得到通知,udev機制會在dev下建立相應的裝置節點,隨後就可以使用該裝置了。
  3. uart_insert_char:驅動通過該介面將資料送到uart框架,進而送到tty快取。需要注意的是,利用該介面時往往還要使用tty框架使用的介面tty_flip_buffer_push進一步將資料重新整理到tty對應的line discipline中去。當然也可以不做,而由line discipline的程式自己做,比如tty_ldisc_N_TTY就使用了tty_flush_to_ldisc來將資料刷到line discipline中
  4. uart_handle_dcd_change:處理載波檢測狀態改變事件。
  5. uart_handle_cts_change:處理clear-to-send狀態改變事件,這個介面和上一個介面是uart提供的不依賴於tty的事件處理函式,“可以認為”是串列埠所特有的事件處理。

總體上uart和上下層的關係如下圖所示:


上層需要使用的下層的介面都是通過資料結構中的函式指標實現的,本圖沒有將它們畫出。

三、讀寫操作

3.1 開啟

類似於tty中的分析,open操作是最關鍵的操作,它由tty中的open呼叫,會:
  1. 由tty找到uart_driver,這個在註冊驅動時會儲存在tty中
  2. 由uart_driver以及tty的index找到對應的uart_state,tty的index在tty_open時由tty_init_dev呼叫initialize_tty_struct來設定,實際上即根據裝置的裝置號找到的
  3. uart_state儲存在tty以及uart_port中
  4. 將tty和tty_port關聯起來
  5. 呼叫uart_startup將繼續初始化uart使得它可以開始工作

3.2 讀

讀操作實際上就是從裝置獲取資訊,linux tty以及uart的設計中,每個uart介面都對應一個tty,當串列埠收到資料時,它應該
  1. 呼叫uart_insert_char將資料放入tty快取或者呼叫tty_prepare_flip_string/tty_prepare_flip_string_flags來申請一片tty快取,然後由自己把資料放到tty快取
  2. 呼叫tty_flip_buffer_push和tty_schedule_flip或者通過其它機制將資料刷到line discipline中,然後由tty對應的line discipline讀出資料並返回給使用者

3.3 寫

寫就是將資料寫入到裝置中,linux tty以及uart的設計中,每個uart介面都對應一個tty,當串列埠收到資料時,當tty往uart寫資料時,它呼叫了uart的寫函式,uart的寫函式很簡單:
  1. 將資料放入uart裝置對應的circ_buf中
  2. 呼叫uart_start,該函式會最終呼叫驅動提供的start_tx函式來啟動傳送

3.4 其它操作

uart支援的其它操作是通過ioctl來實現的。包括設定串列埠的波特率,流控方式等等。