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