天天看點

uart驅動架構及程式設計方法

一、UART介紹

UART(Universal Asynchronous Receiver/Transmitter),中文全稱為通用異步收發傳輸器,是一種異步收發傳輸器,它将要傳輸的資料通過并行到串行轉換後再進行傳輸。該總線雙向通信,可以實作全雙工傳輸和接收。在嵌入式裝置中,UART 用于主機與輔助裝置通信。

1. 通信協定

UART通信協定的工作原理是将傳輸資料的每個比特位一位接一位地傳輸。其中各比特的意義如下:

  • 起始位:在時鐘線為高電平時,資料線發出一個邏輯”0”的信号,表示傳輸字元的開始。
  • 資料位:緊接着起始位之後。資料位的個數可以是5、6、7、8等,構成一個字元。通常采用ASCII碼。從最低位開始傳送,靠時鐘定位。
  • 奇偶校驗位:資料位加上這一位後,使得“1”的位數應為偶數(偶校驗)或奇數(奇校驗),以此來校驗資料傳送的正确性 。
  • 停止位:在時鐘線為高電平時,資料線發出一個邏輯”1”的信号。可以是1位、1.5位、2位的高電平。

由于資料是在傳輸線上定時的,并且每一個裝置有其自己的時鐘,很可能在通信中兩台裝置間出現了小小的不同步。是以停止位不僅僅是表示傳輸的結束,并且提供計算機校正時鐘同步的機會。适用于停止位的位數越多,不同時鐘同步的容忍程度越大,但是資料傳輸率同時也越慢。

  • 空閑位:當時鐘線和資料新都處于邏輯“1”狀态,表示目前線路上沒有資料傳送。
uart驅動架構及程式設計方法

2. 波特率

波特率是衡量資料傳送快慢的名額。表示每秒鐘傳送的符号數(symbol)。一個符号代表的資訊量(比特數)與符号的階數有關。例如傳輸使用256階符号,每8bit代表一個符号,資料傳送速率為120字元/秒,則波特率就是120 baud,比特率是120*8=960bit/s。這兩者的概念很容易搞錯。

UART 的接收和發送是按照相同的波特率進行收發的。波特率發生器産生的時鐘頻率不是波特率時鐘頻率,而是波特率時鐘頻率的16倍,目的是為在接收時進行精确的采樣,以提取出異步的串行資料。根據給定的晶振時鐘和要求的波特率,可以算出波特率分頻計數值。

3. 工作原理

  • 發送資料過程:空閑狀态,線路處于高電位;當收到發送資料指令後,拉低線路一個資料位的時間T,接着資料位按低位到高位依次發送,資料發送完畢後,接着發送奇偶檢驗位和停止位(停止位為高電位),一幀資料發送結束。
  • 接收資料過程: 空閑狀态,線路處于高電位;當檢測到線路的下降沿(線路電位由高電位變為低電位)時說明線路有資料傳輸,按照約定的波特率從低位到高位接收資料,資料接收完畢後,接着接收并比較奇偶檢驗位是否正确,如果正确則通知則通知後續裝置準備接收資料或存入緩存。
  • 采用率:UART是異步傳輸,沒有傳輸同步時鐘。為了能保證資料傳輸的正确性,UART采用16倍資料波特率的時鐘進行采樣。每個資料有16個時鐘采樣,取中間的采樣值,以保證采樣不會滑碼或誤碼。一般UART一幀的資料位為8,這樣即使每一個資料有一個時鐘的誤差,接收端也能正确地采樣到資料。
  • 接收資料時序為:當檢測到資料下降沿時,表明線路上有資料進行傳輸,這時計數器CNT開始計數,當計數器為8時,采樣的值為“0”表示開始位;當計數器為​

    ​24=16*1+8​

    ​時,采樣的值為bit0資料;當計數器的值為​

    ​40=16*2+8​

    ​時,采樣的值為bit1資料;依次類推,進行後面6個資料的采樣。如果需要進行奇偶校驗位,則當計數器的值為​

    ​152=16*9+8​

    ​時,采樣的值為奇偶位;當計數器的值為​

    ​168=16*10+8​

    ​時,采樣的值為“1”表示停止位,一幀資料收發完成。

4. 流控

資料在兩個序列槽傳輸時,常常會出現丢失資料的現象,或者兩台計算機的處理速度不同,如桌上型電腦與單片機之間的通訊,接收端資料緩沖區以滿,此時繼續發送的資料就會丢失,流控制能解決這個問題,當接收端資料處理不過來時,就發出“不再接收”的信号,發送端就停止發送,直到收到“可以繼續發送”的信号再發送資料。是以流控制可以控制資料傳輸的程序,防止資料丢失。

PC機中常用的兩種流控為:硬體流控(包括RTS/CTS、DTR/CTS等)和軟體流控制XON/XOFF(繼續/停止)。

(1)硬體流控

  • 硬體流控制常用的有RTS/CTS流控制和DTR/DSR流控制兩種。
  • **DTR–資料終端就緒(Data Terminal Ready)**低有效,當為低時,表示本裝置自身準備就緒。此信号輸出對端裝置,使用對端裝置決定能否與本裝置通信。
  • **DSR-資料裝置就緒(Data Set Ready)**低有效,此信号由本裝置相連接配接的對端裝置提供,當為低時,本裝置才能與裝置端進行通信。
  • **RTS - 請求發送(資料)(Request To Send)**低有效,此信号由本裝置在需要發送資料給對端裝置時設定。當為低時,表示本裝置有資料需要向對端裝置發送。對端裝置能否接收到本方的發送資料,則通過CTS信号來應答。
  • **CTS - 接收發送(請求)(Clear To Send)**低有效,對端裝置能否接收本方所發送的資料,由CTS決定。若CTS為低,則表示對端的以準備好,可以接收本端發送資料。
  • 以RTS/CTS流控制分析,分析主機發送/接收流程:
  • 實體連接配接
  • 主機的RTS(輸出信号),連接配接到從機的CTS(輸入信号)。主機是CTS(輸入信号),連接配接到從機的RTS(輸入信号)。
  • 主機的發送過程:主機查詢主機的CTS腳信号,此信号連接配接到從機的RTS信号,受從機控制。如果主機CTS信号有效(為低),表示從機的接收FIFO未滿,從機可以接收,此時主機可以向從機發送資料,并且在發送過程中要一直查詢CTS信号是否為有效狀态。主機查詢到CTS無效時,則中止發送。主機的CTS信号什麼時候會無效呢?從機在接收到主機發送的資料時,從機的接收子產品的FIFO如果滿了,則會使從機RTS無效,也即主機的CTS信号無效。主機查詢到CTS無效時,主機發送中止。
  • 主機接收模式:如果主機接收FIFO未滿,那麼使主機RTS信号有效(為低),即從機的CTS信号有效。此時如果從機要發送,發送前會查詢從機的CTS信号,如果有效,則開始發送。并且在發送過程中要一直查詢從機CTS信号的有效狀态,如果無效則終止發送。是否有效由主機的RTS信号決定。如果主機FIFO滿了,則使主機的RTS信号無效,也即從機CTS信号無效,主機接收中止。

