1
0
Fork 0
forked from len0rd/rockbox
foxbox/utils/jztool/src/usb.c
Dominik Riebeling c876d3bbef rbutil: Merge rbutil with utils folder.
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
2021-12-24 18:05:53 +01:00

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;
}