Linux終端tty設備驅動編程

在Linux系統中,終端設備非常重要,沒有終端設備,系統將無法向用戶反饋信息,Linux中包含控制檯、串口和僞終端3類終端設備。
14.1節闡述了終端設備的概念及分類,14.2節給出了Linux終端設備驅動的框架結構,重點描述tty_driver結構體及其成員。14.3~14.5節在14.2節的基礎上,分別給出了Linux終端設備驅動模塊加載/卸載函數和open()、close()函數,數據讀寫流程及tty設備線路設置的編程方法。在Linux中,串口驅動完全遵循tty驅動的框架結構,但是進行了底層操作的再次封裝,14.6節描述了Linux針對串口tty驅動的這一封裝,14.7節則具體給出了串口tty驅動的實現方法。14.8節基於14.6和14.7節的講解給出了串口tty驅動的設計實例,即S3C2410集成UART的驅動。

14.1終端設備
  
在Linux系統中,終端是一種字符型設備,它有多種類型,通常使用tty來簡稱各種類型的終端設備。tty是Teletype的縮寫,Teletype是最早出現的一種終端設備,很像電傳打字機,是由Teletype公司生產的。Linux中包含如下幾類終端設備:
1、串行端口終端(/dev/ttySn
    串行端口終端(Serial Port Terminal)是使用計算機串行端口連接的終端設備。計算機把每個串行端口都看作是一個字符設備。這些串行端口所對應的設備名稱是 /dev/ttyS0(或/dev/tts/0)、/dev/ttyS1(或/dev/tts/1)等,設備號分別是(4,0)、(4,1)等。
    在命令行上把標準輸出重定向到端口對應的設備文件名上就可以通過該端口發送數據,例如,在命令行提示符下鍵入: echo test > /dev/ttyS1會把單詞“test”發送到連接在ttyS1端口的設備上。
2.僞終端(/dev/pty/
   
僞終端(Pseudo Terminal)是成對的邏輯終端設備,並存在成對的設備文件,如/dev/ptyp3和/dev/ttyp3,它們與實際物理設備並不直接相關。如果一個程序把ttyp3看作是一個串行端口設備,則它對該端口的讀/寫操作會反映在該邏輯終端設備對應的ptyp3上,而ptyp3則是另一個程序用於讀寫操作的邏輯設備。這樣,兩個程序就可以通過這種邏輯設備進行互相交流,使用ttyp3的程序會認爲自己正在與一個串行端口進行通信。
    以telnet 爲例,如果某人在使用telnet程序連接到Linux系統,則telnet程序就可能會開始連接到設備ptyp2上,而此時一個getty程序會運行在對應的ttyp2端口上。當telnet從遠端獲取了一個字符時,該字符就會通過ptyp2、ttyp2傳遞給 getty程序,而getty程序則會通過ttyp2、ptyp2和telnet程序返回“login:”字符串信息。這樣,登錄程序與telnet程序 就通過僞終端進行通信。通過使用適當的軟件,可以把2個或多個僞終端設備連接到同一個物理串行端口上。
3.控制檯終端(/dev/ttyn, /dev/console
   
如果當前進程有控制終端(Controlling Terminal)的話,那麼/dev/tty就是當前進程的控制終端的設備特殊文件。可以使用命令“ps –ax”來查看進程與哪個控制終端相連使用命令“tty”可以查看它具體對應哪個實際終端設備。/dev/tty有些類似於到實際所使用終端設備的一個聯接。
    在UNIX系統中,計算機顯示器通常被稱爲控制檯終端(Console)。它仿真了類型爲Linux的一種終端(TERM=Linux),並且有一些設備特殊文件與之相關聯:tty0、tty1、tty2等。當用戶在控制檯上登錄時,使用的是tty1。使用Alt+[F1—F6]組合鍵時,我們就可以切換到tty2、tty3等上面去。tty1–tty6等稱爲虛擬終端,而tty0則是當前所使用虛擬終端的一個別名,系統所產生的信息會發送到該終端上。因此不管當前正在使用哪個虛擬終端,系統信息都會發送到控制檯終端上。用戶可以登錄到不同的虛擬終端上去,因而可以讓系統同時有幾個不同的會話期存在。只有系統或超級用戶root可以向/dev/tty0進行寫操作。
在Linux 中,可以在系統啓動命令行裏指定當前的輸出終端,格式如下:
console=device, options
device指代的是終端設備,可以是tty0(前臺的虛擬終端)、ttyX(第X個虛擬終端)、ttySX(第X個串口)、lp0(第一個並口)等。options指代對device進行的設置,它取決於具體的設備驅動。對於串口設備,參數用來定義爲:波特率、校驗位、位數,格式爲BBBBPN,其中BBBB表示波特率,P表示校驗(n/o/e),N表示位數,默認options是9600n8。
    用戶可以在內核命令行中同時設定多個終端,這樣輸出將會在所有的終端上顯示,而當用戶調用open()打開/dev/console時,最後一個終端將會返回作爲當前值。例如:
console=ttyS1, 9600 console=tty0
定義了2個終端,而調用open()打開/dev/console時,將使用虛擬終端tty0。但是內核消息會在tty0 VGA虛擬終端和串口ttyS1上同時顯示。
    通過查看/proc/tty/drivers文件可以獲知什麼類型的tty設備存在以及什麼驅動被加載到內核,這個文件包括一個當前存在的不同 tty 驅動的列表,包括驅動名、缺省的節點名、驅動的主編號、這個驅動使用的次編號範圍,以及 tty 驅動的類型。例如,下面給出了一個/proc/tty/drivers文件的例子:
 
14.2終端設備驅動結構
   
Linux內核中 tty的層次結構如圖14.1所示,包含tty核心、tty線路規程和tty驅動,tty 線路規程的工作是以特殊的方式格式化從一個用戶或者硬件收到的數據,這種格式化常常採用一個協議轉換的形式,例如 PPP 和 Bluetooth。tty設備發送數據的流程爲:tty核心從一個用戶獲取將要發送給一個 tty設備的數據,tty核心將數據傳遞給tty線路規程驅動,接着數據被傳遞到tty驅動,tty驅動將數據轉換爲可以發送給硬件的格式。接收數據的流程爲: 從tty硬件接收到的數據向上交給tty驅動,進入tty線路規程驅動,再進入 tty 核心,在這裏它被一個用戶獲取。儘管大多數時候tty核心和tty之間的數據傳輸會經歷tty線路規程的轉換,但是tty驅動與tty核心之間也可以直接傳輸數據。
                 <!--[if !vml]--><!--[endif]-->
                           圖14.1 tty分層結構
    圖14.2顯示了與tty相關的主要源文件及數據的流向。tty_io.c定義了tty 設備通用的file_operations結構體並實現了接口函數tty_register_driver()用於註冊tty設備,它會利用 fs/char_dev.c提供的接口函數註冊字符設備,與具體設備對應的tty驅動將實現tty_driver結構體中的成員函數。同時 tty_io.c也提供了tty_register_ldisc()接口函數用於註冊線路規程,n_tty.c文件則實現了tty_disc結構體中的成員。
                 <!--[if !vml]--><!--[endif]-->
圖14.2 tty主要源文件關係及數據流向
從圖14.2可以看出,特定tty設備驅動的主體工作是填充tty_driver結構體中的成員,實現其中的成員函數,tty_driver結構體的定義如代碼清單14.1。
代碼清單14.1 tty_driver結構體
1  struct tty_driver                                                            
2  {                                                                            
3    int magic;                                                                 
4    struct cdev cdev; /* 對應的字符設備cdev */                                                          
5    struct module *owner;   /*這個驅動的模塊擁有者 */                                                   
6    const char *driver_name;                                                   
7    const char *devfs_name;                                                    
8    const char *name;   /* 設備名 */                                                       
9    int name_base; /* offset of printed name */                    
10   int major; /* 主設備號 */                                                  
11   int minor_start; /* 開始次設備號 */                     
12   int minor_num; /* 設備數量 */                                              
13   int num; /* 被分配的設備數量 */                                            
14   short type; /* tty驅動的類型 */                        
15   short subtype; /* tty驅動的子類型 */             
16   struct termios init_termios; /* 初始線路設置 */             
17   int flags; /* tty驅動標誌 */                          
18   int refcount; /*引用計數(針對可加載的tty驅動) */                   
19   struct proc_dir_entry *proc_entry; /* /proc文件系統入口 */         
20   struct tty_driver *other; /* 僅對PTY驅動有意義 */          
21   ...                                                                        
22   /* 接口函數 */                                                             
23   int(*open)(struct tty_struct *tty, struct file *filp);      
24   void(*close)(struct tty_struct *tty, struct file *filp);     
25   int(*write)(struct tty_struct *tty, const unsigned char *buf, int count);  
26   void(*put_char)(struct tty_struct *tty, unsigned char ch);       
27   void(*flush_chars)(struct tty_struct *tty);                    
28   int(*write_room)(struct tty_struct *tty);                        
29   int(*chars_in_buffer)(struct tty_struct *tty);                  
30   int(*ioctl)(struct tty_struct *tty, struct file *file, unsigned int cmd,   
31     unsigned long arg);                                                      
32   void(*set_termios)(struct tty_struct *tty, struct termios *old);    
33   void(*throttle)(struct tty_struct *tty);                       
34   void(*unthrottle)(struct tty_struct *tty);                   
35   void(*stop)(struct tty_struct *tty);                            
36   void(*start)(struct tty_struct *tty);                     
37   void(*hangup)(struct tty_struct *tty);               
38   void(*break_ctl)(struct tty_struct *tty, int state);                   
39   void(*flush_buffer)(struct tty_struct *tty);                       
40   void(*set_ldisc)(struct tty_struct *tty);                     
41   void(*wait_until_sent)(struct tty_struct *tty, int timeout);        
42   void(*send_xchar)(struct tty_struct *tty, char ch);                  
43   int(*read_proc)(char *page, char **start, off_t off, int count, int *eof,  
44     void *data);                                                             
45   int(*write_proc)(struct file *file, const char __user *buffer, unsigned long
46     count, void *data);                                                      
47   int(*tiocmget)(struct tty_struct *tty, struct file *file);           
48   int(*tiocmset)(struct tty_struct *tty, struct file *file, unsigned int set,
49     unsigned int clear);                                                     
50                                                                              
51   struct list_head tty_drivers;                          
52 };   
    tty_driver結構體中的magic表示給這個結構體的“幻數”,設爲 TTY_DRIVER_MAGIC,在 alloc_tty_driver()函數中被初始化。name與driver_name的不同在於後者表示驅動的名字,用在 /proc/tty 和 sysfs中,而前者表示驅動的設備節點名。type 與subtype描述tty驅動的類型和子類型,subtype的值依賴於type,type成員的可能值爲 TTY_DRIVER_TYPE_SYSTEM(由tty子系統內部使用,subtype 應當設爲 SYSTEM_TYPE_TTY、SYSTEM_TYEP_CONSOLE、SYSTEM_TYPE_SYSCONS或 SYSTEM_TYPE_SYSPTMX,這個類型不應當被任何常規tty驅動使用)、TTY_DRIVER_TYPE_CONSOLE(僅被控制檯驅動使用)、TTY_DRIVER_TYPE_SERIAL(被任何串行類型驅動使用,subtype 應當設爲 SERIAL_TYPE_NORMAL 或SERIAL_TYPE_CALLOUT)、TTY_DRIVER_TYPE_PTY(被僞控制檯接口pty使用,此時subtype需要被設置爲 PTY_TYPE_MASTER 或 PTY_TYPE_SLAVE)。init_termios 爲初始線路設置,爲一個termios結構體,這個成員被用來提供一個線路設置集合。termios 用於保存當前的線路設置,這些線路設置控制當前波特率、數據大小、數據流控設置等,這個結構體包含tcflag_t c_iflag(輸入模式標誌)、tcflag_t c_oflag(輸出模式標誌)、tcflag_t c_cflag(控制模式標誌)、tcflag_t c_lflag(本地模式標誌)、cc_t c_line(線路規程類型)、cc_t c_cc[NCCS](一個控制字符數組)等成員。驅動會使用一個標準的數值集初始化這個成員,它拷貝自tty_std_termios變量,tty_std_termos在tty核心中的定義如代碼清單14.2。
代碼清單14.2 tty_std_termios變量
1  struct termios tty_std_termios =
2  {
3   .c_iflag = ICRNL | IXON, /* 輸入模式 */
4   .c_oflag = OPOST | ONLCR, /* 輸出模式 */
5   .c_cflag = B38400 | CS8 | CREAD | HUPCL, /* 控制模式 */
6   .c_lflag = ISIG | ICANON | ECHO | ECHOE | ECHOK |
7   ECHOCTL | ECHOKE | IEXTEN,  /* 本地模式 */
8   .c_cc = INIT_C_CC  /* 控制字符,用來修改終端的特殊字符映射 */
9  };
     tty_driver 結構體中的major、minor_start、minor_num表示主設備號、次設備號及可能的次設備數,name表示設備名(如ttyS),第 23~49行的函數指針實際和tty_operations結構體等同,它們通常需在特定設備tty驅動模塊初始化函數中被賦值。put_char()爲單字節寫函數,當單個字節被寫入設備時這個函數被 tty 核心調用,如果一個 tty 驅動沒有定義這個函數,將使用count參數爲1的write()函數。flush_chars()與wait_until_sent()函數都用於刷新數據到硬件。write_room()指示有多少緩衝區空閒,chars_in_buffer()指示緩衝區中包含的數據數。當 ioctl(2)在設備節點上被調用時,ioctl()函數將被 tty核心調用。當設備的 termios 設置被改變時,set_termios()函數將被tty核心調用。throttle ()、unthrottle()、stop()和start()爲數據抑制函數,這些函數用來幫助控制 tty 核心的輸入緩存。當 tty 核心的輸入緩衝滿時,throttle()函數將被調用,tty驅動試圖通知設備不應當發送字符給它。當 tty 核心的輸入緩衝已被清空時,unthrottle()函數將被調用暗示設備可以接收數據。stop()和start()函數非常像throttle()和 unthrottle()函數,但它們表示 tty 驅動應當停止發送數據給設備以及恢復發送數據。
     當 tty驅動掛起 tty設備時,hangup()函數被調用,在此函數中進行相關的硬件操作。當tty 驅動要在 RS-232 端口上打開或關閉線路的 BREAK 狀態時,break_ctl()線路中斷控制函數被調用。如果state狀態設爲-1,BREAK 狀態打開,如果狀態設爲 0,BREAK 狀態關閉。如果這個函數由 tty 驅動實現,而tty核心將處理TCSBRK、TCSBRKP、TIOCSBRK和 TIOCCBRK這些ioctl命令。flush_buffer()函數用於刷新緩衝區並丟棄任何剩下的數據。set_ldisc()函數用於設置線路規程,當 tty 核心改變tty驅動的線路規程時這個函數被調用,這個函數通常不需要被驅動定義。send_xchar()爲X-類型字符發送函數,這個函數用來發送一個高優先級 XON 或者 XOFF 字符給 tty設備,要被髮送的字符在第2個參數ch中指定。read_proc()和write_proc()爲/proc 讀和寫函數。tiocmget()函數用於獲得tty 設備的線路設置,對應的tiocmset()用於設置tty設備的線路設置,參數set和clear包含了要設置或者清除的線路設置。
    Linux內核提供了一組函數用於操作tty_driver結構體及tty設備,包括:
•  分配tty驅動
struct tty_driver *alloc_tty_driver(int lines);
這個函數返回tty_driver指針,其參數爲要分配的設備數量,line會被賦值給tty_driver的num成員,例如:
xxx_tty_driver = alloc_tty_driver(XXX_TTY_MINORS);
if (!xxx_tty_driver) //分配失敗
 return -ENOMEM; 
•  註冊tty驅動
int tty_register_driver(struct tty_driver *driver);
註冊tty驅動成功時返回0,參數爲由alloc_tty_driver ()分配的tty_driver結構體指針,例如:
retval = tty_register_driver(xxx_tty_driver);
if (retval) //註冊失敗
{
  printk(KERN_ERR "failed to register tiny tty driver");
  put_tty_driver(xxx_tty_driver);
  return retval;
}
•  註銷tty驅動
int tty_unregister_driver(struct tty_driver *driver);
這個函數與tty_register_driver ()對應,tty驅動最終會調用上述函數註銷tty_driver。
•  註冊tty設備
void tty_register_device(struct tty_driver *driver, unsigned index,
    struct device *device);
僅有tty_driver是不夠的,驅動必須依附於設備,tty_register_device()函數用於註冊關聯於tty_driver的設備,index爲設備的索引(範圍是0~driver->num),如:
for (i = 0; i < XXX_TTY_MINORS; ++i)
tty_register_device(xxx_tty_driver, i, NULL);
•  註銷tty設備
void tty_unregister_device(struct tty_driver *driver, unsigned index);
上述函數與tty_register_device()對應,用於註銷tty設備,其使用方法如:
for (i = 0; i < XXX_TTY_MINORS; ++i)
tty_unregister_device(xxx_tty_driver, i);
•  設置tty驅動操作
void tty_set_operations(struct tty_driver *driver, struct tty_operations *op);
上述函數會將tty_operations結構體中的函數指針拷貝給tty_driver對應的函數指針,在具體的tty驅動中,通常會定義1個設備特定的 tty_operations,tty_operations的定義如代碼清單14.3。tty_operations中的成員函數與 tty_driver中的同名成員函數意義完全一致,因此,這裏不再贅述。
代碼清單14.3 tty_operations結構體
1  struct tty_operations
2  {
3     int  (*open)(struct tty_struct * tty, struct file * filp);
4     void (*close)(struct tty_struct * tty, struct file * filp);
5     int  (*write)(struct tty_struct * tty,
6                   const unsigned char *buf, int count);
7     void (*put_char)(struct tty_struct *tty, unsigned char ch);
8     void (*flush_chars)(struct tty_struct *tty);
9     int  (*write_room)(struct tty_struct *tty);
10    int  (*chars_in_buffer)(struct tty_struct *tty);
11    int  (*ioctl)(struct tty_struct *tty, struct file * file,
12                unsigned int cmd, unsigned long arg);
13    void (*set_termios)(struct tty_struct *tty, struct termios * old);
14    void (*throttle)(struct tty_struct * tty);
15    void (*unthrottle)(struct tty_struct * tty);
16    void (*stop)(struct tty_struct *tty);
17    void (*start)(struct tty_struct *tty);
18    void (*hangup)(struct tty_struct *tty);
19    void (*break_ctl)(struct tty_struct *tty, int state);
20    void (*flush_buffer)(struct tty_struct *tty);
21    void (*set_ldisc)(struct tty_struct *tty);
22    void (*wait_until_sent)(struct tty_struct *tty, int timeout);
23    void (*send_xchar)(struct tty_struct *tty, char ch);
24    int (*read_proc)(char *page, char **start, off_t off,
25                      int count, int *eof, void *data);
26    int (*write_proc)(struct file *file, const char __user *buffer,
27                      unsigned long count, void *data);
28    int (*tiocmget)(struct tty_struct *tty, struct file *file);
29    int (*tiocmset)(struct tty_struct *tty, struct file *file,
30                         unsigned int set, unsigned int clear);
31 };
    終端設備驅動都圍繞tty_driver結構體而展開,一般而言,終端設備驅動應包含如下組成:
•  終端設備驅動模塊加載函數和卸載函數,完成註冊和註銷tty_driver,初始化和釋放終端設備對應的tty_driver結構體成員及硬件資源。
•  實現tty_operations結構體中的一系列成員函數,主要是實現open()、close()、write()、tiocmget()、tiocmset()等函數。

14.3終端設備驅動初始化與釋放
14.3.1模塊加載與卸載函數
    tty驅動的模塊加載函數中通常需要分配、初始化tty_driver結構體並申請必要的硬件資源,代碼清單14.4。tty驅動的模塊卸載函數完成與模塊加載函數完成相反的工作。
代碼清單14.4 終端設備驅動模塊加載函數範例
1  /* tty驅動模塊加載函數 */
2  static int __init xxx_init(void)
3  {
4    ...
5    /* 分配tty_driver結構體 */
6    xxx_tty_driver = alloc_tty_driver(XXX_PORTS);
7    /* 初始化tty_driver結構體 */
8    xxx_tty_driver->owner = THIS_MODULE;
9    xxx_tty_driver->devfs_name = "tts/";
10   xxx_tty_driver->name = "ttyS";
11   xxx_tty_driver->major = TTY_MAJOR;
12   xxx_tty_driver->minor_start = 64;
13   xxx_tty_driver->type = TTY_DRIVER_TYPE_SERIAL;
14   xxx_tty_driver->subtype = SERIAL_TYPE_NORMAL;
15   xxx_tty_driver->init_termios = tty_std_termios;
16   xxx_tty_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
17   xxx_tty_driver->flags = TTY_DRIVER_REAL_RAW;
18   tty_set_operations(xxx_tty_driver, &xxx_ops);
19
20   ret = tty_register_driver(xxx_tty_driver);
21   if (ret)
22   {
23     printk(KERN_ERR "Couldn't register xxx serial driver\n");
24     put_tty_driver(xxx_tty_driver);
25     return ret;
26   }
27
28   ...
29   ret = request_irq(...); /* 硬件資源申請 */
30   ...
31 } 
14.3.2打開與關閉函數
    當用戶對tty驅動所分配的設備節點進行open()系統調用時,tty_driver中的open()成員函數將被tty核心調用。tty 驅動必須設置open()成員,否則,-ENODEV將被返回給調用open()的用戶。open()成員函數的第1個參數爲一個指向分配給這個設備的 tty_struct 結構體的指針,第2個參數爲文件指針。
tty_struct結構體被 tty核心用來保存當前tty端口的狀態,它的大多數成員只被 tty核心使用。tty_struct中的幾個重要成員如下:
•  flags標示tty 設備的當前狀態,包括TTY_THROTTLED、TTY_IO_ERROR、TTY_OTHER_CLOSED、TTY_EXCLUSIVE、 TTY_DEBUG、TTY_DO_WRITE_WAKEUP、TTY_PUSH、TTY_CLOSING、TTY_DONT_FLIP、 TTY_HW_COOK_OUT、TTY_HW_COOK_IN、TTY_PTY_LOCK、TTY_NO_WRITE_SPLIT等。
•  ldisc爲給 tty 設備的線路規程。
•  write_wait、read_wait爲給tty寫/讀函數的等待隊列,tty驅動應當在合適的時機喚醒對應的等待隊列。
•  termios爲指向 tty 設備的當前 termios 設置的指針。
•  stopped:1指示是否停止tty設備,tty 驅動可以設置這個值;hw_stopped:1指示是否tty設備已經被停止,tty 驅動可以設置這個值;flow_stopped:1指示是否 tty 設備數據流停止。
•  driver_data、disc_data爲數據指針,用於存儲tty驅動和線路規程的“私有”數據。
驅動中可以定義1個設備相關的結構體,並在open()函數中將其賦值給tty_struct的driver_data成員,如代碼清單14.5。
代碼清單14.5 在tty驅動打開函數中賦值tty_struct的driver_data成員
1  /* 設備“私有”數據結構體 */
2  struct xxx_tty
3  {
4    struct tty_struct *tty; /* tty_struct指針 */
5    int open_count; /* 打開次數 */
6    struct semaphore sem; /* 結構體鎖定信號量 */
7    int xmit_buf; /* 傳輸緩衝區 */
8    ...
9  }
10
11 /* 打開函數 */
12 static int xxx_open(struct tty_struct *tty, struct file *file)
13 {
14   struct xxx_tty *xxx;
15
16   /* 分配xxx_tty */
17   xxx = kmalloc(sizeof(*xxx), GFP_KERNEL);
18   if (!xxx)
19     return  - ENOMEM;
20   /* 初始化xxx_tty中的成員 */
21   init_MUTEX(&xxx->sem);
22   xxx->open_count = 0;
23   ...
24   /* 讓tty_struct中的driver_data指向xxx_tty */
25   tty->driver_data = xxx;
26   xxx->tty = tty;
27   ...
28   return 0;
29 }
    在用戶對前面使用 open()系統調用而創建的文件句柄進行close()系統調用時,tty_driver中的close()成員函數將被tty核心調用。

14.4 數據發送和接收
   
圖14.3給出了終端設備數據發送和接收過程中的數據流以及函數調用關係。用戶在有數據發送給終端設備時,通過“write()系統調用――tty核心――線路規程”的層層調用,最終調用tty_driver結構體中的write()函數完成發送。
    因爲速度和tty硬件緩衝區容量的原因,不是所有的寫程序要求的字符都可以在調用寫函數時被髮送,因此寫函數應當返回能夠發送給硬件的字節數以便用戶程序檢查是否所有的數據被真正寫入。如果在 wirte()調用期間發生任何錯誤,一個負的錯誤碼應當被返回。
                      <!--[if !vml]--><!--[endif]-->
                        圖14.3 終端設備數據發送和接收過程中的數據流和函數調用關係
    tty_driver 的write()函數接受3個參數tty_struct、發送數據指針及要發送的字節數,一般首先會通過tty_struct的driver_data成員得到設備私有信息結構體,然後依次進行必要的硬件操作開始發送,代碼清單14.6給出了tty_driver的write()函數範例。
代碼清單14.6 tty_driver結構體的write()成員函數範例
1  static int xxx_write(struct tty_struct *tty, const unsigned char *buf, int count)
2  {
3    /* 獲得tty設備私有數據 */
4    struct xxx_tty *xxx = (struct xxx_tty*)tty->driver_data;
5    ...
6    /* 開始發送 */
7    while (1)
8    {
9      local_irq_save(flags);
10     c = min_t(int, count, min(SERIAL_XMIT_SIZE - xxx->xmit_cnt - 1,
11       SERIAL_XMIT_SIZE - xxx->xmit_head));
12     if (c <= 0)
13     {
14       local_irq_restore(flags);
15       break;
16     }
17     //拷貝到發送緩衝區
18     memcpy(xxx->xmit_buf + xxx->xmit_head, buf, c);
19     xxx->xmit_head = (xxx->xmit_head + c) &(SERIAL_XMIT_SIZE - 1);
20     xxx->xmit_cnt += c;
21     local_irq_restore(flags);
22
23     buf += c;
24     count -= c;
25     total += c;
26   }
27
28   if (xxx->xmit_cnt && !tty->stopped && !tty->hw_stopped)
29   {
30     start_xmit(xxx);//開始發送
31   }
32   return total; //返回發送的字節數
33 }     
    當tty子系統自己需要發送數據到 tty 設備時,如果沒有實現 put_char()函數,write()函數將被調用,此時傳入的count參數爲1,通過對代碼清單14.7的分析即可獲知。
代碼清單14.7 put_char()函數的write()替代
1  int tty_register_driver(struct tty_driver *driver)
2  {
3    ...
4    if (!driver->put_char)//沒有定義put_char()函數
5      driver->put_char = tty_default_put_char;
6    ...
7  }
8  static void tty_default_put_char(struct tty_struct *tty, unsigned char ch)
9  {
10   tty->driver->write(tty, &ch, 1);//調用tty_driver.write()函數
11 }
    讀者朋友們可能注意到了,tty_driver結構體中沒有提供 read()函數。因爲發送是用戶主動的,而接收即用戶調read()則是讀一片緩衝區中已放好的數據。tty 核心在一個稱爲 struct tty_flip_buffer 的結構體中緩衝數據直到它被用戶請求。因爲tty核心提供了緩衝邏輯,因此每個 tty 驅動並非一定要實現它自身的緩衝邏輯。
    tty驅動不必過於關心tty_flip_buffer 結構體的細節,如果其count字段大於或等於TTY_FLIPBUF_SIZE,這個flip緩衝區就需要被刷新到用戶,刷新通過對 tty_flip_buffer_push()函數的調用來完成,代碼清單代碼清單14.8給出了範例。
代碼清單14.8 tty_flip_buffer_push()範例
1 for (i = 0; i < data_size; ++i)
2 {
3   if (tty->flip.count >= TTY_FLIPBUF_SIZE)
4      tty_flip_buffer_push(tty);//數據填滿向上層“推”
5   tty_insert_flip_char(tty, data[i], TTY_NORMAL);//把數據插入緩衝區
6 }
7 tty_flip_buffer_push(tty);
    從tty 驅動接收到字符通過tty_insert_flip_char()函數被插入到flip緩衝區。該函數的第1個參數是數據應當保存入的 tty_struct結構體,第 2 個參數是要保存的字符,第3個參數是應當爲這個字符設置的標誌,如果字符是一個接收到的常規字符,則設爲TTY_NORMAL,如果是一個特殊類型的指示錯誤的字符,依據具體的錯誤類型,應當設爲TTY_BREAK、 TTY_PARITY或TTY_OVERRUN。

14.5 TTY線路設置
14.5.1線路設置用戶空間接口
 用戶可用如下2種方式改變tty設備的線路設置或者獲取當前線路設置:
 1、調用用戶空間的termios庫函數
     用戶空間的應用程序需引用termios.h頭文件,該頭文件包含了終端設備的I/O接口,實際是由POSIX定義的標準方法。對終端設備操作模式的描述由termios結構體完成,從代碼清單14.2可以看出,這個結構體包含c_iflag、c_oflag、c_cflag、c_lflag和c_cc []幾個成員。
    termios的c_cflag主要包含如下位域信息:CSIZE(字長)、CSTOPB(2個停止位)、 PARENB(奇偶校驗位使能)、PARODD (奇校驗位,當PARENB被使能時)、 CREAD(字符接收使能,如果沒有置位,仍然從端口接收字符,但這些字符都要被丟棄)、 CRTSCTS (如果被置位,使能CTS狀態改變報告)、CLOCAL (如果沒有置位,使能調制解調器狀態改變報告)。
    termios的c_iflag主要包含如下位域信息:INPCK (使能幀和奇偶校驗錯誤檢查)、BRKINT(break將清除終端輸入/輸出隊列,向該終端上前臺的程序發出SIGINT信號)、 PARMRK (奇偶校驗和幀錯誤被標記,在INPCK被設置且IGNPAR未被設置的情況下才有意義)、IGNPAR (忽略奇偶校驗和幀錯誤)、IGNBRK (忽略break)。
    通過tcgetattr()、tcsetattr()函數即可完成對終端設備的操作模式的設置和獲取,這2個函數的原型如下:
int tcgetattr (int fd, struct termios *termios_p);
int tcsetattr (int fd, int optional_actions, struct termios *termios_p);
例如,Raw模式的線路設置爲:
• 非正規模式
• 關閉回顯
• 禁止 CR 到 NL 的映射(ICRNL)、輸入奇偶校驗、輸入第 8 位的截取(ISTRIP)以及輸出流控制
• 8位字符(CS8),奇偶校驗被禁止
• 禁止所有的輸出處理
• 每次一個字節 (c_cc [VMIN] = 1、c_cc [VTIME] = 0)
則對應的對termios結構體的設置就爲:
termios_p->c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
                                   | INLCR | IGNCR | ICRNL | IXON);
termios_p->c_oflag &= ~OPOST;
termios_p->c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
termios_p->c_cflag &= ~(CSIZE | PARENB);
termios_p->c_cflag |= CS8;
通過如下一組函數可完成輸入/輸出波特率的獲取和設置:
speed_t cfgetospeed (struct termios *termios_p); //獲得輸出波特率
speed_t cfgetispeed (struct termios *termios_p); //獲得輸入波特率
int cfsetospeed (struct termios *termios_p, speed_t speed); //設置輸出波特率
int cfsetispeed (struct termios *termios_p, speed_t speed); //設置輸入波特率
如下一組函數則完成線路控制:
int tcdrain (int fd); //等待所有輸出都被髮送
int tcflush (int fd, int queue_selector); //flush輸入/輸出緩存
int tcflow (int fd, int action); // 對輸入和輸出流進行控制
int tcsendbreak (int fd, int duration);//發送break
    tcflush函數刷清(拋棄)輸入緩存(終端驅動程序已接收到,但用戶程序尚未讀取)或輸出緩存(用戶程序已經寫,但驅動尚未發送),queue參數可取TCIFLUSH(刷清輸入隊列)、TCOFLUSH(刷清輸出隊列)或TCIOFLUSH(刷清輸入、輸出隊列)。
    tcflow()對輸入輸出進行流控制,action參數可取TCOOFF(輸出被掛起)、TCOON(重新啓動以前被掛起的輸出)、TCIOFF(發送1個STOP字符,使終端設備暫停發送數據)、TCION(發送1個START字符,使終端恢復發送數據)。
   tcsendbreak()函數在一個指定的時間區間內發送連續的0二進位流。若duration參數爲0,則此種發送延續0.25-0.5秒之間。POSIX.1說明若duration非0,則發送時間依賴於實現。
2、對tty設備節點進行ioctl()調用
     大部分termios庫函數會被轉化爲對tty設備節點的ioctl()調用,例如tcgetattr()、tcsetattr()函數對應着TCGETS、TCSETS IO控制命令。TIOCMGET (獲得MODEM狀態位)、TIOCMSET(設置MODEM狀態位)、TIOCMBIC       (清除指示MODEM位)、TIOCMBIS(設置指示MODEM位)這4個IO控制命令用於獲取和設置MODEM握手,如RTS、CTS、DTR、 DSR、RI、CD等。
14.5.2 tty驅動set_termios函數
    大部分 termios 用戶空間函數被庫轉換爲對驅動節點的 ioctl()調用,而tty ioctl中的大部分命令會被tty核心轉換爲對tty驅動的set_termios()函數的調用。set_termios()函數需要根據用戶對 termios的設置(termios設置包括字長、奇偶校驗位、停止位、波特率等)完成實際的硬件設置。
tty_operations中的set_termios()函數原型爲:
void(*set_termios)(struct tty_struct *tty, struct termios *old);
    新的設置被保存在tty_struct中,舊的設置被保存在old參數中,若新舊參數相同,則什麼都不需要做,對於被改變的設置,需完成硬件上的設置,代碼清單14.9給出了set_termios()函數的例子。
代碼清單14.9 tty驅動程序set_termios()函數範例
1  static void xxx_set_termios(struct tty_struct *tty, struct termios *old_termios)
2  {
3    struct xxx_tty *info = (struct cyclades_port*)tty->driver_data;
4    /* 新設置等同於老設置,什麼也不做 */
5    if (tty->termios->c_cflag == old_termios->c_cflag)
6      return ;
7    ...

9    /* 關閉CRTSCTS硬件流控制 */
10   if ((old_termios->c_cflag &CRTSCTS) && !(cflag &CRTSCTS))
11   {
12     ...
13   }
14
15   /* 打開CRTSCTS硬件流控制 */
16   if (!(old_termios->c_cflag &CRTSCTS) && (cflag &CRTSCTS))
17   {
18     ...
19   }
20
21   /* 設置字節大小 */
22   switch (tty->termios->c_cflag &CSIZE)
23   {
24
25     case CS5:
26     ...
27     case CS6:
28     ...
29     case CS7:
30     ...
31     case CS8:
32     ...
33   }
34
35   /* 設置奇偶校驗 */
36   if (tty->termios->c_cflag &PARENB)
37     if (tty->termios->c_cflag &PARODD)  //奇校驗
38     ...
39     else  //偶校驗
40     ...
41   else //無校驗
42   ...
43 }
14.5.3 tty驅動 tiocmget和tiocmset函數
對TIOCMGET、 TIOCMSET、TIOCMBIC和TIOCMBIS IO控制命令的調用將被tty核心轉換爲對tty驅動tiocmget()函數和tiocmset()函數的調用,TIOCMGET對應tiocmget ()函數,TIOCMSET、TIOCMBIC和TIOCMBIS 對應tiocmset()函數,分別用於讀取Modem控制的設置和進行Modem的設置。代碼清單14.10給出了tiocmget()函數的範例,代 碼清單14.11則給出了tiocmset()函數的範例。
代碼清單14.10 tty驅動程序tiocmget()函數範例
1  static int xxx_tiocmget(struct tty_struct *tty, struct file *file)
2  {
3    struct xxx_tty *info = tty->driver_ data;
4    unsigned int result = 0;
5    unsigned int msr = info->msr;
6    unsigned int mcr = info->mcr;
7    result = ((mcr &MCR_DTR) ? TIOCM_DTR : 0) |  /* DTR 被設置 */
8    ((mcr &MCR_RTS) ? TIOCM_RTS : 0) |  /* RTS 被設置 */
9    ((mcr &MCR_LOOP) ? TIOCM_LOOP : 0) |  /* LOOP 被設置 */
10   ((msr &MSR_CTS) ? TIOCM_CTS : 0) |  /* CTS 被設置 */
11   ((msr &MSR_CD) ? TIOCM_CAR : 0) |  /* CD 被設置*/
12   ((msr &MSR_RI) ? TIOCM_RI : 0) |  /* 振鈴指示被設置 */
13   ((msr &MSR_DSR) ? TIOCM_DSR : 0); /* DSR 被設置 */
14   return result;
15 }
代碼清單14.11 tty驅動程序tiocmset()函數範例
1  static int xxx_tiocmset(struct tty_struct *tty, struct file *file, unsigned
2    int set, unsigned int clear)
3  {
4    struct xxx_tty *info = tty->driver_data;
5    unsigned int mcr = info->mcr;

7    if (set &TIOCM_RTS) /* 設置RTS */
8      mcr |= MCR_RTS;
9    if (set &TIOCM_DTR) /* 設置DTR */
10     mcr |= MCR_RTS;
11
12   if (clear &TIOCM_RTS) /* 清除RTS */
13     mcr &= ~MCR_RTS;
14   if (clear &TIOCM_DTR) /* 清除DTR */
15     mcr &= ~MCR_RTS;
16
17   /* 設置設備新的MCR值 */
18   tiny->mcr = mcr;
19   return 0;
20 }
tiocmget()函數會訪問MODEM狀態寄存器(MSR),而tiocmset()函數會訪問MODEM控制寄存器(MCR)。
14.5.3 tty驅動ioctl函數
    當用戶在tty設備節點上進行ioctl(2) 調用時,tty_operations中的 ioctl()函數會被tty核心調用。如果 tty 驅動不知道如何處理傳遞給它的 ioctl 值,它返回 –ENOIOCTLCMD,之後tty 核心會執行一個通用的操作。
驅動中常見的需處理 的IO控制命令包括TIOCSERGETLSR(獲得這個 tty 設備的線路狀態寄存器LSR 的值)、TIOCGSERIAL(獲得串口線信息)、TIOCMIWAIT(等待 MSR 改變)、TIOCGICOUNT(獲得中斷計數)等。代碼清單14.12給出了tty驅動程序ioctl()函數的範例。
代碼清單14.12 tty驅動程序ioctl()函數範例
1  static int xxx_ioctl(struct tty_struct *tty, struct file *filp, unsigned int
2    cmd, unsigned long arg)
3  {
4    struct xxx_tty *info = tty->driver_data;
5    ...
6    /* 處理各種命令 */
7    switch (cmd)
8    {
9      case TIOCGSERIAL:
10       ...
11     case TIOCSSERIAL:
12       ...
13     case TIOCSERCONFIG:
14       ...
15     case TIOCMIWAIT:
16     ...
17     case TIOCGICOUNT:
18     ...
19     case TIOCSERGETLSR:
20     ...
21   }
22   ...
23 }

14.6 UART設備驅動
   
儘管一個特定的UART設備驅動完全可以遵循14.2~14.5的方法來設計,即定義tty_driver並實現其中的成員函數,但是Linux已經在文件 serial_core.c中實現了UART設備的通用tty驅動層(姑且稱其爲串口核心層),這樣,UART驅動的主要任務演變成實現serial- core.c中定義的一組uart_xxx接口而非tty_xxx接口,如圖14.5所示。
    serial_core.c串口核心層完全可以被當作14.2~14.5節tty設備驅動的實例,它實現了UART設備的tty驅動。
    提示:Linux驅動的這種分層思想在許多類型的設備驅動中都得到了體現,例如上一章IDE設備驅動中,內核實現了通用的IDE層用於處理塊設備I/O請求,而具體的IDE則只需使用ide_xxx這樣的接口,甚至不必理會複雜的塊設備驅動結構。
             <!--[if !vml]--><!--[endif]-->
                        圖14.5 串口核心層
串口核心層爲串口設備驅動提供瞭如下3個結構體:
1、uart_driver
uart_driver包含串口設備的驅動名、設備名、設備號等信息,它封裝了tty_driver,使得底層的UART驅動無需關心tty_driver,其定義如代碼清單14.13。
代碼清單14.13 uart_driver結構體
1  struct uart_driver
2  {
3    struct module *owner;
4    const char *driver_name; //驅動名
5    const char *dev_name;    //設備名
6    const char *devfs_name;  //設備文件系統名
7    int major;  //主設備號
8    int minor;   //次設備號
9    int nr;
10   struct console *cons;
11
12   /* 私有的,底層驅動不應該訪問這些成員,應該被初始化爲NULL */
13   struct uart_state *state;
14   struct tty_driver *tty_driver;
15 };
一個tty驅動必須註冊/註銷tty_driver,而一個UART驅動則演變爲註冊/註銷uart_driver,使用如下接口:
int uart_register_driver(struct uart_driver *drv);
void uart_unregister_driver(struct uart_driver *drv);
實際上,uart_register_driver()和uart_unregister_driver()中分別包含了tty_register_driver()和tty_unregister_driver()的操作,如代碼清單14.14所示。
代碼清單14.14 uart_register_driver()和uart_unregister_driver()函數
1  int uart_register_driver(struct uart_driver *drv)
2  {
3   struct tty_driver *normal = NULL;
4   int i, retval;
5   ...
6    /* 分配tty_driver */
7   normal  = alloc_tty_driver(drv->nr);
8   if (!normal)
9    goto out;
10  drv->tty_driver = normal;
11   /* 初始化tty_driver */
12  normal->owner  = drv->owner;
13  normal->driver_name = drv->driver_name;
14  normal->devfs_name = drv->devfs_name;
15  normal->name  = drv->dev_name;
16  normal->major  = drv->major;
17  normal->minor_start = drv->minor;
18  normal->type  = TTY_DRIVER_TYPE_SERIAL;
19  normal->subtype  = SERIAL_TYPE_NORMAL;
20  normal->init_termios = tty_std_termios;
21  normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
22  normal->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_NO_DEVFS;
23  normal->driver_state    = drv;
24  tty_set_operations(normal, &uart_ops);
25
26  ...
27  /* 註冊tty驅動 */
28  retval = tty_register_driver(normal);
29  out:
30  if (retval < 0) {
31   put_tty_driver(normal);
32   kfree(drv->state);
33  }
34  return retval;
35 }
36
37 void uart_unregister_driver(struct uart_driver *drv)
38 {
39  struct tty_driver *p = drv->tty_driver;
40  tty_unregister_driver(p);  /* 註銷tty驅動 */
41  put_tty_driver(p);
42  kfree(drv->state);
43  drv->tty_driver = NULL;
44 }
2、uart_port
uart_port用於描述一個UART端口(直接對應於一個串口)的I/O端口或I/O內存地址、FIFO大小、端口類型等信息,其定義如代碼清單14.15。
代碼清單14.15 uart_port結構體
1  struct uart_port
2  {
3    spinlock_t lock; /* 端口鎖 */
4    unsigned int iobase; /* IO端口基地址 */
5    unsigned char __iomem *membase; /* IO內存基地址 */
6    unsigned int irq; /* 中斷號 */
7    unsigned int uartclk; /* UART時鐘 */
8    unsigned char fifosize; /* 傳輸fifo大小 */
9    unsigned char x_char; /* xon/xoff字符 */
10   unsigned char regshift; /* 寄存器位移 */
11   unsigned char iotype; /* IO存取類型 */
12
13   #define UPIO_PORT  (0)  /* IO端口*/
14   #define UPIO_HUB6  (1)
15   #define UPIO_MEM  (2)  /* IO內存*/
16   #define UPIO_MEM32  (3)
17   #define UPIO_AU   (4)   /* Au1x00類型IO */
18
19   unsigned int read_status_mask; /* 驅動相關的 */
20   unsigned int ignore_status_mask; /* 驅動相關的 */
21   struct uart_info *info; /* 指向parent信息 */
22   struct uart_icount icount; /* 計數 */
23
24   struct console *cons; /* console結構體 */
25   #ifdef CONFIG_SERIAL_CORE_CONSOLE
26     unsigned long sysrq; /* sysrq超時 */
27   #endif
28
29   upf_t flags;
30
31   #define UPF_FOURPORT  ((__force upf_t) (1 << 1))
32   #define UPF_SAK   ((__force upf_t) (1 << 2))
33   #define UPF_SPD_MASK  ((__force upf_t) (0x1030))
34   #define UPF_SPD_HI  ((__force upf_t) (0x0010))
35   #define UPF_SPD_VHI  ((__force upf_t) (0x0020))
36   #define UPF_SPD_CUST  ((__force upf_t) (0x0030))
37   #define UPF_SPD_SHI  ((__force upf_t) (0x1000))
38   #define UPF_SPD_WARP  ((__force upf_t) (0x1010))
39   #define UPF_SKIP_TEST  ((__force upf_t) (1 << 6))
40   #define UPF_AUTO_IRQ  ((__force upf_t) (1 << 7))
41   #define UPF_HARDPPS_CD  ((__force upf_t) (1 << 11))
42   #define UPF_LOW_LATENCY  ((__force upf_t) (1 << 13))
43   #define UPF_BUGGY_UART  ((__force upf_t) (1 << 14))
44   #define UPF_MAGIC_MULTIPLIER ((__force upf_t) (1 << 16))
45   #define UPF_CONS_FLOW  ((__force upf_t) (1 << 23))
46   #define UPF_SHARE_IRQ  ((__force upf_t) (1 << 24))
47   #define UPF_BOOT_AUTOCONF ((__force upf_t) (1 << 28))
48   #define UPF_IOREMAP  ((__force upf_t) (1 << 31))
49
50   #define UPF_CHANGE_MASK  ((__force upf_t) (0x17fff))
51   #define UPF_USR_MASK  ((__force upf_t)
52       (UPF_SPD_MASK|UPF_LOW_LATENCY))
53   unsigned int mctrl; /* 目前modem控制設置 */
54   unsigned int timeout; /* 基於字符的超時 */
55   unsigned int type; /* 端口類型 */
56   const struct uart_ops *ops; /* UART操作集 */
57   unsigned int custom_divisor;
58   unsigned int line; /* 端口索引 */
59   unsigned long mapbase; /* ioremap後基地址 */
60   struct device *dev; /* parent設備 */
61   unsigned char hub6;
62   unsigned char unused[3];
63 };
串口核心層提供如下函數來添加1個端口:
int uart_add_one_port(struct uart_driver *drv, struct uart_port *port);
    對上述函數的調用應該發生在uart_register_driver()之後,uart_add_one_port()的一個最重要作用是封裝了tty_register_device()。
    uart_add_one_port()的“反函數”是uart_remove_one_port(),其中會調用tty_unregister_device(),原型爲:
int uart_remove_one_port(struct uart_driver *drv, struct uart_port *port);
    驅動中雖然不需要處理uart_port的uart_info成員,但是在發送時,從用戶來的數據被保存在xmit(被定義爲circ_buf,即環形緩衝 區)中,因此UART驅動在發送數據時(一般在發送中斷處理函數中),需要從這個circ_buf獲取上層傳遞下來的字符。
3、uart_ops
     uart_ops 定義了針對UART的一系列操作,包括髮送、接收及線路設置等,如果說tty_driver中的tty_operations對於串口還較爲抽象,那麼 uart_ops則直接面向了串口的UART,其定義如代碼清單14.16。Linux驅動的這種層次非常類似於面向對象編程中基類、派生類的關係,派生類針對特定的事物會更加具體,而基類則站在更高的抽象層次上。
代碼清單14.16 uart_ops結構體
1  struct uart_ops
2  {
3    unsigned int(*tx_empty)(struct uart_port*);
4    void(*set_mctrl)(struct uart_port *, unsigned int mctrl);
5    unsigned int(*get_mctrl)(struct uart_port*);
6    void(*stop_tx)(struct uart_port*);    //停止發送
7    void(*start_tx)(struct uart_port*);   //開始發送
8    void(*send_xchar)(struct uart_port *, char ch); //發送xchar
9    void(*stop_rx)(struct uart_port*);   //停止接收
10   void(*enable_ms)(struct uart_port*);
11   void(*break_ctl)(struct uart_port *, int ctl);
12   int(*startup)(struct uart_port*);
13   void(*shutdown)(struct uart_port*);
14   void(*set_termios)(struct uart_port *, struct termios *new, struct termios
15     *old);        //設置termios
16   void(*pm)(struct uart_port *, unsigned int state, unsigned int oldstate);
17   int(*set_wake)(struct uart_port *, unsigned int state);
18
19   /* 返回1個描述端口類型的字符串 */
20   const char *(*type)(struct uart_port*);
21
22   /* 釋放端口使用的IO和內存資源,必要的情況下,應該進行iounmap操作 */
23   void(*release_port)(struct uart_port*);
24   /* 申請端口使用的IO和內存資源 */
25   int(*request_port)(struct uart_port*);
26  
27   void(*config_port)(struct uart_port *, int);
28   int(*verify_port)(struct uart_port *, struct serial_struct*);
29   int(*ioctl)(struct uart_port *, unsigned int, unsigned long);
30 };
    serial_core.c 中定義了tty_operations的實例,包含uart_open()、uart_close()、uart_write()、 uart_send_xchar()等成員函數(如代碼清單14.17),這些函數會藉助uart_ops結構體中的成員函數來完成具體的操作,代碼清單 14.18給出了tty_operations的uart_send_xchar()成員函數利用uart_ops中start_tx()、 send_xchar()成員函數的例子。
代碼清單14.17 串口核心層的tty_operations實例
1  static struct tty_operations uart_ops =
2  {
3   .open  = uart_open,//串口打開
4   .close  = uart_close,//串口關閉
5   .write  = uart_write,//串口發送
6   .put_char = uart_put_char,//...
7   .flush_chars = uart_flush_chars,
8   .write_room = uart_write_room,
9   .chars_in_buffer= uart_chars_in_buffer,
10  .flush_buffer = uart_flush_buffer,
11  .ioctl  = uart_ioctl,
12  .throttle = uart_throttle,
13  .unthrottle = uart_unthrottle,
14  .send_xchar = uart_send_xchar,
15  .set_termios = uart_set_termios,
16  .stop  = uart_stop,
17  .start  = uart_start,
18  .hangup  = uart_hangup,
19  .break_ctl = uart_break_ctl,
20  .wait_until_sent= uart_wait_until_sent,
21 #ifdef CONFIG_PROC_FS
22  .read_proc = uart_read_proc, //proc入口讀函數
23 #endif
24  .tiocmget = uart_tiocmget,
25  .tiocmset = uart_tiocmset,
26 };
代碼清單14.18 串口核心層的tty_operations與uart_ops關係
1  static void uart_send_xchar(struct tty_struct *tty, char ch)
2  {
3    struct uart_state *state = tty->driver_data;
4    struct uart_port *port = state->port;
5    unsigned long flags;
6    //如果uart_ops中實現了send_xchar成員函數
7    if (port->ops->send_xchar)
8      port->ops->send_xchar(port, ch); 
9    else //uart_ops中未實現send_xchar成員函數
10   {
11     port->x_char = ch; //xchar賦值
12     if (ch)
13     {
14       spin_lock_irqsave(&port->lock, flags);
15       port->ops->start_tx(port);  //發送xchar
16       spin_unlock_irqrestore(&port->lock, flags);
17     }
18   }
19 }
注意:  整個調用流程爲:  系統調用write()->uart_write()(tty_driver)->port->ops->start_tx();
  在使用串口核心層這個通用串口tty驅動層的接口後,一個串口驅動要完成的主要工作將包括:
•  定義uart_driver、uart_ops、uart_port等結構體的實例並在適當的地方根據具體硬件和驅動的情況初始化它們,當然具體設備 xxx的驅動可以將這些結構套在新定義的xxx_uart_driver、xxx_uart_ops、xxx_uart_port之內。
•  在模塊初始化時調用uart_register_driver()和uart_add_one_port()以註冊UART驅動並添加端口,在模塊卸載時 調用uart_unregister_driver()和uart_remove_one_port()以註銷UART驅動並移除端口。
•  根據具體硬件的datasheet實現uart_ops中的成員函數,這些函數的實現成爲UART驅動的主體工作。

14.7實例:S3C2410 UART的驅動
14.7.1 S3C2410串口硬件描述
    S3C2410 內部具有3個獨立的UART控制器,每個控制器都可以工作在Interrupt(中斷)模式或DMA(直接內存訪問)模式,也就是說UART控制器可以在 CPU與UART控制器傳送資料的時候產生中斷或DMA請求。S3C2410集成的每個UART均具有16字節的FIFO,支持的最高波特率可達到 230.4Kbps。
ULCONn(UART Line Control Register)寄存器用於S3C2410 UART的線路控制,用於設置模式、每幀的數據位數、停止位數及奇偶校驗,如表14.1。
表14.1 S3C2410 UART的ULCONn寄存器
ULCONn 位              描述
保留 [7] 
紅外模式 [6]           0:正常模式  1:紅外模式
奇偶校驗 [5:3]         0xx:無校驗  100:奇校驗  101:偶校驗  ...
停止位 [2]             0:1個停止位  1:2個停止位
字長 [1:0]             00:5位 01:6位 10:7位 11:8位
UCONn(UART Control Register)寄存器用於從整體上控制S3C2410 UART的中斷模式及工作模式(DMA、中斷、輪詢)等,如表14.2。
表14.2 S3C2410 UART的UCONn寄存器
UCONn 位               描述
時鐘選擇 [10]            爲UART的波特率產生選擇PCLK或UCLK時鐘
Tx中斷 [9]               0:脈衝 1:電平
Rx中斷 [8]               0:脈衝 1:電平
Rx超時使能 [7]          當UART被使能,使能/禁止Rx超時中斷  0:禁止  1:使能
Rx錯誤狀態中斷使能 [6]   使能接收異常中斷(如break、幀錯誤、校驗錯、溢出等)
loopback [5]              0:正常模式 1:迴環
發送break [4]            設置該位將造成UART在1幀的時間內發送break,當發送完break後,該位將自動被清除
發送模式 [3:2]            發送數據到UART的模式,00:禁止 01:中斷或輪詢 10:DMA0(僅針對UART0)、DMA3(僅針對UART3) 11:DMA1(僅針對UART1)
接收模式 [1:0]            從UART接收數據的模式,00:禁止 01:中斷或輪詢 10:DMA0(僅針對UART0)
     UFCONn(UART FIFO Conrtol Register)寄存器用於S3C2410 UART的FIFO控制,用於控制FIFO中斷的觸發級別以及復位時是否清空FIFO中的內容,如表14.3。
 表14.3 S3C2410 UART的UFCONn寄存器
UFCONn 位              描述
Tx FIFO觸發級別 [7:6]    決定發送FIFO的觸發級別: 00:空 01:4字節 10:8字節 11:12字節
Rx FIFO觸發級別 [5:4]    決定接收FIFO的觸發級別: 00:4字節 01:8字節 10:12字節 11:16字節
Tx FIFO復位 [2]          復位FIFO後自動清除FIFO  0:正常 1:Tx FIFO復位
Rx FIFO復位 [1]          復位FIFO後自動清除FIFO  0:正常 1:Tx FIFO復位
FIFO使能 [0]             0:禁止 1:使能
代碼清單14.19給出了UFCONn寄存器的位掩碼和默認設置(使能FIFO、Tx FIFO爲空時觸發中斷、Rx FIFO中包含8個字節時觸發中斷)。
代碼清單14.19 S3C2410 UART UFCONn寄存器的位掩碼和默認設置
1  #define S3C2410_UFCON_FIFOMODE   (1<<0)
2  #define S3C2410_UFCON_TXTRIG0   (0<<6)
3  #define S3C2410_UFCON_RXTRIG8   (1<<4)
4  #define S3C2410_UFCON_RXTRIG12   (2<<4)

6  #define S3C2410_UFCON_RESETBOTH   (3<<1)
7  #define S3C2410_UFCON_RESETTX   (1<<2)
8  #define S3C2410_UFCON_RESETRX   (1<<1)

10 #define S3C2410_UFCON_DEFAULT   (S3C2410_UFCON_FIFOMODE | \
11        S3C2410_UFCON_TXTRIG0  | \
12        S3C2410_UFCON_RXTRIG8 )
UFSTATn(UART FIFO Status Register)寄存器用於表徵UART FIFO的狀態,如表14.4。
表14.4 S3C2410 UART的UFSTATn寄存器
UFSTATn 位              描述
保留 [15:10] 
Tx FIFO滿 [9]            當Tx FIFO滿後,將自動被設置爲1  0:0字節 ≤ Tx FIFO數據數 ≤ 15  1:Tx FIFO數據數 = 15
Rx FIFO滿 [8]            當Rx FIFO滿後,將自動被設置爲1  0:0字節 ≤ Rx FIFO數據數 ≤ 15  1:Tx FIFO數據數 = 15
Tx FIFO數據數 [7:4] 
Rx FIFO數據數 [3:0]
由於UFSTATn寄存器中的Tx FIFO數據數和Rx FIFO數據數分別佔據[7:4]和[3:0]位,因此定義S3C2410_UFSTAT_TXSHIFT和 S3C2410_UFSTAT_RXSHIFT分別爲4和0,代碼清單14.20給出了UFSTATn寄存器的位掩碼等信息。
代碼清單14.20 S3C2410 UART UFSTATn寄存器的位掩碼
1 #define S3C2410_UFSTAT_TXFULL   (1<<9)
2 #define S3C2410_UFSTAT_RXFULL   (1<<8)
3 #define S3C2410_UFSTAT_TXMASK   (15<<4)
4 #define S3C2410_UFSTAT_TXSHIFT   (4)
5 #define S3C2410_UFSTAT_RXMASK   (15<<0)
6 #define S3C2410_UFSTAT_RXSHIFT   (0)
UTXHn(UART Transmit Buffer Register)和 URXHn(UART Receive Buffer Register)分別是UART發送和接收數據寄存器,這2個寄存器存放着發送和接收的數據。
UTRSTATn(UART TX/RX Status Register)寄存器反映了發送和接收的狀態,通過這個寄存器,驅動程序可以判斷URXHn中是否有數據接收到或UTXHn是否爲空,這個寄存器主要在非FIFO模式時使用。
UMCONn(UART Modem Control Register)用於S3C2410 UART的modem控制,設置是否使用RTS流控,若採用流控,可選擇自動流控(Auto Flow Control,AFC)或由軟件控制RTS信號的“高”或“低”電平。
14.7.2 S3C2410串口驅動數據結構
S3C2410串口驅動中uart_driver結構體實例的定義如代碼清單14.21,設備名爲“s3c2410_serial”,驅動名爲“ttySAC”。
代碼清單14.21 S3C2410串口驅動uart_driver結構體
1  #define S3C24XX_SERIAL_NAME "ttySAC"
2  #define S3C24XX_SERIAL_DEVFS    "tts/"
3  #define S3C24XX_SERIAL_MAJOR 204
4  #define S3C24XX_SERIAL_MINOR 64