(2)軟體流控

  • 由于電纜的限制,在普通的控制通訊中一般不采用硬體流控制,而是使用軟體流控制。一般通過XON/XOFF來實作軟體流控制。
  • 常用方法是:
  • 當接收端的輸入緩沖區内資料量超過設定的高位時,就向資料發送端發送XOFF字元後就立即停止發送資料。
  • 當接收端的輸入緩沖區内資料量低于設定的低位時,就向資料發送端發送XON字元(十進制的17或Control-Q),發送端收到XON字元後就立即開始發送資料。
  • 一般可從裝置配套源程式中找到發送端收到XON字元後就立即發送資料。一般可以從裝置配套源程式中找到發送的是什麼位元組。應注意,若傳輸的是二進制的資料,标志字元也可能在資料流中出現而引起誤操作,這是軟體流控的缺陷,而硬體流控不會出現這樣的問題。

二、UART驅動程式設計

首先,帶大家簡單回顧一下tty架構情況,詳情可參考​​一文徹底講清Linux tty子系統架構及程式設計執行個體​​

uart驅動架構及程式設計方法
  • 整個 tty架構大概的樣子如上圖所示,簡單來分的話可以說成兩層:
  • 一層是下層我們的序列槽驅動層,它直接與硬體相接觸,我們需要填充一個 struct uart_ops 的結構體;
  • 另一層是 tty 層,包括 tty 核心以及線路規程,它們各自都有一個 Ops 結構,使用者空通過間是 tty 注冊的字元裝置節點來通路。
  • 發送資料的流程為:tty核心從一個使用者擷取将要發送給一個tty裝置的資料,tty核心将資料傳遞給tty線路規程驅動,接着資料被傳到tty驅動,tty驅動将資料轉換為可以發給硬體的格式。
  • 接收資料的流程為:從tty硬體接收到的資料向上交給tty驅動,接着進入tty線路規程驅動,再進入tty核心,在這裡它被一個使用者擷取。

1. UART驅動編寫

  • 【分析】以最常見的S3C2410平台為例叙述:
  1. 在使用序列槽核心層這個通用序列槽tty驅動層的接口後,一個序列槽驅動要完成的主要工作将包括:定義uart_driver、uart_ops、uart_port等結構體的執行個體,并在适當的地方根據具體硬體和驅動的情況初始化它們。(當然具體裝置xxx的驅動可以将這些結構套在新定義的xxx_uart_driver、xxx_uart_ops,xxx_uart_port之内)
  2. 在子產品初始化時調用​

    ​uart_register_driver()​

    ​​和​

    ​uart_add_one_port()​

    ​以注冊UART驅動并添加端口;在子產品解除安裝時調用​

    ​uart_unregister_driver()​

    ​​和​

    ​uart_remove_one_port()​

    ​以登出UART驅動并移除端口。
  • 以S3C2410序列槽驅動的子產品加載函數中會調用uart_register_driver()注冊s3c24xx_uart_drv這個uart_driver。初始化過程如下:
  1. ​s3c2410_serial_init()​

    ​;
  2. ​platform_driver_register()​

    ​;
  3. 比對後,​

    ​s3c24xx_serial_probe()​

    ​被執行,它包含下面的函數;
  4. ​s3c24xx_serial_init_port()​

    ​被執行以初始化UART端口,它包含下面的函數;
  5. ​uart_add_one_port()​

    ​被執行以添加端口。
  1. 根據具體硬體的datasheet實作uart_ops中的成員函數,這些函數的實作是UART驅動的主體工作。S3C2410序列槽驅動​

    ​uart_ops​

    ​​結構體中的startup ()成員函數​

    ​s3c24xx_serial_startup()​

    ​用于啟動端口,申請端口的發送、接收中斷,使能端口的發送和接收。

(1) 注冊uart_driver

  • 在uart driver的初始階段(module_init()),需要将我們的​

    ​struct uart_driver​

    ​結構變量注冊到核心,其注冊流程大緻為:
  1. uart_register_driver
  2. 申請n個uart_state結構的空間(根據driver支援的最大裝置數,申請n個uart_state空間,每一個uart_state都有一個uart_port。)
  3. 配置設定及初始化tty_driver結構(配置設定一個tty_driver,設定預設波特率、檢驗方式等,并将uart_driver->tty_driver指向它)
  4. 設定tty_driver的操作集——tty_operations(它是tty核心與序列槽驅動通信的接口)
  5. 設定tty_port的操作集——tty_port_operations(初始化每一個uart_state的tty_port)
  6. 注冊tty_driver(注冊uart_driver實際上就是注冊tty_driver,與使用者空間打交道的工作完全交給tty_driver,這一部分是核心實作好的不需要修改)
  7. 完畢

(2) 添加/移除uart_port

  • ​uart_add_one_port​

    ​接口用于注冊一個uart port 到uart driver上,此後uart driver就可以通路對應的uart port進行資料收發。對該函數的調用應該發生在uart_register_driver()之後,它的一個最重要作用是封裝了tty_register_device()。
  • ​uart_remove_one_port​

    ​接口是上述函數的反函數,其最重要作用是會調用tty_unregister_device()。
/* 将uart_port添加到相應的uart_driver中,成功傳回0 */
int uart_add_one_port(struct uart_driver *drv,
                      struct uart_port *uport);
    
/*将uart_port從相應的uart_driver中移除,成功傳回0 */
int uart_remove_one_port(struct uart_driver *drv, 
                         struct uart_port *uport);      
  • 該接口在uart driver中的probe函數中調用,是以必須保證晚于uart_register_driver的注冊過程。
  • uart driver在調用接口前,要手動設定uart_port的操作集uart_ops,使得通過調用​

    ​uart_add_one_port​

    ​接口後驅動完成硬體的操作接口注冊。
  • uart添加port流程如下圖所示:
uart驅動架構及程式設計方法

2. 資料收發流程分析

(1)打開裝置

uart驅動架構及程式設計方法

(2)資料發送流程(write)

uart驅動架構及程式設計方法

(3) 資料接收流程(read)

uart驅動架構及程式設計方法

(4)關閉裝置(close)

uart驅動架構及程式設計方法

3. 登出流程

(1) 移除uart_port

此接口用于從uart driver上登出一個uart port,該接口在uart driver中的remove函數中調用。uart移除port的流程如圖3-9所示:

uart驅動架構及程式設計方法

(2) 登出uart_driver

此接口在uart driver中調用,用來從kernel中登出uart_driver,調用階段在uart driver的退出階段,例如:module_exit(),uart driver的登出流程如圖3.10所示

uart驅動架構及程式設計方法

三、UART驅動中的資料結構

  • 序列槽驅動資料結構總圖
uart驅動架構及程式設計方法
  • 以s3c2440開發闆為例,講述其中UART驅動的資料結構構成:
  • 因為我們和開發闆的人機互動的接口是Windows下的序列槽控制台。這就是上面所說的控制台終端。但是我們用了console = ttySAC0。即把序列槽終端當做控制台終端。是以我們要研究具體的代碼需要cd到serial子目錄下。即序列槽終端目錄。ls顯示serial下的檔案結點。如圖所示:
uart驅動架構及程式設計方法
  • 我們主要關心的是兩類檔案:
  • 一類是與體系結構和闆載資源無關的通用序列槽操作檔案(samsung.c)。
  • 一類是與體系結構相關的硬體操作檔案(s3c2440.c s3c2410.c s5pv210.c等)。我們為了得到具體的調用鍊。在具體的發送函數中加入回溯。如圖所示。
uart驅動架構及程式設計方法
  • 我們得到的函數調用鍊是這樣的(以發送函數。即檔案的寫操作為例)

write-> sys_write-> vfs_write-> redirected_tty_write-> tty_write-> n_tty_write-> uart_write-> uart_start-> s3c24xx_serial_start_tx

