乾坤合一~Linux設備驅動之終端設備驅動

  • 多想擁你在個人懷裏
  • 卻沒法超越那距離
  • 美好回憶漸漸地遠去
  • 盼望此生出現奇蹟
  •  
  • 無盡的想念
  • 荒了容顏
  • 無助的愛戀
  • 從未改變

這是今天的旋律,,,,此生今世,高不可攀~html

1 終端設備ios

  終端是一種字符型設備,一般使用tty簡稱各類類型的設備數據結構

1.1 串行端口終端(/dev/ttySn)函數

  串行端口終端 (Serial Port Terminal )是使用計算機串行端口鏈接的終端設備。計算機把每一個串行端口都看做是一個字符設備。在命令行上把標準輸出重定向到端口對應的設備文件名上就能夠經過端口發送數據。ui

1.2 僞終端(/dev/pty/)spa

  僞終端 (Pseudo Terminal )是成對的邏輯終端設備,並存在成對的設備文件。以telnet 爲例,若是某人在使用telnet 程序鏈接到Linux 系統,則telnet 程序就可能會開始鏈接到設備ptyp2 上,而此時一個getty 程序會運行在對應的ttyp2 端口上。 當telnet 從遠端獲取了一個字符時, 字符就會經過ptyp2 、ttyp2 傳遞給getty 程序,而getty 程序則會經過ttyp2 、ptyp2 和telnet 程序返回「login: 」字符串信息。這樣,登陸程序與telnet 程序就經過僞終端進行通訊。經過使用適當的軟件,能夠把兩個或多個僞終端設備鏈接到同一個物理串行端口上。命令行

1.3 控制檯終端(/dev/ttyn,/dev/console)指針

  若是當前進程有控制終端 (Controlling Terminal ),那麼/dev/tty 就是當前進程的控 制終端的設備特殊文件。可使用命令「ps ax 」來查看進程與哪一個控制終端相連,使用命令 「tty 」能夠查看它具體對應哪一個實際終端設備。/dev/tty 有些相似於到實際所 使用終端設備的一個鏈接。rest

  在Linux 系統中,能夠在系統啓動命令行裏指定當前的輸出終端,格式以下:code

console=device, options 
//device 指代的是終端設備
//options 指代對device 進行的設置,它取決於具體的設備驅動

2 終端設備驅動結構

終端設備驅動都圍繞tty_driver 結構體而展開,通常而言,終端設備驅動應包含以下組成:

  • 終端設備驅動模塊加載函數和卸載函數:完成註冊和註銷 tty_driver,初始化 和釋放終端設備對應的tty_driver 結構體成員及硬件資源。
  • 實現tty_operations 結構體中的一系列成員函數:主要是實現open()、close()、 write()、tiocmget()、tiocmset()等函數

2.1 tty層次結構

  Linux內核tty驅動結構包括tty 核心、tty 線路規程和tty 驅動,從用戶獲取數據以後,其發送數據的結構圖以下:

接收數據的流程圖以下:

2.2 tty_dirver結構體
  特定tty 設備驅動的主體工做是填充tty_driver 結構體中的成員,實現其中的成員函數,tty_driver 結構體的定義以下: 

