From 860428c1b8ee26b327ec3c9fb8943fd17a49d755 Mon Sep 17 00:00:00 2001
From: Antonio Ospite <ospite@studenti.unina.it>
Date: Sun, 21 Jul 2013 00:10:28 +0200
Subject: [PATCH] am7xxx: implement am7xxx_send_image_async()

Implement am7xxx_send_image_async() the non-blocking version of
am7xxx_send_image().

This way user programs can more easily overlap encoding and
communication and achieve higher framerates.
---
 src/am7xxx.c | 151 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/am7xxx.h |  26 ++++++++++
 2 files changed, 177 insertions(+)

diff --git a/src/am7xxx.c b/src/am7xxx.c
index f37ad70..3e6b461 100644
--- a/src/am7xxx.c
+++ b/src/am7xxx.c
@@ -130,6 +130,8 @@ static const struct am7xxx_usb_device_descriptor supported_devices[] = {
 
 struct _am7xxx_device {
 	libusb_device_handle *usb_device;
+	struct libusb_transfer *transfer;
+	int transfer_completed;
 	uint8_t buffer[AM7XXX_HEADER_WIRE_SIZE];
 	am7xxx_device_info *device_info;
 	am7xxx_context *ctx;
@@ -372,6 +374,117 @@ static int send_data(am7xxx_device *dev, uint8_t *buffer, unsigned int len)
 	return 0;
 }
 
+static void send_data_async_complete_cb(struct libusb_transfer *transfer)
+{
+	am7xxx_device *dev = (am7xxx_device *)(transfer->user_data);
+	int *completed = &(dev->transfer_completed);
+	int transferred = transfer->actual_length;
+	int ret;
+
+	if (transferred != transfer->length) {
+		error(dev->ctx, "transferred: %d (expected %u)\n",
+		      transferred, transfer->length);
+	}
+
+	switch (transfer->status) {
+	case LIBUSB_TRANSFER_COMPLETED:
+		ret = 0;
+		break;
+	case LIBUSB_TRANSFER_TIMED_OUT:
+		ret = LIBUSB_ERROR_TIMEOUT;
+		break;
+	case LIBUSB_TRANSFER_STALL:
+		ret = LIBUSB_ERROR_PIPE;
+		break;
+	case LIBUSB_TRANSFER_OVERFLOW:
+		ret = LIBUSB_ERROR_OVERFLOW;
+		break;
+	case LIBUSB_TRANSFER_NO_DEVICE:
+		ret = LIBUSB_ERROR_NO_DEVICE;
+		break;
+	case LIBUSB_TRANSFER_ERROR:
+	case LIBUSB_TRANSFER_CANCELLED:
+		ret = LIBUSB_ERROR_IO;
+		break;
+	default:
+		error(dev->ctx, "unrecognised status code %d", transfer->status);
+		ret = LIBUSB_ERROR_OTHER;
+	}
+
+	if (ret < 0)
+		error(dev->ctx, "libusb transfer failed: %s",
+		      libusb_error_name(ret));
+
+	libusb_free_transfer(transfer);
+	transfer = NULL;
+
+	*completed = 1;
+}
+
+static inline void wait_for_trasfer_completed(am7xxx_device *dev)
+{
+	while (!dev->transfer_completed) {
+		int ret = libusb_handle_events_completed(dev->ctx->usb_context,
+							 &(dev->transfer_completed));
+		if (ret < 0) {
+			if (ret == LIBUSB_ERROR_INTERRUPTED)
+				continue;
+			error(dev->ctx, "libusb_handle_events failed: %s, cancelling transfer and retrying",
+			      libusb_error_name(ret));
+			libusb_cancel_transfer(dev->transfer);
+			continue;
+		}
+	}
+}
+
+static int send_data_async(am7xxx_device *dev, uint8_t *buffer, unsigned int len)
+{
+	int ret;
+	uint8_t *transfer_buffer;
+
+	dev->transfer = libusb_alloc_transfer(0);
+	if (dev->transfer == NULL) {
+		error(dev->ctx, "cannot allocate transfer (%s)\n",
+		      strerror(errno));
+		return -ENOMEM;
+	}
+
+	/* Make a copy of the buffer so the caller can safely reuse it just
+	 * after libusb_submit_transfer() has returned. This technique
+	 * requires more allocations than a proper double-buffering approach
+	 * but it takes a lot less code. */
+	transfer_buffer = malloc(len);
+	if (transfer_buffer == NULL) {
+		error(dev->ctx, "cannot allocate transfer buffer (%s)\n",
+		      strerror(errno));
+		ret = -ENOMEM;
+		goto err;
+	}
+	memcpy(transfer_buffer, buffer, len);
+
+	dev->transfer->flags |= LIBUSB_TRANSFER_FREE_BUFFER;
+	libusb_fill_bulk_transfer(dev->transfer, dev->usb_device, 0x1,
+				  transfer_buffer, len,
+				  send_data_async_complete_cb, dev, 0);
+
+	/* wait for the previous transfer to complete */
+	wait_for_trasfer_completed(dev);
+
+	trace_dump_buffer(dev->ctx, "sending -->", buffer, len);
+
+	dev->transfer_completed = 0;
+	ret = libusb_submit_transfer(dev->transfer);
+	if (ret < 0)
+		goto err;
+
+	return 0;
+
+err:
+	libusb_free_transfer(dev->transfer);
+	dev->transfer = NULL;
+	return ret;
+}
+
 static void serialize_header(struct am7xxx_header *h, uint8_t *buffer)
 {
 	uint8_t **buffer_iterator = &buffer;
@@ -496,6 +609,7 @@ static am7xxx_device *add_new_device(am7xxx_context *ctx,
 
 	new_device->ctx = ctx;
 	new_device->desc = desc;
+	new_device->transfer_completed = 1;
 
 	devices_list = &(ctx->devices_list);
 
@@ -786,6 +900,7 @@ AM7XXX_PUBLIC int am7xxx_close_device(am7xxx_device *dev)
 		return -EINVAL;
 	}
 	if (dev->usb_device) {
+		wait_for_trasfer_completed(dev);
 		libusb_release_interface(dev->usb_device, dev->desc->interface_number);
 		libusb_close(dev->usb_device);
 		dev->usb_device = NULL;
@@ -950,6 +1065,42 @@ AM7XXX_PUBLIC int am7xxx_send_image(am7xxx_device *dev,
 	return send_data(dev, image, image_size);
 }
 
+AM7XXX_PUBLIC int am7xxx_send_image_async(am7xxx_device *dev,
+					  am7xxx_image_format format,
+					  unsigned int width,
+					  unsigned int height,
+					  uint8_t *image,
+					  unsigned int image_size)
+{
+	int ret;
+	struct am7xxx_header h = {
+		.packet_type     = AM7XXX_PACKET_TYPE_IMAGE,
+		.direction       = AM7XXX_DIRECTION_OUT,
+		.header_data_len = sizeof(struct am7xxx_image_header),
+		.unknown2        = 0x3e,
+		.unknown3        = 0x10,
+		.header_data = {
+			.image = {
+				.format     = format,
+				.width      = width,
+				.height     = height,
+				.image_size = image_size,
+			},
+		},
+	};
+
+	ret = send_header(dev, &h);
+	if (ret < 0)
+		return ret;
+
+	if (image == NULL || image_size == 0) {
+		warning(dev->ctx, "Not sending any data, check the 'image' or 'image_size' parameters\n");
+		return 0;
+	}
+
+	return send_data_async(dev, image, image_size);
+}
+
 AM7XXX_PUBLIC int am7xxx_set_power_mode(am7xxx_device *dev, am7xxx_power_mode power)
 {
 	int ret;
diff --git a/src/am7xxx.h b/src/am7xxx.h
index 1c353ab..ec9869d 100644
--- a/src/am7xxx.h
+++ b/src/am7xxx.h
@@ -242,6 +242,32 @@ int am7xxx_send_image(am7xxx_device *dev,
 		      unsigned int image_size);
 
 /**
+ * Queue transfer of an image for display on an am7xxx device and return immediately.
+ *
+ * This is the function that actually makes the device display something.
+ * Static pictures can be sent just once and the device will keep showing them
+ * until another image get sent or some command resets or turns off the display.
+ * 
+ * @note This _async() variant makes a copy of the image buffer, so the caller
+ * is free to reuse the buffer just after the function returns.
+ *
+ * @param[in] dev A pointer to the structure representing the device to get info of
+ * @param[in] format The format the image is in (see @link am7xxx_image_format @endlink enum)
+ * @param[in] width The width of the image
+ * @param[in] height The height of the image
+ * @param[in] image A buffer holding data in the format specified by the format parameter
+ * @param[in] image_size The size in bytes of the image buffer
+ *
+ * @return 0 on success, a negative value on error
+ */
+int am7xxx_send_image_async(am7xxx_device *dev,
+			    am7xxx_image_format format,
+			    unsigned int width,
+			    unsigned int height,
+			    unsigned char *image,
+			    unsigned int image_size);
+
+/**
  * Set the power mode of an am7xxx device.
  *
  * @note When setting the mode to AM7XXX_POWER_OFF the display can't be turned
-- 
2.1.4