uart驅動架構及程式設計方法
  • 從具體代碼上來看。這些函數基本上都是通過結構體中的函數指針調用。我們可以把這個調用鍊分為三個部分。即tty子系統核心、tty鍊路規程、tty驅動:
  1. tty核心:是對整個tty裝置的抽象,對使用者提供統一的接口,包括sys_write->vfs_write
  2. tty線路規程:是對傳輸資料的格式化,在tty_ldisc_N_TTY變量中描述,包括redirected_tty_write-> tty_write->n_tty_write
  3. tty驅動:是面向tty裝置的硬體驅動,這裡面真正的對硬體進行操作,包括uart_write-> uart_start-> s3c24xx_serial_start_tx

這是從具體函數的角度來看的調用鍊。下面為了從資料結構的角度來分析調用鍊,介紹linux核心中針對于這一個序列槽硬體的主要資料結構。

(1) uart_driver

就是uart驅動程式結構,封裝了tty_driver,使得底層的UART驅動無需關心tty_driver,其定義在 include/linux/serial_core.h 檔案中,具體定義如下:

struct uart_driver{
    struct module   *owner;
    const char      *driver_name;
    const char      *dev_name;
    int             major;
    int             minor;
    int             nr;
    struct console  *cons;
    /* these are private;the low level driver should not
     * touch these; they should be initialised to NULL
     */
    struct uart_state *state;
    struct tty_driver *tty_driver;
}      

(2) uart_port

uart_port用于描述一個UART端口(直接對應于一個序列槽)的I/O端口或者IO記憶體位址、FIFO大小、端口類型等資訊。

struct uart_port {
    spinlock_t lock;                    /* 序列槽端口鎖 */
    unsignedint iobase;                 /* IO端口基位址 */
    unsignedchar __iomem *membase;      /* IO記憶體基位址,經映射(如ioremap)後的IO記憶體虛拟基位址 */
    unsignedint irq;                    /* 中斷号 */
    unsignedint uartclk;                /* 序列槽時鐘 */
    unsignedint fifosize;               /* 序列槽FIFO緩沖大小 */
    unsignedchar x_char;                /* xon/xoff字元 */
    unsignedchar regshift;              /* 寄存器位移 */
    unsignedchar iotype;                /* IO通路方式 */
    unsignedchar unused1;

    #define UPIO_PORT (0)               /* IO端口 */
    #define UPIO_HUB6 (1)
    #define UPIO_MEM (2)                /* IO記憶體 */
    #define UPIO_MEM32 (3)
    #define UPIO_AU (4)                 /* Au1x00 type IO */
    #define UPIO_TSI (5)                /* Tsi108/109 type IO */
    #define UPIO_DWAPB (6)              /* DesignWare APB UART */
    #define UPIO_RM9000 (7)             /* RM9000 type IO */

    unsignedint read_status_mask;       /* 關心的Rx error status */
    unsignedint ignore_status_mask;     /* 忽略的Rx error status */
    struct uart_info *info;             //重要,見下面
    struct uart_icount  icount;         /* 序列槽資訊計數器,包含了發送和接收字元計數等。分别在發送和接收中斷處理函數中管理這些計數。*/ 

    struct console *cons;               /* console結構體 */
#ifdef CONFIG_SERIAL_CORE_CONSOLE
    unsignedlong sysrq;                 /* sysrq timeout */
#endif

    upf_t flags;

    #define UPF_FOURPORT        ((__forceupf_t)(1 << 1))
    #define UPF_SAK             ((__forceupf_t)(1 << 2))
    #define UPF_SPD_MASK        ((__forceupf_t)(0x1030))
    #define UPF_SPD_HI          ((__forceupf_t)(0x0010))
    #define UPF_SPD_VHI         ((__forceupf_t)(0x0020))
    #define UPF_SPD_CUST        ((__forceupf_t)(0x0030))
    #define UPF_SPD_SHI         ((__forceupf_t)(0x1000))
    #define UPF_SPD_WARP        ((__forceupf_t)(0x1010))
    #define UPF_SKIP_TEST       ((__forceupf_t)(1 << 6))
    #define UPF_AUTO_IRQ        ((__forceupf_t)(1 << 7))
    #define UPF_HARDPPS_CD      ((__forceupf_t)(1 << 11))
    #define UPF_LOW_LATENCY     ((__forceupf_t)(1 << 13))
    #define UPF_BUGGY_UART      ((__forceupf_t)(1 << 14))
    #define UPF_MAGIC_MULTIPLIER((__force upf_t)(1 << 16))
    #define UPF_CONS_FLOW       ((__forceupf_t)(1 << 23))
    #define UPF_SHARE_IRQ       ((__forceupf_t)(1 << 24))
    #define UPF_BOOT_AUTOCONF   ((__forceupf_t)(1 << 28))
    #define UPF_FIXED_PORT      ((__forceupf_t)(1 << 29))
    #define UPF_DEAD            ((__forceupf_t)(1 << 30))
    #define UPF_IOREMAP         ((__forceupf_t)(1 << 31))

    #define UPF_CHANGE_MASK     ((__forceupf_t)(0x17fff))
    #define UPF_USR_MASK        ((__forceupf_t)(UPF_SPD_MASK|UPF_LOW_LATENCY))

    unsigned int mctrl;         /* 目前的moden設定 */
    unsigned int timeout;       /* character-based timeout */
    unsigned int type;          /* 端口類型 */
    const struct uart_ops *ops; /* 序列槽端口操作函數集 */
    unsigned int custom_divisor;
    unsigned int  line;         /* 端口索引 */
    resource_size_t mapbase;    /* IO記憶體實體基位址,可用于ioremap */
    struct device *dev;         /* 父裝置 */
    unsigned char hub6;         /* this should be in the 8250 driver */
    unsigned char suspended;
    unsigned char unused[2];
    void*private_data;          /* 端口私有資料,一般為platform資料指針 */
};      

(3) uart_ops

uart_ops定義了針對UART的一系列操作。注意這裡不要把uart_ops結構和uart_ops變量混淆。uart_ops結構是我們這裡的資料結構。而uart_ops變量則是一個tty_operations的變量。

在serial_core.c中定義了tty_operations的執行個體。即uart_ops變量,包含uart_open();uart_close();uart_send_xchar()等成員函數,這些函數借助uart_ops結構體中的成員函數來完成具體的操作:

struct uart_ops {
unsignedint(*tx_empty)(struct uart_port *);   /* 序列槽的Tx FIFO緩存是否為空 */
void(*set_mctrl)(struct uart_port *,unsignedint mctrl); /* 設定序列槽modem控制 */
unsignedint(*get_mctrl)(struct uart_port *);  /* 擷取序列槽modem控制 */
void(*stop_tx)(struct uart_port *);       /* 禁止序列槽發送資料 */
void(*start_tx)(struct uart_port *);      /* 使能序列槽發送資料 */
void(*send_xchar)(struct uart_port *,char ch);  /* 發送xChar */
void(*stop_rx)(struct uart_port *);       /* 禁止序列槽接收資料 */
void(*enable_ms)(struct uart_port *);     /* 使能modem的狀态信号 */
void(*break_ctl)(struct uart_port *,int ctl); /* 設定break信号 */
int(*startup)(struct uart_port *);  /* 啟動序列槽,應用程式打開序列槽裝置檔案時,該函數會被調用 */
void(*shutdown)(struct uart_port *);/* 關閉序列槽,應用程式關閉序列槽裝置檔案時,該函數會被調用 */
void(*set_termios)(struct uart_port *,struct ktermios *new,struct ktermios *old); /* 設定序列槽參數 */
void(*pm)(struct uart_port *,unsignedint state,
unsignedint oldstate);  /* 序列槽電源管理 */
int(*set_wake)(struct uart_port *,unsignedint state);/* */
constchar*(*type)(struct uart_port *);  /* 傳回一描述序列槽類型的字元串 */
void(*release_port)(struct uart_port *);/* 釋放序列槽已申請的IO端口/IO記憶體資源,必要時還需iounmap */
int(*request_port)(struct uart_port *); /* 申請必要的IO端口/IO記憶體資源,必要時還可以重新映射序列槽端口 */
void(*config_port)(struct uart_port *,int); /* 執行序列槽所需的自動配置 */
int(*verify_port)(struct uart_port *,struct serial_struct *); /* 核實新序列槽的資訊 */
int(*ioctl)(struct uart_port *,unsignedint,unsignedlong); /* IO控制 */

#ifdef CONFIG_CONSOLE_POLL
  int (*poll_init)(struct uart_port *);
  void (*poll_put_char)(struct uart_port *, unsigned char);
  int (*poll_get_char)(struct uart_port *);
#endif
};      

