open 函数被 tty 核心调用, 当一个用户对这个 tty 驱动被分配的设备节点调用 open
时. tty 核心使用一个指向分配给这个设备的 tty_struct 结构的指针调用它, 还用一个
文件指针. 这个 open 成员必须被一个 tty 驱动为它能正确工作而设置; 否则, -ENODEV
被返回给用户当调用 open 时.
当调用这个 open 函数, tty 驱动被期望或者保存一些传递给它的 tty_struct 变量中的
数据, 或者保存一个可以基于端口次编号来引用的静态数组中的数据. 这是有必要的, 所
以 tty 驱动知道哪个设备在被引用当以后的 close, write, 和其他函数被调用时.
tiny_tty 驱动保存一个指针在 tty 结构中, 如同下面代码所见到:
static int tiny_open(struct tty_struct *tty, struct file *file)
{
struct tiny_serial *tiny;
struct timer_list *timer;
int index;
/* initialize the pointer in case something fails */
tty->driver_data = NULL;
/* get the serial object associated with this tty pointer */
index = tty->index;
tiny = tiny_table[index];
if (tiny == NULL)
{
/* first time accessing this device, let's create it */
tiny = kmalloc(sizeof(*tiny), GFP_KERNEL);
if (!tiny)
return -ENOMEM;
init_MUTEX(&tiny->sem);
tiny->open_count = 0;
tiny->timer = NULL;
tiny_table[index] = tiny;
}
down(&tiny->sem);
/* save our structure within the tty structure */
tty->driver_data = tiny;
tiny->tty = tty;
在这个代码中, tiny_serial 结构被保存在 tty 结构中. 这允许 tiny_write,
tiny_write_room, 和 tiny_close 函数来获取 tiny_serial 结构和正确操作它.
tiny_serial 结构定义为:
struct tiny_serial
{
struct tty_struct *tty; ?/* pointer to the tty for this device */
int open_count; /* number of times this port has been opened */
struct semaphore sem; /* locks this structure */
struct timer_list *timer;
};
如同我们已见到的, open_count 变量初始化为 0 在第一次打开端口的 open 调用中. 这
是一个典型的引用计数, 因为一个 tty 驱动的 open 和 close 函数可能对同一个设备多
次调用以便多个进程来读写数据. 为正确处理所有的事情, 必须保持一个这个端口被打开
或者关闭的次数计数; 这个驱动递增和递减这个计数在打开使用时. 当打开第一次被打开,
任何必要的硬件初始化和内存分配可以做. 当端口被最后一次关闭, 任何必要的硬件关闭
和内存清理可以做.
tiny_open 函数的剩下部分展示了如何跟踪设备被打开的次数:
++tiny->open_count;
if (tiny->open_count == 1)
{
/* this is the first time this port is opened */
/* do any hardware initialization needed here */
}
open 函数必须返回或者一个负的错误号如果发生事情阻止了成功打开, 或者一个 0 来表
示成功.
close 函数指针被 tty 核心调用, 在用户对前面使用 open 调用而创建的文件句柄调用
close 时. 这表示设备应当在这次被关闭. 但是, 因为 open 函数可被多次调用, close
函数也可多次调用. 因此这个函数应当跟踪它被调用的次数来决定是否硬件应当在此次真
正被关闭. tiny_tty 驱动做这个使用下面的代码:
static void do_close(struct tiny_serial *tiny)
{
down(&tiny->sem);
if (!tiny->open_count)
{
/* port was never opened */
goto exit;
}
--tiny->open_count;
if (tiny->open_count <= 0)
{
/* The port is being closed by the last user. */
/* Do any hardware specific stuff here */
/* shut down our timer */
del_timer(tiny->timer);
}
exit:
up(&tiny->sem);
}
static void tiny_close(struct tty_struct *tty, struct file *file)
{
struct tiny_serial *tiny = tty->driver_data;
if (tiny)
do_close(tiny);
}
tiny_close 函数只是调用 do_close 函数来完成实际的关闭设备工作. 因此关闭逻辑不
必在这里和驱动被卸载和端口被打开时重复. close 函数没有返回值, 因为它不被认为会
失败.
write 函数被用户在有数据发送给硬件时调用. 首先 tty 核心接收到调用, 接着它传递
数据到 tty 驱动的 write 函数. tty 核心还告知 tty 驱动要发送的数据大小.
有时, 因为速度和 tty 硬件的缓冲区容量, 不是所有的写程序要求的字符可以在调用写
函数时发送. 这个写函数应当返回能够发送给硬件的字符数( 或者在以后时间可排队发
送 ), 因此用户程序可以检查是否所有的数据真正写入. 这种检查在用户空间非常容易完
成, 比一个内核驱动站着睡眠直到所有的请求数据能够被发送. 如果任何错误发生在
wirte 调用期间, 一个负的错误值应当被返回代替被写入的字节数.
write 函数可从中断上下文和用户上下文中被调用. 知道这一点是重要的, 因为 tty 驱
动不应当调用任何可能当它在中断上下文中睡眠的函数. 这些包括任何可能调用调度的函
数, 例如普通的函数 copy_from_user, kmalloc, 和 printk. 如果你确实想睡眠, 确信
去首先检查是否驱动在中断上下文, 通过调用 calling_in_interrupt.
这个例子 tiny tty 驱动没有连接到任何真实的硬件, 因此它的写函数简单地将要写的什
么数据记录到内核调试日志. 它使用下面的代码做这个:
static int tiny_write(struct tty_struct *tty, const unsigned char *buffer, int count)
{
struct tiny_serial *tiny = tty->driver_data;
int i;
int retval = -EINVAL;
if (!tiny)
return -ENODEV;
down(&tiny->sem);
if (!tiny->open_count)
/* port was not opened */
goto exit;
/* fake sending the data out a hardware port by
* writing it to the kernel debug log.
*/
printk(KERN_DEBUG "%s - ", __FUNCTION__);
for (i = 0; i < count; ++i)
printk("%02x ", buffer[i]);
printk("\n");
exit:
up(&tiny->sem);
return retval;
}
当 tty 子系统自己需要发送数据到 tty 设备之外, write 函数被调用. 如果 tty 驱动
在 tty_struct 中没有实现 put_char 函数, 这会发生. 在这种情况下, tty 核心用一个
数据大小为 1 来使用 write 函数回调. 这普遍发生在 tty 核心想转换一个新行字符为
一个换行和新行字符. 这里的最大的问题是 tty 驱动的 write 函数必须不返回 0 对于
这类的调用. 这意味着驱动必须写那个数据的字节到设备, 因为调用者( tty 核心 ) 不
缓冲数据和在之后的时间重试. 因为 write 函数不能知道是否它在被调用来替代
put_char, 即便只有一个字节的数据被发送, 尽力实现 write 函数以至于它一直至少在
返回前写一个字节. 许多当前的 USB-到-串口的 tty 驱动没有遵照这个规则, 并且因此,
一些终端类型不能正确工作当连接到它们时.
write_room 函数被调用当 tty 核心想知道多少空间在写缓冲中 tty 驱动可用. 这个数
字时时改变随着字符清空写缓冲以及调用写函数时, 添加字符到这个缓冲.
static int tiny_write_room(struct tty_struct *tty)
{
struct tiny_serial *tiny = tty->driver_data;
int room = -EINVAL;
if (!tiny)
return -ENODEV;
down(&tiny->sem);
if (!tiny->open_count)
{
/* port was not opened */
goto exit;
}
/* calculate how much room is left in the device */
room = 255;
exit:
up(&tiny->sem);
return room;
}
一个工作的 tty 驱动不需要在 tty_driver 结构中的 chars_in_buffer 函数, 但是它被
推荐. 这个函数被调用当 tty 核心想知道多少字符仍然保留在 tty 驱动的写缓冲中要被
发送. 如果驱动能够存储字符在它发送它们到硬件之前, 它应当实现这个函数为了 tty
核心能够知道是否所有的驱动中的数据已经流出.
3 个 tty_driver 结构中的函数回调可以用来刷新任何驱动保留的数据. 它们不被要求实
现, 但是推荐如果 tty 驱动能够缓冲数据在它发送给硬件之前. 前 2 个函数回调称为
flush_chars 和 wait_until_sent. 这些函数被调用当 tty 核心使用 put_char 函数回
调已发送了许多字符给 tty 驱动. flush_chars 函数回调被调用当 tty 核心要 tty 驱
动启动发送这些字符到硬件, 如果它尚未启动. 这个函数被允许在所有的数据发送给硬件
之前返回. wait_until_sent 函数回调以非常相同的发生工作; 但是它必须等待直到所有
的字符在返回到 tty 核心前被发送, 或者知道超时值到时. 如果这个传递给
wait_until_sent 函数回调的超时值设为 0, 函数应当等待直到它完成这个操作.
剩下的数据刷新函数回调是 flush_buffer. 它被 tty 核心调用当 tty 驱动要刷新所有
的仍然在它的写缓冲的数据. 任何保留在缓冲中的数据被丢失并且没发送给设备.