6  static struct uart_driver s3c24xx_uart_drv =
7  {
8   .owner  = THIS_MODULE,
9   .dev_name = "s3c2410_serial",
10  .nr  = 3,
11  .cons  = S3C24XX_SERIAL_CONSOLE,
12  .driver_name = S3C24XX_SERIAL_NAME,
13  .devfs_name = S3C24XX_SERIAL_DEVFS,
14  .major  = S3C24XX_SERIAL_MAJOR,
15  .minor  = S3C24XX_SERIAL_MINOR,
16 };
S3C2410 串口驅動中定義了結構體s3c24xx_uart_port,該結構體中封裝了uart_port結構體及一些針對S3C2410 UART的附加信息,代碼清單14.22給出了s3c24xx_uart_port結構體及其實例s3c24xx_serial_ports[]數組。
代碼清單14.22 S3C2410串口驅動s3c24xx_uart_port結構體
1  struct s3c24xx_uart_port
2  {
3   unsigned char   rx_claimed;
4   unsigned char   tx_claimed;

6   struct s3c24xx_uart_info *info;
7   struct s3c24xx_uart_clksrc *clksrc;
8   struct clk   *clk;
9   struct clk   *baudclk;
10  struct uart_port  port;
11 };
12
13 static struct s3c24xx_uart_port s3c24xx_serial_ports[NR_PORTS] = {
14  [0] = {
15   .port = {
16    .lock  = SPIN_LOCK_UNLOCKED,
17    .iotype  = UPIO_MEM,
18    .irq  = IRQ_S3CUART_RX0,
19    .uartclk = 0,
20    .fifosize = 16,
21    .ops  = &s3c24xx_serial_ops,
22    .flags  = UPF_BOOT_AUTOCONF,
23    .line  = 0,//端口索引:0
24   }
25  },
26  [1] = {
27   .port = {
28    .lock  = SPIN_LOCK_UNLOCKED,
29    .iotype  = UPIO_MEM,
30    .irq  = IRQ_S3CUART_RX1,
31    .uartclk = 0,
32    .fifosize = 16,
33    .ops  = &s3c24xx_serial_ops,
34    .flags  = UPF_BOOT_AUTOCONF,
35    .line  = 1, //端口索引:1
36   }
37  },
38 #if NR_PORTS > 2
39
40  [2] = {
41   .port = {
42    .lock  = SPIN_LOCK_UNLOCKED,
43    .iotype  = UPIO_MEM,
44    .irq  = IRQ_S3CUART_RX2,
45    .uartclk = 0,
46    .fifosize = 16,
47    .ops  = &s3c24xx_serial_ops,
48    .flags  = UPF_BOOT_AUTOCONF,
49    .line  = 2, //端口索引:2
50   }
51  }
52 #endif
53 };
S3C2410串口驅動中uart_ops結構體實例的定義如代碼清單14.23,將一系列s3c24xx_serial_函數賦值給了uart_ops結構體的成員。
代碼清單14.23 S3C2410串口驅動uart_ops結構體
1  static struct uart_ops s3c24xx_serial_ops =
2  {
3   .pm  = s3c24xx_serial_pm,
4   .tx_empty = s3c24xx_serial_tx_empty,//發送緩衝區空
5   .get_mctrl = s3c24xx_serial_get_mctrl,//得到modem控制設置
6   .set_mctrl = s3c24xx_serial_set_mctrl, //設置modem控制(MCR)
7   .stop_tx = s3c24xx_serial_stop_tx, //停止接收字符
8   .start_tx = s3c24xx_serial_start_tx,//開始傳輸字符
9   .stop_rx = s3c24xx_serial_stop_rx, //停止接收字符
10  .enable_ms = s3c24xx_serial_enable_ms,// modem狀態中斷使能
11  .break_ctl = s3c24xx_serial_break_ctl,// 控制break信號的傳輸
12  .startup = s3c24xx_serial_startup,//啓動端口
13  .shutdown = s3c24xx_serial_shutdown,// 禁用端口
14  .set_termios = s3c24xx_serial_set_termios,//改變端口參數
15  .type  = s3c24xx_serial_type,//返回描述特定端口的常量字符串指針
16  .release_port = s3c24xx_serial_release_port,//釋放端口占用的內存及IO資源
17  .request_port = s3c24xx_serial_request_port,//申請端口所需的內存和IO資源
18  .config_port = s3c24xx_serial_config_port,//執行端口所需的自動配置步驟
19  .verify_port = s3c24xx_serial_verify_port,//驗證新的串行端口信息
20 };
set_mctrl()函數的原型爲:
void (*set_mctrl)(struct uart_port *port, u_int mctrl);
它將參數port所對應的調制解調器控制線的值設爲參數mctrl的值。
get_mctrl()函數的原型爲:
unsigned int (*get_mctrl)(struct uart_port *port);
該函數返回調制解調器控制輸入的現有狀態,這些狀態信息包括:TIOCM_CD(CD 信號狀態)、TIOCM_CTS(CTS信號狀態)、TIOCM_DSR(DSR信號狀態)、TIOCM_RI(RI信號狀態)等。如果信號被置爲有效,則對應位將被置位。
端口啓動函數startup()的原型爲:
int (*startup)(struct uart_port *port, struct uart_info *info);
該函數申請所有中斷資源,初始化底層驅動狀態,並開啓端口爲可接收數據的狀態。
shutdown()函數完成與startup()函數的作用相反,其原型:
void (*shutdown)(struct uart_port *port, struct uart_info *info);
這個函數禁用端口,釋放所有的中斷資源。
回過頭來看s3c24xx_uart_port結構體,其中的s3c24xx_uart_info成員(代碼清單14.22第6行)是一些針對S3C2410 UART的信息,其定義如代碼清單14.24。
代碼清單14.24 S3C2410串口驅動s3c24xx_uart_info結構體
1  static struct s3c24xx_uart_info s3c2410_uart_inf =
2  {
3   .name  = "Samsung S3C2410 UART",
4   .type  = PORT_S3C2410,
5   .fifosize = 16,
6   .rx_fifomask = S3C2410_UFSTAT_RXMASK,
7   .rx_fifoshift = S3C2410_UFSTAT_RXSHIFT,
8   .rx_fifofull = S3C2410_UFSTAT_RXFULL,
9   .tx_fifofull = S3C2410_UFSTAT_TXFULL,
10  .tx_fifomask = S3C2410_UFSTAT_TXMASK,
11  .tx_fifoshift = S3C2410_UFSTAT_TXSHIFT,
12  .get_clksrc = s3c2410_serial_getsource,
13  .set_clksrc = s3c2410_serial_setsource,
14  .reset_port = s3c2410_serial_resetport,
15 };
在S3C2410串口驅動中,針對UART的設置(UCONn、ULCONn、UFCONn寄存器等)被封裝到s3c2410_uartcfg結構體中,其定義如代碼清單14.25。
代碼清單14.25 S3C2410串口驅動s3c2410_uartcfg結構體
1  struct s3c2410_uartcfg
2  {
3    unsigned char hwport; /* 硬件端口號 */
4    unsigned char unused;
5    unsigned short flags;
6    unsigned long uart_flags; /* 缺省的uart標誌 */

8    unsigned long ucon; /* 端口的ucon值 */
9    unsigned long ulcon; /* 端口的ulcon值 */
10   unsigned long ufcon; /* 端口的ufcon值 */
11
12   struct s3c24xx_uart_clksrc *clocks;
13   unsigned int clocks_size;
14 };
14.7.3 S3C2410串口驅動初始化與釋放
    在S3C2410 串口驅動的模塊加載函數中會調用uart_register_driver()註冊s3c24xx_uart_drv這個uart_driver,同時經過s3c2410_serial_init()→s3c24xx_serial_init()→platform_driver_register()的調用導致s3c24xx_serial_probe()被執行,而s3c24xx_serial_probe()函數中會調用 s3c24xx_serial_init_port()初始化UART端口並調用uart_add_one_port()添加端口,整個過程的對應代碼如 清單14.26。
