小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

串行設備驅動3

 jemeen 2012-02-16

串行設備驅動3  

2011-11-05 12:17:45|  分類: linux驅動 |字號 訂閱

come from:http://blog.sina.com.cn/s/blog_569a151b0100nu8w.html

TTY驅動

讓我們開始看看tty驅動的核心結構體和注冊函數。有三個結構體非常重要:

  1. 定義于include/linux/tty.h中的tty_struct結構。此結構包含了和打開的tty相關的所有的狀態(tài)信息。其結構體成員眾多,下面列出了一些重要的成員:

  struct tty_struct {

    int magic;                     

    struct tty_driver *driver;     

    struct tty_ldisc ldisc;        

    

    struct tty_flip_buffer flip;   

    

 

    wait_queue_head_t write_wait;  

    wait_queue_head_t read_wait;   

    

  };

  1. tty_struct結構中的tty_flip_buffer結構體。這是數據收集和處理機制的中樞:

  struct tty_flip_buffer {

    

    struct semaphore pty_sem;     

    char *char_buf_ptr;           

    

    unsigned char char_buf[2*TTY_FLIPBUF_SIZE];

    

  };

底層的串行驅動將flip緩沖區(qū)的一半用于數據收集,線路規(guī)程則使用另一半來進行數據處理。數據處理伴隨著串行驅動和線路規(guī)程所使用的緩沖區(qū)指針的移動而持續(xù)進行。從drivers/char/tty_io.c文件的flush_to_ldisc()函數中可看到flip的確切行為。

在最近的內核中,tty_flip_buffer結構體有些改動,該結構體目前由緩沖區(qū)頭部(tty_bufhead)和緩沖區(qū)鏈表(tty_buffer)組成:

struct tty_bufhead {

  

  struct semaphore pty_sem;            

  struct tty_buffer *head, tail, free;

  

};

 

struct tty_buffer {

  struct tty_buffer *next;

  char *char_buf_ptr;       

  

  unsigned long data[0];    

};

  1. 定義于include/linux/tty_driver.h文件中的tty_driver結構體。它規(guī)定了tty驅動和高層的編程接口:

struct tty_driver {

  int magic;             

  

  int major;             

  int minor_start;       

  

  

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

  

};

UART驅動一樣,tty驅動也需要完成兩個步驟以向內核注冊自身:

1.

調用tty_register_driver(struct tty_driver *tty_d)tty核心注冊自身。

2.

調用

tty_register_device(struct tty_driver *tty_d,

                    unsigned device_index,

                    struct device *device)

注冊它支持的每個單獨的tty。

本章不會給出tty驅動的實例,Linux內核中有一些通用的tty驅動:

  • 16章討論的藍牙模擬串口,就是用tty驅動的形式實現的。此驅動(drivers/net/bluetooth/rfcomm/tty.c)在初始化階段調用tty_register_driver(),在處理每個到來的藍牙連接時調用tty_register_device()
  • Linux桌面上,為了使用系統(tǒng)控制臺,如果你工作在字符模式下,將需要虛擬終端(virtual terminals,VTs)服務,如果你處在圖形模式下,將需要偽終端(pseudo terminals,PTYs)服務。虛擬終端和偽終端都是用tty驅動實現的,分別位于drivers/char/vt.cdrivers/char/pty.c。
  • 傳統(tǒng)的UART使用的tty驅動位于drivers/serial/serial_core.c。
  • USB到串口轉換器的tty驅動位于drivers/usb/serial/usb-serial.c

線路規(guī)程

線路規(guī)程提供了一套靈活的機制,使得用戶運行不同的應用時,使用相同的串行驅動。底層的物理驅動和tty驅動完成從硬件上收發(fā)數據,而線路規(guī)程則負責處理這些數據,并在內核空間和用戶空間之間進行數據的傳遞。

串行子系統(tǒng)支持17種標準的線路規(guī)程。當你打開串口時系統(tǒng)會綁定默認的線路規(guī)程N_TTY,它實現了終端I/O處理。N_TTY負責加工從鍵盤接收到的字符。根據用戶需要,它完成控制字符到“新起一行”的映射,進行小寫字符至大寫字符的轉換,tab、echo字符傳遞給關聯(lián)的虛擬終端。N_TTY也支持原始編輯模式,此時,它將所有前述的處理都交給用戶程序。下一章《輸入設備驅動》中的圖7.3展示了鍵盤子系統(tǒng)如何和N_TTY相關聯(lián)。前一節(jié)“TTY驅動”中列出的tty驅動例子默認就是使用N_TTY。

線路規(guī)程也實現通過串行傳輸協(xié)議的網絡接口。PPPN_PPP)和SLIPN_SLIP)子系統(tǒng)中的線路規(guī)程完成將包組幀、分配相應的網絡數據結構并將數據傳送至相應的網絡協(xié)議棧的工作。其它的線路規(guī)程還包括紅外數據(N_IRDA)和藍牙主機控制接口(N_HCI)。