struct tty driver 
      { 
        int magic; //表示給這個結構體的 「幻數 」,設爲TTY_DRIVER_MAGIC,在 alloc_tty_driver() 函數中被初始化。
       struct cdev cdev; /*    應的字符設備cdev */ 
        struct module *owner;     /*這個驅動的模塊擁有者 */ 

        const char *driver name; 
                        
        const char *devfs name; 
        const char *name;     /* 設備名 */ 
                
        int name base; /* offset of printed name */ 
        int major; /* 主設備號 */ 
           
        int minor start; /* 開始次設備號 */ 
                 
        int minor num; /* 設備數量 */ 
        int num; /* 被分配的設備數量 */ 
        short type; /* tty驅動的類型 */ 
        short subtype; /* tty 驅動的子類型 */ 
                             
        struct termios init termios; /* 初始線路設置 */ 
  //init_termios 爲初始線路設置,爲一個termios 結構體,這個成員被用來提供一個線路設置集合。
       int flags; /* tty 驅動標誌 */ 
        int refcount; /*引用計數 (針 可加載的tty驅動) */ 
                     
        struct proc dir entry *proc entry; /* /proc 文件系統入口 */ 
                  
        struct tty driver *other; /* 僅 PTY 驅動有意義 */ 
        ... 
        /* 接口函數 */ 
                               
        int (*open)(struct tty struct *tty, struct file *filp); 
                               
       void (*close)(struct tty struct *tty, struct file *filp); 
                                 
        int (*write)(struct tty struct *tty, const unsigned char *buf, int count); 
               
       void (*put char)(struct tty struct *tty, unsigned char ch); 
                  
      void (*flush chars)(struct tty struct *tty); 
               
        int (*write room)(struct tty struct *tty); 
                 
        int (*chars in buffer)(struct tty struct *tty); 
                      
        int (*ioctl)(struct tty struct *tty, struct file *file,unsigned int cmd,unsigned long arg); 
               
       void (*set termios)(struct tty struct *tty, struct termios *old); 
                              
       void (*throttle)(struct tty struct *tty); 
                                   
       void (*unthrottle)(struct tty struct *tty); 
                             
       void (*stop)(struct tty struct *tty); 
                                 
       void (*start)(struct tty struct *tty); 
                                 
       void (*hangup)(struct tty struct *tty); 
                  
       void (*break ctl)(struct tty struct *tty, int state); 
                   
       void (*flush buffer)(struct tty struct *tty); 
                
       void (*set ldisc)(struct tty struct *tty); 
              
       void (*wait until sent)(struct tty struct *tty, int timeout); 
                 
       void (*send xchar)(struct tty struct *tty, char ch); 
                       
        int (*read proc)(char *page, char **start, off t off, int count, int *eof,  void *data); 
                  
        int (*write proc)(struct file *file, const char        user *buffer, unsigned long count, void *data); 
                          
        int (*tiocmget)(struct tty struct *tty, struct file *file); 
                                
        int (*tiocmset)(struct tty struct *tty, struct file *file,unsigned int set, unsigned int clear); 

        struct list head tty drivers; 
      }; 

2.3 tty_std_termos結構體

  驅動會使用一個標準的數值集初始化這個成員,這個數值集來源於tty_std_termios 變量,tty_std_termos 在tty 核心中的定義以下:

struct termios tty std termios = 
      { 
          _ 
       .c iflag = ICRNL | IXON, /* 輸入模式 */ 
          _ 
       .c oflag = OPOST | ONLCR, /* 輸出模式 */ 
          _ 
       .c cflag = B38400 | CS8 | CREAD | HUPCL, /* 控制模式 */ 
          _ 
       .c lflag = ISIG | ICANON | ECHO | ECHOE | ECHOK | 
       ECHOCTL | ECHOKE | IEXTEN,  /* 本地模式 */ 
          _          _ _ 
       .c cc = INIT C CC  /* 控制字符,用來修改終端的特殊字符映射 */ 
      }; 

2.4 tty_driver 結構體及tty 設備的操做

//1 分配tty 驅動 
    struct tty driver *alloc tty driver(int lines); 
  //  這個函數返回 tty_driver 指針,其參數爲要分配的設備數量,line會被賦值給 tty_driver 的num 成員

//2 註冊tty 驅動 
    int tty register driver(struct tty driver *driver); 
  //  註冊tty 驅動成功時返回0,參數爲由alloc_tty_driver ()分配的tty_driver 結構體指針

//3 註銷tty 驅動 
    int tty unregister driver(struct tty driver *driver);
 // 這個函數與tty_register_driver ()對應,tty 驅動最終會調用上述函數註銷tty_driver

//4 註冊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 )

//5 註銷tty 設備 
    void tty unregister device (struct tty driver *driver,unsigned index); 
   // 上述函數與tty_register_device()對應,用於註銷tty 設備

//6 設置tty 驅動操做 
    void     tty set operations(struct     tty driver     *driver,     struct tty operations *op); 
 // 上述函數會將tty_operations  結構體中的函數指針複製到tty_driver 對應的函數指針

3 設備的初始化和釋放

3.1 模塊加載和卸載函數

  tty 驅動的模塊加載函數中一般須要分配、初始化tty_driver 結構體並申請必要的硬件資源,下面舉一個終端設備驅動的模塊加載函數的例子,代碼以下:

/* tty 驅動的模塊加載函數 */ 

      static int     init xxx init (void) 

      { 

        ... 

        /* 分配tty driver 結構體 */ 

        xxx tty driver = alloc tty driver(XXX PORTS); 

        /* 初始化tty driver 結構體 */ 

        xxx tty driver->owner = THIS MODULE; 
 
        xxx tty driver->devfs name = "tts/"; 
 
       xxx tty driver->name = "ttyS"; 
            
       xxx tty driver->major = TTY MAJOR; 
         
       xxx tty driver->minor start = 64; 
     
       xxx tty driver->type = TTY DRIVER TYPE SERIAL; 
         
       xxx tty driver->subtype = SERIAL TYPE NORMAL; 
          
       xxx tty driver->init termios = tty std termios; 
         
       xxx tty driver->init termios.c cflag = B9600 |CS8 |CREAD |HUPCL | CLOCAL; 
          
       xxx tty driver->flags = TTY DRIVER REAL RAW; 
          
       tty set operations(xxx tty driver, &xxx ops); 

       ret = tty register driver(xxx tty driver); 
       if (ret) 
       { 
                    
         printk(KERN ERR "Couldn't register xxx serial driver\n"); 
              
         put tty driver(xxx tty driver); 
         return ret; 
       } 
      
       ... 
                 
       ret = request irq (...); /* 硬件資源申請 */ 
       ... 
     }         