而uart_ops變量是tty_operations型的一個變量。如下圖所示:

static const struct tty_operations uart_ops = {
    .open           = uart_open,
    .close          = uart_close,
    .write          = uart_write,
    .put_char       = uart_put_char,
    .flush_chars    = uart_flush_chars,
    .write_room     = uart_write_room,
    .chars_in_buffer= uart_chars_in_buffer,
    .flush_buffer   = uart_flush_buffer,
    .ioctl          = uart_ioctl,
    .throttle       = uart_throttle,
    .unthrottle     = uart_unthrottle,
}      

(4) uart_state

uart_state是uart的狀态結構

struct uart_state{
    struct tty_port     port;
    int                 pm_state;
    struct circ_buf     xmit;
    struct tasklet_struct tlet;
    struct uart_port    *uart_port;
};
#define UART_XMIT_SIZE  PAGE_SIZE      

(5) uart_info

uart_info是uart的資訊結構,它有兩個成員在底層序列槽驅動會用到:xmit和tty:

  1. 使用者空間程式通過序列槽發送資料時,上層驅動将使用者資料儲存在xmit;而序列槽發送中斷處理函數就是通過xmit擷取到使用者資料并将它們發送出去。
  2. 序列槽接收中斷處理函數需要通過tty将接收到的資料傳遞給行規則層。
struct uart_info {
    struct tty_struct *tty;     //處理接受資料
    struct circ_buf xmit;       //上層需要發送
    uif_t flags;
    /*
    * Definitions for info->flags. These are _private_ to serial_core,and
    * are specific to this structure. They may be queried by low leveldrivers.
    */
    #define UIF_CHECK_CD ((__force uif_t)(1 << 25))
    #define UIF_CTS_FLOW ((__force uif_t)(1 << 26))
    #define UIF_NORMAL_ACTIVE ((__force uif_t)(1 << 29))
    #define UIF_INITIALIZED ((__force uif_t)(1 << 31))
    #define UIF_SUSPENDED ((__force uif_t)(1 << 30))
    int blocked_open;
    struct tasklet_struct tlet;    //上層驅動任務等待隊列的
    wait_queue_head_t open_wait;
    wait_queue_head_t delta_msr_wait;
};      

在這個體系結構下定義為s3c24xx_uart_info:

struct s3c24xx_uart_info{
    char        *name;
    unsigned int    type;
    unsigned int    fifosize;
    unsigned long   rx_fifomask;
    unsigned long   rx_fifoshift;
    unsigned long   rx_fifofull;
}      
  • 是以很顯然,用資料結構來描述函數調用鍊就是:

uart_driver -> uart_state-> uart_port-> uart_ops-> 特定的函數指針。

(6) 發送函數及中斷服務例程

使能發送并沒有真正的發送,而隻是使能發送中斷(​

​enable_irq(ourport->tx_irq);​

​)而已。

static void s3c24xx_serial_start_tx(struct uart_port *port)
{
    struct s3c24xx_uart_port *ourport = to_ourport(port);
    if(!tx_enabled(port)){
        if(port->flags & UPF_CONS_FLOW)
            s3c24xx_serial_rx_disable(port);
        enable_irq(ourport->tx_irq);
        tx_enabled(port) = 1;
    }
}      

這是因為ARM9處理器上有一個循環緩沖。使用者從write系統調用傳下來的資料就會寫入這個UTXH0寄存器。發送完事之後處理器會産生一個内部中斷。我們通過這個内部中斷就可以實作流控過程,我們打開晶片手冊可以看到如下字樣:

uart驅動架構及程式設計方法

如下才是發送中斷的ISR(Interrupt Service Routine)中斷服務例程。一個irqreturn_t類型的handler。

static irqreturn_t s3c24xx_serial_tx_chars(int irq, void *id)
{
    struct s3c24xx_uart_port *ourport = id;
    struct uart_port *port = &ourport->port;
    struct cirt_buf *xmit = &port->state->xmit;
    int count = 256;
    
    if(port->x_char){
        wr_regb(port,S3C2410_UTXH, port->x_char);
        port->icount.tx++;
        port->x_char = 0;
        goto out;
    }
    ...
}      

這個​

​wr_regb(port, S3C2410_UTXH, port->x_char);​

​就是往特定寄存器寫的過程。

至此我們的分析已經結束。相信讀者對于Linux下的tty子系統已經有一個概觀了。下面是這個uart驅動的總圖。結合資料結構的調用鍊。Linux核心完成了驅動模型和特定硬體的分離:

uart驅動架構及程式設計方法

在下一篇文章中,将繼續講解如何實際編寫一個序列槽子產品,歡迎點贊加關注!

四、核心代碼分析(s3c2410平台)

(1)平台資源

static struct resource s3c2410_uart0_resource[] = {
    ...
};

static struct resource s3c2410_uart1_resource[] = {
    ...
};

static struct resource s3c2410_uart2_resource[] = {
    ...
};      
  • 在檔案linux/arch/arm/plat-samsung/dev-uart.c中定義了每個序列槽對應的平台裝置。
static struct platform_device s3c24xx_uart_device0 = {
    .id = 0,
};

static struct platform_device s3c24xx_uart_device1 = {
    .id = 1,
};

static struct platform_device s3c24xx_uart_device2 = {
    .id = 2,
};      
  • 在檔案linux/arch/arm/mach-s3c2440/mach-smdk2440.c中有一些序列槽寄存器的初始化配置。
static struct s3c2410_uartcfg smdk2440_uartcfgs[] __initdata = {
    [0] = {
        ...
    },

    [1] = {
        ...
    },

    /* IR port */

    [2] = {
        ...
    },
};      
  • 在檔案linux/arch/arm/mach-s3c2440/mach-smdk2440.c中将調用函數**s3c24xx_init_uarts()**将上面的硬體資源,初始化配置,平台裝置整合到一起。

在檔案 linux/arch/arm/plat-s3c/init.c中有:

static int __init s3c_arch_init(void)
{
    ...
    /*這個函數将序列槽所對應的平台裝置添加到了核心*/
    ret = platform_add_devices(s3c24xx_uart_devs, nr_uarts);
    return ret;
}      

(2)序列槽裝置驅動原理淺析

我認為任何裝置在linux中的實作就“兩條線”:一是裝置模型的建立,二是讀寫資料流。序列槽驅動也是這樣。

  • 序列槽裝置模型建立:

序列槽裝置驅動的核心結構體在檔案linux/drivers/serial/samsuing.c中如下