設備例子:觸摸控制器

本節(jié)通過實現一個簡單的串行觸摸屏控制器的線路規(guī)程,來深入線路規(guī)程的內幕。圖6.6顯示了觸摸控制器和嵌入式掌上電腦的連接。由于觸摸控制器的有限狀態(tài)機(FSM)能夠很好地描述串行層提供的接口和功用,因此將可根據它實現線路規(guī)程。

 6.6. PC-derivative上觸摸控制器連接圖

OpenClose

為了創(chuàng)建線路規(guī)程,需要定義tty_ldisc結構體,并向內核注冊指定的入口函數集。清單6.2包括了實現了以上功能的觸摸控制器實例的部分代碼。

清單 6.2. 線路規(guī)程操作


Code View:

struct tty_ldisc n_touch_ldisc = {

  TTY_LDISC_MAGIC,         

  "n_tch",                 

  N_TCH,                   

  n_touch_open,            

  n_touch_close,           

  n_touch_flush_buffer,    

  n_touch_chars_in_buffer,

  n_touch_read,            

  n_touch_write,           

  n_touch_ioctl,           

  NULL,                    

  n_touch_poll,            

  n_touch_receive_buf,     

  n_touch_receive_room,    

  n_touch_write_wakeup     

};

 

 

 

if ((err = tty_register_ldisc(N_TCH, &n_touch_ldisc))) {

  return err;

}

 

                                    

 

 

在清單6.2中,n_tch是線路規(guī)程名,N_TCH是線路規(guī)程的ID號。你需要在include/linux/tty.h中定義其值(此頭文件中包括所有線路規(guī)程的定義)。在/proc/tty/ldiscs可發(fā)現正使用的線路規(guī)程。

線路規(guī)程從ttyflip緩沖區(qū)對應的部分收集、處理數據,然后拷貝處理后的數據至本地讀緩沖區(qū)。對于N_TCH,n_touch_receive_room()返回讀緩沖區(qū)中的剩余內存數,n_touch_chars_in_buffer()返回讀緩沖區(qū)中已經處理過的、準備送至用戶空間的字符個數。由于N_TCH是只讀設備,n_touch_write()  n_touch_write_wakeup()將不進行任何操作。n_touch_open()用于為線路規(guī)程的主要數據結構分配內存,可參照清單6.3

清單6.3. 打開線路規(guī)程


Code View:

 

struct n_touch {

  int current_state;       

  spinlock_t touch_lock;   

  struct tty_struct *tty;  

  

  

} *n_tch;

 

 

 

static int

n_touch_open(struct tty_struct *tty)

{

  

  if (!(n_tch = kmalloc(sizeof(struct n_touch), GFP_KERNEL))) {

    return -ENOMEM;

  }

  memset(n_tch, 0, sizeof(struct n_touch));

 

  tty->disc_data = n_tch;

  

  tty->read_buf = kmalloc(BUFFER_SIZE, GFP_KERNEL);

  if (!tty->read_buf) return -ENOMEM;

 

  

  memset(tty->read_buf, 0, BUFFER_SIZE);

 

  

  spin_lock_init(&ntch->touch_lock);

 

  

  

 

  return 0;

}

 

                                    

 

 

當打開連有觸摸控制器的串口時,你可能想將N_TCH而非N_TTY設置為默認的線路規(guī)程,“改變線路規(guī)程”一節(jié)中介紹的方法可以實現從用戶空間改變線路規(guī)程的目的。

讀數據過程

對于中斷驅動的設備,讀取數據的過程通常由一前一后兩個線程組成:

  1. 由于發(fā)起讀數據請求而從用戶空間發(fā)起的頂層線程;
  2. 由中斷處理程序(接收來自設備的數據)喚醒的底層線程。

6.7顯示了這兩個與讀數據流程相關的線程。中斷處理程序將receive_buf()(在我們的例子中是n_touch_receive_buf())函數當作任務進行排隊。通過設置tty->low_latency可重載這一行為。

 6.7. 線路規(guī)程讀數據過程

在觸摸控制器的數據手冊詳細描述了觸摸控制器和處理器之間的專用通信協(xié)議,而驅動則用前面討論過的有限狀態(tài)機來實現此協(xié)議。清單6.4中將有限狀態(tài)機作為receive_buf()入口點n_touch_receive_buf()的一部分。

清單 6.4. n_touch_receive_buf() 方法


Code View:

static void

n_touch_receive_buf(struct tty_struct *tty,

                    const unsigned char *cp, char *fp, int count)