代碼清單14.26 S3C2410串口驅動初始化過程
1   static int __init s3c24xx_serial_modinit(void)
2   {
3    int ret;
4     //註冊uart_driver
5    ret = uart_register_driver(&s3c24xx_uart_drv);
6    if (ret < 0) {
7     printk(KERN_ERR "failed to register UART driver\n");
8     return -1;
9    }
10    //初始化s3c2410的串口
11   s3c2410_serial_init();
12 
13   return 0;
14  }
15 
16  static inline int s3c2410_serial_init(void)
17  {
18   return s3c24xx_serial_init(&s3c2410_serial_drv, &s3c2410_uart_inf);
19  }
20 
21  static int s3c24xx_serial_init(struct platform_driver *drv,
22            struct s3c24xx_uart_info *info)
23  {
24   dbg("s3c24xx_serial_init(%p,%p)\n", drv, info);
25   return platform_driver_register(drv);//註冊平臺驅動
26  }
27 
28  //平臺驅動probe()函數
29  static int s3c24xx_serial_probe(struct platform_device *dev,
30      struct s3c24xx_uart_info *info)
31  {
32   struct s3c24xx_uart_port *ourport;
33   int ret;
34 
35   dbg("s3c24xx_serial_probe(%p, %p) %d\n", dev, info, probe_index);
36 
37   ourport = &s3c24xx_serial_ports[probe_index];
38   probe_index++;
39 
40   dbg("%s: initialising port %p...\n", __FUNCTION__, ourport);
41    //初始化uart端口
42   ret = s3c24xx_serial_init_port(ourport, info, dev);
43   if (ret < 0)
44    goto probe_err;
45 
46   dbg("%s: adding port\n", __FUNCTION__);
47   //添加uart_port
48   uart_add_one_port(&s3c24xx_uart_drv, &ourport->port);
49   platform_set_drvdata(dev, &ourport->port);
50 
51   return 0;
52 
53   probe_err:
54   return ret;
55  }
56  //42行調用的s3c24xx_serial_init_port()函數
57  static int s3c24xx_serial_init_port(struct s3c24xx_uart_port *ourport,
58          struct s3c24xx_uart_info *info,
59          struct platform_device *platdev)
60  {
61   struct uart_port *port = &ourport->port;
62   struct s3c2410_uartcfg *cfg;
63   struct resource *res;
64 
65   dbg("s3c24xx_serial_init_port: port=%p, platdev=%p\n", port, platdev);
66 
67   if (platdev == NULL)
68    return -ENODEV;
69 
70   cfg = s3c24xx_dev_to_cfg(&platdev->dev);
71 
72   if (port->mapbase != 0)
73    return 0;
74 
75   if (cfg->hwport > 3)
76    return -EINVAL;
77 
78   /* 爲端口設置info成員 */
79   port->dev = &platdev->dev;
80   ourport->info = info;
81 
82   /* 初始化fifosize */
83   ourport->port.fifosize = info->fifosize;
84 
85   dbg("s3c24xx_serial_init_port: %p (hw %d)...\n", port, cfg->hwport);
86 
87   port->uartclk = 1;
88    /* 如果使用流控 */
89   if (cfg->uart_flags & UPF_CONS_FLOW) {
90    dbg("s3c24xx_serial_init_port: enabling flow control\n");
91    port->flags |= UPF_CONS_FLOW;
92   }
93 
94   /* 利用平臺資源中記錄的信息初始化uart端口的基地址、中斷號 */
95   res = platform_get_resource(platdev, IORESOURCE_MEM, 0);
96   if (res == NULL) {
97    printk(KERN_ERR "failed to find memory resource for uart\n");
98    return -EINVAL;
99   }
100
101  dbg("resource %p (%lx..%lx)\n", res, res->start, res->end);
102
103  port->mapbase = res->start;
104  port->membase = S3C24XX_VA_UART+(res->start - S3C24XX_PA_UART);
105  port->irq = platform_get_irq(platdev, 0);
106
107  ourport->clk = clk_get(&platdev->dev, "uart");
108
109  dbg("port: map=%08x, mem=%08x, irq=%d, clock=%ld\n",
110      port->mapbase, port->membase, port->irq, port->uartclk);
111
112  /* 復位fifo並設置uart */
113  s3c24xx_serial_resetport(port, cfg);
114  return 0;
115 }
116 //113行調用的s3c24xx_serial_resetport()函數
117 static inline int s3c24xx_serial_resetport(struct uart_port * port,
118         struct s3c2410_uartcfg *cfg)
119 {
120  struct s3c24xx_uart_info *info = s3c24xx_port_to_info(port);
121
122  return (info->reset_port)(port, cfg);
123 }
124 //122行調用的info->reset_port()函數
125 static int s3c2410_serial_resetport(struct uart_port *port,
126         struct s3c2410_uartcfg *cfg)
127 {
128  dbg("s3c2410_serial_resetport: port=%p (%08lx), cfg=%p\n",
129      port, port->mapbase, cfg);
130
131  wr_regl(port, S3C2410_UCON,  cfg->ucon);
132  wr_regl(port, S3C2410_ULCON, cfg->ulcon);
133
134  /* 復位2個fifo */
135  wr_regl(port, S3C2410_UFCON, cfg->ufcon | S3C2410_UFCON_RESETBOTH);
136  wr_regl(port, S3C2410_UFCON, cfg->ufcon);
137
138  return 0;
139 }
    在S3C2410串口驅動模塊被卸載時,它會最終調用uart_remove_one_port()釋放uart_port並調用uart_unregister_driver()註銷uart_driver,如代碼清單14.27所示。
