From 3d8a08ca25c3041ac677335e51341d966a9b370b Mon Sep 17 00:00:00 2001 From: Amaury Pouly Date: Sun, 7 Feb 2016 21:42:15 +0000 Subject: 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 --- utils/hwstub/lib/hwstub.cpp | 627 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 627 insertions(+) create mode 100644 utils/hwstub/lib/hwstub.cpp (limited to 'utils/hwstub/lib/hwstub.cpp') diff --git a/utils/hwstub/lib/hwstub.cpp b/utils/hwstub/lib/hwstub.cpp new file mode 100644 index 0000000..7c81146 --- /dev/null +++ b/utils/hwstub/lib/hwstub.cpp @@ -0,0 +1,627 @@ +/*************************************************************************** + * __________ __ ___. + * 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 +#include + +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>& list) +{ + std::unique_lock 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& 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 dev) +{ + for(auto cb : m_callbacks) + cb.callback(shared_from_this(), arrived, dev); +} + +void context::change_device(bool arrived, std::shared_ptr dev) +{ + std::unique_lock 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 lock(m_mutex); + /* fetch new list */ + std::vector 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> 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 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 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 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 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 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 lock(m_mutex); + m_poller->stop(); +} + +/** + * Context Poller + */ + +context_poller::context_poller(std::weak_ptr 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 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 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 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 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 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 ctx) + :m_ctx(ctx), m_connected(true) +{ +} + +device::~device() +{ +} + +error device::open(std::shared_ptr& handle) +{ + std::unique_lock 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 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 lock(m_mutex); + m_connected = false; +} + +bool device::connected() +{ + return m_connected; +} + +std::shared_ptr device::get_context() +{ + return m_ctx.lock(); +} + +/** + * Handle + */ + +handle::handle(std::shared_ptr dev) + :m_dev(dev) +{ +} + +handle::~handle() +{ +} + +std::shared_ptr handle::get_device() +{ + return m_dev; +} + +error handle::get_desc(uint16_t desc, void *buf, size_t& buf_sz) +{ + std::unique_lock 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 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 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 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 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 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 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 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 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 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 + 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 ctx) + :device(ctx) +{ +} + +dummy_device::~dummy_device() +{ +} + +error dummy_device::open_dev(std::shared_ptr& 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 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 -- cgit v1.1