3.2 打開與關閉函數

  當用戶對tty 驅動所分配的設備節點進行open()系統調用時,tty_driver 中的open() 成員函數將被 tty 核心調用。tty 驅動必須設置open()成員,不然,ENODEV 將被返回給調用open() 的用戶。tty_struct 結構體被 tty 核心用來保存當前tty 端口的狀態,它的大多數成員只被tty核心使用,驅動中能夠定義一個設備相關的結體,並在open()函數中將其賦值給tty_struct的driver_data 成員,其代碼以下:

/* 設備 「私有」數據結構體 */ 
                  
      struct xxx tty 
      { 
            
        struct tty struct *tty; /* tty struct 指針 */ 
           
        int open count; /* 打開次數 */ 
        struct semaphore sem; /* 結構體鎖定信號量 */ 
              
        intxmit buf; /* 傳輸 衝區 */ 
        ... 
       } 
      
     /* 打開函數 */ 
            
     static int xxx open (struct tty struct *tty, struct file *file) 
     { 
               
       struct xxx tty *xxx; 
                        
       /* 分配xxx tty */ 
                                
       xxx = kmalloc(sizeof(*xxx), GFP KERNEL); 
       if (!xxx) 
         return  - ENOMEM; 
                 
       /* 初始化xxx tty 中的成員 */ 
           
       init MUTEX (&xxx->sem); 
                
       xxx->open count = 0; 
        ... 
            
       /* 使tty struct 中的driver data 指向xxx tty */ 
                  
       tty->driver data = xxx; 
       xxx->tty = tty; 
        ... 
       return 0; 
     } 

4 數據發送和接收

  用戶在有數據發送給終端設備時,經過 「write()系統調用—tty 核心—線路規程」的層層調用,最終調用tty_driver 結構體中的write()函數完成發送。

4.1 tty_driver 的write()函數

   tty_driver 的write()函數接受3 個參數:tty_struct、發送數據指針及要發送的字節數,通常首先會經過tty_struct 的driver_data 成員獲得設備私有信息結構體,而後依次進行必要的硬件操做開始發送,相關的代碼以下:

static int xxx write (struct tty struct *tty, const unsigned char *buf, int count) 
      { 
        /* 得到tty 設備私有數據 */ 
      
        struct xxx tty *xxx = (struct xxx tty*)tty->driver data; 
        ... 
        /* 開始發送 */ 
        while (1) 
        { 
         
          local irq save (flags); 
              
         c = min t (int, count, min (SERIAL XMIT SIZE - xxx->xmit cnt - 1, 
               
           SERIAL XMIT SIZE - xxx->xmit head)); 

         if (c <= 0) 
         { 
                
           local irq restore (flags); 
           break; 
         } 
         //複製到發送 衝區 

          memcpy (xxx->xmit buf + xxx->xmit head, buf, c); 
           
         xxx->xmit head = (xxx->xmit head + c) &(SERIAL XMIT SIZE - 1); 
             
         xxx->xmit cnt += c; 
              
         local irq restore (flags); 

         buf += c; 

         count -= c; 

         total += c; 

        } 

       if (xxx->xmit cnt && !tty->stopped && !tty->hw stopped) 

        { 

         start xmit (xxx);//開始發送 

        } 

       return total; //返回發送的字節數 

     }         

4.2 put_char()函數的write()替代

  當tty 子系統本身須要發送數據到tty 設備時,若是沒有實現put_char() 函數,write()函數將被調用,此時傳入的count 參數爲1,經過對如下代碼的分析便可知。

int tty register driver (struct tty driver *driver) 
      { 
        ... 
                      
        if (!driver->put char)//沒有定義put char()函數 
                    
          driver->put char = tty default put char; 
        ... 
      } 
                     
      static void tty default put char(struct tty struct *tty, unsigned char ch) 
       { 
                                                     
       tty->driver->write (tty, &ch, 1);//調用tty driver.write ()函數 
     } 

