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