代碼清單14.27 S3C2410串口驅動釋放過程
1  static void __exit s3c24xx_serial_modexit(void)
2  {
3   s3c2410_serial_exit();
4    //註銷uart_driver
5   uart_unregister_driver(&s3c24xx_uart_drv);
6  }

8  static inline void s3c2410_serial_exit(void)
9  {
10  //註銷平臺驅動
11  platform_driver_unregister(&s3c2410_serial_drv);
12 }
13
14 static int s3c24xx_serial_remove(struct platform_device *dev)
15 {
16  struct uart_port *port = s3c24xx_dev_to_port(&dev->dev);
17
18  //移除uart端口
19  if (port)
20   uart_remove_one_port(&s3c24xx_uart_drv, port);
21
22  return 0;
23 }
     上述代碼中對S3C24xx_serial_remove()的調用發生在platform_driver_unregister()之後,由於S3C2410的UART是集成於SoC芯片內部的一個獨立的硬件單元,因此也被作爲1個平臺設備而定義。
14.7.4 S3C2410串口數據收發
     S3C2410串口驅動uart_ops結構體的startup ()成員函數s3c24xx_serial_startup()用於啓動端口,申請端口的發送、接收中斷,使能端口的發送和接收,其實現如代碼清單14.28。
代碼清單14.28 S3C2410串口驅動startup ()函數
1  static int s3c24xx_serial_startup(struct uart_port *port)
2  {
3   struct s3c24xx_uart_port *ourport = to_ourport(port);
4   int ret;

6   dbg("s3c24xx_serial_startup: port=%p (%08lx,%p)\n",
7       port->mapbase, port->membase);

9   rx_enabled(port) = 1;//置接收使能狀態爲1
10   //申請接收中斷
11  ret = request_irq(RX_IRQ(port),
12      s3c24xx_serial_rx_chars, 0,
13      s3c24xx_serial_portname(port), ourport);
14
15  if (ret != 0) {
16   printk(KERN_ERR "cannot get irq %d\n", RX_IRQ(port));
17   return ret;
18  }
19  
20  ourport->rx_claimed = 1;
21
22  dbg("requesting tx irq...\n");
23
24  tx_enabled(port) = 1;//置發送使能狀態爲1
25   //申請發送中斷
26  ret = request_irq(TX_IRQ(port),
27      s3c24xx_serial_tx_chars, 0,
28      s3c24xx_serial_portname(port), ourport);
29
30  if (ret) {
31   printk(KERN_ERR "cannot get irq %d\n", TX_IRQ(port));
32   goto err;
33  }
34
35  ourport->tx_claimed = 1;
36
37  dbg("s3c24xx_serial_startup ok\n");
38
39  /* 端口復位代碼應該已經爲端口控制設置了正確的寄存器 */
40
41  return ret;
42
43  err:
44  s3c24xx_serial_shutdown(port);
45  return ret;
46 }
s3c24xx_serial_startup()的“反函數”爲s3c24xx_serial_shutdown(),其釋放中斷,禁止發送和接收,實現如代碼清單14.29。
代碼清單14.29 S3C2410串口驅動shutdown ()函數
1  static void s3c24xx_serial_shutdown(struct uart_port *port)
2  {
3   struct s3c24xx_uart_port *ourport = to_ourport(port);

5   if (ourport->tx_claimed) {
6    free_irq(TX_IRQ(port), ourport);
7    tx_enabled(port) = 0; //置發送使能狀態爲0
8    ourport->tx_claimed = 0;
9   }
10
11  if (ourport->rx_claimed) {
12   free_irq(RX_IRQ(port), ourport);
13   ourport->rx_claimed = 0;
14   rx_enabled(port) = 0; //置接收使能狀態爲1
15  }
16 }
    S3C2410 串口驅動uart_ops結構體的tx_empty()成員函數s3c24xx_serial_tx_empty()用於判斷髮送緩衝區是否爲空,其實現 如代碼清單14.30,當使能FIFO模式的時候,判斷UFSTATn寄存器,否則判斷UTRSTATn寄存器的相應位。