static struct uart_driver s3c24xx_uart_drv = {
    .owner = THIS_MODULE,
    .dev_name = "s3c2410_serial", 
    .nr = CONFIG_SERIAL_SAMSUNG_UARTS,
    .cons = S3C24XX_SERIAL_CONSOLE,
    .driver_name = S3C24XX_SERIAL_NAME,
    .major = S3C24XX_SERIAL_MAJOR,
    .minor = S3C24XX_SERIAL_MINOR,
};      
  • 序列槽驅動的注冊
static int __init s3c24xx_serial_modinit(void)
{
    ...
    ret = uart_register_driver(&s3c24xx_uart_drv);
    ...
}

int uart_register_driver(struct uart_driver *drv)
{
    ...

    //每一個端口對應一個state
    drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL);
    ...
    normal = alloc_tty_driver(drv->nr);  //配置設定該序列槽驅動對應的tty_driver 
    ...
    drv->tty_driver = normal;                //讓drv->tty_driver字段指向這個tty_driver
    ...
    normal->driver_name = drv->driver_name;
    normal->name = drv->dev_name;
    normal->major = drv->major;
    normal->minor_start = drv->minor;
    ...
    //設定該tty驅動對應的操作函數集tty_operations (linux/drivers/char/core.c)
    tty_set_operations(normal, &uart_ops); 
    ...
    retval = tty_register_driver(normal);   //将tty驅動注冊到核心
    ...
}      

(3)序列槽本身是一個字元裝置

其實tty驅動的本質是一個字元裝置,在檔案 linux/drivers/char/tty_io.c中,這樣才能通過操作裝置檔案操作硬體

int tty_register_driver(struct tty_driver *driver)
{
    ...
    cdev_init(&driver->cdev, &tty_fops);
    driver->cdev.owner = driver->owner;
    error = cdev_add(&driver->cdev, dev, driver->num);
    ...
}      

它所關聯的操作函數集tty_fops在檔案linux/drivers/char/tty_io.c中實作

static const struct file_operations tty_fops = {
    .llseek = no_llseek,
    .read = tty_read,
    .write = tty_write,
    ...
    .open = tty_open,
    ...
};      

到此序列槽的驅動作為tty_driver被注冊到了核心。前面提到序列槽的每一個端口都是作為平台裝置被添加到核心的。那麼這些平台裝置就對應着有它們的平台裝置驅動。在檔案linux/drivers/serial/s3c2440.c中有:

static struct platform_driver s3c2440_serial_driver = {
    .probe = s3c2440_serial_probe,
    .remove = __devexit_p(s3c24xx_serial_remove),
    .driver = {
        .name = "s3c2440-uart",
        .owner = THIS_MODULE,
    },
};      

當其驅動與裝置比對時就會調用他的探測函數:

static int s3c2440_serial_probe(struct platform_device *dev)
{
    return s3c24xx_serial_probe(dev, &s3c2440_uart_inf);
}      

每一個端口都有一個描述它的結構體s3c24xx_uart_port ,在檔案linux/drivers/serial/samsuing.c中

static struct s3c24xx_uart_port s3c24xx_serial_ports[CONFIG_SERIAL_SAMSUNG_UARTS] = {
    [0] = {
        .port = {
            .lock = __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[0].port.lock),
            .iotype = UPIO_MEM,
            .irq = IRQ_S3CUART_RX0,     //該端口的中斷号
            .uartclk = 0,
            .fifosize = 16,
            .ops = &s3c24xx_serial_ops, //該端口的操作函數集
            .flags = UPF_BOOT_AUTOCONF,
            .line = 0,                  //端口編号
        }
    },
    ...
}      

上面探測函數的具體工作是函數s3c24xx_serial_probe()來完成的

int s3c24xx_serial_probe(struct platform_device *dev,struct s3c24xx_uart_info *info)
{
    ...
    //根據平台裝置提供的硬體資源等資訊初始化端口描述結構體中的一些字段
    ret = s3c24xx_serial_init_port(ourport, info, dev);
    
    //前面注冊了序列槽驅動,這裡便要注冊序列槽裝置
    uart_add_one_port(&s3c24xx_uart_drv, &ourport->port);
    ...
}

int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)
{
    ...
    //前面說序列槽驅動是tty_driver,這裡可以看到序列槽裝置其實是tty_dev
    tty_dev = tty_register_device(drv->tty_driver, uport->line, uport->dev);
    ...
}      

(4)序列槽資料流分析

在序列槽裝置模型建立中提到了三個操作函數集,uart_ops ,tty_fops,s3c24xx_serial_ops資料的流動便是這些操作函數間的調用,這些調用關系如下:

uart驅動架構及程式設計方法

在對一個裝置進行其他操作之前必須先打開它,linux/drivers/char/tty_io.c

static const struct file_operations tty_fops = {
    ...
    .open = tty_open,
    ...
};

static int tty_open(struct inode *inode, struct file *filp)
{
    ...
    dev_t device = inode->i_rdev;
    ...
    driver = get_tty_driver(device, &index); //根據端口裝置号擷取它的索引号
    ...
    if (tty) {
        ...
    } else
        tty = tty_init_dev(driver, index, 0); //建立一個tty_struct 并初始化
    ...
}

struct tty_struct *tty_init_dev(struct tty_driver *driver, int idx,int first_ok)
{
    ...
    tty = alloc_tty_struct();                       //配置設定一個tty_struct結構
    initialize_tty_struct(tty, driver, idx);        //一些字段的初始化,
    retval = tty_driver_install_tty(driver, tty);   //完成的主要工作是driver->ttys[idx] = tty;
    ...
    /*
    下面函數主要做的就是調用線路規程的打開函數ld->ops->open(tty)。
    在這個打開函數中配置設定了一個重要的資料緩存
    tty->read_buf = kzalloc(N_TTY_BUF_SIZE, GFP_KERNEL);
    */
    retval = tty_ldisc_setup(tty, tty->link);
}

void initialize_tty_struct(struct tty_struct *tty,struct tty_driver *driver, int idx)
{
    ...
    //擷取線路規程操作函數集tty_ldisc_N_TTY,并做這樣的工作tty->ldisc = ld;
    tty_ldisc_init(tty);
    ...
    /*
    下面函數的主要工作是INIT_DELAYED_WORK(&tty->buf.work, flush_to_ldisc);
    初始化一個延時tty->buf.work 并關聯一個處理函數flush_to_ldisc(),這個函數将在
    資料讀取的時候用到。
    */
    tty_buffer_init(tty);
    ...
    tty->driver = driver; 
    tty->ops = driver->ops; //這裡的ops就是struct tty_operations uart_ops
    tty->index = idx; //idx就是該tty_struct對應端口的索引号
    tty_line_name(driver, idx, tty->name);
}      

端口裝置打開之後就可以進行讀寫操作了,這裡隻讨論資料的讀取,在檔案 linux/drivers/char/tty_io.c中,

static const struct file_operations tty_fops = {
    ...
    .read = tty_read,
    ...
};

static ssize_t tty_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
    ...
    ld = tty_ldisc_ref_wait(tty);   //擷取線路規程結構體
    if (ld->ops->read)                //調用線路規程操作函數集中的n_tty_read()函數
        i = (ld->ops->read)(tty, file, buf, count);
    else
        ...
}      

在linux/drivers/char/N_tty.c中:

struct tty_ldisc_ops tty_ldisc_N_TTY = {
    ...
    .open            = n_tty_open,
    ...
    .read            = n_tty_read,
    ...
};

