From c876d3bbefe0dc00c27ca0c12d29da5874946962 Mon Sep 17 00:00:00 2001 From: Dominik Riebeling Date: Wed, 15 Dec 2021 21:04:28 +0100 Subject: 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 --- utils/jztool/src/usb.c | 291 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 291 insertions(+) create mode 100644 utils/jztool/src/usb.c (limited to 'utils/jztool/src/usb.c') diff --git a/utils/jztool/src/usb.c b/utils/jztool/src/usb.c new file mode 100644 index 0000000000..cfc3ba60cb --- /dev/null +++ b/utils/jztool/src/usb.c @@ -0,0 +1,291 @@ +/*************************************************************************** + * __________ __ ___. + * 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; +} -- cgit v1.2.3