代碼清單14.30 S3C2410串口驅動tx_empty()函數
1  /* 檢查發送緩衝區/FIFO是否爲空 */
2  static unsigned int s3c24xx_serial_tx_empty(struct uart_port *port)
3  {
4    //fifo模式,檢查UFSTATn寄存器
5    struct s3c24xx_uart_info *info = s3c24xx_port_to_info(port);
6    unsigned long ufstat = rd_regl(port, S3C2410_UFSTAT);
7    unsigned long ufcon = rd_regl(port, S3C2410_UFCON);

9    if (ufcon &S3C2410_UFCON_FIFOMODE)
10   {
11     if ((ufstat &info->tx_fifomask) != 0 ||  //Tx fifo數據數非0
12     (ufstat &info->tx_fifofull))   // Tx fifo滿
13       return 0;   //0:非空
14
15     return 1; //1:空
16   }
17
18   return s3c24xx_serial_txempty_nofifo(port);
19 }
20
21 static int s3c24xx_serial_txempty_nofifo(struct uart_port *port)
22 {
23   //非fifo模式,檢查UTRSTATn寄存器
24   return (rd_regl(port, S3C2410_UTRSTAT) &S3C2410_UTRSTAT_TXE);
25 }
    S3C2410 串口驅動uart_ops結構體的start_tx ()成員函數s3c24xx_serial_start_tx()用於啓動發送,而stop_rx()成員函數 s3c24xx_serial_stop_rx()用於停止發送,代碼清單14.31給出了這2個函數的實現。
