A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 291 lines 9.4 kB view raw
1/*************************************************************************** 2 * __________ __ ___. 3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 * \/ \/ \/ \/ \/ 8 * $Id$ 9 * 10 * Copyright (C) 2021 Aidan MacDonald 11 * 12 * This program is free software; you can redistribute it and/or 13 * modify it under the terms of the GNU General Public License 14 * as published by the Free Software Foundation; either version 2 15 * of the License, or (at your option) any later version. 16 * 17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 18 * KIND, either express or implied. 19 * 20 ****************************************************************************/ 21 22#include "jztool_private.h" 23#include <stdlib.h> 24#include <stdbool.h> 25#include <string.h> 26 27#define VR_GET_CPU_INFO 0 28#define VR_SET_DATA_ADDRESS 1 29#define VR_SET_DATA_LENGTH 2 30#define VR_FLUSH_CACHES 3 31#define VR_PROGRAM_START1 4 32#define VR_PROGRAM_START2 5 33 34/** \brief Open a USB device 35 * \param jz Context 36 * \param devptr Returns pointer to the USB device upon success 37 * \param vend_id USB vendor ID 38 * \param prod_id USB product ID 39 * \return either JZ_SUCCESS if device was opened, or an error below 40 * \retval JZ_ERR_OUT_OF_MEMORY malloc failed 41 * \retval JZ_ERR_USB libusb error (details are logged) 42 * \retval JZ_ERR_NO_DEVICE can't unambiguously find the device 43 */ 44int jz_usb_open(jz_context* jz, jz_usbdev** devptr, uint16_t vend_id, uint16_t prod_id) 45{ 46 int rc; 47 jz_usbdev* dev = NULL; 48 libusb_device_handle* usb_handle = NULL; 49 libusb_device** dev_list = NULL; 50 ssize_t dev_index = -1, dev_count; 51 52 rc = jz_context_ref_libusb(jz); 53 if(rc < 0) 54 return rc; 55 56 dev = malloc(sizeof(struct jz_usbdev)); 57 if(!dev) { 58 rc = JZ_ERR_OUT_OF_MEMORY; 59 goto error; 60 } 61 62 dev_count = libusb_get_device_list(jz->usb_ctx, &dev_list); 63 if(dev_count < 0) { 64 jz_log(jz, JZ_LOG_ERROR, "libusb_get_device_list: %s", libusb_strerror(dev_count)); 65 rc = JZ_ERR_USB; 66 goto error; 67 } 68 69 for(ssize_t i = 0; i < dev_count; ++i) { 70 struct libusb_device_descriptor desc; 71 rc = libusb_get_device_descriptor(dev_list[i], &desc); 72 if(rc < 0) { 73 jz_log(jz, JZ_LOG_WARNING, "libusb_get_device_descriptor: %s", 74 libusb_strerror(rc)); 75 continue; 76 } 77 78 if(desc.idVendor != vend_id || desc.idProduct != prod_id) 79 continue; 80 81 if(dev_index >= 0) { 82 /* not the best, but it is the safest thing */ 83 jz_log(jz, JZ_LOG_ERROR, "Multiple devices match ID %04x:%04x", 84 (unsigned int)vend_id, (unsigned int)prod_id); 85 jz_log(jz, JZ_LOG_ERROR, "Please ensure only one player is plugged in, and try again"); 86 rc = JZ_ERR_NO_DEVICE; 87 goto error; 88 } 89 90 dev_index = i; 91 } 92 93 if(dev_index < 0) { 94 jz_log(jz, JZ_LOG_ERROR, "No device with ID %04x:%04x found", 95 (unsigned int)vend_id, (unsigned int)prod_id); 96 rc = JZ_ERR_NO_DEVICE; 97 goto error; 98 } 99 100 rc = libusb_open(dev_list[dev_index], &usb_handle); 101 if(rc < 0) { 102 jz_log(jz, JZ_LOG_ERROR, "libusb_open: %s", libusb_strerror(rc)); 103 rc = JZ_ERR_USB; 104 goto error; 105 } 106 107 rc = libusb_claim_interface(usb_handle, 0); 108 if(rc < 0) { 109 jz_log(jz, JZ_LOG_ERROR, "libusb_claim_interface: %s", libusb_strerror(rc)); 110 rc = JZ_ERR_USB; 111 goto error; 112 } 113 114 jz_log(jz, JZ_LOG_DEBUG, "Opened device (%p, ID %04x:%04x)", 115 dev, (unsigned int)vend_id, (unsigned int)prod_id); 116 dev->jz = jz; 117 dev->handle = usb_handle; 118 *devptr = dev; 119 rc = JZ_SUCCESS; 120 121 exit: 122 if(dev_list) 123 libusb_free_device_list(dev_list, true); 124 return rc; 125 126 error: 127 if(dev) 128 free(dev); 129 if(usb_handle) 130 libusb_close(usb_handle); 131 jz_context_unref_libusb(jz); 132 goto exit; 133} 134 135/** \brief Close a USB device 136 * \param dev Device to close; memory will be freed automatically 137 */ 138void jz_usb_close(jz_usbdev* dev) 139{ 140 jz_log(dev->jz, JZ_LOG_DEBUG, "Closing device (%p)", dev); 141 libusb_release_interface(dev->handle, 0); 142 libusb_close(dev->handle); 143 jz_context_unref_libusb(dev->jz); 144 free(dev); 145} 146 147// Does an Ingenic-specific vendor request 148// Written with X1000 in mind but other Ingenic CPUs have the same commands 149static int jz_usb_vendor_req(jz_usbdev* dev, int req, uint32_t arg, 150 void* buffer, int buflen) 151{ 152 int rc = libusb_control_transfer(dev->handle, 153 LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, 154 req, arg >> 16, arg & 0xffff, buffer, buflen, 1000); 155 156 if(rc < 0) { 157 jz_log(dev->jz, JZ_LOG_ERROR, "libusb_control_transfer: %s", libusb_strerror(rc)); 158 rc = JZ_ERR_USB; 159 } else { 160 static const char* req_names[] = { 161 "GET_CPU_INFO", 162 "SET_DATA_ADDRESS", 163 "SET_DATA_LENGTH", 164 "FLUSH_CACHES", 165 "PROGRAM_START1", 166 "PROGRAM_START2", 167 }; 168 169 jz_log(dev->jz, JZ_LOG_DEBUG, "Issued %s %08lu", 170 req_names[req], (unsigned long)arg); 171 rc = JZ_SUCCESS; 172 } 173 174 return rc; 175} 176 177// Bulk transfer wrapper 178static int jz_usb_transfer(jz_usbdev* dev, bool write, size_t len, void* buf) 179{ 180 int xfered = 0; 181 int ep = write ? LIBUSB_ENDPOINT_OUT|1 : LIBUSB_ENDPOINT_IN|1; 182 int rc = libusb_bulk_transfer(dev->handle, ep, buf, len, &xfered, 10000); 183 184 if(rc < 0) { 185 jz_log(dev->jz, JZ_LOG_ERROR, "libusb_bulk_transfer: %s", libusb_strerror(rc)); 186 rc = JZ_ERR_USB; 187 } else if(xfered != (int)len) { 188 jz_log(dev->jz, JZ_LOG_ERROR, "libusb_bulk_transfer: incorrect amount of data transfered"); 189 rc = JZ_ERR_USB; 190 } else { 191 jz_log(dev->jz, JZ_LOG_DEBUG, "Transferred %zu bytes %s", 192 len, write ? "to device" : "from device"); 193 rc = JZ_SUCCESS; 194 } 195 196 return rc; 197} 198 199// Memory send/receive primitive, performs the necessary vendor requests 200// and then tranfers data using the bulk endpoint 201static int jz_usb_sendrecv(jz_usbdev* dev, bool write, uint32_t addr, 202 size_t len, void* data) 203{ 204 int rc; 205 rc = jz_usb_vendor_req(dev, VR_SET_DATA_ADDRESS, addr, NULL, 0); 206 if(rc < 0) 207 return rc; 208 209 rc = jz_usb_vendor_req(dev, VR_SET_DATA_LENGTH, len, NULL, 0); 210 if(rc < 0) 211 return rc; 212 213 return jz_usb_transfer(dev, write, len, data); 214} 215 216/** \brief Write data to device memory 217 * \param dev USB device 218 * \param addr Address where data should be written 219 * \param len Length of the data, in bytes, should be positive 220 * \param data Data buffer 221 * \return either JZ_SUCCESS on success or a failure code 222 */ 223int jz_usb_send(jz_usbdev* dev, uint32_t addr, size_t len, const void* data) 224{ 225 return jz_usb_sendrecv(dev, true, addr, len, (void*)data); 226} 227 228/** \brief Read data to device memory 229 * \param dev USB device 230 * \param addr Address to read from 231 * \param len Length of the data, in bytes, should be positive 232 * \param data Data buffer 233 * \return either JZ_SUCCESS on success or a failure code 234 */ 235int jz_usb_recv(jz_usbdev* dev, uint32_t addr, size_t len, void* data) 236{ 237 return jz_usb_sendrecv(dev, false, addr, len, data); 238} 239 240/** \brief Execute stage1 program jumping to the specified address 241 * \param dev USB device 242 * \param addr Address to begin execution at 243 * \return either JZ_SUCCESS on success or a failure code 244 */ 245int jz_usb_start1(jz_usbdev* dev, uint32_t addr) 246{ 247 return jz_usb_vendor_req(dev, VR_PROGRAM_START1, addr, NULL, 0); 248} 249 250/** \brief Execute stage2 program jumping to the specified address 251 * \param dev USB device 252 * \param addr Address to begin execution at 253 * \return either JZ_SUCCESS on success or a failure code 254 */ 255int jz_usb_start2(jz_usbdev* dev, uint32_t addr) 256{ 257 return jz_usb_vendor_req(dev, VR_PROGRAM_START2, addr, NULL, 0); 258} 259 260/** \brief Ask device to flush CPU caches 261 * \param dev USB device 262 * \return either JZ_SUCCESS on success or a failure code 263 */ 264int jz_usb_flush_caches(jz_usbdev* dev) 265{ 266 return jz_usb_vendor_req(dev, VR_FLUSH_CACHES, 0, NULL, 0); 267} 268 269/** \brief Ask device for CPU info string 270 * \param dev USB device 271 * \param buffer Buffer to hold the info string 272 * \param buflen Size of the buffer, in bytes 273 * \return either JZ_SUCCESS on success or a failure code 274 * 275 * The buffer will always be null terminated, but to ensure the info string is 276 * not truncated the buffer needs to be at least `JZ_CPUINFO_BUFLEN` byes long. 277 */ 278int jz_usb_get_cpu_info(jz_usbdev* dev, char* buffer, size_t buflen) 279{ 280 char tmpbuf[JZ_CPUINFO_BUFLEN]; 281 int rc = jz_usb_vendor_req(dev, VR_GET_CPU_INFO, 0, tmpbuf, 8); 282 if(rc != JZ_SUCCESS) 283 return rc; 284 285 if(buflen > 0) { 286 strncpy(buffer, tmpbuf, buflen); 287 buffer[buflen - 1] = 0; 288 } 289 290 return JZ_SUCCESS; 291}