summaryrefslogtreecommitdiff
path: root/utils/jztool/src/usb.c
diff options
context:
space:
mode:
Diffstat (limited to 'utils/jztool/src/usb.c')
-rw-r--r--utils/jztool/src/usb.c291
1 files changed, 291 insertions, 0 deletions
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 @@
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}