代碼清單14.31 S3C2410串口驅動start_tx ()、stop_rx()函數
1  static void s3c24xx_serial_start_tx(struct uart_port *port)
2  {
3   if (!tx_enabled(port)) {//如果端口發送未使能
4    if (port->flags & UPF_CONS_FLOW)
5     s3c24xx_serial_rx_disable(port);

7    enable_irq(TX_IRQ(port));//使能發送中斷
8    tx_enabled(port) = 1;//置端口發送使能狀態爲1
9   }
10 }
11
12 static void s3c24xx_serial_stop_tx(struct uart_port *port)
13 {
14  if (tx_enabled(port)) {//如果端口發送已使能
15   disable_irq(TX_IRQ(port));//禁止發送中斷
16   tx_enabled(port) = 0;//置端口發送使能狀態爲0
17   if (port->flags & UPF_CONS_FLOW)
18    s3c24xx_serial_rx_enable(port);
19  }
20 }
     S3C2410 串口驅動uart_ops結構體的stop_rx ()成員函數s3c24xx_serial_stop_rx ()用於停止接收,代碼清單14.32給出了這個函數的實現。注意uart_ops中沒有start_rx()成員,因爲接收並不是由“我方”啓動,而是 由“它方”的發送觸發“我方”的接收中斷,“我方”再被動響應。