static ssize_t n_tty_read(struct tty_struct *tty, struct file *file, unsigned char __user *buf, size_t nr)
{
    ...
    while (nr) {
        ...
        if (tty->icanon && !L_EXTPROC(tty)) {
            //如果設定了tty->icanon 就從緩存tty->read_buf[]中逐個資料讀取,并判斷讀出的每一個數//據的正确性或是其他資料類型等。
            eol = test_and_clear_bit(tty->read_tail,tty->read_flags);
            c = tty->read_buf[tty->read_tail];
            ...
        } else {
            ...
            //如果沒有設定tty->icanon就從緩存tty->read_buf[]中批量讀取資料,之是以要進行兩次讀
            //取是因為緩存tty->read_buf[]是個環形緩存
            uncopied = copy_from_read_buf(tty, &b, &nr);
            uncopied += copy_from_read_buf(tty, &b, &nr);
            ...
        }
    }
    ...
}      

使用者空間是從緩存tty->read_buf[]中讀取資料的,那麼緩存tty->read_buf[]中的資料是從那裡來的呢?分析如下:

回到檔案 linux/drivers/serial/samsuing.c中,序列槽資料接收中斷處理函數實作如下:(這是序列槽最原始的資料流入的地方)

static irqreturn_t  s3c24xx_serial_rx_chars(int irq, void *dev_id)
{
    ...
    while (max_count-- > 0) {
        ...
        ch = rd_regb(port, S3C2410_URXH); //從資料接收緩存中讀取一個資料
        ...
        flag = TTY_NORMAL; //普通資料,還可能是其他資料類型在此不做讨論
        ...
        /*
        下面函數做的最主要工作是這樣
        struct tty_buffer *tb = tty->buf.tail;
        tb->flag_buf_ptr[tb->used] = flag;
        tb->char_buf_ptr[tb->used++] = ch;
        将讀取的資料和該資料對應标志插入 tty->buf。
        */
        uart_insert_char(port, uerstat, S3C2410_UERSTAT_OVERRUN, ch, flag);
    }
    tty_flip_buffer_push(tty); //将讀取到的max_count個資料向上層傳遞。
out:
    return IRQ_HANDLED;
}

void tty_flip_buffer_push(struct tty_struct *tty)
{
    ...
    if (tty->low_latency)
        flush_to_ldisc(&tty->buf.work.work);
    else
        schedule_delayed_work(&tty->buf.work, 1); 
        //這裡這個延時work在上面序列槽裝置打開中提到過,該work的處理函數也是flush_to_ldisc。
}

static void flush_to_ldisc(struct work_struct *work)
{
    ...
    while ((head = tty->buf.head) != NULL) {
        ...
        char_buf = head->char_buf_ptr + head->read;
        flag_buf = head->flag_buf_ptr + head->read;
        ...
        //剛才在序列槽接收中斷處理函數中,将接收到的資料和資料标志存到tty->buf中,現在将
        //這些資料和标志用char_buf 和flag_buf指向進一步向上傳遞。
        disc->ops->receive_buf(tty, char_buf,flag_buf, count);
        spin_lock_irqsave(&tty->buf.lock, flags);
    }
}      

上面調用的函數disc->ops->receive_buf在檔案linux/drivers/char/N_tty.c中實作

struct tty_ldisc_ops tty_ldisc_N_TTY = {
    ...
    .receive_buf     = n_tty_receive_buf,
    ...
};

static void n_tty_receive_buf(struct tty_struct *tty, const unsigned char *cp, char *fp, int count)
{
    ...
    //現在可以看到緩沖區tty->read_buf 中資料的由來了。
    if (tty->real_raw) {
        //如果設定了tty->real_raw将上面講到的些傳入資料批量拷貝到tty->read_head中。
        //對環形緩存區的資料拷貝需要進行兩次,第一次拷貝從目前位置考到緩存的末尾,如果還//有沒考完的資料而且緩存區開始出處還有剩餘空間,就把沒考完的資料考到開始的剩餘空
        //間中。
        spin_lock_irqsave(&tty->read_lock, cpuflags);
        i = min(N_TTY_BUF_SIZE - tty->read_cnt,N_TTY_BUF_SIZE - tty->read_head);
        i = min(count, i);
        memcpy(tty->read_buf + tty->read_head, cp, i);
        tty->read_head = (tty->read_head + i) & (N_TTY_BUF_SIZE-1);
        tty->read_cnt += i;
        cp += i;
        count -= i;
        i = min(N_TTY_BUF_SIZE - tty->read_cnt, N_TTY_BUF_SIZE - tty->read_head);
        i = min(count, i);
        memcpy(tty->read_buf + tty->read_head, cp, i);
        tty->read_head = (tty->read_head + i) & (N_TTY_BUF_SIZE-1);
        tty->read_cnt += i;
        spin_unlock_irqrestore(&tty->read_lock, cpuflags);
    } else {
        for (i = count, p = cp, f = fp; i; i--, p++) {
        //如果沒有設定tty->real_raw,就根據傳入資料标志分類擷取資料。
        ...
        }
        ...
    }
    ...
}      

到此,資料讀取的整個過程就結束了。可以看出資料讀取可以分為兩個階段:一個階段是上層函數從環形緩存區tty->read_buf 讀取資料,第二階段是底層函數将接收的資料考到環形緩存區tty->read_buf 中。

(5)序列槽驅動程式

序列槽驅動之tty

  • 概念解析:在Linux中,終端是一類字元裝置,他包括多種類型通常使用tty來簡稱各種中斷裝置:
  • 序列槽終端(/dev/ttyS*):序列槽終端是使用序列槽連接配接的終端裝置,Linux中将每個序列槽裝置 都看作一個字元裝置,這些串行端口對應的裝置名稱是/dev/ttySAC0 和/dev/ttySAC1
  • 控制台終端(/dev/console):在Linux中,計算中的輸出裝置裝置通常被稱為控制台終端(console).這裡特指printk()資訊輸出的涉筆。注意:/dev/console 是一個虛拟的裝置,他需要映射到真正的tty上。比如通過核心啟動參數“console = ttySAC0”就是把console 映射到序列槽0,經常被核心所使用。注意:這裡的終端是一個虛拟裝置,虛拟 裝置必須和實際的裝置聯系起來console = ttySAC0 系統啟動時候就關聯起來了
  • 虛拟終端(/dev/tty*):當使用者登入的時候使用的是虛拟終端,使用快捷鍵組合:ctcl+alt+[F1-F6]組合鍵就可以切換到tty1,tty2,tty3等上面去。tty1-tty6等稱為虛拟終端,而tty0 是目前使用的終端的一個别名。主要是提供給應用程式使用。
  • tty架構
  • tty核心:tty核心是對整個tty裝置的抽象,并提供單一的接口
  • tty線路規劃:tty線路規程是對資料的傳輸的格式化,比如需要實作某種協定,就需要将協定的實作代碼放在該位置
  • tty驅動:是面向tty裝置的硬體驅動

注意:Linux中的擷取回溯資訊使用函數 dump_stack()用來顯示各種函數的調用資訊。

