X-Git-Url: https://git.ao2.it/libam7xxx.git/blobdiff_plain/79143dacbe050e95c7d16b14a9237da31f77a99a..8799de17e84b701462fc2299a78f41958fe82b43:/src/am7xxx.c diff --git a/src/am7xxx.c b/src/am7xxx.c index 0daa259..3c7c5b4 100644 --- a/src/am7xxx.c +++ b/src/am7xxx.c @@ -64,37 +64,74 @@ static void log_message(am7xxx_context *ctx, #define debug(ctx, ...) log_message(ctx, AM7XXX_LOG_DEBUG, __func__, 0, __VA_ARGS__) #define trace(ctx, ...) log_message(ctx, AM7XXX_LOG_TRACE, NULL, 0, __VA_ARGS__) +struct am7xxx_ops { + int (*set_power_mode)(am7xxx_device *dev, am7xxx_power_mode power); + int (*set_zoom_mode)(am7xxx_device *dev, am7xxx_zoom_mode zoom); +}; + struct am7xxx_usb_device_descriptor { const char *name; uint16_t vendor_id; uint16_t product_id; + uint8_t configuration; /* The bConfigurationValue of the device */ + uint8_t interface_number; /* The bInterfaceNumber of the device */ + struct am7xxx_ops ops; }; -static struct am7xxx_usb_device_descriptor supported_devices[] = { +static int default_set_power_mode(am7xxx_device *dev, am7xxx_power_mode power); +static int default_set_zoom_mode(am7xxx_device *dev, am7xxx_zoom_mode zoom); + +#define DEFAULT_OPS { \ + .set_power_mode = default_set_power_mode, \ + .set_zoom_mode = default_set_zoom_mode, \ +} + +static const struct am7xxx_usb_device_descriptor supported_devices[] = { { .name = "Acer C110", .vendor_id = 0x1de1, .product_id = 0xc101, + .configuration = 2, + .interface_number = 0, + .ops = DEFAULT_OPS, }, { .name = "Acer C112", .vendor_id = 0x1de1, .product_id = 0x5501, + .configuration = 2, + .interface_number = 0, + .ops = DEFAULT_OPS, }, { .name ="Aiptek PocketCinema T25", .vendor_id = 0x08ca, .product_id = 0x2144, + .configuration = 2, + .interface_number = 0, + .ops = DEFAULT_OPS, }, { .name = "Philips/Sagemcom PicoPix 1020", .vendor_id = 0x21e7, .product_id = 0x000e, + .configuration = 2, + .interface_number = 0, + .ops = DEFAULT_OPS, }, { .name = "Philips/Sagemcom PicoPix 2055", .vendor_id = 0x21e7, .product_id = 0x0016, + .configuration = 2, + .interface_number = 0, + }, + { + .name = "Philips/Sagemcom PicoPix 2330", + .vendor_id = 0x21e7, + .product_id = 0x0019, + .configuration = 1, + .interface_number = 0, }, }; @@ -106,8 +143,12 @@ static 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; + const struct am7xxx_usb_device_descriptor *desc; am7xxx_device *next; }; @@ -239,7 +280,10 @@ static void debug_dump_header(am7xxx_context *ctx, struct am7xxx_header *h) debug(ctx, "BEGIN\n"); debug(ctx, "packet_type: 0x%08x (%u)\n", h->packet_type, h->packet_type); - debug(ctx, "direction: 0x%02hhx (%hhu)\n", h->direction, h->direction); + debug(ctx, "direction: 0x%02hhx (%hhu) (%s)\n", h->direction, h->direction, + h->direction == AM7XXX_DIRECTION_IN ? "IN" : + h->direction == AM7XXX_DIRECTION_OUT ? "OUT" : + "UNKNOWN"); debug(ctx, "header_data_len: 0x%02hhx (%hhu)\n", h->header_data_len, h->header_data_len); debug(ctx, "unknown2: 0x%02hhx (%hhu)\n", h->unknown2, h->unknown2); debug(ctx, "unknown3: 0x%02hhx (%hhu)\n", h->unknown3, h->unknown3); @@ -343,6 +387,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; @@ -383,16 +538,17 @@ static int read_header(am7xxx_device *dev, struct am7xxx_header *h) unserialize_header(dev->buffer, h); - debug_dump_header(dev->ctx, h); - if (h->direction == AM7XXX_DIRECTION_IN) { ret = 0; } else { error(dev->ctx, - "Received a packet with direction AM7XXX_DIRECTION_OUT, weird!\n"); + "Expected an AM7XXX_DIRECTION_IN packet, got one with direction = %d. Weird!\n", + h->direction); ret = -EINVAL; } + debug_dump_header(dev->ctx, h); + out: return ret; } @@ -403,7 +559,13 @@ static int send_header(am7xxx_device *dev, struct am7xxx_header *h) debug_dump_header(dev->ctx, h); + /* For symmetry with read_header() we should check here for + * h->direction == AM7XXX_DIRECTION_OUT but we just ensure that in all + * the callers and save some cycles here. + */ + serialize_header(h, dev->buffer); + ret = send_data(dev, dev->buffer, AM7XXX_HEADER_WIRE_SIZE); if (ret < 0) error(dev->ctx, "failed to send data\n"); @@ -411,6 +573,28 @@ static int send_header(am7xxx_device *dev, struct am7xxx_header *h) return ret; } +static int send_command(am7xxx_device *dev, am7xxx_packet_type type) +{ + struct am7xxx_header h = { + .packet_type = type, + .direction = AM7XXX_DIRECTION_OUT, + .header_data_len = 0x00, + .unknown2 = 0x3e, + .unknown3 = 0x10, + .header_data = { + .data = { + .field0 = 0, + .field1 = 0, + .field2 = 0, + .field3 = 0, + }, + }, + }; + + return send_header(dev, &h); +} + + /* When level == AM7XXX_LOG_FATAL do not check the log_level from the context * and print the message unconditionally, this makes it possible to print * fatal messages even early on initialization, before the context has been @@ -440,7 +624,8 @@ static void log_message(am7xxx_context *ctx, return; } -static am7xxx_device *add_new_device(am7xxx_context *ctx) +static am7xxx_device *add_new_device(am7xxx_context *ctx, + const struct am7xxx_usb_device_descriptor *desc) { am7xxx_device **devices_list; am7xxx_device *new_device; @@ -450,8 +635,6 @@ static am7xxx_device *add_new_device(am7xxx_context *ctx) return NULL; } - devices_list = &(ctx->devices_list); - new_device = malloc(sizeof(*new_device)); if (new_device == NULL) { fatal("cannot allocate a new device (%s)\n", strerror(errno)); @@ -460,6 +643,10 @@ static am7xxx_device *add_new_device(am7xxx_context *ctx) memset(new_device, 0, sizeof(*new_device)); new_device->ctx = ctx; + new_device->desc = desc; + new_device->transfer_completed = 1; + + devices_list = &(ctx->devices_list); if (*devices_list == NULL) { *devices_list = new_device; @@ -515,7 +702,7 @@ typedef enum { static int scan_devices(am7xxx_context *ctx, scan_op op, unsigned int open_device_index, am7xxx_device **dev) { - int num_devices; + ssize_t num_devices; libusb_device** list; unsigned int current_index; int i; @@ -546,15 +733,15 @@ static int scan_devices(am7xxx_context *ctx, scan_op op, continue; for (j = 0; j < ARRAY_SIZE(supported_devices); j++) { - if (desc.idVendor == supported_devices[j].vendor_id - && desc.idProduct == supported_devices[j].product_id) { + if (desc.idVendor == supported_devices[j].vendor_id && + desc.idProduct == supported_devices[j].product_id) { if (op == SCAN_OP_BUILD_DEVLIST) { am7xxx_device *new_device; info(ctx, "am7xxx device found, index: %d, name: %s\n", current_index, supported_devices[j].name); - new_device = add_new_device(ctx); + new_device = add_new_device(ctx, &supported_devices[j]); if (new_device == NULL) { /* XXX, the caller may want * to call am7xxx_shutdown() if @@ -587,8 +774,32 @@ static int scan_devices(am7xxx_context *ctx, scan_op op, goto out; } - libusb_set_configuration((*dev)->usb_device, 2); - libusb_claim_interface((*dev)->usb_device, 0); + /* XXX, the device is now open, if any + * of the calls below fail we need to + * close it again before bailing out. + */ + + ret = libusb_set_configuration((*dev)->usb_device, + (*dev)->desc->configuration); + if (ret < 0) { + debug(ctx, "libusb_set_configuration failed\n"); + debug(ctx, "Cannot set configuration %hhu\n", + (*dev)->desc->configuration); + goto out_libusb_close; + } + + ret = libusb_claim_interface((*dev)->usb_device, + (*dev)->desc->interface_number); + if (ret < 0) { + debug(ctx, "libusb_claim_interface failed\n"); + debug(ctx, "Cannot claim interface %hhu\n", + (*dev)->desc->interface_number); +out_libusb_close: + libusb_close((*dev)->usb_device); + (*dev)->usb_device = NULL; + goto out; + } + goto out; } current_index++; @@ -610,11 +821,111 @@ out: return ret; } +/* Device specific operations */ + +static int default_set_power_mode(am7xxx_device *dev, am7xxx_power_mode power) +{ + int ret; + struct am7xxx_header h = { + .packet_type = AM7XXX_PACKET_TYPE_POWER, + .direction = AM7XXX_DIRECTION_OUT, + .header_data_len = sizeof(struct am7xxx_power_header), + .unknown2 = 0x3e, + .unknown3 = 0x10, + }; + + switch(power) { + case AM7XXX_POWER_OFF: + h.header_data.power.bit2 = 0; + h.header_data.power.bit1 = 0; + h.header_data.power.bit0 = 0; + break; + + case AM7XXX_POWER_LOW: + h.header_data.power.bit2 = 0; + h.header_data.power.bit1 = 0; + h.header_data.power.bit0 = 1; + break; + + case AM7XXX_POWER_MIDDLE: + h.header_data.power.bit2 = 0; + h.header_data.power.bit1 = 1; + h.header_data.power.bit0 = 0; + break; + + case AM7XXX_POWER_HIGH: + h.header_data.power.bit2 = 0; + h.header_data.power.bit1 = 1; + h.header_data.power.bit0 = 1; + break; + + case AM7XXX_POWER_TURBO: + h.header_data.power.bit2 = 1; + h.header_data.power.bit1 = 0; + h.header_data.power.bit0 = 0; + break; + + default: + error(dev->ctx, "Unsupported power mode.\n"); + return -EINVAL; + }; + + ret = send_header(dev, &h); + if (ret < 0) + return ret; + + return 0; +} + +static int default_set_zoom_mode(am7xxx_device *dev, am7xxx_zoom_mode zoom) +{ + int ret; + struct am7xxx_header h = { + .packet_type = AM7XXX_PACKET_TYPE_ZOOM, + .direction = AM7XXX_DIRECTION_OUT, + .header_data_len = sizeof(struct am7xxx_zoom_header), + .unknown2 = 0x3e, + .unknown3 = 0x10, + }; + + switch(zoom) { + case AM7XXX_ZOOM_ORIGINAL: + h.header_data.zoom.bit1 = 0; + h.header_data.zoom.bit0 = 0; + break; + + case AM7XXX_ZOOM_H: + h.header_data.zoom.bit1 = 0; + h.header_data.zoom.bit0 = 1; + break; + + case AM7XXX_ZOOM_H_V: + h.header_data.zoom.bit1 = 1; + h.header_data.zoom.bit0 = 0; + break; + + case AM7XXX_ZOOM_TEST: + h.header_data.zoom.bit1 = 1; + h.header_data.zoom.bit0 = 1; + break; + + default: + error(dev->ctx, "Unsupported zoom mode.\n"); + return -EINVAL; + }; + + ret = send_header(dev, &h); + if (ret < 0) + return ret; + + return 0; +} + /* Public API */ AM7XXX_PUBLIC int am7xxx_init(am7xxx_context **ctx) { - int ret = 0; + int ret; *ctx = malloc(sizeof(**ctx)); if (*ctx == NULL) { @@ -631,7 +942,7 @@ AM7XXX_PUBLIC int am7xxx_init(am7xxx_context **ctx) if (ret < 0) goto out_free_context; - libusb_set_debug((*ctx)->usb_context, 3); + libusb_set_debug((*ctx)->usb_context, LIBUSB_LOG_LEVEL_INFO); ret = scan_devices(*ctx, SCAN_OP_BUILD_DEVLIST , 0, NULL); if (ret < 0) { @@ -664,6 +975,7 @@ AM7XXX_PUBLIC void am7xxx_shutdown(am7xxx_context *ctx) while (current) { am7xxx_device *next = current->next; am7xxx_close_device(current); + free(current->device_info); free(current); current = next; } @@ -691,12 +1003,28 @@ AM7XXX_PUBLIC int am7xxx_open_device(am7xxx_context *ctx, am7xxx_device **dev, ret = scan_devices(ctx, SCAN_OP_OPEN_DEVICE, device_index, dev); if (ret < 0) { errno = ENODEV; + goto out; } else if (ret > 0) { warning(ctx, "device %d already open\n", device_index); errno = EBUSY; ret = -EBUSY; + goto out; + } + + /* Philips/Sagemcom PicoPix projectors require that the DEVINFO packet + * is the first one to be sent to the device in order for it to + * successfully return the correct device information. + * + * So, if there is not a cached version of it (from a previous open), + * we ask for device info at open time, + */ + if ((*dev)->device_info == NULL) { + ret = am7xxx_get_device_info(*dev, NULL); + if (ret < 0) + error(ctx, "cannot get device info\n"); } +out: return ret; } @@ -707,7 +1035,8 @@ AM7XXX_PUBLIC int am7xxx_close_device(am7xxx_device *dev) return -EINVAL; } if (dev->usb_device) { - libusb_release_interface(dev->usb_device, 0); + wait_for_trasfer_completed(dev); + libusb_release_interface(dev->usb_device, dev->desc->interface_number); libusb_close(dev->usb_device); dev->usb_device = NULL; } @@ -718,23 +1047,14 @@ AM7XXX_PUBLIC int am7xxx_get_device_info(am7xxx_device *dev, am7xxx_device_info *device_info) { int ret; - struct am7xxx_header h = { - .packet_type = AM7XXX_PACKET_TYPE_DEVINFO, - .direction = AM7XXX_DIRECTION_OUT, - .header_data_len = 0x00, - .unknown2 = 0x3e, - .unknown3 = 0x10, - .header_data = { - .devinfo = { - .native_width = 0, - .native_height = 0, - .unknown0 = 0, - .unknown1 = 0, - }, - }, - }; + struct am7xxx_header h = { 0 }; - ret = send_header(dev, &h); + if (dev->device_info) { + memcpy(device_info, dev->device_info, sizeof(*device_info)); + return 0; + } + + ret = send_command(dev, AM7XXX_PACKET_TYPE_DEVINFO); if (ret < 0) return ret; @@ -742,12 +1062,27 @@ AM7XXX_PUBLIC int am7xxx_get_device_info(am7xxx_device *dev, if (ret < 0) return ret; - device_info->native_width = h.header_data.devinfo.native_width; - device_info->native_height = h.header_data.devinfo.native_height; + if (h.packet_type != AM7XXX_PACKET_TYPE_DEVINFO) { + error(dev->ctx, "expected packet type: %d, got %d instead!\n", + AM7XXX_PACKET_TYPE_DEVINFO, h.packet_type); + errno = ENOTSUP; + return -ENOTSUP; + } + + dev->device_info = malloc(sizeof(*dev->device_info)); + if (dev->device_info == NULL) { + error(dev->ctx, "cannot allocate a device info (%s)\n", + strerror(errno)); + return -ENOMEM; + } + memset(dev->device_info, 0, sizeof(*dev->device_info)); + + dev->device_info->native_width = h.header_data.devinfo.native_width; + dev->device_info->native_height = h.header_data.devinfo.native_height; #if 0 /* No reason to expose these in the public API until we know what they mean */ - device_info->unknown0 = h.header_data.devinfo.unknown0; - device_info->unknown1 = h.header_data.devinfo.unknown1; + dev->device_info->unknown0 = h.header_data.devinfo.unknown0; + dev->device_info->unknown1 = h.header_data.devinfo.unknown1; #endif return 0; @@ -851,100 +1186,60 @@ AM7XXX_PUBLIC int am7xxx_send_image(am7xxx_device *dev, return send_data(dev, image, image_size); } -AM7XXX_PUBLIC int am7xxx_set_power_mode(am7xxx_device *dev, am7xxx_power_mode power) +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_POWER, + .packet_type = AM7XXX_PACKET_TYPE_IMAGE, .direction = AM7XXX_DIRECTION_OUT, - .header_data_len = sizeof(struct am7xxx_power_header), + .header_data_len = sizeof(struct am7xxx_image_header), .unknown2 = 0x3e, .unknown3 = 0x10, - }; - - switch(power) { - case AM7XXX_POWER_OFF: - h.header_data.power.bit2 = 0; - h.header_data.power.bit1 = 0; - h.header_data.power.bit0 = 0; - break; - - case AM7XXX_POWER_LOW: - h.header_data.power.bit2 = 0; - h.header_data.power.bit1 = 0; - h.header_data.power.bit0 = 1; - break; - - case AM7XXX_POWER_MIDDLE: - h.header_data.power.bit2 = 0; - h.header_data.power.bit1 = 1; - h.header_data.power.bit0 = 0; - break; - - case AM7XXX_POWER_HIGH: - h.header_data.power.bit2 = 0; - h.header_data.power.bit1 = 1; - h.header_data.power.bit0 = 1; - break; - - case AM7XXX_POWER_TURBO: - h.header_data.power.bit2 = 1; - h.header_data.power.bit1 = 0; - h.header_data.power.bit0 = 0; - break; - - default: - error(dev->ctx, "Unsupported power mode.\n"); - return -EINVAL; + .header_data = { + .image = { + .format = format, + .width = width, + .height = height, + .image_size = image_size, + }, + }, }; ret = send_header(dev, &h); if (ret < 0) return ret; - return 0; + 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_zoom_mode(am7xxx_device *dev, am7xxx_zoom_mode zoom) +AM7XXX_PUBLIC int am7xxx_set_power_mode(am7xxx_device *dev, am7xxx_power_mode power) { - int ret; - struct am7xxx_header h = { - .packet_type = AM7XXX_PACKET_TYPE_ZOOM, - .direction = AM7XXX_DIRECTION_OUT, - .header_data_len = sizeof(struct am7xxx_zoom_header), - .unknown2 = 0x3e, - .unknown3 = 0x10, - }; - - switch(zoom) { - case AM7XXX_ZOOM_ORIGINAL: - h.header_data.zoom.bit1 = 0; - h.header_data.zoom.bit0 = 0; - break; - - case AM7XXX_ZOOM_H: - h.header_data.zoom.bit1 = 0; - h.header_data.zoom.bit0 = 1; - break; - - case AM7XXX_ZOOM_H_V: - h.header_data.zoom.bit1 = 1; - h.header_data.zoom.bit0 = 0; - break; - - case AM7XXX_ZOOM_TEST: - h.header_data.zoom.bit1 = 1; - h.header_data.zoom.bit0 = 1; - break; + if (dev->desc->ops.set_power_mode == NULL) { + warning(dev->ctx, + "setting power mode is unsupported on this device\n"); + return 0; + } - default: - error(dev->ctx, "Unsupported zoom mode.\n"); - return -EINVAL; - }; + return dev->desc->ops.set_power_mode(dev, power); +} - ret = send_header(dev, &h); - if (ret < 0) - return ret; +AM7XXX_PUBLIC int am7xxx_set_zoom_mode(am7xxx_device *dev, am7xxx_zoom_mode zoom) +{ + if (dev->desc->ops.set_zoom_mode == NULL) { + warning(dev->ctx, + "setting zoom mode is unsupported on this device\n"); + return 0; + } - return 0; + return dev->desc->ops.set_zoom_mode(dev, zoom); }