代碼清單14.32 S3C2410串口驅動stop_rx ()函數
1  static void s3c24xx_serial_stop_rx(struct uart_port *port)
2  {
3   if (rx_enabled(port)) {//如果接收爲使能
4    dbg("s3c24xx_serial_stop_rx: port=%p\n", port);
5    disable_irq(RX_IRQ(port));//禁止接收中斷
6    rx_enabled(port) = 0;//置接收使能狀態爲0
7   }
8  }
    在S3C2410 串口驅動中,與數據收發關係最密切的函數不是上述uart_ops成員函數,而是s3c24xx_serial_startup()爲發送和接收中斷註冊 的中斷處理函數s3c24xx_serial_rx_chars()和s3c24xx_serial_tx_chars()。s3c24xx_serial_rx_chars ()讀取URXHn寄存器以獲得接收到的字符,並調用uart_insert_char()將該字符添加了tty設備的flip緩衝區中,當接收到64個 字符或者不再能接受到字符後,調用tty_flip_buffer_push()函數向上層“推”tty設備的flip緩衝,其實現如代碼清單 14.33。
代碼清單14.33 S3C2410串口驅動接收中斷處理函數
1  static irqreturn_t s3c24xx_serial_rx_chars(int irq, void *dev_id, struct
2    pt_regs *regs)
3  {
4    struct s3c24xx_uart_port *ourport = dev_id;
5    struct uart_port *port = &ourport->port; //獲得uart_port
6    struct tty_struct *tty = port->info->tty; //獲得tty_struct
7    unsigned int ufcon, ch, flag, ufstat, uerstat;
8    int max_count = 64;

10   while (max_count-- > 0)
11   {
12     ufcon = rd_regl(port, S3C2410_UFCON);
13     ufstat = rd_regl(port, S3C2410_UFSTAT);
14     //如果接收到0個字符
15     if (s3c24xx_serial_rx_fifocnt(ourport, ufstat) == 0)
16       break;
17
18     uerstat = rd_regl(port, S3C2410_UERSTAT);
19     ch = rd_regb(port, S3C2410_URXH); //讀出字符
20
21     if (port->flags &UPF_CONS_FLOW)
22     {
23       int txe = s3c24xx_serial_txempty_nofifo(port);
24
25       if (rx_enabled(port)) //如果端口爲使能接收狀態
26       {
27         if (!txe) //如果發送緩衝區爲空
28         {
29           rx_enabled(port) = 0; //置端口爲使能接收狀態爲0
30           continue;
31         }
32       }
33       else   //端口爲禁止接收狀態
34       {
35         if (txe)  //如果發送緩衝區非空
36         {
37           ufcon |= S3C2410_UFCON_RESETRX;
38           wr_regl(port, S3C2410_UFCON, ufcon);
39           rx_enabled(port) = 1;//置端口爲使能接收狀態爲1
40           goto out;
41         }
42         continue;
43       }
44     }
45
46     /* 將接收到的字符寫入buffer */
47     flag = TTY_NORMAL;
48     port->icount.rx++;
49
50     if (unlikely(uerstat &S3C2410_UERSTAT_ANY))
51     {
52       dbg("rxerr: port ch=0x%02x, rxs=0x%08x\n", ch, uerstat);
53
54        if (uerstat &S3C2410_UERSTAT_BREAK)
55       {
56         dbg("break!\n");
57         port->icount.brk++;
58         if (uart_handle_break(port))
59           goto ignore_char;
60       }
61
62       if (uerstat &S3C2410_UERSTAT_FRAME)
63         port->icount.frame++;
64       if (uerstat &S3C2410_UERSTAT_OVERRUN)
65         port->icount.overrun++;
66
67       uerstat &= port->read_status_mask;
68
69       if (uerstat &S3C2410_UERSTAT_BREAK)
70         flag = TTY_BREAK;
71       else if (uerstat &S3C2410_UERSTAT_PARITY)
72         flag = TTY_PARITY;
73       else if (uerstat &(S3C2410_UERSTAT_FRAME | S3C2410_UERSTAT_OVERRUN))
74         flag = TTY_FRAME;
75     }
76
77     if (uart_handle_sysrq_char(port, ch, regs)) //處理sysrq字符
78       goto ignore_char;
79     //插入字符到tty設備的flip 緩衝
80     uart_insert_char(port, uerstat, S3C2410_UERSTAT_OVERRUN, ch, flag);
81
82     ignore_char: continue;
83   }
84   tty_flip_buffer_push(tty);  //刷新tty設備的flip 緩衝
85
86   out: return IRQ_HANDLED;
87 }
    上述代碼第80行的uart_insert_char()函數是串口核心層對tty_insert_flip_char()的封裝,它作爲內聯函數被定義於serial_core.h文件中。