{

 

  

  

 

  

…………………………………………………………………………

  

  switch (tty->disc_data->current_state) {

    case RESET:

      

      tty->driver->write(tty, 0, mode_stream_command,

                         sizeof(mode_stream_command));

      tty->disc_data->current_state = STREAM_DATA;

      

      break;

    case STREAM_DATA:

      

      break;

    case PARSING:

      

      tty->disc_data->current_state = PARSED;

      break;

    case PARSED:

      

    }

…………………………………………………………………………

 

  if (tty->disc_data->current_state == PARSED) {

    

    spin_lock_irqsave(&tty->disc_data->touch_lock, flags);

    for (i=0; i < PACKET_SIZE; i++) {

      tty->disc_data->read_buf[tty->disc_data->read_head] =

                           tty->disc_data->current_pkt[i];

      tty->disc_data->read_head =

                 (tty->disc_data->read_head + 1) & (BUFFER_SIZE – 1);

      tty->disc_data->read_cnt++;

    }

    spin_lock_irqrestore(&tty->disc_data->touch_lock, flags);

 

    

 

  }

}

 

                                    

 

 

n_touch_receive_buf() 處理從串行驅動來的數據。它和觸摸控制器進行一系列命令/響應的交互,將接收的坐標和壓下/釋放信息放入線路規(guī)程讀緩沖區(qū)。對讀緩沖區(qū)的訪問必須借助自旋鎖的加鎖能力來依次進行,如圖6.7所示,該自旋鎖被ldisc.receive_buf()  ldisc.read() 線程(在我們的例子中分別是n_touch_receive_buf()  n_touch_read())同時使用。如清單6.4n_touch_receive_buf()通過直接調用串行驅動的write()入口函數將命令分發(fā)給觸摸控制器。

n_touch_receive_buf()需要做如下操作:

  1. 如果沒有數據可獲得,圖6.7中的頂層的read()線程會將調用進程置為休眠狀態(tài)。因此n_touch_receive_buf()必須將其喚醒,使其讀取剛處理過的數據。
  2. 如果線路規(guī)程耗盡了讀緩沖空間,n_touch_receive_buf()必須要求串行驅動中止從設備接收數據。當它將數據搬移至用戶空間并釋放讀緩沖區(qū)中的內存后,ldisc.read()負責重新開啟數據接收。串行驅動利用軟件或硬件流控機制完成數據接收的中止和重啟。

清單6.5實現了上面操作。

清單 6.5. 喚醒讀線程和中止串行驅動


 

 

 

if (waitqueue_active(&tty->read_wait) &&

    (tty->read_cnt >= tty->minimum_to_wake))

  wake_up_interruptible(&tty->read_wait);

}

 

if (n_touch_receive_room(tty) < TOUCH_THROTTLE_THRESHOLD) {

  tty->driver.throttle(tty);

}

 

 

 

等待隊列tty->read_wait用于在ldisc.read()ldisc.receive_buf()線程之間實現同步。當ldisc.read()發(fā)現可讀的數據時,將調用進程加入等待隊列,當有數據可讀時,ldisc.receive_buf()喚醒ldisc.read()線程。因此,n_touch_read()完成如下操作:

·         當仍然無可讀數據時,將調用進程放入read_wait隊列中使其休眠。當數據到來時由n_touch_receive_buf()喚醒此進程。

·         若數據可獲得,從本地讀緩沖區(qū)(tty->read_buf[tty->read_tail])中收集數據,并分發(fā)至用戶空間。

·         若串行驅動被中止,并且在讀操作后讀緩沖區(qū)中又有了足夠的可用空間,請求串行驅動重啟。

 

網絡線路規(guī)程通常分配sk_buff(第15章“網絡接口卡”中將討論到的基本的Linux網絡數據結構),并用作讀緩沖區(qū)。由于網絡線路規(guī)程的receive_buf()將接收的拷貝至sk_buff將其直接傳送至相應的協(xié)議棧,因此沒有read()函數。

寫數據過程

線路規(guī)程的write()入口函數需要完成一些數據傳送至底層驅動之前必要的后處理工作。

如果底層驅動不能接受線路規(guī)程提供的所有數據,線路規(guī)程會將請求發(fā)送數據的線程置于休眠狀態(tài)。當驅動準備好接受更多的數據時,驅動的中斷處理例程將線路規(guī)程喚醒。為了達成此目的,驅動調用由線路規(guī)程注冊的write_wakeup()方法。類似于前面章節(jié)中討論過的read_wait實現同步,此處通過等待隊列tty->write_wait來實現相應的同步。

很多網絡線路規(guī)程沒有write()方法。協(xié)議實現時直接將數據幀向下傳送給串行設備驅動。然而,這些線路規(guī)程通常仍然有write_wakeup()入口點,以響應串行驅動的傳輸更多數據的請求。

因為觸摸控制器是只讀設備,N_TCH也沒有write()方法。正如在清單6.4中所見,當需要發(fā)送命令幀給控制器時,接收路徑中的例程直接和底層的UART驅動交互。

    本站是提供個人知識管理的網絡存儲空間,所有內容均由用戶發(fā)布,不代表本站觀點。請注意甄別內容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現有害或侵權內容,請點擊一鍵舉報。
    轉藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多