rockbox/utils/hwstub/lib/hwstub.cpp
Amaury Pouly 3d8a08ca25 hwstub: rewrite and expand library
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
2016-04-08 19:37:30 +02:00

627 lines
16 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 <algorithm>
#include <cstring>
namespace hwstub {
std::ostream cnull(0);
std::wostream wcnull(0);
std::string error_string(error err)
{
switch(err)
{
case error::SUCCESS: return "success";
case error::ERROR: return "unspecified error";
case error::DISCONNECTED: return "device was disconnected";
case error::PROBE_FAILURE: return "probing failed";
case error::NO_CONTEXT: return "context was destroyed";
case error::USB_ERROR: return "unspecified USB error";
case error::DUMMY: return "operation on dummy device";
case error::NO_SERVER: return "server could not be reached";
case error::SERVER_DISCONNECTED: return "server disconnected";
case error::SERVER_MISMATCH: return "incompatible server";
case error::NET_ERROR: return "network error";
case error::PROTOCOL_ERROR: return "network protocol error";
case error::TIMEOUT: return "timeout";
case error::OVERFLW: return "overflow";
default: return "unknown error";
}
}
/**
* Context
*/
context::context()
:m_next_cb_ref(0)
{
clear_debug();
}
context::~context()
{
}
void context::set_debug(std::ostream& os)
{
m_debug = &os;
}
std::ostream& context::debug()
{
return *m_debug;
}
error context::get_device_list(std::vector<std::shared_ptr<device>>& list)
{
std::unique_lock<std::recursive_mutex> lock(m_mutex);
error err = update_list();
if(err != error::SUCCESS)
return err;
list.resize(m_devlist.size());
for(size_t i = 0; i < m_devlist.size(); i++)
list[i] = m_devlist[i];
return error::SUCCESS;
}
error context::get_dummy_device(std::shared_ptr<device>& dev)
{
// NOTE: can't use make_shared() because of the protected ctor */
dev.reset(new dummy_device(shared_from_this()));
return error::SUCCESS;
}
void context::notify_device(bool arrived, std::shared_ptr<device> dev)
{
for(auto cb : m_callbacks)
cb.callback(shared_from_this(), arrived, dev);
}
void context::change_device(bool arrived, std::shared_ptr<device> dev)
{
std::unique_lock<std::recursive_mutex> lock(m_mutex);
if(arrived)
{
/* add to the list (assumed it's not already there) */
m_devlist.push_back(dev);
/* notify */
notify_device(arrived, dev);
}
else
{
dev->disconnect();
/* notify first */
notify_device(arrived, dev);
auto it = std::find(m_devlist.begin(), m_devlist.end(), dev);
if(it != m_devlist.end())
m_devlist.erase(it);
}
}
error context::update_list()
{
std::unique_lock<std::recursive_mutex> lock(m_mutex);
/* fetch new list */
std::vector<ctx_dev_t> new_list;
void* ptr;
error err = fetch_device_list(new_list, ptr);
if(err != error::SUCCESS)
return err;
/* determine devices that have left */
std::vector<std::shared_ptr<device>> to_del;
for(auto dev : m_devlist)
{
bool still_there = false;
for(auto new_dev : new_list)
if(match_device(new_dev, dev))
still_there = true;
if(!still_there)
to_del.push_back(dev);
}
for(auto dev : to_del)
change_device(false, dev);
/* determine new devices */
std::vector<ctx_dev_t> to_add;
for(auto new_dev : new_list)
{
bool exists = false;
for(auto dev : m_devlist)
if(match_device(new_dev, dev))
exists = true;
if(!exists)
to_add.push_back(new_dev);
}
/* create new devices */
for(auto dev : to_add)
{
std::shared_ptr<device> new_dev;
err = create_device(dev, new_dev);
if(err == error::SUCCESS)
change_device(true, new_dev);
}
/* destroy list */
destroy_device_list(ptr);
return error::SUCCESS;
}
context::callback_ref_t context::register_callback(const notification_callback_t& fn)
{
std::unique_lock<std::recursive_mutex> lock(m_mutex);
struct callback_t cb;
cb.callback = fn;
cb.ref = m_next_cb_ref++;
m_callbacks.push_back(cb);
return cb.ref;
}
void context::unregister_callback(callback_ref_t ref)
{
std::unique_lock<std::recursive_mutex> lock(m_mutex);
for(auto it = m_callbacks.begin(); it != m_callbacks.end(); ++it)
{
if((*it).ref == ref)
{
m_callbacks.erase(it);
return;
}
}
}
void context::start_polling(std::chrono::milliseconds interval)
{
std::unique_lock<std::recursive_mutex> lock(m_mutex);
/* create poller on demand */
if(!m_poller)
m_poller.reset(new context_poller(shared_from_this(), interval));
else
m_poller->set_interval(interval);
m_poller->start();
}
void context::stop_polling()
{
std::unique_lock<std::recursive_mutex> lock(m_mutex);
m_poller->stop();
}
/**
* Context Poller
*/
context_poller::context_poller(std::weak_ptr<context> ctx, std::chrono::milliseconds interval)
:m_ctx(ctx), m_running(false), m_exit(false), m_interval(interval)
{
m_thread = std::thread(context_poller::thread, this);
}
context_poller::~context_poller()
{
/* set exit flag, wakeup thread, wait for exit */
m_mutex.lock();
m_exit = true;
m_mutex.unlock();
m_cond.notify_one();
m_thread.join();
}
void context_poller::set_interval(std::chrono::milliseconds interval)
{
std::unique_lock<std::mutex> lock(m_mutex);
/* change interval, wakeup thread to take new interval into account */
m_interval = interval;
m_cond.notify_one();
}
void context_poller::start()
{
std::unique_lock<std::mutex> lock(m_mutex);
/* change running flag, wakeup thread to start polling */
m_running = true;
m_cond.notify_one();
}
void context_poller::stop()
{
std::unique_lock<std::mutex> lock(m_mutex);
/* change running flag, wakeup thread to stop polling */
m_running = false;
m_cond.notify_one();
}
void context_poller::poll()
{
std::unique_lock<std::mutex> lock(m_mutex);
while(true)
{
/* if asked, exit */
if(m_exit)
break;
/* if running, poll and then sleep for some time */
if(m_running)
{
std::shared_ptr<context> ctx = m_ctx.lock();
if(ctx)
ctx->update_list();
ctx.reset();
m_cond.wait_for(lock, m_interval);
}
/* if not, sleep until awaken */
else
m_cond.wait(lock);
}
}
void context_poller::thread(context_poller *poller)
{
poller->poll();
}
/**
* Device
*/
device::device(std::shared_ptr<context> ctx)
:m_ctx(ctx), m_connected(true)
{
}
device::~device()
{
}
error device::open(std::shared_ptr<handle>& handle)
{
std::unique_lock<std::recursive_mutex> lock(m_mutex);
/* get a pointer so that it's not destroyed during the runtime of the function,
* the pointer will be released at the end of the function */
std::shared_ptr<context> ctx = get_context();
if(!ctx)
return error::NO_CONTEXT;
/* do not even try if device is disconnected */
if(!connected())
return error::DISCONNECTED;
/* NOTE at the moment handle is state-less which means that we can
* safely give the same handle each time open() is called without callers
* interfering with each other. If handle eventually get a state,
* one will need to create a proxy class to encapsulate the state */
handle = m_handle.lock();
if(has_multiple_open() || !handle)
{
error err = open_dev(handle);
m_handle = handle;
return err;
}
else
return error::SUCCESS;
}
void device::disconnect()
{
std::unique_lock<std::recursive_mutex> lock(m_mutex);
m_connected = false;
}
bool device::connected()
{
return m_connected;
}
std::shared_ptr<context> device::get_context()
{
return m_ctx.lock();
}
/**
* Handle
*/
handle::handle(std::shared_ptr<device > dev)
:m_dev(dev)
{
}
handle::~handle()
{
}
std::shared_ptr<device> handle::get_device()
{
return m_dev;
}
error handle::get_desc(uint16_t desc, void *buf, size_t& buf_sz)
{
std::unique_lock<std::recursive_mutex> lock(m_mutex);
/* get a pointer so that it's not destroyed during the runtime of the function,
* the pointer will be released at the end of the function */
std::shared_ptr<context> ctx = m_dev->get_context();
if(!ctx)
return error::NO_CONTEXT;
/* ensure valid status */
error err = status();
if(err != error::SUCCESS)
return err;
return get_dev_desc(desc, buf, buf_sz);
}
error handle::get_log(void *buf, size_t& buf_sz)
{
std::unique_lock<std::recursive_mutex> lock(m_mutex);
/* get a pointer so that it's not destroyed during the runtime of the function,
* the pointer will be released at the end of the function */
std::shared_ptr<context> ctx = m_dev->get_context();
if(!ctx)
return error::NO_CONTEXT;
/* ensure valid status */
error err = status();
if(err != error::SUCCESS)
return err;
return get_dev_log(buf, buf_sz);
}
error handle::exec(uint32_t addr, uint16_t flags)
{
std::unique_lock<std::recursive_mutex> lock(m_mutex);
/* get a pointer so that it's not destroyed during the runtime of the function,
* the pointer will be released at the end of the function */
std::shared_ptr<context> ctx = m_dev->get_context();
if(!ctx)
return error::NO_CONTEXT;
/* ensure valid status */
error err = status();
if(err != error::SUCCESS)
return err;
return exec_dev(addr, flags);
}
error handle::read(uint32_t addr, void *buf, size_t& sz, bool atomic)
{
std::unique_lock<std::recursive_mutex> lock(m_mutex);
/* get a pointer so that it's not destroyed during the runtime of the function,
* the pointer will be released at the end of the function */
std::shared_ptr<context> ctx = m_dev->get_context();
if(!ctx)
return error::NO_CONTEXT;
/* ensure valid status */
error err = status();
if(err != error::SUCCESS)
return err;
/* split transfer as needed */
size_t cnt = 0;
uint8_t *bufp = (uint8_t *)buf;
while(sz > 0)
{
size_t xfer = std::min(sz, get_buffer_size());
err = read_dev(addr, buf, xfer, atomic);
if(err != error::SUCCESS)
return err;
sz -= xfer;
bufp += xfer;
addr += xfer;
cnt += xfer;
}
sz = cnt;
return error::SUCCESS;
}
error handle::write(uint32_t addr, const void *buf, size_t& sz, bool atomic)
{
std::unique_lock<std::recursive_mutex> lock(m_mutex);
/* get a pointer so that it's not destroyed during the runtime of the function,
* the pointer will be released at the end of the function */
std::shared_ptr<context> ctx = m_dev->get_context();
if(!ctx)
return error::NO_CONTEXT;
/* ensure valid status */
error err = status();
if(err != error::SUCCESS)
return err;
/* split transfer as needed */
size_t cnt = 0;
const uint8_t *bufp = (uint8_t *)buf;
while(sz > 0)
{
size_t xfer = std::min(sz, get_buffer_size());
err = write_dev(addr, buf, xfer, atomic);
if(err != error::SUCCESS)
return err;
sz -= xfer;
bufp += xfer;
addr += xfer;
cnt += xfer;
}
sz = cnt;
return error::SUCCESS;
}
error handle::status() const
{
/* check context */
if(!m_dev->get_context())
return error::NO_CONTEXT;
if(!m_dev->connected())
return error::DISCONNECTED;
else
return error::SUCCESS;
}
namespace
{
template<typename T>
error helper_get_desc(handle *h, uint8_t type, T& desc)
{
size_t sz = sizeof(desc);
error ret = h->get_desc(type, &desc, sz);
if(ret != error::SUCCESS)
return ret;
if(sz != sizeof(desc) || desc.bDescriptorType != type ||
desc.bLength != sizeof(desc))
return error::ERROR;
else
return error::SUCCESS;
}
}
error handle::get_version_desc(hwstub_version_desc_t& desc)
{
return helper_get_desc(this, HWSTUB_DT_VERSION, desc);
}
error handle::get_layout_desc(hwstub_layout_desc_t& desc)
{
return helper_get_desc(this, HWSTUB_DT_LAYOUT, desc);
}
error handle::get_stmp_desc(hwstub_stmp_desc_t& desc)
{
return helper_get_desc(this, HWSTUB_DT_STMP, desc);
}
error handle::get_pp_desc(hwstub_pp_desc_t& desc)
{
return helper_get_desc(this, HWSTUB_DT_PP, desc);
}
error handle::get_jz_desc(hwstub_jz_desc_t& desc)
{
return helper_get_desc(this, HWSTUB_DT_JZ, desc);
}
error handle::get_target_desc(hwstub_target_desc_t& desc)
{
return helper_get_desc(this, HWSTUB_DT_TARGET, desc);
}
/** Dummy device */
dummy_device::dummy_device(std::shared_ptr<context> ctx)
:device(ctx)
{
}
dummy_device::~dummy_device()
{
}
error dummy_device::open_dev(std::shared_ptr<handle>& handle)
{
handle.reset(new dummy_handle(shared_from_this()));
return error::SUCCESS;
}
bool dummy_device::has_multiple_open() const
{
return true;
}
/** Dummy handle */
dummy_handle::dummy_handle(std::shared_ptr<device> dev)
:handle(dev)
{
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 = 0;
m_desc_layout.dCodeSize = 0;
m_desc_layout.dStackStart = 0;
m_desc_layout.dStackSize = 0;
m_desc_layout.dBufferStart = 0;
m_desc_layout.dBufferSize = 1;
m_desc_target.bLength = sizeof(m_desc_target);
m_desc_target.bDescriptorType = HWSTUB_DT_TARGET;
m_desc_target.dID = HWSTUB_TARGET_UNK;
strcpy(m_desc_target.bName, "Dummy target");
}
dummy_handle::~dummy_handle()
{
}
error dummy_handle::read_dev(uint32_t addr, void *buf, size_t& sz, bool atomic)
{
(void) addr;
(void) buf;
(void) sz;
(void) atomic;
return error::DUMMY;
}
error dummy_handle::write_dev(uint32_t addr, const void *buf, size_t& sz, bool atomic)
{
(void) addr;
(void) buf;
(void) sz;
(void) atomic;
return error::DUMMY;
}
error dummy_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;
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 dummy_handle::get_dev_log(void *buf, size_t& buf_sz)
{
(void) buf;
(void) buf_sz;
return error::DUMMY;
}
error dummy_handle::exec_dev(uint32_t addr, uint16_t flags)
{
(void) addr;
(void) flags;
return error::DUMMY;
}
error dummy_handle::status() const
{
error err = handle::status();
return err == error::SUCCESS ? error::DUMMY : err;
}
size_t dummy_handle::get_buffer_size()
{
return 1;
}
} // namespace hwstub