3d8a08ca25
Rewrite the hwstub library in C++, with a clean and modular design. The library was designed from the ground up to be aware of multithreading issues and to handle memory allocation nicely with shared pointers. Compared to the original library, it brings the following major features: - support for JZ boot devices, it is very easy to add support for others - support for network transparent operations (through sockets): both tcp and unix domains are support Change-Id: I75899cb9c7aa938c17ede2bb3f468e7a55d625b4
728 lines
22 KiB
C++
728 lines
22 KiB
C++
/***************************************************************************
|
|
* __________ __ ___.
|
|
* 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 <cstring> /* 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> 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<context>(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<libusb_device*>(dev);
|
|
}
|
|
|
|
hwstub::context::ctx_dev_t context::to_ctx_dev(libusb_device *dev)
|
|
{
|
|
return static_cast<ctx_dev_t>(dev);
|
|
}
|
|
|
|
error context::fetch_device_list(std::vector<ctx_dev_t>& 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<hwstub::device>& 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<hwstub::device> hwdev)
|
|
{
|
|
device *udev = dynamic_cast<device*>(hwdev.get());
|
|
return udev != nullptr && udev->native_device() == dev;
|
|
}
|
|
|
|
/**
|
|
* Device
|
|
*/
|
|
device::device(std::shared_ptr<hwstub::context> 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<hwstub::handle>& 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<hwstub::device> 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<hwstub::device> 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<struct hwstub_write_req_t *>(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<hwstub::device> 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
|