libusb_bulk_transfer 异步同步

同步方式
libusb_bulk_transfer(devh, ep_bulk, buf, CAM_BUF_SZ, &len, timeout);
进入libusb研究,发现libusb是采用异步方式来实现的。在do_sync_bulk_transfer中:
 

 

staticint do_sync_bulk_transfer(struct libusb_device_handle *dev_handle,
unsignedchar endpoint,unsignedchar*buffer,int length,
int*transferred,unsignedint timeout,unsignedchar type)
{
libusb_fill_bulk_transfer(transfer, dev_handle, endpoint, buffer, length,
bulk_transfer_cb,&completed, timeout);
transfer->type = type;
 
= libusb_submit_transfer(transfer);
if(<0){
libusb_free_transfer(transfer);
return r;
}
 
while(!completed){
= libusb_handle_events(HANDLE_CTX(dev_handle));
}
}
这里libusb_fill_bulk_transfer来填充bulk transfer,而后libusb_submit_transfer提交bulk transfer,最后用libusb_handle_events来等待完成。当收到回应后,bulk_transfer_cb回调设置completed,从而阻塞被接触,函数返回。
 
分段处理bulk transfer
libusb_submit_transfer最终调到了submit_bulk_transfer。该函数会检查buffer大小,若是大于MAX_BULK_BUFFER_LENGTH(16K),则分红若干16K大小的urb,每一个urb指向用户buffer的适当位置,而后用ioctl(IOCTL_USBFS_SUBMITURB)提交每一个urb。
 
收到数据时会判断是否收齐全部urb。在handle_bulk_completion中,若urb_idx为最后一个urb,则认为收齐了全部的urb。最终会调用usbi_handle_transfer_completion来调用urb callback。
 
timeout处理
若libusb_bulk_transfer传入的timeout为0,则没有timeout,libusb会一直等待数据。在libusb_handle_events中设置了一个2s的poll timeout,libusb会在while中一直poll,每次poll的timeout为2s。
 
若设置了timeout,libusb_submit_transfer会按照timeout升序将transfer插入到libusb_context的flying_transfers列表中,而后提交transfer(固然会分段了,如上所述)
 
libusb_handle_events会根据本身设置的2s timeout和flying_transfers中的timeout得出实际timeout,而后用poll查询usb fd。
 
问题根源
libusb阻塞的缘由就是超时。有时usb指纹头返回数据较慢,在指定的timeout时间内没有完成全部urb请求,进入超时处理。handle_timeout()会cancel掉为完成的urb(IOCTL_USBFS_DISCARDURB)。在do_sync_bulk_transfer中,因为未完成全部urb,bulk_transfer_cb没有被调用,从而会阻塞。libusb_handle_events会继续以2s超时来查询fd,但因为urb已经取消,设备会返回-2(ENOENT)。
 
按道理讲,即便超时,libusb也应该在超时后返回。libusb为何没有返回呢?handle_bulk_completion函数中,若读到ENOENT,awaiting_discard会递减至0。与awaiting_discard一块儿的还有awaiting_reap,若两者均为0,也会调用bulk_transfer_cb通知用户。
 
awaiting_discard在超时时cancel urb时被设置。若ioctl(IOCTL_USBFS_DISCARDURB)返回EINVAL,则awaiting_reap会递增。
 
通过打印验证,发如今cancel urb时,对于已经完成的urb,会返回EINVAL。而未完成的urb,则返回0。我想这大概是一个bug,正确作法应该是不要取消已经完成的urb。