linux下TTY驅動(serial)

/dev/console是系統控制檯,是與操作系統交互的設備,系統所產生的信息會發送到該設備上。

如果一個終端設備要實現console功能必須向內核註冊struct console結構,如果要實現tty功能,要向內核tty子系統註冊struct tty_driver結構。

tty0是系統自動打開的,但不用於用戶登錄。在framebuffer設備沒有啓用的系統中,可以使用/dev/tty0訪問顯卡。

串行通信設備驅動是不能被用戶直接使用的,必須被抽象爲一個tty設備,然後配置使用默認的線路規則(n_tty.c),經tty設備驅動子系統在系統中註冊爲字符設備。

linux默認線路規則是N_TTY(標準字符終端I/O處理規則).

write函數的阻塞版本在內核裏使用等待隊列實現的。


  之前因爲是剛入門所以看了串口有關的東西,一開始看了stm32f407上的串口編程(Keil MDK),那算是裸的驅動了。 

 linux下的串口如果要正常工作的話,就必須通過TTY這個子系統,TTY子系統算是比較複雜,概念也是比較複雜的,有三類驅動程序:控制檯,串口,pty。任何tty驅動程序的主要數據結構是結構tty_driver.概念性的問題還是去看一下《linux設備驅動程序》這本書,它也算是驅動工程師必讀的聖經 了。接下來就開始直接看代碼了。

 從xxx_uart_probe函數開始(至於爲什麼希望看一下我之前寫的platform_device).

         res = platform_get_resource(dev, IORESOURCE_MEM, 0);
         if(res == NULL)
         {
                 ret = -ENOMEM;
                 goto free;
         }
 
         base = ioremap(res->start, res->end-res->start);
         if (!base)
         {
                 ret = -ENOMEM;
                 goto free;
         }
 
         sprintf(uart_name, "%s", dev->name + 6);
         uap->clk = clk_get(&dev->dev, uart_name);
         if (IS_ERR(uap->clk)) {
                 ret = PTR_ERR(uap->clk);
                 goto unmap;
         }
  一開始依舊是獲取一些之前platform_device已經註冊號的資源(中斷,內存空間和時鐘等等)。同時註冊一些uart_ops回調函數,這些回調函數是驅動層自己實現的,最後上層的相關函數最終都會調用到這些回調函數。

需要注意的是,在tty子系統中,要註冊3次operation函數,但是每次的operation都是不同的,有調用關係,我把另外兩個先貼出來:

 最後調用serial_core.c中的add_one_port();之後所有的操作都由TTY子系統自動去完成了。

 static struct uart_ops amba_pl011_pops = {
         .tx_empty       = pl01x_tx_empty,
         .set_mctrl      = pl011_set_mctrl,
         .get_mctrl      = pl01x_get_mctrl,
         .stop_tx        = pl011_stop_tx,
         .start_tx       = pl011_start_tx,
         .stop_rx        = pl011_stop_rx,
         .enable_ms      = pl011_enable_ms,
         .break_ctl      = pl011_break_ctl,
         .startup        = pl011_startup,
         .shutdown       = pl011_shutdown,
         .flush_buffer   = pl011_dma_flush_buffer,
         .set_termios    = pl011_set_termios,
         .type           = pl011_type,
         .release_port   = pl010_release_port,
         .request_port   = pl010_request_port,
         .config_port    = pl010_config_port,
         .verify_port    = pl010_verify_port,
 #ifdef CONFIG_CONSOLE_POLL
         .poll_get_char = pl010_get_poll_char,
         .poll_put_char = pl010_put_poll_char,
 #endif
 };

uart_configure_port();

tty_register_device();//註冊設備到tty框架中去

device_create(); 

註冊tty_device的順序是xxx_uart_probe()->add_one_port()->tty_register_device().

/tty/serial/serial_core.c

uart_register_driver();//這個函數是uart框架自己提供的函數,我們只要將參數傳遞進去,該框架就會自動註冊該驅動。

在這個函數中主要是分配tty_driver結構體的內存空間,初始化一些默認的值。並且註冊一些tty_operation相關的回調函數。

tty_set_operations(normal, &uart_ops);

 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,
         .send_xchar     = uart_send_xchar,
         .set_termios    = uart_set_termios,
         .set_ldisc      = uart_set_ldisc,
         .stop           = uart_stop,
         .start          = uart_start,
         .hangup         = uart_hangup,
         .break_ctl      = uart_break_ctl,
         .wait_until_sent= uart_wait_until_sent,
 #ifdef CONFIG_PROC_FS
         .proc_fops      = &uart_proc_fops,
 #endif
         .tiocmget       = uart_tiocmget,
         .tiocmset       = uart_tiocmset,
         .get_icount     = uart_get_icount,
 #ifdef CONFIG_CONSOLE_POLL
         .poll_init      = uart_poll_init,
         .poll_get_char  = uart_poll_get_char,
         .poll_put_char  = uart_poll_put_char,
 #endif
 };
tty/tty_io.c

最終調用tty_register_driver();//註冊驅動到tty框架中去。

cdev_init(&driver->cdev, &tty_fops);

cdev_add();

  static const struct file_operations tty_fops = {
          .llseek         = no_llseek,
          .read           = tty_read,
          .write          = tty_write,
          .poll           = tty_poll,
          .unlocked_ioctl = tty_ioctl,
          .compat_ioctl   = tty_compat_ioctl,
          .open           = tty_open,
          .release        = tty_release,
          .fasync         = tty_fasync,
  };

註冊tty_driver的順序是uart_register_driver()->tty_register_driver().

tty/n_tty.c

(void) tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY);//我們在程序中設置串口數據格式和波特率都是在下面回調函數中的ioctl來設置的。

 struct tty_ldisc_ops tty_ldisc_N_TTY = {
         .magic           = TTY_LDISC_MAGIC,
         .name            = "n_tty",
         .open            = n_tty_open,
         .close           = n_tty_close,
         .flush_buffer    = n_tty_flush_buffer,
         .chars_in_buffer = n_tty_chars_in_buffer,
         .read            = n_tty_read,
         .write           = n_tty_write,
         .ioctl           = n_tty_ioctl,
         .set_termios     = n_tty_set_termios,
         .poll            = n_tty_poll,
         .receive_buf     = n_tty_receive_buf,
         .write_wakeup    = n_tty_write_wakeup
 };

tty_ldisc_begin();//設置默認的線路規程

console_init();


  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章