/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id$ * * Copyright (C) 2021 Aidan MacDonald * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * ****************************************************************************/ #include "jztool_private.h" #include #include #include #define VR_GET_CPU_INFO 0 #define VR_SET_DATA_ADDRESS 1 #define VR_SET_DATA_LENGTH 2 #define VR_FLUSH_CACHES 3 #define VR_PROGRAM_START1 4 #define VR_PROGRAM_START2 5 /** \brief Open a USB device * \param jz Context * \param devptr Returns pointer to the USB device upon success * \param vend_id USB vendor ID * \param prod_id USB product ID * \return either JZ_SUCCESS if device was opened, or an error below * \retval JZ_ERR_OUT_OF_MEMORY malloc failed * \retval JZ_ERR_USB libusb error (details are logged) * \retval JZ_ERR_NO_DEVICE can't unambiguously find the device */ int jz_usb_open(jz_context* jz, jz_usbdev** devptr, uint16_t vend_id, uint16_t prod_id) { int rc; jz_usbdev* dev = NULL; libusb_device_handle* usb_handle = NULL; libusb_device** dev_list = NULL; ssize_t dev_index = -1, dev_count; rc = jz_context_ref_libusb(jz); if(rc < 0) return rc; dev = malloc(sizeof(struct jz_usbdev)); if(!dev) { rc = JZ_ERR_OUT_OF_MEMORY; goto error; } dev_count = libusb_get_device_list(jz->usb_ctx, &dev_list); if(dev_count < 0) { jz_log(jz, JZ_LOG_ERROR, "libusb_get_device_list: %s", libusb_strerror(dev_count)); rc = JZ_ERR_USB; goto error; } for(ssize_t i = 0; i < dev_count; ++i) { struct libusb_device_descriptor desc; rc = libusb_get_device_descriptor(dev_list[i], &desc); if(rc < 0) { jz_log(jz, JZ_LOG_WARNING, "libusb_get_device_descriptor: %s", libusb_strerror(rc)); continue; } if(desc.idVendor != vend_id || desc.idProduct != prod_id) continue; if(dev_index >= 0) { /* not the best, but it is the safest thing */ jz_log(jz, JZ_LOG_ERROR, "Multiple devices match ID %04x:%04x", (unsigned int)vend_id, (unsigned int)prod_id); jz_log(jz, JZ_LOG_ERROR, "Please ensure only one player is plugged in, and try again"); rc = JZ_ERR_NO_DEVICE; goto error; } dev_index = i; } if(dev_index < 0) { jz_log(jz, JZ_LOG_ERROR, "No device with ID %04x:%04x found", (unsigned int)vend_id, (unsigned int)prod_id); rc = JZ_ERR_NO_DEVICE; goto error; } rc = libusb_open(dev_list[dev_index], &usb_handle); if(rc < 0) { jz_log(jz, JZ_LOG_ERROR, "libusb_open: %s", libusb_strerror(rc)); rc = JZ_ERR_USB; goto error; } rc = libusb_claim_interface(usb_handle, 0); if(rc < 0) { jz_log(jz, JZ_LOG_ERROR, "libusb_claim_interface: %s", libusb_strerror(rc)); rc = JZ_ERR_USB; goto error; } jz_log(jz, JZ_LOG_DEBUG, "Opened device (%p, ID %04x:%04x)", dev, (unsigned int)vend_id, (unsigned int)prod_id); dev->jz = jz; dev->handle = usb_handle; *devptr = dev; rc = JZ_SUCCESS; exit: if(dev_list) libusb_free_device_list(dev_list, true); return rc; error: if(dev) free(dev); if(usb_handle) libusb_close(usb_handle); jz_context_unref_libusb(jz); goto exit; } /** \brief Close a USB device * \param dev Device to close; memory will be freed automatically */ void jz_usb_close(jz_usbdev* dev) { jz_log(dev->jz, JZ_LOG_DEBUG, "Closing device (%p)", dev); libusb_release_interface(dev->handle, 0); libusb_close(dev->handle); jz_context_unref_libusb(dev->jz); free(dev); } // Does an Ingenic-specific vendor request // Written with X1000 in mind but other Ingenic CPUs have the same commands static int jz_usb_vendor_req(jz_usbdev* dev, int req, uint32_t arg, void* buffer, int buflen) { int rc = libusb_control_transfer(dev->handle, LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, req, arg >> 16, arg & 0xffff, buffer, buflen, 1000); if(rc < 0) { jz_log(dev->jz, JZ_LOG_ERROR, "libusb_control_transfer: %s", libusb_strerror(rc)); rc = JZ_ERR_USB; } else { static const char* req_names[] = { "GET_CPU_INFO", "SET_DATA_ADDRESS", "SET_DATA_LENGTH", "FLUSH_CACHES", "PROGRAM_START1", "PROGRAM_START2", }; jz_log(dev->jz, JZ_LOG_DEBUG, "Issued %s %08lu", req_names[req], (unsigned long)arg); rc = JZ_SUCCESS; } return rc; } // Bulk transfer wrapper static int jz_usb_transfer(jz_usbdev* dev, bool write, size_t len, void* buf) { int xfered = 0; int ep = write ? LIBUSB_ENDPOINT_OUT|1 : LIBUSB_ENDPOINT_IN|1; int rc = libusb_bulk_transfer(dev->handle, ep, buf, len, &xfered, 10000); if(rc < 0) { jz_log(dev->jz, JZ_LOG_ERROR, "libusb_bulk_transfer: %s", libusb_strerror(rc)); rc = JZ_ERR_USB; } else if(xfered != (int)len) { jz_log(dev->jz, JZ_LOG_ERROR, "libusb_bulk_transfer: incorrect amount of data transfered"); rc = JZ_ERR_USB; } else { jz_log(dev->jz, JZ_LOG_DEBUG, "Transferred %zu bytes %s", len, write ? "to device" : "from device"); rc = JZ_SUCCESS; } return rc; } // Memory send/receive primitive, performs the necessary vendor requests // and then tranfers data using the bulk endpoint static int jz_usb_sendrecv(jz_usbdev* dev, bool write, uint32_t addr, size_t len, void* data) { int rc; rc = jz_usb_vendor_req(dev, VR_SET_DATA_ADDRESS, addr, NULL, 0); if(rc < 0) return rc; rc = jz_usb_vendor_req(dev, VR_SET_DATA_LENGTH, len, NULL, 0); if(rc < 0) return rc; return jz_usb_transfer(dev, write, len, data); } /** \brief Write data to device memory * \param dev USB device * \param addr Address where data should be written * \param len Length of the data, in bytes, should be positive * \param data Data buffer * \return either JZ_SUCCESS on success or a failure code */ int jz_usb_send(jz_usbdev* dev, uint32_t addr, size_t len, const void* data) { return jz_usb_sendrecv(dev, true, addr, len, (void*)data); } /** \brief Read data to device memory * \param dev USB device * \param addr Address to read from * \param len Length of the data, in bytes, should be positive * \param data Data buffer * \return either JZ_SUCCESS on success or a failure code */ int jz_usb_recv(jz_usbdev* dev, uint32_t addr, size_t len, void* data) { return jz_usb_sendrecv(dev, false, addr, len, data); } /** \brief Execute stage1 program jumping to the specified address * \param dev USB device * \param addr Address to begin execution at * \return either JZ_SUCCESS on success or a failure code */ int jz_usb_start1(jz_usbdev* dev, uint32_t addr) { return jz_usb_vendor_req(dev, VR_PROGRAM_START1, addr, NULL, 0); } /** \brief Execute stage2 program jumping to the specified address * \param dev USB device * \param addr Address to begin execution at * \return either JZ_SUCCESS on success or a failure code */ int jz_usb_start2(jz_usbdev* dev, uint32_t addr) { return jz_usb_vendor_req(dev, VR_PROGRAM_START2, addr, NULL, 0); } /** \brief Ask device to flush CPU caches * \param dev USB device * \return either JZ_SUCCESS on success or a failure code */ int jz_usb_flush_caches(jz_usbdev* dev) { return jz_usb_vendor_req(dev, VR_FLUSH_CACHES, 0, NULL, 0); } /** \brief Ask device for CPU info string * \param dev USB device * \param buffer Buffer to hold the info string * \param buflen Size of the buffer, in bytes * \return either JZ_SUCCESS on success or a failure code * * The buffer will always be null terminated, but to ensure the info string is * not truncated the buffer needs to be at least `JZ_CPUINFO_BUFLEN` byes long. */ int jz_usb_get_cpu_info(jz_usbdev* dev, char* buffer, size_t buflen) { char tmpbuf[JZ_CPUINFO_BUFLEN]; int rc = jz_usb_vendor_req(dev, VR_GET_CPU_INFO, 0, tmpbuf, 8); if(rc != JZ_SUCCESS) return rc; if(buflen > 0) { strncpy(buffer, tmpbuf, buflen); buffer[buflen - 1] = 0; } return JZ_SUCCESS; }