aboutsummaryrefslogtreecommitdiff
path: root/src/libusb1-glue.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libusb1-glue.c')
-rw-r--r--src/libusb1-glue.c227
1 files changed, 171 insertions, 56 deletions
diff --git a/src/libusb1-glue.c b/src/libusb1-glue.c
index 3098553..636c544 100644
--- a/src/libusb1-glue.c
+++ b/src/libusb1-glue.c
@@ -52,6 +52,7 @@
* spend a bit of time collecting data. Higher values also
* make connecting/disconnecting more reliable.
*/
+#define USB_START_TIMEOUT 5000
#define USB_TIMEOUT_DEFAULT 20000
#define USB_TIMEOUT_LONG 60000
static inline int get_timeout(PTP_USB* ptp_usb)
@@ -109,6 +110,8 @@ static short ptp_write_func(unsigned long,
PTPDataHandler*, void *data, unsigned long*);
static short ptp_read_func (unsigned long,
PTPDataHandler*, void *data, unsigned long*, int);
+static short ptp_read_cancel_func (PTPParams* params,
+ uint32_t transactionid);
static int usb_get_endpoint_status(PTP_USB* ptp_usb,
int ep, uint16_t* status);
@@ -687,7 +690,7 @@ LIBMTP_error_number_t LIBMTP_Detect_Raw_Devices(LIBMTP_raw_device_t ** devices,
retdevs[i].device_entry.device_flags = mtp_device_table[j].device_flags;
// This device is known to the developers
- LIBMTP_ERROR("Device %d (VID=%04x and PID=%04x) is a %s %s.\n",
+ LIBMTP_INFO("Device %d (VID=%04x and PID=%04x) is a %s %s.\n",
i,
desc.idVendor,
desc.idProduct,
@@ -837,6 +840,7 @@ ptp_read_func (
PTP_USB *ptp_usb = (PTP_USB *)data;
unsigned long toread = 0;
int ret = 0;
+ uint16_t handler_ret = 0;
int xread;
unsigned long curread = 0;
unsigned char *bytes;
@@ -861,7 +865,6 @@ ptp_read_func (
// This is the largest block we'll need to read in.
bytes = malloc(CONTEXT_BLOCK_SIZE);
while (curread < size) {
-
LIBMTP_USB_DEBUG("Remaining size to read: 0x%04lx bytes\n", size - curread);
// check equal to condition here
@@ -894,16 +897,20 @@ ptp_read_func (
LIBMTP_USB_DEBUG("Reading in 0x%04lx bytes\n", toread);
ret = USB_BULK_READ(ptp_usb->handle,
- ptp_usb->inep,
- bytes,
- toread,
- &xread,
- ptp_usb->timeout);
+ ptp_usb->inep,
+ bytes,
+ toread,
+ &xread,
+ ptp_usb->timeout);
LIBMTP_USB_DEBUG("Result of read: 0x%04x (%d bytes)\n", ret, xread);
- if (ret != LIBUSB_SUCCESS)
+ if (ret == LIBUSB_ERROR_TIMEOUT) {
+ return PTP_ERROR_TIMEOUT;
+ }
+ else if (ret != LIBUSB_SUCCESS){
return PTP_ERROR_IO;
+ }
LIBMTP_USB_DEBUG("<==USB IN\n");
if (xread == 0)
@@ -915,45 +922,54 @@ ptp_read_func (
if (expect_terminator_byte && xread == toread)
{
LIBMTP_USB_DEBUG("<==USB IN\nDiscarding extra byte\n");
-
xread--;
}
- int putfunc_ret = handler->putfunc(NULL, handler->priv, xread, bytes);
- if (putfunc_ret != PTP_RC_OK)
- return putfunc_ret;
+ if (handler) {
+ handler_ret = handler->putfunc(NULL, handler->priv, xread, bytes);
+ if (handler_ret != PTP_RC_OK) {
+ LIBMTP_ERROR("LIBMTP error writing to fd or memory by handler."
+ "Not enough memory or temp/destination free space?");
+ free (bytes);
+ return PTP_ERROR_CANCEL;
+ }
+ }
- ptp_usb->current_transfer_complete += xread;
+ if (ptp_usb->callback_active)
+ ptp_usb->current_transfer_complete += xread;
curread += xread;
// Increase counters, call callback
if (ptp_usb->callback_active) {
if (ptp_usb->current_transfer_complete >= ptp_usb->current_transfer_total) {
- // send last update and disable callback.
- ptp_usb->current_transfer_complete = ptp_usb->current_transfer_total;
- ptp_usb->callback_active = 0;
+ // send last update and disable callback.
+ ptp_usb->current_transfer_complete = ptp_usb->current_transfer_total;
+ ptp_usb->callback_active = 0;
}
if (ptp_usb->current_transfer_callback != NULL) {
- int ret;
- ret = ptp_usb->current_transfer_callback(ptp_usb->current_transfer_complete,
- ptp_usb->current_transfer_total,
- ptp_usb->current_transfer_callback_data);
- if (ret != 0) {
- return PTP_ERROR_CANCEL;
- }
+ ret = ptp_usb->current_transfer_callback(ptp_usb->current_transfer_complete,
+ ptp_usb->current_transfer_total,
+ ptp_usb->current_transfer_callback_data);
+ if (ret != 0) {
+ LIBMTP_USB_DEBUG("ptp_read_func cancelled by user callback\n");
+ free (bytes);
+ return PTP_ERROR_CANCEL;
+ }
}
}
if (xread < toread) /* short reads are common */
break;
}
- if (readbytes) *readbytes = curread;
+
+ if (readbytes)
+ *readbytes = curread;
free (bytes);
// there might be a zero packet waiting for us...
if (readzero &&
- !FLAG_NO_ZERO_READS(ptp_usb) &&
- curread % ptp_usb->outep_maxpacket == 0) {
+ !FLAG_NO_ZERO_READS(ptp_usb) &&
+ curread % ptp_usb->inep_maxpacket == 0) {
unsigned char temp;
int zeroresult = 0, xread;
@@ -961,11 +977,11 @@ ptp_read_func (
LIBMTP_USB_DEBUG("Zero Read\n");
zeroresult = USB_BULK_READ(ptp_usb->handle,
- ptp_usb->inep,
- &temp,
- 0,
+ ptp_usb->inep,
+ &temp,
+ 0,
&xread,
- ptp_usb->timeout);
+ ptp_usb->timeout);
if (zeroresult != LIBUSB_SUCCESS)
LIBMTP_INFO("LIBMTP panic: unable to read in zero packet, response 0x%04x", zeroresult);
}
@@ -973,6 +989,69 @@ ptp_read_func (
return PTP_RC_OK;
}
+/*
+ * When cancelling a read from device.
+ * The device can take time to really stop sending in data, so we have to
+ * read and discard it.
+ * Stop when we encounter a timeout (so no more data in after 300ms).
+ * Corner case: Lets imagine that the cancel will arrive just for the last bytes
+ * of a file, and so that the transfer would still complete. The current code
+ * will also discard the "reply status" frame. That makes sense because from
+ * the host point of view, the end of the file will not have be written.
+ *
+ */
+static short
+ptp_read_cancel_func (
+ PTPParams* params,
+ uint32_t transactionid
+) {
+ PTP_USB *ptp_usb = (PTP_USB *) params->data;
+ uint16_t ret = 0;
+ PTPContainer MyEvent;
+ unsigned long xread = 0;
+ int old_callback_active = ptp_usb->callback_active;
+ int oldtimeout = 60000;
+
+
+ get_usb_device_timeout(ptp_usb, &oldtimeout);
+
+ ptp_usb->callback_active = 0;
+ /* Set a timeout similar to the one of windows in such a case: 300ms */
+ set_usb_device_timeout(ptp_usb, 300);
+
+ params->cancelreq_func(params, transactionid);
+
+
+ ret = params->devstatreq_func(params);
+ while (ret == PTP_RC_DeviceBusy) {
+ usleep(200000);
+ ret = params->devstatreq_func(params);
+ }
+
+ while (1) {
+ ret = ptp_read_func(ptp_usb->inep_maxpacket,
+ NULL,
+ params->data,
+ &xread,
+ 0);
+
+ if (ret != PTP_RC_OK)
+ break;
+ }
+
+ // Probably a "transfert cancelled" event will be raised.
+ // We have to clear it or a device like the "GoPro" will not reply anymore after
+ memset(&MyEvent,0,sizeof(MyEvent));
+ ptp_usb_event_check(params, &MyEvent);
+
+ /* Restore previous values */
+ ptp_usb->callback_active = old_callback_active;
+ set_usb_device_timeout(ptp_usb, oldtimeout);
+
+
+ return PTP_ERROR_CANCEL;
+}
+
static short
ptp_write_func (
unsigned long size,
@@ -1271,7 +1350,7 @@ ptp_usb_senddata (PTPParams* params, PTPContainer* ptp,
bytes_left_to_transfer = size-datawlen;
ret = PTP_RC_OK;
while(bytes_left_to_transfer > 0) {
- int max_long_transfer = ULONG_MAX + 1 - packet_size;
+ unsigned long max_long_transfer = ULONG_MAX + 1 - packet_size;
ret = ptp_write_func (bytes_left_to_transfer > max_long_transfer ? max_long_transfer : bytes_left_to_transfer,
handler, params->data, &written);
if (ret != PTP_RC_OK)
@@ -1371,25 +1450,25 @@ ptp_usb_getdata (PTPParams* params, PTPContainer* ptp, PTPDataHandler *handler)
handler->putfunc(
params, handler->priv, rlen - PTP_USB_BULK_HDR_LEN, usbdata.payload.data
);
- if (putfunc_ret != PTP_RC_OK)
- return putfunc_ret;
+ if (putfunc_ret != PTP_RC_OK)
+ return ptp_read_cancel_func(params, ptp->Transaction_ID);
/* stuff data directly to passed data handler */
while (1) {
unsigned long readdata;
- uint16_t xret;
- xret = ptp_read_func(
+ ret = ptp_read_func(
0x20000000,
handler,
params->data,
&readdata,
- 0
- );
- if (xret != PTP_RC_OK)
- return xret;
- if (readdata < 0x20000000)
- break;
+ 0);
+ if (ret == PTP_ERROR_CANCEL)
+ return ptp_read_cancel_func(params, ptp->Transaction_ID);
+ if (ret != PTP_RC_OK)
+ return ret;
+ if (readdata < 0x20000000)
+ break;
}
return PTP_RC_OK;
}
@@ -1442,7 +1521,7 @@ ptp_usb_getdata (PTPParams* params, PTPContainer* ptp, PTPDataHandler *handler)
usbdata.payload.data
);
if (putfunc_ret != PTP_RC_OK)
- return putfunc_ret;
+ return ptp_read_cancel_func(params, ptp->Transaction_ID);
if (FLAG_NO_ZERO_READS(ptp_usb) &&
len+PTP_USB_BULK_HDR_LEN == ptp_usb->inep_maxpacket) {
@@ -1484,12 +1563,16 @@ ptp_usb_getdata (PTPParams* params, PTPContainer* ptp, PTPDataHandler *handler)
}
ret = ptp_read_func(len - (rlen - PTP_USB_BULK_HDR_LEN),
- handler,
- params->data, &rlen, 1);
-
- if (ret != PTP_RC_OK) {
- break;
+ handler,
+ params->data,
+ &rlen,
+ 1);
+ if (ret == PTP_ERROR_CANCEL) {
+ ptp_read_cancel_func(params, ptp->Transaction_ID);
+ break;
}
+ if (ret != PTP_RC_OK)
+ break;
} while (0);
return ret;
}
@@ -1774,6 +1857,34 @@ ptp_usb_control_cancel_request (PTPParams *params, uint32_t transactionid) {
return PTP_RC_OK;
}
+/**
+ * PTP class level device status request
+ */
+uint16_t
+ptp_usb_control_device_status_request (PTPParams *params) {
+ PTP_USB *ptp_usb = (PTP_USB *)(params->data);
+ int ret;
+ unsigned char buffer[4];
+ // In theory, only 2x16 bytes are needed based on linux mtp implementation
+ // But the pima spec is not clear
+
+ ret = libusb_control_transfer(ptp_usb->handle,
+ LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE,
+ 0x67, 0x0000, 0x0000,
+ buffer,
+ sizeof(buffer),
+ ptp_usb->timeout);
+ if (ret < sizeof(buffer))
+ return PTP_ERROR_IO;
+
+ ret = dtoh16a(&buffer[2]);
+ LIBMTP_USB_DEBUG("Device status request returned: 0x%04x \n", ret);
+ if (ret != PTP_RC_OK && ret != PTP_RC_DeviceBusy && ret != PTP_RC_TransactionCanceled)
+ return PTP_ERROR_IO;
+
+ return ret;
+}
+
static int init_ptp_usb(PTPParams* params, PTP_USB* ptp_usb, libusb_device* dev)
{
libusb_device_handle *device_handle;
@@ -1786,6 +1897,7 @@ static int init_ptp_usb(PTPParams* params, PTP_USB* ptp_usb, libusb_device* dev)
params->getresp_func=ptp_usb_getresp;
params->getdata_func=ptp_usb_getdata;
params->cancelreq_func=ptp_usb_control_cancel_request;
+ params->devstatreq_func=ptp_usb_control_device_status_request;
params->data=ptp_usb;
params->transaction_id=0;
/*
@@ -1812,10 +1924,11 @@ static int init_ptp_usb(PTPParams* params, PTP_USB* ptp_usb, libusb_device* dev)
libusb_kernel_driver_active(device_handle, ptp_usb->interface)
) {
if (LIBUSB_SUCCESS != libusb_detach_kernel_driver(device_handle, ptp_usb->interface)) {
- perror("libusb_detach_kernel_driver() failed, continuing anyway...");
+ perror("libusb_detach_kernel_driver() failed, continuing anyway...");
}
}
+
/*
* Check if the config is set to something else than what we want
* to use. Only set the configuration if we absolutely have to.
@@ -1851,15 +1964,11 @@ static int init_ptp_usb(PTPParams* params, PTP_USB* ptp_usb, libusb_device* dev)
}
}
- /*
- * It seems like on kernel 2.6.31 if we already have it open on another
- * pthread in our app, we'll get an error if we try to claim it again,
- * but that error is harmless because our process already claimed the interface
- */
usbresult = libusb_claim_interface(device_handle, ptp_usb->interface);
-
- if (usbresult != 0)
- fprintf(stderr, "ignoring libusb_claim_interface() = %d", usbresult);
+ if (usbresult != 0) {
+ fprintf(stderr, "error returned by libusb_claim_interface() = %d", usbresult);
+ return -1;
+ }
/*
* If the altsetting is set to something different than we want, switch
@@ -2175,6 +2284,8 @@ LIBMTP_error_number_t configure_usb_device(LIBMTP_raw_device_t *device,
return LIBMTP_ERROR_CONNECTING;
}
+ /* Special short timeout for the first trial of opensession. */
+ set_usb_device_timeout(ptp_usb, USB_START_TIMEOUT);
/*
* This works in situations where previous bad applications
* have not used LIBMTP_Release_Device on exit
@@ -2192,6 +2303,7 @@ LIBMTP_error_number_t configure_usb_device(LIBMTP_raw_device_t *device,
return LIBMTP_ERROR_CONNECTING;
}
+ /* Normal timeout will have been restored by init_ptp_usb */
/* Device has been reset, try again */
if ((ret = ptp_opensession(params, 1)) == PTP_ERROR_IO) {
LIBMTP_ERROR("LIBMTP PANIC: failed to open session on second attempt\n");
@@ -2218,6 +2330,9 @@ LIBMTP_error_number_t configure_usb_device(LIBMTP_raw_device_t *device,
return LIBMTP_ERROR_CONNECTING;
}
+ /* If everything is good, ensure to reset the timeout to the correct value */
+ set_usb_device_timeout(ptp_usb, get_timeout(ptp_usb));
+
/* OK configured properly */
*usbinfo = (void *) ptp_usb;
libusb_free_device_list (devs, 0);