linux ttyusb读写_浅析ttyUSB驱动usb_serial_driver-ch341

浅析ttyUSB驱动usb_serial_driver-ch341

sudo insmod /lib/modules/2.6.22-14-generic/kernel/drivers/usb/serial/usbserial.ko vendor=0x8086 product=0xd001

安装之后可以通过ttyUSBx与任何usb设备进行数据传输,但是如果usb设备端接收pc下发的数据速度跟不上,比如设备端可能需要处理pc下发的数据用掉一段时间,这时tty->driver->write(tty, b, nr);可能返回0,也就是在执行usb_serial_generic_write()函数时,因为port->write_urb_busy = 1;发送正繁忙,进而会执行schedule让出cpu,虽然使用usbserial.ko模块可以与任何usb设备进行通信,但是当进行大量数据传输时,速度并不乐观,下面会做一个粗略的分析,找到了速度慢的原因,和一个保守的解决方案[gliethttp_20080526]。

usb_register(&ch341_driver)->含有如下两行语句,

drvwrap.driver.bus = &usb_bus_type;

drvwrap.driver.probe = usb_probe_interface;

usb总线发现硬件之后,会在恰当的时候调用__driver_attach()函数,__driver_attach()会调用usb_bus_type.match(),对于usb设备一定返回成功,

然后执行really_probe(dev, drv);暂时将dev->driver = drv;之后执行

device_driver->probe函数,对于usb总线上的设备就是调用usb_probe_interface,

usb_probe_interface函数会搜索所有usb总线上挂接的usb_driver驱动,检查是否和该dev设备的id匹配,如果匹配,那么调用usb_driver->probe()函数,对于usbserial驱动来说,就是调用usb_serial_probe,在该函数中如果

usbserial所属的usb_serial_driver驱动包含probe函数,那么调用之,对于ch341没有,在usb_serial_probe函数中分配完port对应的管道缓冲区之后,执行usb_serial_driver->attach()函数,对于ch341来说就是ch341_attach,使用control控制管道,发送波特率设置参数给ch341串口设备,紧接着调用get_free_serial (serial, num_ports, &minor);

该函数会查找serial_table[i]的空缺位置,然后serial->minor = minor;于是serial有了次设备号minor,

应用程序会调用serial_open()->serial->type->open(port, filp);也就是ch341_open详细的打开serial串口,

对于ch341,就是将波特率配置值发送给串口ch341设备而已,

用户调用serial_write()来向ch341发送串口数据,serial_write()会调用port->serial->type->write(port, buf, count);因为ch341没有定义自己的write写函数,所以会调用usb_serial_generic_write函数实现具体的发送操作,但每次最多只能发送一个输出端点大小的数据,具体发送流程是这样的:

sys_write()->vfs_write()->

drivers/char/tty_io.c->tty_fops.tty_write()->do_tty_write()->

tty_ldisc_N_TTY.write_chan()->调用

tty->driver->write(tty, b, nr)->

serial_write(struct tty_struct * tty, const unsigned char *buf, int count)->

port->serial->type->write(port, buf, count)->

usb_serial_generic_write()

对于open的安装过程简单来说是这样的

sys_open()->tty_open()->

然后设置操作函数集filp->f_op = &tty_fops;

这样操作函数集tty_fops会调用ch341的驱动,因为ch341没有设置read和write函数,所以对于ch341的ttyUSBx写操作,最终调用usb_serial_generic_write,同理读调用usb_serial_generic_read,对于generic类型串口读写函数,每次最大收发字节数为相应端点的端点大小,

tty_write会检测所有的counts待收发数据是否全部完成,如果循环完成了,tty_write返回,进而vfs_write返回,进而sys_write返回,于是read()和write()

对于usb_serial串口发送数据速度超级慢,不是因为usb_serial_generic_write函数速度慢,它的速度已经是全速了,问题主要出在调用usb_serial_generic_write函数的上级函数中,主要原因在这里:

do_tty_write()

...

chunk = 2048;

...

/* Do the write .. */

for (;;) {

size_t size = count;

if (size > chunk)

size = chunk;

ret = -EFAULT;

if (copy_from_user(tty->write_buf, buf, size))

break;

lock_kernel();

ret = write(tty, file, tty->write_buf, size);

//gliethttp_20080526

//write就是

//write_chan(),在write_chan函数中

//while (1) {

// while (nr > 0) {

// c = tty->driver->write(tty, b, nr);

//因为pc主机速度很快,所以c经常会等于0,说明上一次提交的发送数据还没有发送完毕,busy中,所以返回0

// if (c < 0) {

// retval = c;

// goto break_out;

// }

// if (!c)

// break;

// b += c;

// nr -= c;

// }

// schedule();//当c==0时,也就是usb通信管道繁忙时,将会执行schedule

//}

//对于ch341或者其他的usbserial硬件,c等于out端点大小,一般为16字节或者64字节,大多数等于0,

//所以上面copy_from_user(tty->write_buf, buf, size)拷贝了2k的数据,

//将在write(tty, file, tty->write_buf, size);中以端点大小为单位,通过tty->driver->write一组组的循环发送出去

//当c==0时,也就是usb通信管道繁忙时,将会执行schedule,内核调度出本task之后,什么时候返回是未知的,因为当前

//的task没有等待任何事件,所以属于变相告诉kernel,“我现在闲的很,什么事情都没有了,kernel你让其他task执行吧,

//如果没有任何人需要cpu的时候,你就回过头来看看我,不用把我放在心上了”,而事实上本task还有很多数据在堆积,等待发送处理,

//但就是因为没有使用wakeup之类的咚咚机制,来等待事件完成后的即时唤醒,傻傻的让出cpu,

//所以当连续发送大量数据的时候,这种切换出去、切换回来时间不确定性会导致速度指数级下降!

//所以我觉得,在tty->driver->write(tty, b, nr)中使用信号等待机制,等待数据发送完毕,这将使得数据发送速度指数级提升!

//或者直接使用usb_bulk_msg完成1个端点大小数据发送,这样可以一直等到数据发送完毕!

unlock_kernel();

if (ret <= 0)

break;

written += ret;

buf += ret;

count -= ret;

if (!count)

break;

ret = -ERESTARTSYS;

if (signal_pending(current))

break;

cond_resched();

//为了均衡系统,尝试调度出自己,这是在为系统的其他线程做好事,这样的不霸道行为也是降低速度的原因之一!

}

因为是内核研读笔记,比较凌乱,凑活了[gliethttp_20080526]


版权声明:本文为weixin_35918198原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。