/** * @file o3000_upgrade.c * @brief O-3000 firmware upgrade * @author Patrick Roth - roth@stettbacher.ch * @author Patrick Brunner - brunner@stettbacher.ch * @author Sophia Papagiannaki - papagiannaki@stettbacher.ch * @version 0.1 * @date 2020-05-12 * @copyright 2020 Stettbacher Signal Processing AG * * @remarks * *
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
* 
* */ #include #include #include #include #include #include #include "o3000_upgrade.h" #include "md5_helper.h" #include "o3000.h" #define BUF_LEN 256 #define TRANSFER_SIZE 512 #define MAX_RETRIES 5 #define BL_OUT_EP (2) #define XML_IN_EP (2 | LIBUSB_ENDPOINT_IN) #define XML_OUT_EP (1) // table of valid bootloader ids const char* bl_ids[] = { "Streaming Camera Bootloader in HS mode", // version without proper product id "Camera O-30xx (HS mode)", // version proper product id }; // table of valid application ids const char* app_ids[] = { "Streaming Camera in HS mode", // version without proper product id (LEGACY) "Camera O-3010 (HS mode)", // rolling shutter, mono version (LEGACY) "Camera O-3020 (HS mode)", // rolling shutter, color version (LEGACY) "Camera O-3010", // rolling shutter, mono version (without speed mode) "Camera O-3020", // rolling shutter, color version (without speed mode) "Camera O-3110", // global shutter, mono version "Camera O-3120", // global shutter, color version }; static struct cam_usb_t { struct libusb_context *ctx; // libusb context struct libusb_device_handle *dev_handle; // the USB device handle struct libusb_device *dev; // the USB device struct libusb_device_descriptor desc; uint32_t transfer_size; struct libusb_transfer *transfer_data_out; ///< libusb specific transfer structure struct libusb_transfer *transfer_xml_out; ///< libusb specific transfer structure struct libusb_transfer *transfer_xml_in; ///< libusb specific transfer structure int data_buf_size; ///< image data buffer size in bytes } cam_usb; enum cam_mode_t { CM_UNKNOWN = 0, CM_BOOTLOADER, CM_APPLICATION, }; volatile static bool xml_out_xfer_done; volatile static bool data_out_xfer_done; /** * @brief Transfer done callback for XML out * * Note: Transfer done only means submitted, not actually completed! * @param transfer Pointer to XML transfer which has been submitted */ static void LIBUSB_CALL cb_xml_out_xfer_done(struct libusb_transfer *transfer) { if (transfer->status != LIBUSB_TRANSFER_COMPLETED) { printf("Error: %s: XML out transfer failed with status %d.\n",__func__, transfer->status); } xml_out_xfer_done = true; } /** * @brief Transfer done callback for data out * * Note: Transfer done only means submitted, not actually completed! * @param transfer Pointer to XML transfer which has been submitted */ static void LIBUSB_CALL cb_data_out_xfer_done(struct libusb_transfer *transfer) { if (transfer->status != LIBUSB_TRANSFER_COMPLETED) { printf("Error: %s: Data out transfer failed with status %d.\n",__func__, transfer->status); } data_out_xfer_done = true; } /** * Wait until XML transfer has finished. */ static void wait_for_xml_xfer_done(void) { while(!xml_out_xfer_done) { libusb_handle_events(cam_usb.ctx); }; } /** * Wait until data transfer has finished. */ static void wait_for_data_xfer_done(void) { while(!data_out_xfer_done) { libusb_handle_events(cam_usb.ctx); }; } /** * @brief Send XML packet. * * Note: this function accesses the global structure cam_usb! * @param msg Message content * @param msg_len Message content length * @retval 0: success, otherwise: error */ static int send_xml(char *msg, int msg_len) { int retval; cam_usb.transfer_xml_out = libusb_alloc_transfer(0); if (!cam_usb.transfer_xml_out) return -1; // throw away transfer after use cam_usb.transfer_xml_out->flags = LIBUSB_TRANSFER_FREE_TRANSFER; libusb_fill_bulk_transfer(cam_usb.transfer_xml_out, cam_usb.dev_handle, XML_OUT_EP, (unsigned char*)msg, msg_len, cb_xml_out_xfer_done, (unsigned char*)msg, 0); retval = libusb_submit_transfer(cam_usb.transfer_xml_out); if(retval) { printf("Error: %s: Failed to send XML packet.\n", __func__); return -1; } return 0; } /** * @brief Send binary packet. * * Note: this function accesses the global structure cam_usb! * @param data_buf data buffer * @param data_len data length * @retval 0: success, otherwise: error */ static int send_data(unsigned char *data_buf, int data_len) { int retval; cam_usb.transfer_data_out = libusb_alloc_transfer(0); if (!cam_usb.transfer_data_out) return -1; // throw away transfer after use cam_usb.transfer_data_out->flags = LIBUSB_TRANSFER_FREE_TRANSFER; libusb_fill_bulk_transfer(cam_usb.transfer_data_out, cam_usb.dev_handle, BL_OUT_EP, (unsigned char*)data_buf, data_len, cb_data_out_xfer_done, (unsigned char*)data_buf, 0); retval = libusb_submit_transfer(cam_usb.transfer_data_out); if(retval) { printf("Error: %s: Failed to send binary data packet.\n", __func__); return -1; } return 0; } /** * * Open libusb. * * Note: this function accesses the global structure cam_usb! * @retval Camera mode (see @ref cam_mode_t) * */ static enum cam_mode_t usb_open(void) { int r,i; unsigned char buf[BUF_LEN]; enum cam_mode_t mode = CM_UNKNOWN; int retries = MAX_RETRIES; int num_bl_ids; int num_app_ids; if (libusb_init(&cam_usb.ctx) < 0) { printf("Error: %s: Failed to initialise libusb.\n", __func__); return mode; } // setting up USB logging. That's tricky, as LOG_LEVEL_INFO prints (almost) no log messages, // whereas the next more verbose level LOG_LEVEL_DEBUG will flood the terminal libusb_set_debug(cam_usb.ctx, LIBUSB_LOG_LEVEL_INFO); // If the camera has just performed a reset, it might not be ready yet. // We try MAX_RETRIES times, before we give up. while(retries > 0) { if((cam_usb.dev_handle = libusb_open_device_with_vid_pid(cam_usb.ctx, O3000_VID, O3000_PID)) == NULL) { printf("retry: %d\n",retries); retries--; sleep(1); } else { break; } } if(retries <= 0) { printf("Error: %s: Could not find/open device after %d retries\nAre you root?\n", __func__, MAX_RETRIES); libusb_exit(cam_usb.ctx); return mode; } if((r = libusb_claim_interface(cam_usb.dev_handle, 0)) < 0) { printf("Error: %s: usb_claim_interface error %d.\n", __func__, r); libusb_close(cam_usb.dev_handle); libusb_exit(cam_usb.ctx); return mode; } cam_usb.dev = libusb_get_device(cam_usb.dev_handle); r = libusb_get_device_descriptor (cam_usb.dev, &cam_usb.desc); if(r) { printf("Error: %s: libusb_get_device_descriptor error %d.\n", __func__, r); } // determine camera mode r = libusb_get_string_descriptor_ascii(cam_usb.dev_handle, cam_usb.desc.iProduct, buf, BUF_LEN); if(r > 0) { // get number of app/bootloader ids num_app_ids = sizeof(app_ids)/sizeof(app_ids[0]); num_bl_ids = sizeof(bl_ids)/sizeof(bl_ids[0]); // look for bootloader ids for(i=0; i" \ " %s " \ "", "application"); xml_out_xfer_done = false; send_xml(msg, strlen(msg)); wait_for_xml_xfer_done(); usb_close(); cam_mode = usb_open(); return cam_mode; } /** * Create and send the firmware update package information. * * @param data pointer to firmware update data * @param data_len firmware update data lenght in bytes */ static void create_and_send_update_info(unsigned char *data, int data_len) { char msg[BUF_LEN]; char md5[33]; // compute MD5 digest getMd5ChecksumString(data, data_len, md5); snprintf(msg, BUF_LEN, "" \ "" \ "
%s
" \ "%d" \ "%s" \ "
" \ "
", "application", data_len, md5); xml_out_xfer_done = false; send_xml(msg, strlen(msg)); wait_for_xml_xfer_done(); } /** * Send the firmware update package data, close and * open again usb connection. * * @param data pointer to firmware update data * @param data_len firmware update data lenght in bytes * @return camera mode (see @ref cam_mode_t) */ static enum cam_mode_t send_update_data(unsigned char *data, int data_len) { int num_chunks; int last_chunk_size; enum cam_mode_t cam_mode = CM_UNKNOWN; // calculate number of chunks to be sent and size of last chunk num_chunks = data_len / TRANSFER_SIZE; last_chunk_size = data_len - (num_chunks * TRANSFER_SIZE); // send complete chunks for(int i = 0; i < num_chunks; i++) { data_out_xfer_done = false; send_data(data + i*TRANSFER_SIZE, TRANSFER_SIZE); wait_for_data_xfer_done(); } // send incomplete chunk (if available) if(last_chunk_size > 0) { data_out_xfer_done = false; send_data(data + num_chunks*TRANSFER_SIZE, last_chunk_size); wait_for_data_xfer_done(); } usb_close(); cam_mode = usb_open(); return cam_mode; } /** * Try MAX_RETRIES to open camera and check if * it started in bootloader mode. * * @return camera mode (see @ref cam_mode_t) */ static enum cam_mode_t wait_for_bootloader_mode() { int i; enum cam_mode_t cam_mode = CM_UNKNOWN; for (i = 0; i < MAX_RETRIES; i++){ usb_close(); cam_mode = usb_open(); if(cam_mode != CM_BOOTLOADER) { continue; // try again } break; // started in bootloader mode so exit loop and return } return cam_mode; } /** * Do firmware upgrade of the device. * * @param data pointer to firmware update data binary * @param data_len firmware update data lenght in bytes * @return 0 on success, error code on failure */ int firmware_upgrade(unsigned char *data, int data_len) { enum cam_mode_t cam_mode = CM_UNKNOWN; cam_mode = usb_open(); if(cam_mode == CM_UNKNOWN) { printf("Error: %s: Failed to initialise usb.\n", __func__); return O3000_ERROR_USB_INIT_FAILURE; } /** * If camera started in application mode, * it has to be forced into upgrade mode. */ if (cam_mode == CM_APPLICATION) { cam_mode = force_into_upgrade_mode(); } /** * Now camera should start in bootloader mode. * If not, try MAX_RETRIES times. If still * not in bootloader mode, return error code. */ if (cam_mode != CM_BOOTLOADER) { cam_mode = wait_for_bootloader_mode(); if (cam_mode != CM_BOOTLOADER) { printf("Error: Timeout! Camera won't start in bootloader mode\n"); usb_close(); return O3000_ERROR_BOOTLOADER_TIMEOUT; } } /** * Camera is finally in bootloader mode, ready * for the upgrade. So, send xml package first * and then binary data. */ create_and_send_update_info(data, data_len); cam_mode = send_update_data(data, data_len); usb_close(); if(cam_mode == CM_APPLICATION) { printf("Camera upgrade successful.\n"); } else if (cam_mode == CM_BOOTLOADER) { printf("Error: %s: Camera is still in bootloader mode.\n", __func__); return O3000_ERROR_OTHER; } else if (cam_mode == CM_UNKNOWN) { printf("Error: %s: Failed to initialise usb.\n", __func__); return O3000_ERROR_USB_INIT_FAILURE; } return 0; }