14.4 数据发送和接收
图 14.3 所示终端设备数据发送和接收过程中的数据流以及函数调用关系。
图 14.3 终端设备数据发送和接收过程中的数据流以及函数调用关系
用户在有数据发送给终端设备时,通过“write()系统调用 — tty 核心 — 线路规程”的层层调用,最终调用 tty_driver结构体中的 write()函数完成发送。
因为传输速度和 tty 硬件缓冲区容量的原因,不是所有的写程序要求的字符都可以在调用写函数时被发送,因此写函数应当返回能够发送给硬件的字节数以便用户程序检查是否所有的数据被真正写入。如果在 write()调用期间发生任何错误,一个负的错误码应当被返回。
tty_driver 的 write()函数接受 3 个参数 tty_struct、发送数据指针及要发送的字节数。
int (*write)(struct tty_struct * tty, const unsigned char *buf, int count);
一般首先会通过 tty_struct 的 driver_data 成员得到设备私有信息结构体,然后依次进行必要的硬件操作开始
发送,代码清单 14.6 为 tty_driver 的 write()。
代码清单 14.6 tty_driver 结构体的 write()成员函数
static int xxx_write(struct tty_struct *tty, const unsigned char *buf, int count)
{
/* 获得 tty 设备私有数据 */
struct xxx_tty *xxx = (struct xxx_tty*)tty->driver_data;
...
/* 开始发送 */
while (1) {
local_irq_save(flags); /*把中断状态保存到flags中,禁止当前处理器上的中断*/
c = min_t(int, count, min(SERIAL_XMIT_SIZE - xxx->xmit_cnt - 1,
SERIAL_XMIT_SIZE - xxx->xmit_head));
if (c <= 0) {
local_irq_restore(flags);/*恢复保存的中断状态标志,使能当前处理器上的中断*/
break;
}
/* 拷贝到发送缓冲区 */
memcpy(xxx->xmit_buf + xxx->xmit_head, buf, c);
xxx->xmit_head = (xxx->xmit_head + c) &(SERIAL_XMIT_SIZE - 1);
xxx->xmit_cnt += c;
local_irq_restore(flags);/*恢复保存的中断状态标志,使能当前处理器上的中断*/
buf += c;
count -= c;
total += c;
}
if (xxx->xmit_cnt && !tty->stopped && !tty->hw_stopped)
start_xmit(xxx);/* 开始发送 */
return total; /* 返回发送的字节数 */}
当 tty 子系统自己需要发送数据到 tty 设备时,如果没有实现 put_char()函数,write()函数将被调用,此时传入的 count 参数为 1,通过对代码清单 14.7 的分析即可获知。
代码清单 14.7 put_char()函数的 write()替代
drivers/char/tty_io.c
/*
* Called by a tty driver to register itself.
*/
int tty_register_driver(struct tty_driver *driver)
{
int error;
int i;
dev_t dev;
void **p = NULL;
if (driver->flags & TTY_DRIVER_INSTALLED)
return 0;
if (!(driver->flags & TTY_DRIVER_DEVPTS_MEM) && driver->num) {
p = kzalloc(driver->num * 3 * sizeof(void *), GFP_KERNEL);
if (!p)
return -ENOMEM;
}
if (!driver->major) {
error = alloc_chrdev_region(&dev, driver->minor_start, driver->num,
driver->name);
if (!error) {
driver->major = MAJOR(dev);
driver->minor_start = MINOR(dev);
}
} else {
dev = MKDEV(driver->major, driver->minor_start);
error = register_chrdev_region(dev, driver->num, driver->name);
}
if (error < 0) {
kfree(p);
return error;
}
if (p) {
driver->ttys = (struct tty_struct **)p;
driver->termios = (struct ktermios **)(p + driver->num);
driver->termios_locked = (struct ktermios **)(p + driver->num * 2);
} else {
driver->ttys = NULL;
driver->termios = NULL;
driver->termios_locked = NULL;
}
cdev_init(&driver->cdev, &tty_fops);
driver->cdev.owner = driver->owner;
error = cdev_add(&driver->cdev, dev, driver->num);
if (error) {
unregister_chrdev_region(dev, driver->num);
driver->ttys = NULL;
driver->termios = driver->termios_locked = NULL;
kfree(p);
return error;
}
if (!driver->put_char)//没有定义 put_char()函数
driver->put_char = tty_default_put_char;
mutex_lock(&tty_mutex);
list_add(&driver->tty_drivers, &tty_drivers); /* 临界资源 */
mutex_unlock(&tty_mutex);
if ( !(driver->flags & TTY_DRIVER_DYNAMIC_DEV) ) {
for(i = 0; i < driver->num; i++)
tty_register_device(driver, i, NULL);
}
proc_tty_register_driver(driver);
return 0;
}
/*
* The default put_char routine if the driver did not define one.
*/
static void tty_default_put_char(struct tty_struct *tty, unsigned char ch)
{
tty->driver->write(tty, &ch, 1);//调用 tty_driver.write()函数
}
备注:
tty_operations 结构体中没有提供 read()函数。因为发送是用户主动的,而接收即用户调 read()则是读一片缓冲区中已放好的数据。tty 核心在一个称为 struct tty_flip_buffer 的结构体中缓冲数据直到它被用户请求。因为 tty 核心提供了缓冲逻辑,因此每个 tty驱动并非一定要实现它自身的缓冲逻辑。
tty 驱动不必过于关心 tty_flip_buffer 结构体的细节,如果其 count 字段大于或等于 TTY_FLIPBUF_SIZE,这个 flip 缓冲区就需要被刷新到用户,刷新通过对 tty_flip_buffer_push()函数的调用来完成。
代码清单 14.8 tty_flip_buffer_push()范例
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], TTY_NORMAL); /* 把数据插入缓冲区 */
}
tty_flip_buffer_push(tty);
分析:
从 tty 驱动接收到字符将被 tty_insert_flip_char()函数插入 flip 缓冲区。该函数的第 1 个参数是数据应当保存入的 tty_struct 结构体,第 2 个参数是要保存的字符,第 3 个参数是应当为这个字符设置的标志,如果字符是一个接收到的常规字符,则设为 TTY_NORMAL,如果是一个特殊类型的指示错误的字符,依据具体的错误类型,应当设为 TTY_BREAK、TTY_PARITY 或TTY_OVERRUN。