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。
相關文章
相關標籤/搜索