forked from len0rd/rockbox
rbutil uses several components from the utils folder, and can be considered part of utils too. Having it in a separate folder is an arbitrary split that doesn't help anymore these days, so merge them. This also allows other utils to easily use libtools.make without the need to navigate to a different folder. Change-Id: I3fc2f4de19e3e776553efb5dea5f779dfec0dc21
291 lines
9.1 KiB
C
291 lines
9.1 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* 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 <stdlib.h>
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
|
|
#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;
|
|
}
|