如代碼清單14.34,s3c24xx_serial_tx_chars()讀取uart_info中環形緩衝區中的字符,寫入調用UTXHn寄存器。
代碼清單14.34 S3C2410串口驅動發送中斷處理函數
1  static irqreturn_t s3c24xx_serial_tx_chars(int irq, void *id, struct pt_regs
2    *regs)
3  {
4    struct s3c24xx_uart_port *ourport = id;
5    struct uart_port *port = &ourport->port;
6    struct circ_buf *xmit = &port->info->xmit;  //得到環形緩衝區
7    int count = 256;   //最多1次發256個字符

9    if (port->x_char) //如果定義了xchar,發送
10   {
11     wr_regb(port, S3C2410_UTXH, port->x_char);
12     port->icount.tx++;
13     port->x_char = 0;
14     goto out;
15   }
16
17   /* 如果沒有更多的字符需要發送,或者uart Tx停止,則停止uart並退出 */
18   if (uart_circ_empty(xmit) || uart_tx_stopped(port))
19   {
20     s3c24xx_serial_stop_tx(port);
21     goto out;
22   }
23
24   /* 嘗試把環行buffer中的數據發空 */
25   while (!uart_circ_empty(xmit) && count-- > 0)
26   {
27     if (rd_regl(port, S3C2410_UFSTAT) &ourport->info->tx_fifofull)
28       break;
29
30     wr_regb(port, S3C2410_UTXH, xmit->buf[xmit->tail]);
31     xmit->tail = (xmit->tail + 1) &(UART_XMIT_SIZE - 1);
32     port->icount.tx++;
33   }
34   /* 如果環形緩衝區中剩餘的字符少於WAKEUP_CHARS,喚醒上層 */
35   if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
36     uart_write_wakeup(port);
37
38   if (uart_circ_empty(xmit)) //如果發送環形buffer爲空
39     s3c24xx_serial_stop_tx(port); //停止發送
40
41   out: return IRQ_HANDLED;
42 }
    上述代碼第35行的宏WAKEUP_CHARS的含義爲:當發送環形緩衝區中的字符數小於該數時,驅動將請求上層向下傳遞更多的數據,uart_write_wakeup()完成此目的。
uart_circ_chars_pending()、uart_circ_empty()是定義於serial_core.h中的宏,分別返回環形緩衝區剩餘的字符數以及判斷緩衝區是否爲空。
14.7.5 S3C2410串口線路設置
     S3C2410 串口驅動uart_ops結構體的set_termios()成員函數用於改變端口的參數設置,包括波特率、字長、停止位、奇偶校驗等,它會根據傳遞給它 的port、termios參數成員的值設置S3C2410 UART的ULCONn、UCONn、UMCONn等寄存器,其實現如代碼清單14.35。
代碼清單14.35 S3C2410串口驅動set_termios()函數
1   static void s3c24xx_serial_set_termios(struct uart_port *port,
2              struct termios *termios,
3              struct termios *old)
4   {
5    struct s3c2410_uartcfg *cfg = s3c24xx_port_to_cfg(port);
6    struct s3c24xx_uart_port *ourport = to_ourport(port);
7    struct s3c24xx_uart_clksrc *clksrc = NULL;
8    struct clk *clk = NULL;
9    unsigned long flags;
10   unsigned int baud, quot;
11   unsigned int ulcon;
12   unsigned int umcon;
13
14   /* 不支持modem控制信號線 */
15   termios->c_cflag &= ~(HUPCL | CMSPAR);
16   termios->c_cflag |= CLOCAL;
17
18   /* 請求內核計算分頻以便產生對應的波特率 */
19   baud = uart_get_baud_rate(port, termios, old, 0, 115200*8);
20
21   if (baud == 38400 && (port->flags & UPF_SPD_MASK) == UPF_SPD_CUST)
22    quot = port->custom_divisor;
23   else
24    quot = s3c24xx_serial_getclk(port, &clksrc, &clk, baud);
25
26   /* 檢查以確定是否需要改變時鐘源 */
27   if (ourport->clksrc != clksrc || ourport->baudclk != clk) {
28    s3c24xx_serial_setsource(port, clksrc);
29
30    if (ourport->baudclk != NULL && !IS_ERR(ourport->baudclk)) {
31     clk_disable(ourport->baudclk);
32     ourport->baudclk  = NULL;
33    }
34
35    clk_enable(clk);
36
37    ourport->clksrc = clksrc;
38    ourport->baudclk = clk;
39   }
40
41    /* 設置字長 */
42   switch (termios->c_cflag & CSIZE) {
43   case CS5:
44    dbg("config: 5bits/char\n");
45    ulcon = S3C2410_LCON_CS5;
46    break;
47   case CS6:
48    dbg("config: 6bits/char\n");
49    ulcon = S3C2410_LCON_CS6;
50    break;
51   case CS7:
52    dbg("config: 7bits/char\n");
53    ulcon = S3C2410_LCON_CS7;
54    break;
55   case CS8:
56   default:
57    dbg("config: 8bits/char\n");
58    ulcon = S3C2410_LCON_CS8;
59    break;
60   }
61
62   /* 保留以前的lcon IR設置 */
63   ulcon |= (cfg->ulcon & S3C2410_LCON_IRM);
64
65   if (termios->c_cflag & CSTOPB)
66    ulcon |= S3C2410_LCON_STOPB;
67    /* 設置是否採用RTS、CTS自動流空 */
68   umcon = (termios->c_cflag & CRTSCTS) ? S3C2410_UMCOM_AFC : 0;
69
70   if (termios->c_cflag & PARENB) {
71    if (termios->c_cflag & PARODD)
72     ulcon |= S3C2410_LCON_PODD;//計校驗
73    else
74     ulcon |= S3C2410_LCON_PEVEN;//偶校驗
75   } else {
76    ulcon |= S3C2410_LCON_PNONE;//無校驗
77   }
78
79   spin_lock_irqsave(&port->lock, flags);
80
81   dbg("setting ulcon to %08x, brddiv to %d\n", ulcon, quot);
82
83   wr_regl(port, S3C2410_ULCON, ulcon);
84   wr_regl(port, S3C2410_UBRDIV, quot);
85   wr_regl(port, S3C2410_UMCON, umcon);
86
87   dbg("uart: ulcon = 0x%08x, ucon = 0x%08x, ufcon = 0x%08x\n",
88       rd_regl(port, S3C2410_ULCON),
89       rd_regl(port, S3C2410_UCON),
90       rd_regl(port, S3C2410_UFCON));
91
92   /* 更新端口的超時 */
93   uart_update_timeout(port, termios->c_cflag, baud);
94
95   /* 我們對什麼字符狀態狀態標誌感興趣?*/
96   port->read_status_mask = S3C2410_UERSTAT_OVERRUN;
97   if (termios->c_iflag & INPCK)
98    port->read_status_mask |= S3C2410_UERSTAT_FRAME | S3C2410_UERSTAT_PARITY;
99
100  /* 我們要忽略什麼字符狀態標誌?*/
101  port->ignore_status_mask = 0;
102  if (termios->c_iflag & IGNPAR)
103   port->ignore_status_mask |= S3C2410_UERSTAT_OVERRUN;
104  if (termios->c_iflag & IGNBRK && termios->c_iflag & IGNPAR)
105   port->ignore_status_mask |= S3C2410_UERSTAT_FRAME;
106
107  /* 如果CREAD未設置,忽略所用字符 */
108  if ((termios->c_cflag & CREAD) == 0)
109   port->ignore_status_mask |= RXSTAT_DUMMY_READ;
110
111  spin_unlock_irqrestore(&port->lock, flags);
112 }
    由於S3C2410集成UART並不包含完整的Modem控制信號線,因此其uart_ops結構體的get_mctrl()、set_mctrl()成員 函數的實現非常簡單,如代碼清單14.36,get_mctrl()返回DSR一直有效,而CTS則根據UMSTATn寄存器的內容獲得, set_mctrl()目前爲空。
代碼清單14.36 S3C2410串口驅動get_mctrl()和set_mctrl()函數
1  static unsigned int s3c24xx_serial_get_mctrl(struct uart_port *port)
2  {
3   unsigned int umstat = rd_regb(port,S3C2410_UMSTAT);

5   if (umstat & S3C2410_UMSTAT_CTS)//CTS信號有效(低電平)
6    return TIOCM_CAR | TIOCM_DSR | TIOCM_CTS;
7   else
8    return TIOCM_CAR | TIOCM_DSR;
9  }
10
11 static void s3c24xx_serial_set_mctrl(struct uart_port *port, unsigned int mctrl)
12 {
13  /* todo:可能移除AFC,並手工進行CTS */
14 }
14.8總結
    TTY設備驅動的主體工作圍繞tty_driver這個結構體的成員函數展開,主要應實現其中的數據發送和接收流程以及tty設備線路設置接口函數。
針對串口,內核實現了串口核心層,這個層實現了串口設備通用的tty_driver。因此,串口設備驅動的主體工作從tty_driver轉移到了uart_driver。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章