The tty Layer
做为开始,让我们到内核的tty层看看。但我们输入一个命令行或者使用串口连接时, tty层总会被用到。
每个tty 驱动都需要创建一个tty_driver结构体来描述该驱动,然后注册该驱动到tty层。该结构体定义在文件include/linux/tty_driver.h中。附件【可从地址ftp.linuxjournal.com/pub/lj/listings/issue100/5896.tgz得到】中的 listing1,给出了该结构体和内核版本2.4.18一样。该结构体很大,让我们把他分成较小的几部分。
Maginc域应该被设置为TTY_DRIVER_MAGIC。tty层使用该值去确认 tty层正处理一个tty 驱动。
driver_name和name域用于描述你的 tty驱动,driver_name域应该设置为具有描述性的内容,该内容会在文件/proc/tty/drivers中显示。而name域 用于描述设备文件/dev 或者devfs的基础名字。例如,串口驱动设置driver_name为serial, 当devfs不使能时name为ttyS否则设置为tty%d.当devfs使能时,为驱动创建的新的设备结点使用 name域。当注册到 tty 子系统时,名字中%d 被赋值为设备的次设备号。
如果你的设备不是从0开始编号,就要使用name_base域。对大多数驱动而言,该域设置为0.
major, minor_start和 num域用于描述分配给你驱动的主设备号/子设备号。如果你需要常见一个新的驱动,请读文档Documentation/devices.txt去为你的驱动获得一个主设备号。对想了解什么驱动使用的是什么主从设备号的而言,该文集也是较好的文档。minor_start域用于描述设备的次设备号从哪里开始,num域用于描述多少个不同的此设备号分配给你的驱动。
因此,如果分配给你驱动的主设备号为188,因此:
major: 188,
minor_start: 0,
TTY_DRIVER_TYPE_SYSTEM:用于通知tty子系统它正处理一个内部的tty驱动。如果该域被设置,那么子类型应该被设置为SYSTEM_TYPE_TTY, SYSTEM_TYPE_CONSOLE, SYSTEM_TYPE_SYSCONS 或者 SYSTEM_TYPE_SYSPTMX.该类型不应当用于正常的tty驱动。
TTY_DRIVER_TYPE_CONSOLE:只被用于控制台驱动,其他类型的驱动不使用该标志。
TTY_DRIVER_TYPE_SERIAL:用于任何串行的驱动,如果该值被设置,那么子类型值根据驱动的类型,应该设置为SERIAL_TYPE_NORMAL 或者 SERIAL_TYPE_CALLOUT,这是最常用的类型。
TTY_DRIVER_TYPE_PTY:用于伪终端接口,如果该值被设置,子类型需要被设置为PTY_TYPE_MASTER或者PTY_TYPE_SLAVE。
init_termios域用于创建设备最初的termios(行设置和速度)。
flag域根据驱动的需要,设置为下面域的组合值:
TTY_DRIVER_INSTALLED:如果该位被设置,驱动不会被注册到tty层,因此不要设置该位。
TTY_DRIVER_RESET_TERMIOS:如果改位被设置,当使用该设备的最后一个进程关闭该设备时,tty 层会复位termios setting。对console and pty
驱动而言,该位是有用的。
TTY_DRIVER_REAL_RAW:如果该位被设置,表示不特殊处理奇偶校验或中断字符,都看成是数据。下面的不会翻译?
TTY_DRIVER_REAL_RAW: if this bit is set, it indicates that the driver guarantees to set notifications of parity or break characters up to the line driver if the line driver has not asked to be notified of them. This is usually set for all drivers, as it allows the line driver to be optimized a little better.
TTY_DRIVER_NO_DEVFS:如果该位被设置,函数tty_register_driver不创建devfs项,也就是设备结点。当需要根据是否存在物理设备,动态创建和破坏子设备时,该位是有用的。比如,USB转串口的驱动,USB Modem驱动等.
其他的域只被pty驱动使用,其他的驱动不要使用。
struct tty_struct **ttys;
struct ktermios **termios;
这里的tty_struct和ktermios指针数组是用于tty层处理不同的子设备,tty驱动不要出处理该数据。
driver_state域只被pty驱动使用,其他的驱动不要使用
在tty_driver结构体中,有个函数指针列表。当tty 层通过这些函数指针调到tty驱动。并不是所有的函数都需要被定义,但有些是不需的。但open tty驱动创建的设备结点,tty 层会调用open函数。tty层调用该函数,输入参数是分配给该设备的tty_struct and file. open函数是必须要定义的,否则返回-ENODEV。
close函数当tty层release以前open函数创建的文件指针时被调用。
当tty层传送数据到tty设备时,该函数被调用。数据可以来值用户或者内核空间(如果数据来自用户空间,from_user域应当被设置)。函数的返回值应当是实际写入设备的字符个数。
put_char,flush_chars发送单个字符到设备
write_room,chars_in_buffer分别描述write buffer中有多少空间,还有多少数据。这里说的是驱动层,不是像读操作的read buffer一样。
ioctl但应用层通过设备结点调用ioctl时,tty层调用该函数。
当设备的termios settings改变时,tty层调用驱动层的set_termios。
throttle 和unthrottle用于控制tty 层的输入buffer,也就是设备输出数据到tty层。当tty层不能接收到更多的数据时调用throttle函数,当tty层可以接收更多的数据时unthrottle被调用。实际上就是read操作。
stop and start函数的作用和 throttle and unthrottle类似。
hangup function被调用,当挂起某个设备时。
如果tty驱动需要打开或者关闭BREAK status,函数break_ctrl被调用。
当 tty driver要刷新write buffer 中的数据时flush_buffer 函数被调用,这意味这这些数据会丢失,不会被发送到设备。
当tty 层需要改变行规时,set_ldisc被调用。一般情况下,不用设置该函数。
当tty层要发送write buffer中所有的数据到device时,wait_until_sen被调用。该函数不返回直到发送完,可以睡眠。
send_xchar用于发送高优先级的 XON/XOFF字符。
No Read?
为什么没有读操作的函数?
从上面函数列表中,我们发现tty驱动没有实现读函数。tty 层包含一个buffer,用于当读 tty设备结点时,把该buffe中的数据发送到用户空间。该 buffer需要tty 驱动去填写当接收到设备的数据。因为tty接收到的数据不能被输出到用户空间直到用户请求数据,所以该模型是必须的。使用这个方法tty 层去缓存接收的数据,而tty驱动不用关心读数据的阻塞等问题。
最小的 tty 驱动:
现在我们已经浏览了tty_driver的各个数据成员,其中一些是tty 驱动必须的如:
create the tiny_open, tiny_close, tiny_write and tiny_write_room.[这个tiny_write_room也是必须的!!]
为了模拟实际的硬件,tty 驱动创建了一个周期为2秒的时钟去发送数据到tty 层。
Flow of Data
1]place the data into flip buffer;tty_insert_flip_char
2]to make the filp buffer data to read buffer;tty_flip_buffer_push
When data is received from the hardware, it needs to be placed into the tty device's flip buffer. This can be done with the following bit of code:
for (i = 0; i < data_size; ++i) {
if (tty->flip.count >= TTY_FLIPBUF_SIZE)
tty_flip_buffer_push(tty);
tty_insert_flip_char(tty, data[i], 0);
}
tty_flip_buffer_push(tty);
http://www.linuxjournal.com/article/5896?page=0,0;
该文写在 2002年,好多内容已经改变如设备结点的创建,现在的读 buffer好像多了一级 tty_buffer, 原来可能只有 read_buf.不管怎么说主要的思想没有改变。
我们从内核如何处理系统控制台和串口开始,写一系列关于设备驱动开发的文章。欢迎来到新栏目“Driving Me Nuts”。在该栏目中,我们将会分析多个Linux驱动子系统,理解子系统提供的接口,和对一个具体驱动程序提供的要求。如果有人想讲解某个具体的子系统,请给我发mail.
做为开始,让我们到内核的tty层看看。但我们输入一个命令行或者使用串口连接时, tty层总会被用到。
每个tty 驱动都需要创建一个tty_driver结构体来描述该驱动,然后注册该驱动到tty层。该结构体定义在文件include/linux/tty_driver.h中。附件【可从地址ftp.linuxjournal.com/pub/lj/listings/issue100/5896.tgz得到】中的 listing1,给出了该结构体和内核版本2.4.18一样。该结构体很大,让我们把他分成较小的几部分。
Maginc域应该被设置为TTY_DRIVER_MAGIC。tty层使用该值去确认 tty层正处理一个tty 驱动。
driver_name和name域用于描述你的 tty驱动,driver_name域应该设置为具有描述性的内容,该内容会在文件/proc/tty/drivers中显示。而name域 用于描述设备文件/dev 或者devfs的基础名字。例如,串口驱动设置driver_name为serial, 当devfs不使能时name为ttyS否则设置为tty%d.当devfs使能时,为驱动创建的新的设备结点使用 name域。当注册到 tty 子系统时,名字中%d 被赋值为设备的次设备号。
如果你的设备不是从0开始编号,就要使用name_base域。对大多数驱动而言,该域设置为0.
major, minor_start和 num域用于描述分配给你驱动的主设备号/子设备号。如果你需要常见一个新的驱动,请读文档Documentation/devices.txt去为你的驱动获得一个主设备号。对想了解什么驱动使用的是什么主从设备号的而言,该文集也是较好的文档。minor_start域用于描述设备的次设备号从哪里开始,num域用于描述多少个不同的此设备号分配给你的驱动。
因此,如果分配给你驱动的主设备号为188,因此:
major: 188,
minor_start: 0,
num: 255,
TTY_DRIVER_TYPE_SYSTEM:用于通知tty子系统它正处理一个内部的tty驱动。如果该域被设置,那么子类型应该被设置为SYSTEM_TYPE_TTY, SYSTEM_TYPE_CONSOLE, SYSTEM_TYPE_SYSCONS 或者 SYSTEM_TYPE_SYSPTMX.该类型不应当用于正常的tty驱动。
TTY_DRIVER_TYPE_CONSOLE:只被用于控制台驱动,其他类型的驱动不使用该标志。
TTY_DRIVER_TYPE_SERIAL:用于任何串行的驱动,如果该值被设置,那么子类型值根据驱动的类型,应该设置为SERIAL_TYPE_NORMAL 或者 SERIAL_TYPE_CALLOUT,这是最常用的类型。
TTY_DRIVER_TYPE_PTY:用于伪终端接口,如果该值被设置,子类型需要被设置为PTY_TYPE_MASTER或者PTY_TYPE_SLAVE。
init_termios域用于创建设备最初的termios(行设置和速度)。
flag域根据驱动的需要,设置为下面域的组合值:
TTY_DRIVER_INSTALLED:如果该位被设置,驱动不会被注册到tty层,因此不要设置该位。
TTY_DRIVER_RESET_TERMIOS:如果改位被设置,当使用该设备的最后一个进程关闭该设备时,tty 层会复位termios setting。对console and pty
驱动而言,该位是有用的。
TTY_DRIVER_REAL_RAW:如果该位被设置,表示不特殊处理奇偶校验或中断字符,都看成是数据。下面的不会翻译?
TTY_DRIVER_REAL_RAW: if this bit is set, it indicates that the driver guarantees to set notifications of parity or break characters up to the line driver if the line driver has not asked to be notified of them. This is usually set for all drivers, as it allows the line driver to be optimized a little better.
TTY_DRIVER_NO_DEVFS:如果该位被设置,函数tty_register_driver不创建devfs项,也就是设备结点。当需要根据是否存在物理设备,动态创建和破坏子设备时,该位是有用的。比如,USB转串口的驱动,USB Modem驱动等.
其他的域只被pty驱动使用,其他的驱动不要使用。
struct tty_struct **ttys;
struct ktermios **termios;
这里的tty_struct和ktermios指针数组是用于tty层处理不同的子设备,tty驱动不要出处理该数据。
driver_state域只被pty驱动使用,其他的驱动不要使用
在tty_driver结构体中,有个函数指针列表。当tty 层通过这些函数指针调到tty驱动。并不是所有的函数都需要被定义,但有些是不需的。但open tty驱动创建的设备结点,tty 层会调用open函数。tty层调用该函数,输入参数是分配给该设备的tty_struct and file. open函数是必须要定义的,否则返回-ENODEV。
close函数当tty层release以前open函数创建的文件指针时被调用。
当tty层传送数据到tty设备时,该函数被调用。数据可以来值用户或者内核空间(如果数据来自用户空间,from_user域应当被设置)。函数的返回值应当是实际写入设备的字符个数。
put_char,flush_chars发送单个字符到设备
write_room,chars_in_buffer分别描述write buffer中有多少空间,还有多少数据。这里说的是驱动层,不是像读操作的read buffer一样。
ioctl但应用层通过设备结点调用ioctl时,tty层调用该函数。
当设备的termios settings改变时,tty层调用驱动层的set_termios。
throttle 和unthrottle用于控制tty 层的输入buffer,也就是设备输出数据到tty层。当tty层不能接收到更多的数据时调用throttle函数,当tty层可以接收更多的数据时unthrottle被调用。实际上就是read操作。
stop and start函数的作用和 throttle and unthrottle类似。
hangup function被调用,当挂起某个设备时。
如果tty驱动需要打开或者关闭BREAK status,函数break_ctrl被调用。
当 tty driver要刷新write buffer 中的数据时flush_buffer 函数被调用,这意味这这些数据会丢失,不会被发送到设备。
当tty 层需要改变行规时,set_ldisc被调用。一般情况下,不用设置该函数。
当tty层要发送write buffer中所有的数据到device时,wait_until_sen被调用。该函数不返回直到发送完,可以睡眠。
send_xchar用于发送高优先级的 XON/XOFF字符。
No Read?
为什么没有读操作的函数?
从上面函数列表中,我们发现tty驱动没有实现读函数。tty 层包含一个buffer,用于当读 tty设备结点时,把该buffe中的数据发送到用户空间。该 buffer需要tty 驱动去填写当接收到设备的数据。因为tty接收到的数据不能被输出到用户空间直到用户请求数据,所以该模型是必须的。使用这个方法tty 层去缓存接收的数据,而tty驱动不用关心读数据的阻塞等问题。
最小的 tty 驱动:
现在我们已经浏览了tty_driver的各个数据成员,其中一些是tty 驱动必须的如:
create the tiny_open, tiny_close, tiny_write and tiny_write_room.[这个tiny_write_room也是必须的!!]
为了模拟实际的硬件,tty 驱动创建了一个周期为2秒的时钟去发送数据到tty 层。
Flow of Data
1]place the data into flip buffer;tty_insert_flip_char
2]to make the filp buffer data to read buffer;tty_flip_buffer_push
When data is received from the hardware, it needs to be placed into the tty device's flip buffer. This can be done with the following bit of code:
for (i = 0; i < data_size; ++i) {
if (tty->flip.count >= TTY_FLIPBUF_SIZE)
tty_flip_buffer_push(tty);
tty_insert_flip_char(tty, data[i], 0);
}
tty_flip_buffer_push(tty);