4.3 tty_flip_buffer_push()範例 

  tty 核心在一個稱爲struct tty_flip_buffer的結構體中緩衝數據直到它被用戶請求。由於tty 核心提供了緩衝邏輯,所以每一個tty驅動並不是必定要實現它自身的緩衝邏輯。若是其count 字段大於或等於 TTY_ FLIP BUF_SIZE ,這個 flip 緩衝區就須要被刷新到用戶,刷新經過對 tty_flip_buffer_push()函數的調用來完成,其相關代碼以下:

for (i = 0; i < data size; ++i) 
     { 
                   
       if (tty->flip .count >= TTY FLIPBUF SIZE) 
             
          tty flip buffer push (tty);//數據填滿向上層 「推」

        tty insert flip char(tty, data[i], TTY NORMAL);//把數據插入 衝區 
     } 
      
     tty flip buffer push (tty);

5 tty線路設置

5.1 線路設置用戶空間接口 

 1) 調用用戶空間的termios 庫函數

  用戶空間的應用程序需引用termios.h 頭文件, 頭文件包含了終端設備的I/O 接口,實際是由POSIX 定義的標準方法。對終端設備操做模式的描述由termios 結體完成,經過tcgetattr() 、tcsetattr() 函數便可完成對終端設備的操做模式的設置和獲取,這兩個函數的原型以下:

int tcgetattr (int fd, struct termios *termios p); 
                   
int tcsetattr (int fd, int optional actions, struct termios *termios p); 

2) 對tty 設備節點進行ioctl()調用

  大部分termios 庫函數會被轉化爲對tty 設備節點的ioctl()調用,例如tcgetattr() 、 tcsetattr() 函數對應着TCGETS、TCSETS IO 控制命令。 TIOCMGET(得到 MODEM 狀態位)、TIOCMSET (設置 MODEM 狀態位)、TIOCMBIC(清除指示MODEM 位)、TIOCMBIS (設置指示MODEM 位)這 4 個I/O 控制命令用於獲取和設置MODEM 握手,如RTS、CTS、DTR、DSR、RI、 CD 等。

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); 

5.3 tty 驅動的tiocmget 和tiocmset 函數

  對TIOCMGET、TIOCMSET、TIOCMBIC 和TIOCMBIS IO 控制命令的調用將被tty 核心轉換爲對 tty 驅動 tiocmget()函數和tiocmset()函數的調,TIOCMGET 對應tiocmget()函數,TIOCMSET、TIOCMBIC 和TIOCMBIS 對應tiocmset()函數,分別用於取Modem 控制的設置和進行Modem 的設置,tty 驅動程序的tiocmget()函數範例以下:

static int xxx tiocmget (struct tty struct *tty, struct file *file) 

      { 

        struct xxx tty *info = tty->driver  data; 

        unsigned int result = 0; 

        unsigned int msr = info->msr; 

        unsigned int mcr = info->mcr; 

        result = ((mcr &MCR DTR) ? TIOCM DTR : 0) |  /* DTR 被設置 */ 

         ((mcr &MCR RTS) ? TIOCM RTS : 0) | /* RTS 被設置 */ 

         ((mcr &MCR LOOP) ? TIOCM LOOP : 0) | /* LOOP 被設置 */ 

        ((msr &MSR CTS) ? TIOCM CTS : 0) | /* CTS 被設置 */ 

        ((msr &MSR CD) ? TIOCM CAR : 0) | /* CD 被設置*/ 

        ((msr &MSR RI) ? TIOCM RI : 0) | /* 振鈴指示被設置 */ 

        ((msr &MSR DSR) ? TIOCM DSR : 0); /* DSR 被設置 */ 

       return result; 

     } 

5.4 tty 驅動的ioctl 函數 

  當用戶在tty 設備節點上進行ioctl(2)調用時,tty_operations 中的 ioctl()函數會被 tty 核心調用。若是 tty 驅動不知道如何處理傳遞給它的 ioctl 值,它返回 ENOIOCTLCMD ,以後tty核心會 行一個通用的操做。tty 驅動程序的ioctl()函數範例代碼以下:

static int xxx ioctl(struct tty struct *tty, struct file *filp, unsigned int 

        cmd, unsigned long arg) 

      { 

        struct xxx tty *info = tty->driver data; 

        ... 

        /* 處理各類命令 */ 

        switch (cmd) 

        { 

          case TIOCGSERIAL: 
           ... 
         case TIOCSSERIAL: 
           ... 
         case TIOCSERCONFIG: 
           ... 
         case TIOCMIWAIT: 
         ... 
         case TIOCGICOUNT: 
         ... 
         case TIOCSERGETLSR: 
         ... 
       } 
       ... 
     }     

 

   版權全部,轉載請註明轉載地址:http://www.cnblogs.com/lihuidashen/p/4508674.html

相關文章
相關標籤/搜索