序列槽驅動程式的結構

  • 分析:序列槽驅動需要提供給使用者讀資料的功能,寫資料,打開序列槽和關閉序列槽的功能。打開之前需要對肯定需要對序列槽進行初始化的工作。
  • 重要資料結構:
  • UART 驅動程式結構:struct uart_driver//一個序列槽對應一個序列槽驅動,用于描述序列槽結構
  • UART 端口結構:struct uart_port//有幾個序列槽就對應幾個port
  • UART 相關操作函數結構struct uart_ops//對應相關序列槽所支援的操作函集
  • UART 狀态結構:struct uart_state
  • UART 資訊結構:struct uart_info
  • 序列槽初始化:
  1. 定義并描述序列槽:struct uart_driver
  2. 注冊序列槽驅動程式:uart_register_driver
  1. 取出相應的序列槽
  2. 初始化該取出的序列槽
  • 序列槽驅動之打開驅動:
  • 系統調用過程:使用者使用open()函數打開裝置檔案
  1. 打開裝置檔案肯定有對應的裝置驅動檔案打開函數:file_operations.
  2. 在使用uart_register_driver ()注冊序列槽驅動的時候,該函數裡面會調用函數tty_register_driver(),該函數會調用cdev_init()函數和cdev_add()。
  3. 從這裡可以看出tty裝置是屬于字元裝置
——>核心調用cdev結構中的file-operations指針所指向結構裡的tty-open()函數——>tty-open()函數緊接着調用tty_struct 結構中的tty_operations指針所指向的結構裡的uart_open() 函數——>該函數接着調用uart_satarup()函數——>該函數會調用uart_port 結構中的ops 指針所指向是操作函數集合中檔案操作函數。
  • 打開步驟:使能序列槽接收功能——>為資料的接收注冊中斷處理程式——>使能發送功能——>為發送資料注冊中斷處理程式
  • 序列槽驅動之發送資料:
  • 系統調用過程:使用者通過使用write()函數實作發送資料

write()函數調用cdev結構中的file_operations 指針所指向的結構裡tty_write()函數——>該函數會調用tty_ldisc_ops 結構中的write指針所指向的函數n_tty_write()函數——>該函數會調用uart_tty 結構中的指針write所指向的函數uart_write()函數——>該函數會調用函數uart_startup()函數——>該函數會調用uart_port結構中的ops指針所指的操作函數集合中的檔案發送函數。

注意:當使能中斷後,驅動程式發現FIFO 中資料量小于某一個值就會觸發中斷,在中斷處理程式中實作相應的資料發送

  • 循環緩沖:在使用函數write()的時候,會經過tty核的處理,在處理的過程中,會将要發現的資料放到一個地方,這個地方就叫做循環緩沖
注意:FIFO 中的資料是先入先出。對于循環緩沖中的資料,在沒有資料的時候 ,tail 和head 是在緩沖資料的同一個位置,當往其中裝入資料的時候,head 跟随寫入資料的變化而變化,但是發送資料的起始位置是在tail
  • 資料發送過程:
  1. 判斷是否有需要發送的x_char,如果用,通過UTXH寄存器發送。注意:x_char用于表示接收資料端的狀态用,是否滿足接收資料的條件
  2. 判斷循環緩沖或序列槽狀态不允許發送資料則需要停止發送資料
  3. 使用while()循環來發資料,發送規則<1>循環換緩沖有資料<2>發送的資料量不到256。注意:
  1. 發送FIFO中資料為滿時停止發送
  2. 從循環緩沖中的尾部(tail)中取資料,并将資料送入到UTXH寄存中
  3. 使用自加運算來調整循環緩沖中的資料的位置
  1. 如果循環緩沖中的資料量低于256 則喚醒在發送時阻塞的程序(在之前有程序需要王循環緩沖中發送資料,但是發現循環緩沖中的沒有足夠的空間進行來存放要發送的資料,則程序進入休眠狀态)
  2. 如果循環緩沖中資料為空則需要關閉中斷,否則将不斷産生中斷影響系統的穩定運作
  • 序列槽驅動程式之資料接收:
  • 系統調用過程:應用程式要接收資料肯定要使用read() 函數
  • 流程:
使用者程式調用函數read()——>read() 函數會調用結構file_operations中read指針所指向的tty_read()函數——>該函數會調用結構tty_ldisc_tty結構中的指針read所指向的n_tty_read()函數——>
  • n_tty_read()函數分析:
  1. 将應用程式執行程序狀态設定成(TASK_INTERRUPTIBLE)。注意:設定成該狀态但是程式并不會直接進入阻塞态,需要在程序排程中才會進入相應的狀态
  2. 如果沒有資料可讀,這通過 排程程式實作将程序進入阻塞态度
  3. 如果readbuf中有資料則從中讀取資料
  • 驅動程式處理流程:
  1. 讀取UFCON寄存器
  2. 讀取UFSTAT寄存器
  3. UFSTAT中的fifocnt 的大小為零退出處理
  4. UFSTAT 中的UERSAT判斷錯誤類型
  5. 從URXH中取出接收到的資料
  6. 進行流控處理
  7. 根據UERSTAT的值記錄具體的錯誤類型
  8. 如果接受到的是sysrq字元,則調用特殊處理函數-uart_handle_sysrq_chgr()
  9. 把字元送入序列槽驅動的buffer,調用函數uart_insert_char
  10. 将序列槽驅動buffer中的資料送進線路規程的read_buf ,調用函數tty_flip_buffer_push
  • 流控:當發送資料給接收方,接收方的資料緩沖區裡面沒有空餘空間可以使用的時候就需要通知發送方停止發送資料,否則發送方的資料就不能被接收到。
  • 流控方式:
  • 軟體流控:接收端通過串行線向發送端發送資料x_off表示不再接收,當可以再接收時再向發送端發送x_on。
  • 硬體流控:通過使用硬體聯系連接配接起來,當不能接收資料的時候就會使 相應連線産生高電平,發送端每發送一次說都會檢測該引腳

五、執行個體

該代碼摘取自LDD3一書中,它建立了一個簡單的序列槽驅動,為了便于大家進行學習,它沒有使用實際的序列槽硬體,而是使用了一個定時器來模拟從其它硬體接收到資料。相信通過前面4個章節的講解,大家應該能夠看懂下面的代碼

/*
 * This driver shows how to create a minimal serial driver.  It does not rely on
 * any backing hardware, but creates a timer that emulates data being received
 * from some kind of hardware.
 */

#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <linux/serial.h>
#include <linux/serial_core.h>
#include <linux/module.h>


#define DRIVER_AUTHOR "Greg Kroah-Hartman <[email protected]>"
#define DRIVER_DESC "Tiny serial driver"

/* Module information */
MODULE_AUTHOR( DRIVER_AUTHOR );
MODULE_DESCRIPTION( DRIVER_DESC );
MODULE_LICENSE("GPL");

#define DELAY_TIME    HZ * 2    /* 每發送1個字元延遲2秒 */
#define TINY_DATA_CHARACTER 't'

#define TINY_SERIAL_MAJOR 240   /* 主裝置号 */
#define TINY_SERIAL_MINORS  1   /* 次裝置個數 */
#define UART_NR       1   /* 端口數 */

#define TINY_SERIAL_NAME  "ttytiny" /* 自定義序列槽驅動名 */

#define MY_NAME     TINY_SERIAL_NAME

static struct timer_list *timer;

static void tiny_stop_tx(struct uart_port *port, unsigned int tty_stop)
{
}

static void tiny_stop_rx(struct uart_port *port)
{
}

static void tiny_enable_ms(struct uart_port *port)
{
}

static void tiny_tx_chars(struct uart_port *port)
{
  struct circ_buf *xmit = &port->info->xmit;
  int count;

  if (port->x_char) {
    pr_debug("wrote %2x", port->x_char);
    port->icount.tx++;
    port->x_char = 0;
    return;
  }
  if (uart_circ_empty(xmit) || uart_tx_stopped(port)) {
    tiny_stop_tx(port, 0);
    return;
  }

  count = port->fifosize >> 1;
  do {
    pr_debug("wrote %2x", xmit->buf[xmit->tail]);
    xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
    port->icount.tx++;
    if (uart_circ_empty(xmit))
      break;
  } while (--count > 0);

  if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
    uart_write_wakeup(port);

  if (uart_circ_empty(xmit))
    tiny_stop_tx(port, 0);
}

