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