/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id$ * * Copyright (C) 2015 by Amaury Pouly * * 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 "hwstub.hpp" #include "hwstub_usb.hpp" #include /* for memcpy */ namespace hwstub { namespace usb { const uint8_t VR_GET_CPU_INFO = 0; const uint8_t VR_SET_DATA_ADDRESS = 1; const uint8_t VR_SET_DATA_LENGTH = 2; const uint8_t VR_FLUSH_CACHES = 3; const uint8_t VR_PROGRAM_START1 = 4; const uint8_t VR_PROGRAM_START2 = 5; /** * Context */ context::context(libusb_context *ctx, bool cleanup_ctx) :m_usb_ctx(ctx), m_cleanup_ctx(cleanup_ctx) { } context::~context() { if(m_cleanup_ctx) libusb_exit(m_usb_ctx); } std::shared_ptr context::create(libusb_context *ctx, bool cleanup_ctx, std::string *error) { (void) error; if(ctx == nullptr) libusb_init(nullptr); // NOTE: can't use make_shared() because of the protected ctor */ return std::shared_ptr(new context(ctx, cleanup_ctx)); } libusb_context *context::native_context() { return m_usb_ctx; } libusb_device *context::from_ctx_dev(ctx_dev_t dev) { return reinterpret_cast(dev); } hwstub::context::ctx_dev_t context::to_ctx_dev(libusb_device *dev) { return static_cast(dev); } error context::fetch_device_list(std::vector& list, void*& ptr) { libusb_device **usb_list; ssize_t ret = libusb_get_device_list(m_usb_ctx, &usb_list); if(ret < 0) return error::ERROR; ptr = (void *)usb_list; list.clear(); for(int i = 0; i < ret; i++) if(device::is_hwstub_dev(usb_list[i])) list.push_back(to_ctx_dev(usb_list[i])); return error::SUCCESS; } void context::destroy_device_list(void *ptr) { /* remove all references */ libusb_free_device_list((libusb_device **)ptr, 1); } error context::create_device(ctx_dev_t dev, std::shared_ptr& hwdev) { // NOTE: can't use make_shared() because of the protected ctor */ hwdev.reset(new device(shared_from_this(), from_ctx_dev(dev))); return error::SUCCESS; } bool context::match_device(ctx_dev_t dev, std::shared_ptr hwdev) { device *udev = dynamic_cast(hwdev.get()); return udev != nullptr && udev->native_device() == dev; } /** * Device */ device::device(std::shared_ptr ctx, libusb_device *dev) :hwstub::device(ctx), m_dev(dev) { libusb_ref_device(dev); } device::~device() { libusb_unref_device(m_dev); } libusb_device *device::native_device() { return m_dev; } bool device::is_hwstub_dev(libusb_device *dev) { struct libusb_device_descriptor dev_desc; struct libusb_config_descriptor *config = nullptr; int intf = 0; if(libusb_get_device_descriptor(dev, &dev_desc) != 0) goto Lend; if(libusb_get_config_descriptor(dev, 0, &config) != 0) goto Lend; /* Try to find Rockbox hwstub interface or a JZ device */ if(rb_handle::find_intf(&dev_desc, config, intf) || jz_handle::is_boot_dev(&dev_desc, config)) { libusb_free_config_descriptor(config); return true; } Lend: if(config) libusb_free_config_descriptor(config); return false; } error device::open_dev(std::shared_ptr& handle) { int intf = -1; /* open the device */ libusb_device_handle *h; int err = libusb_open(m_dev, &h); if(err != LIBUSB_SUCCESS) return error::ERROR; /* fetch some descriptors */ struct libusb_device_descriptor dev_desc; struct libusb_config_descriptor *config = nullptr; if(libusb_get_device_descriptor(m_dev, &dev_desc) != 0) goto Lend; if(libusb_get_config_descriptor(m_dev, 0, &config) != 0) goto Lend; /* Try to find Rockbox hwstub interface */ if(rb_handle::find_intf(&dev_desc, config, intf)) { libusb_free_config_descriptor(config); /* create the handle */ // NOTE: can't use make_shared() because of the protected ctor */ handle.reset(new rb_handle(shared_from_this(), h, intf)); } /* Maybe this is a JZ device ? */ else if(jz_handle::is_boot_dev(&dev_desc, config)) { libusb_free_config_descriptor(config); /* create the handle */ // NOTE: can't use make_shared() because of the protected ctor */ handle.reset(new jz_handle(shared_from_this(), h)); } else { libusb_free_config_descriptor(config); return error::ERROR; } /* the class will perform some probing on creation: check that it actually worked */ if(handle->valid()) return error::SUCCESS; /* abort */ handle.reset(); // will close the libusb handle return error::ERROR; Lend: if(config) libusb_free_config_descriptor(config); libusb_close(h); return error::ERROR; } bool device::has_multiple_open() const { /* libusb only allows one handle per device */ return false; } uint8_t device::get_bus_number() { return libusb_get_bus_number(native_device()); } uint8_t device::get_address() { return libusb_get_device_address(native_device()); } uint16_t device::get_vid() { /* NOTE: doc says it's cached so it should always succeed */ struct libusb_device_descriptor dev_desc; libusb_get_device_descriptor(native_device(), &dev_desc); return dev_desc.idVendor; } uint16_t device::get_pid() { /* NOTE: doc says it's cached so it should always succeed */ struct libusb_device_descriptor dev_desc; libusb_get_device_descriptor(native_device(), &dev_desc); return dev_desc.idProduct; } /** * USB handle */ handle::handle(std::shared_ptr dev, libusb_device_handle *handle) :hwstub::handle(dev), m_handle(handle) { set_timeout(std::chrono::milliseconds(100)); } handle::~handle() { libusb_close(m_handle); } error handle::interpret_libusb_error(int err) { if(err >= 0) return error::SUCCESS; if(err == LIBUSB_ERROR_NO_DEVICE) return error::DISCONNECTED; else return error::USB_ERROR; } error handle::interpret_libusb_error(int err, size_t expected_val) { if(err < 0) return interpret_libusb_error(err); if((size_t)err != expected_val) return error::ERROR; return error::SUCCESS; } error handle::interpret_libusb_size(int err, size_t& out_siz) { if(err < 0) return interpret_libusb_error(err); out_siz = (size_t)err; return error::SUCCESS; } void handle::set_timeout(std::chrono::milliseconds ms) { m_timeout = ms.count(); } /** * Rockbox Handle */ rb_handle::rb_handle(std::shared_ptr dev, libusb_device_handle *handle, int intf) :hwstub::usb::handle(dev, handle), m_intf(intf), m_transac_id(0), m_buf_size(1) { m_probe_status = error::SUCCESS; /* claim interface */ if(libusb_claim_interface(m_handle, m_intf) != 0) m_probe_status = error::PROBE_FAILURE; /* check version */ if(m_probe_status == error::SUCCESS) { struct hwstub_version_desc_t ver_desc; m_probe_status = get_version_desc(ver_desc); if(m_probe_status == error::SUCCESS) { if(ver_desc.bMajor != HWSTUB_VERSION_MAJOR || ver_desc.bMinor < HWSTUB_VERSION_MINOR) m_probe_status = error::PROBE_FAILURE; } } /* get buffer size */ if(m_probe_status == error::SUCCESS) { struct hwstub_layout_desc_t layout_desc; m_probe_status = get_layout_desc(layout_desc); if(m_probe_status == error::SUCCESS) m_buf_size = layout_desc.dBufferSize; } } rb_handle::~rb_handle() { } size_t rb_handle::get_buffer_size() { return m_buf_size; } error rb_handle::status() const { error err = handle::status(); if(err == error::SUCCESS) err = m_probe_status; return err; } error rb_handle::get_dev_desc(uint16_t desc, void *buf, size_t& buf_sz) { return interpret_libusb_size(libusb_control_transfer(m_handle, LIBUSB_REQUEST_TYPE_STANDARD | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_IN, LIBUSB_REQUEST_GET_DESCRIPTOR, desc << 8, m_intf, (unsigned char *)buf, buf_sz, m_timeout), buf_sz); } error rb_handle::get_dev_log(void *buf, size_t& buf_sz) { return interpret_libusb_size(libusb_control_transfer(m_handle, LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_IN, HWSTUB_GET_LOG, 0, m_intf, (unsigned char *)buf, buf_sz, m_timeout), buf_sz); } error rb_handle::exec_dev(uint32_t addr, uint16_t flags) { struct hwstub_exec_req_t exec; exec.dAddress = addr; exec.bmFlags = flags; return interpret_libusb_error(libusb_control_transfer(m_handle, LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_OUT, HWSTUB_EXEC, 0, m_intf, (unsigned char *)&exec, sizeof(exec), m_timeout), sizeof(exec)); } error rb_handle::read_dev(uint32_t addr, void *buf, size_t& sz, bool atomic) { struct hwstub_read_req_t read; read.dAddress = addr; error err = interpret_libusb_error(libusb_control_transfer(m_handle, LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_OUT, HWSTUB_READ, m_transac_id, m_intf, (unsigned char *)&read, sizeof(read), m_timeout), sizeof(read)); if(err != error::SUCCESS) return err; return interpret_libusb_size(libusb_control_transfer(m_handle, LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_IN, atomic ? HWSTUB_READ2_ATOMIC : HWSTUB_READ2, m_transac_id++, m_intf, (unsigned char *)buf, sz, m_timeout), sz); } error rb_handle::write_dev(uint32_t addr, const void *buf, size_t& sz, bool atomic) { size_t hdr_sz = sizeof(struct hwstub_write_req_t); uint8_t *tmp_buf = new uint8_t[sz + hdr_sz]; struct hwstub_write_req_t *req = reinterpret_cast(tmp_buf); req->dAddress = addr; memcpy(tmp_buf + hdr_sz, buf, sz); error ret = interpret_libusb_error(libusb_control_transfer(m_handle, LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_OUT, atomic ? HWSTUB_WRITE_ATOMIC : HWSTUB_WRITE, m_transac_id++, m_intf, (unsigned char *)req, sz + hdr_sz, m_timeout), sz + hdr_sz); delete[] tmp_buf; return ret; } bool rb_handle::find_intf(struct libusb_device_descriptor *dev, struct libusb_config_descriptor *config, int& intf_idx) { (void) dev; /* search hwstub interface */ for(unsigned i = 0; i < config->bNumInterfaces; i++) { /* hwstub interface has only one setting */ if(config->interface[i].num_altsetting != 1) continue; const struct libusb_interface_descriptor *intf = &config->interface[i].altsetting[0]; /* check class/subclass/protocol */ if(intf->bInterfaceClass == HWSTUB_CLASS && intf->bInterfaceSubClass == HWSTUB_SUBCLASS && intf->bInterfaceProtocol == HWSTUB_PROTOCOL) { /* found it ! */ intf_idx = i; return true; } } return false; } /** * JZ Handle */ namespace { uint16_t jz_bcd(char *bcd) { uint16_t v = 0; for(int i = 0; i < 4; i++) v = (bcd[i] - '0') | v << 4; return v; } } jz_handle::jz_handle(std::shared_ptr dev, libusb_device_handle *handle) :hwstub::usb::handle(dev, handle) { m_probe_status = probe(); } jz_handle::~jz_handle() { } error jz_handle::probe() { char cpuinfo[8]; /* Get CPU info and devise descriptor */ error err = jz_cpuinfo(cpuinfo); if(err != error::SUCCESS) return err; struct libusb_device_descriptor dev_desc; err = interpret_libusb_error(libusb_get_device_descriptor( libusb_get_device(m_handle), &dev_desc), 0); if(err != error::SUCCESS) return err; /** parse CPU info */ /* if cpuinfo if of the form JZxxxxVy then extract xxxx */ if(cpuinfo[0] == 'J' && cpuinfo[1] == 'Z' && cpuinfo[6] == 'V') m_desc_jz.wChipID = jz_bcd(cpuinfo + 2); /* if cpuinfo if of the form Bootxxxx then extract xxxx */ else if(strncmp(cpuinfo, "Boot", 4) == 4) m_desc_jz.wChipID = jz_bcd(cpuinfo + 4); /* else use usb id */ else m_desc_jz.wChipID = dev_desc.idProduct; m_desc_jz.bRevision = 0; /** Retrieve product string */ memset(m_desc_target.bName, 0, sizeof(m_desc_target.bName)); err = interpret_libusb_error(libusb_get_string_descriptor_ascii(m_handle, dev_desc.iProduct, (unsigned char *)m_desc_target.bName, sizeof(m_desc_target.bName))); if(err != error::SUCCESS) return err; /** The JZ4760 and JZ4760B cannot be distinguished by the above information, * for this the best way I have found is to check the SRAM size: 48KiB vs 16KiB. * This requires to enable AHB1 and SRAM clock and read/write to SRAM, but * this code will leaves registers and ram is the same state as before. * In case of failure, simply assume JZ4760. */ if(m_desc_jz.wChipID == 0x4760) probe_jz4760b(); /** Fill descriptors */ m_desc_version.bLength = sizeof(m_desc_version); m_desc_version.bDescriptorType = HWSTUB_DT_VERSION; m_desc_version.bMajor = HWSTUB_VERSION_MAJOR; m_desc_version.bMinor = HWSTUB_VERSION_MINOR; m_desc_version.bRevision = 0; m_desc_layout.bLength = sizeof(m_desc_layout); m_desc_layout.bDescriptorType = HWSTUB_DT_LAYOUT; m_desc_layout.dCodeStart = 0xbfc00000; /* ROM */ m_desc_layout.dCodeSize = 0x2000; /* 8kB per datasheet */ m_desc_layout.dStackStart = 0; /* As far as I can tell, the ROM uses no stack */ m_desc_layout.dStackSize = 0; m_desc_layout.dBufferStart = 0x080000000; m_desc_layout.dBufferSize = 0x4000; m_desc_target.bLength = sizeof(m_desc_target); m_desc_target.bDescriptorType = HWSTUB_DT_TARGET; m_desc_target.dID = HWSTUB_TARGET_JZ; m_desc_jz.bLength = sizeof(m_desc_jz); m_desc_jz.bDescriptorType = HWSTUB_DT_JZ; /* claim interface */ if(libusb_claim_interface(m_handle, 0) != 0) m_probe_status = error::PROBE_FAILURE; return m_probe_status; } error jz_handle::read_reg32(uint32_t addr, uint32_t& value) { size_t sz = sizeof(value); error err = read_dev(addr, &value, sz, true); if(err == error::SUCCESS && sz != sizeof(value)) err = error::ERROR; return err; } error jz_handle::write_reg32(uint32_t addr, uint32_t value) { size_t sz = sizeof(value); error err = write_dev(addr, &value, sz, true); if(err == error::SUCCESS && sz != sizeof(value)) err = error::ERROR; return err; } error jz_handle::probe_jz4760b() { /* first read CPM_CLKGR1 */ const uint32_t cpm_clkgr1_addr = 0xb0000028; uint32_t cpm_clkgr1; error err = read_reg32(cpm_clkgr1_addr, cpm_clkgr1); if(err != error::SUCCESS) return err; /* Bit 7 controls AHB1 clock and bit 5 the SRAM. Note that SRAM is on AHB1. * Only ungate if gated */ uint32_t cpm_clkgr1_mask = 1 << 7 | 1 << 5; if(cpm_clkgr1 & cpm_clkgr1_mask) { /* ungate both clocks */ err = write_reg32(cpm_clkgr1_addr, cpm_clkgr1 & ~cpm_clkgr1_mask); if(err != error::SUCCESS) return err; } /* read first word of SRAM and then at end (supposedly) */ uint32_t sram_addr = 0xb32d0000; uint32_t sram_end_addr = sram_addr + 16 * 1024; /* SRAM is 16KiB on JZ4760B */ uint32_t sram_start, sram_end; err = read_reg32(sram_addr, sram_start); if(err != error::SUCCESS) goto Lrestore; err = read_reg32(sram_end_addr, sram_end); if(err != error::SUCCESS) goto Lrestore; /* if start and end are different, clearly the size is not 16KiB and this is * JZ4760 and we have nothing to do */ if(sram_start != sram_end) goto Lrestore; /* now reverse all bits of the first word */ sram_start ^= 0xffffffff; err = write_reg32(sram_addr, sram_start); if(err != error::SUCCESS) goto Lrestore; /* and read again at end */ err = read_reg32(sram_end_addr, sram_end); if(err != error::SUCCESS) goto Lrestore; /* if they are still equal, we identified JZ4760B */ if(sram_start == sram_end) m_desc_jz.bRevision = 'B'; /* restore SRAM value */ sram_start ^= 0xffffffff; err = write_reg32(sram_addr, sram_start); if(err != error::SUCCESS) goto Lrestore; Lrestore: /* restore gates if needed */ if(cpm_clkgr1 & cpm_clkgr1_mask) return write_reg32(cpm_clkgr1_addr, cpm_clkgr1); else return error::SUCCESS; } size_t jz_handle::get_buffer_size() { return m_desc_layout.dBufferSize; } error jz_handle::status() const { error err = handle::status(); if(err == error::SUCCESS) err = m_probe_status; return err; } error jz_handle::get_dev_desc(uint16_t desc, void *buf, size_t& buf_sz) { void *p = nullptr; switch(desc) { case HWSTUB_DT_VERSION: p = &m_desc_version; break; case HWSTUB_DT_LAYOUT: p = &m_desc_layout; break; case HWSTUB_DT_TARGET: p = &m_desc_target; break; case HWSTUB_DT_JZ: p = &m_desc_jz; break; default: break; } if(p == nullptr) return error::ERROR; /* size is in the bLength field of the descriptor */ size_t desc_sz = *(uint8_t *)p; buf_sz = std::min(buf_sz, desc_sz); memcpy(buf, p, buf_sz); return error::SUCCESS; } error jz_handle::get_dev_log(void *buf, size_t& buf_sz) { (void) buf; buf_sz = 0; return error::SUCCESS; } error jz_handle::exec_dev(uint32_t addr, uint16_t flags) { (void) flags; /* FIXME the ROM always do call so the stub can always return, this behaviour * cannot be changed */ /* NOTE assume that exec at 0x80000000 is a first stage load with START1, * otherwise flush cache and use START2 */ if(addr == 0x80000000) return jz_start1(addr); error ret = jz_flush_caches(); if(ret == error::SUCCESS) return jz_start2(addr); else return ret; } error jz_handle::read_dev(uint32_t addr, void *buf, size_t& sz, bool atomic) { (void) atomic; /* NOTE disassembly shows that the ROM will do atomic read on aligned words */ error ret = jz_set_addr(addr); if(ret == error::SUCCESS) ret = jz_set_length(sz); if(ret == error::SUCCESS) ret = jz_upload(buf, sz); return ret; } error jz_handle::write_dev(uint32_t addr, const void *buf, size_t& sz, bool atomic) { (void) atomic; /* NOTE disassembly shows that the ROM will do atomic read on aligned words */ /* IMPORTANT BUG Despite what the manual suggest, one must absolutely NOT send * a VR_SET_DATA_LENGTH request for a write, otherwise it will have completely * random effects */ error ret = jz_set_addr(addr); if(ret == error::SUCCESS) ret = jz_download(buf, sz); return ret; } bool jz_handle::is_boot_dev(struct libusb_device_descriptor *dev, struct libusb_config_descriptor *config) { (void) config; /* don't bother checking the config descriptor and use the device ID only */ return dev->idVendor == 0x601a && dev->idProduct >= 0x4740 && dev->idProduct <= 0x4780; } error jz_handle::jz_cpuinfo(char cpuinfo[8]) { return interpret_libusb_error(libusb_control_transfer(m_handle, LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, VR_GET_CPU_INFO, 0, 0, (unsigned char *)cpuinfo, 8, m_timeout), 8); } error jz_handle::jz_set_addr(uint32_t addr) { return interpret_libusb_error(libusb_control_transfer(m_handle, LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, VR_SET_DATA_ADDRESS, addr >> 16, addr & 0xffff, NULL, 0, m_timeout), 0); } error jz_handle::jz_set_length(uint32_t size) { return interpret_libusb_error(libusb_control_transfer(m_handle, LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, VR_SET_DATA_LENGTH, size >> 16, size & 0xffff, NULL, 0, m_timeout), 0); } error jz_handle::jz_upload(void *data, size_t& length) { int xfer = 0; error err = interpret_libusb_error(libusb_bulk_transfer(m_handle, LIBUSB_ENDPOINT_IN | 1, (unsigned char *)data, length, &xfer, m_timeout)); length = xfer; return err; } error jz_handle::jz_download(const void *data, size_t& length) { int xfer = 0; error err = interpret_libusb_error(libusb_bulk_transfer(m_handle, LIBUSB_ENDPOINT_OUT | 1, (unsigned char *)data, length, &xfer, m_timeout)); length = xfer; return err; } error jz_handle::jz_start1(uint32_t addr) { return interpret_libusb_error(libusb_control_transfer(m_handle, LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, VR_PROGRAM_START1, addr >> 16, addr & 0xffff, NULL, 0, m_timeout), 0); } error jz_handle::jz_flush_caches() { return interpret_libusb_error(libusb_control_transfer(m_handle, LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, VR_FLUSH_CACHES, 0, 0, NULL, 0, m_timeout), 0); } error jz_handle::jz_start2(uint32_t addr) { return interpret_libusb_error(libusb_control_transfer(m_handle, LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, VR_PROGRAM_START2, addr >> 16, addr & 0xffff, NULL, 0, m_timeout), 0); } } // namespace usb } // namespace hwstub