1. 程式人生 > >Linux 驅動 tty終端 呼叫過程分析

Linux 驅動 tty終端 呼叫過程分析

核心版本: 2.6.32.2
仍在修改中 2018.12.21

需進一步瞭解核心啟動過程中start_kernel以及module_init呼叫
module_init(serial8250_init);
module_init(tty_init);
console_init在start_kernel中呼叫

Register

serial8250_init(void): drivers/serial/8250.c

  • 呼叫uart_register_driver(&serial8250_reg),serial8250_reg為struct
    uart_driver,
    在這裡插入圖片描述

其沒有fops結構,uart port才有具體的uart_ops結構
在這裡插入圖片描述

  • serial8250_isa_devs=platform_device_alloc(“serial8250”,-1);
    platform_device_add(serial8250_isa_devs
  • serial8250_register_ports(&serial8250_reg, &serial8250_isa_devs->dev);
  • platform_driver_register(&serial8250_isa_driver);

uart_register_driver(struct uart_driver *drv)

:drivers/serial/serial_core.c
drv傳入引數&serial8250_reg

  • struct tty_driver normal = alloc_tty_driver(drv->nr);
    drv->tty_driver = normal; 將serial8250_reg的引數傳給normal
    在這裡插入圖片描述
  • tty_set_operations(normal, &uart_ops);
    在這裡插入圖片描述
  • 呼叫tty_port_init, UART_NR個port, 由CONFIG決定
    在這裡插入圖片描述
  • tty_register_driver(normal)

tty_register_driver(struct tty_driver *driver)

: drivers/char/tty_io.c
driver傳入引數normal

  • register_chrdev_region(dev, driver->num, driver->name); dev_t dev為空
  • cdev_init(&driver->cdev, &tty_fops): cdev原為空,
    在這裡插入圖片描述
    初始化cdev,將fops和cdev聯合,為cdev_add作準備。
  • cdev_add(&driver->cdev, dev, driver->num): 呼叫kobj_map()函式向系統中新增裝置

tty_init在這個過程中沒被呼叫? 什麼時候呼叫?

  • 核心編譯時就會呼叫tty_init等module_init函式對tty終端進行cdev_init和cdev_add,以及生成/dev/tty檔案
  • 若作為模組編譯時, 什麼時候呼叫?
    busybox的insmod會呼叫新編譯的.ko中所有的module_init函式?比如8250其會呼叫8250和tty的init函式

write呼叫過程:

系統呼叫write(), 作業系統sys_write()呼叫, 進一步tty_write(), 下從此開始分析呼叫, 系統呼叫部分未了解
tty還有line discpline, 呼叫write, read, ioctl等會呼叫line discpline.
tty_wirte(), 呼叫do_tty_write()
do_tty_write(ld->ops->write, tty, file, buf, count): drivers/char/tty_io.c
在這裡插入圖片描述

  • ld = tty_ldisc_ref_wait(tty);
    • 呼叫ld = tty_ldisc_try(tty)
    • 呼叫ld = get_ldisc(tty->ldisc);
    • ld為返回的struct tty_ldisc, 見ld->ops和tty_ldisc_N_TTY部分分析,
      得知ld->ops應該為tty_ldisc_N_TTY
      在這裡插入圖片描述
    • ld->ops->write對應n_tty_write(drivers/char/n_tty.c), 舊版核心為write_chan
  • 呼叫write(tty, file, tty->write_buf, size), 即呼叫n_tty_write(tty, file,
    tty->write_buf, size)

n_tty_write(tty, file, tty->write_buf, size)

  • 呼叫tty->ops->flush_chars(tty)
    在這裡插入圖片描述
  • 呼叫uart_flush_chars(tty)
    uart_flush_chars(tty)
    在這裡插入圖片描述
  • 呼叫uart_start(tty)
    uart_start(tty)
    在這裡插入圖片描述
  • 先後呼叫__uart_start和port->ops->start_tx(port)
  • port=tty->driver_data->uart_port
  • 即執行tty->driver_data->uart_port->start_tx(port) port和uart關係見下
  • 實際執行具體晶片如serial8250_start_tx
    在這裡插入圖片描述
  • serial8250_start_tx呼叫serial_icr_write(up, UART_ACR, up->acr)和serial_out
    在這裡插入圖片描述
  • Up->port.serial_out在early_serial_setup(struct uart_port *port)中設定p->serial_out = port->serial_out;

*int __init early_serial_setup(struct uart_port port)
初始化時引數port怎麼傳入??

ld->ops和tty_ldisc_N_TTY

console_init(void): drivers/char/tty_io.c, console_init什麼時候呼叫? Start_kernel呼叫很多初始化函式

  • 開機初始化控制檯, 呼叫tty_ldisc_begin, line discpline在控制檯初始化同時初始化
    在這裡插入圖片描述
  • 呼叫tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY); N_TTY=0
    *tty_register_ldisc(int disc, struct tty_ldisc_ops new_ldisc)
  • tty_ldiscs[disc] = new_ldisc; 即繫結tty_ldisc_N_TTY為line discpline的operations
    *struct tty_ldisc_ops get_ldops(int disc)
  • 獲得operation
    在這裡插入圖片描述
    *tty_ldisc tty_ldisc_get(int disc)
  • 呼叫ldops = get_ldops(disc);
  • ld->ops = ldops;
  • return ld
    回到tty_write()函式中
  • tty = (struct tty_struct *)file->private_data;
  • file->private_data在tty_open時設立file->private_data=tty, 呼叫tty_init_dev後呼叫tty_ldisc_setup後呼叫initialize_tty_struct後呼叫tty_ldisc_init進一步呼叫tty_ldisc_get(N_TTY)實現N_TTY和operation的繫結
  • 結論: tty->ldisc->ops為tty_ldisc_N_TTY

uart和port之間的關係
serial8250_console_init開始
待更新