static void tiny_start_tx(struct uart_port *port, unsigned int tty_start)
{
}

static void tiny_timer(unsigned long data)
{
  struct uart_port *port;
  struct tty_struct *tty;


  port = (struct uart_port *)data;
  if (!port)
    return;
  if (!port->info)
    return;
  tty = port->info->tty;
  if (!tty)
    return;

  /* add one character to the tty port */
  /* this doesn't actually push the data through unless tty->low_latency is set */
  tty_insert_flip_char(tty, TINY_DATA_CHARACTER, 0);

  tty_flip_buffer_push(tty);

  /* resubmit the timer again */
  timer->expires = jiffies + DELAY_TIME;
  add_timer(timer);

  /* see if we have any data to transmit */
  tiny_tx_chars(port);
}

static unsigned int tiny_tx_empty(struct uart_port *port)
{
  return 0;
}

static unsigned int tiny_get_mctrl(struct uart_port *port)
{
  return 0;
}

static void tiny_set_mctrl(struct uart_port *port, unsigned int mctrl)
{
}

static void tiny_break_ctl(struct uart_port *port, int break_state)
{
}

static void tiny_set_termios(struct uart_port *port,
                 struct termios *new, struct termios *old)
{
  int baud, quot, cflag = new->c_cflag;
  /* get the byte size */
  switch (cflag & CSIZE) {
  case CS5:
    printk(KERN_DEBUG " - data bits = 5\n");
    break;
  case CS6:
    printk(KERN_DEBUG " - data bits = 6\n");
    break;
  case CS7:
    printk(KERN_DEBUG " - data bits = 7\n");
    break;
  default: // CS8
    printk(KERN_DEBUG " - data bits = 8\n");
    break;
  }

  /* determine the parity */
  if (cflag & PARENB)
    if (cflag & PARODD)
      pr_debug(" - parity = odd\n");
    else
      pr_debug(" - parity = even\n");
  else
    pr_debug(" - parity = none\n");

  /* figure out the stop bits requested */
  if (cflag & CSTOPB)
    pr_debug(" - stop bits = 2\n");
  else
    pr_debug(" - stop bits = 1\n");

  /* figure out the flow control settings */
  if (cflag & CRTSCTS)
    pr_debug(" - RTS/CTS is enabled\n");
  else
    pr_debug(" - RTS/CTS is disabled\n");

  /* Set baud rate */
        baud = uart_get_baud_rate(port, new, old, 0, port->uartclk/16);
        quot = uart_get_divisor(port, baud);
  
  //UART_PUT_DIV_LO(port, (quot & 0xff));
  //UART_PUT_DIV_HI(port, ((quot & 0xf00) >> 8));
}

/* 啟動端口 */
static int tiny_startup(struct uart_port *port) 
{
  /* this is the first time this port is opened */
  /* do any hardware initialization needed here */

  /* create our timer and submit it */
  if (!timer) {
    timer = kmalloc(sizeof(*timer), GFP_KERNEL);
    if (!timer)
      return -ENOMEM;
  }
  timer->data = (unsigned long)port;
  timer->expires = jiffies + DELAY_TIME;
  timer->function = tiny_timer;      //逾時回調函數
  add_timer(timer);
  return 0;
}

/* 關閉端口 */
static void tiny_shutdown(struct uart_port *port)
{
  /* The port is being closed by the last user. */
  /* Do any hardware specific stuff here */

  /* shut down our timer */
  del_timer(timer);
}

static const char *tiny_type(struct uart_port *port)
{
  return "tinytty";
}

static void tiny_release_port(struct uart_port *port)
{

}

static int tiny_request_port(struct uart_port *port)
{
  return 0;
}

static void tiny_config_port(struct uart_port *port, int flags)
{
}

static int tiny_verify_port(struct uart_port *port, struct serial_struct *ser)
{
  return 0;
}

static struct uart_ops tiny_ops = {         //5.初始化UART端口操作方法
  .tx_empty = tiny_tx_empty,          
  .set_mctrl  = tiny_set_mctrl,
  .get_mctrl  = tiny_get_mctrl,
  .stop_tx  = tiny_stop_tx,
  .start_tx = tiny_start_tx,
  .stop_rx  = tiny_stop_rx,
  .enable_ms  = tiny_enable_ms,
  .break_ctl  = tiny_break_ctl,
  .startup  = tiny_startup,
  .shutdown = tiny_shutdown,
  .set_termios  = tiny_set_termios,
  .type     = tiny_type,
  .release_port = tiny_release_port,
  .request_port = tiny_request_port,
  .config_port  = tiny_config_port,
  .verify_port  = tiny_verify_port,
};

static struct uart_port tiny_port = {       //4.初始化UART端口
  .ops    = &tiny_ops,
};

static struct uart_driver tiny_reg = {        //3.初始化UART驅動
  .owner    = THIS_MODULE,
  .driver_name= TINY_SERIAL_NAME,
  .dev_name = TINY_SERIAL_NAME,
  .major    = TINY_SERIAL_MAJOR,
  .minor    = TINY_SERIAL_MINORS,
  .nr     = UART_NR,
};

static int __init tiny_init(void)
{
  int result;
  printk(KERN_INFO "Tiny serial driver loaded\n");
  result = uart_register_driver(&tiny_reg);   //1.注冊UART驅動
  if (result)
    return result;

  result = uart_add_one_port(&tiny_reg, &tiny_port);  //2.為UART添加端口
  if (result)
    uart_unregister_driver(&tiny_reg);

  return result;
}

module_init(tiny_init);      

【參考】

.break_ctl  = tiny_break_ctl,
.startup  = tiny_startup,
.shutdown = tiny_shutdown,
.set_termios  = tiny_set_termios,
.type     = tiny_type,
.release_port = tiny_release_port,
.request_port = tiny_request_port,
.config_port  = tiny_config_port,
.verify_port  = tiny_verify_port,      
};
static struct uart_port tiny_port = { //4.初始化UART端口
 .ops = &tiny_ops,
 };static struct uart_driver tiny_reg = { //3.初始化UART驅動
 .owner = THIS_MODULE,
 .driver_name= TINY_SERIAL_NAME,
 .dev_name = TINY_SERIAL_NAME,
 .major = TINY_SERIAL_MAJOR,
 .minor = TINY_SERIAL_MINORS,
 .nr = UART_NR,
 };static int __init tiny_init(void)
 {
 int result;
 printk(KERN_INFO “Tiny serial driver loaded\n”);
 result = uart_register_driver(&tiny_reg); //1.注冊UART驅動
 if (result)
 return result;      
result = uart_add_one_port(&tiny_reg, &tiny_port);  //2.為UART添加端口
if (result)
  uart_unregister_driver(&tiny_reg);

return result;      
}
module_init(tiny_init);      
**【參考】**

[基于Linux的tty架構及UART驅動詳解 (qq.com)](https://mp.weixin.qq.com/s?__biz=MzUxMjEyNDgyNw==&mid=2247491957&idx=1&sn=a5bdd1d224465b167bf239dae2131fff&chksm=f96b9381ce1c1a9792157647b644d2e41f67bd3050ee8fa0ff3f32ef601930cb8ec2c53c90f0&scene=178&cur_album_id=1502410824114569216#rd)      

繼續閱讀