summaryrefslogtreecommitdiffstats
path: root/utils/hwstub/lib
diff options
context:
space:
mode:
authorAmaury Pouly <amaury.pouly@gmail.com>2016-02-07 21:42:15 +0000
committerAmaury Pouly <amaury.pouly@gmail.com>2016-04-08 19:37:30 +0200
commit3d8a08ca25c3041ac677335e51341d966a9b370b (patch)
tree1bf06dea354e3ae95c1ec91b6ee259d0ac21659c /utils/hwstub/lib
parent56dc54d38ac6c1d47ea6dbae88b1e5f7fee9f3ec (diff)
downloadrockbox-3d8a08ca25c3041ac677335e51341d966a9b370b.tar.gz
rockbox-3d8a08ca25c3041ac677335e51341d966a9b370b.tar.bz2
rockbox-3d8a08ca25c3041ac677335e51341d966a9b370b.zip
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
Diffstat (limited to 'utils/hwstub/lib')
-rw-r--r--utils/hwstub/lib/Makefile13
-rw-r--r--utils/hwstub/lib/hwstub.cpp627
-rw-r--r--utils/hwstub/lib/hwstub.h70
-rw-r--r--utils/hwstub/lib/hwstub_net.cpp1351
-rw-r--r--utils/hwstub/lib/hwstub_protocol.h1
-rw-r--r--utils/hwstub/lib/hwstub_uri.cpp332
-rw-r--r--utils/hwstub/lib/hwstub_usb.cpp728
-rw-r--r--utils/hwstub/lib/hwstub_virtual.cpp335
8 files changed, 3382 insertions, 75 deletions
diff --git a/utils/hwstub/lib/Makefile b/utils/hwstub/lib/Makefile
index 7c455e4586..92dd358ce3 100644
--- a/utils/hwstub/lib/Makefile
+++ b/utils/hwstub/lib/Makefile
@@ -1,16 +1,21 @@
-CC=gcc
AR=ar
-CFLAGS=-W -Wall -O2 `pkg-config --cflags libusb-1.0` -std=c99 -g -fPIC
-LDFLAGS=`pkg-config --libs libusb-1.0` -fPIC
+INCLUDE=../include
+CFLAGS=-W -Wall -O2 `pkg-config --cflags libusb-1.0` -std=c99 -g -fPIC -D_XOPEN_SOURCE=700 -I$(INCLUDE)
+CXXFLAGS=-W -Wall -O2 `pkg-config --cflags libusb-1.0` -std=c++11 -g -fPIC -D_XOPEN_SOURCE=700 -I$(INCLUDE)
+LDFLAGS=`pkg-config --libs libusb-1.0` -fPIC -lpthread
LIB=libhwstub.a
SRC=$(wildcard *.c)
-OBJ=$(SRC:.c=.o)
+SRCXX=$(wildcard *.cpp)
+OBJ=$(SRCXX:.cpp=.oxx) $(SRCXX:.cpp=.o)
all: $(LIB)
%.o: %.c
$(CC) $(CFLAGS) -c -o $@ $<
+%.oxx: %.cpp
+ $(CXX) $(CXXFLAGS) -c -o $@ $<
+
$(LIB): $(OBJ)
$(AR) rcs $@ $^
diff --git a/utils/hwstub/lib/hwstub.cpp b/utils/hwstub/lib/hwstub.cpp
new file mode 100644
index 0000000000..7c81146c77
--- /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 <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
diff --git a/utils/hwstub/lib/hwstub.h b/utils/hwstub/lib/hwstub.h
deleted file mode 100644
index 4d12de8eda..0000000000
--- a/utils/hwstub/lib/hwstub.h
+++ /dev/null
@@ -1,70 +0,0 @@
-/***************************************************************************
- * __________ __ ___.
- * Open \______ \ ____ ____ | | _\_ |__ _______ ___
- * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
- * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
- * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
- * \/ \/ \/ \/ \/
- * $Id$
- *
- * Copyright (C) 2012 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.
- *
- ****************************************************************************/
-#ifndef __HWSTUB__
-#define __HWSTUB__
-
-#include <libusb.h>
-#include "hwstub_protocol.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/**
- *
- * Low-Level interface
- *
- */
-
-struct hwstub_device_t;
-
-/* Returns hwstub interface, or -1 if none was found */
-int hwstub_probe(libusb_device *dev);
-/* Helper function which returns a list of all hwstub devices found. The caller
- * must unref all of them when done, possibly using libusb_free_device_list().
- * Return number of devices or <0 on error */
-ssize_t hwstub_get_device_list(libusb_context *ctx, libusb_device ***list);
-/* Returns NULL on error */
-struct hwstub_device_t *hwstub_open(libusb_device_handle *handle);
-/* Returns 0 on success. Does *NOT* close the usb handle */
-int hwstub_release(struct hwstub_device_t *dev);
-
-/* Returns number of bytes filled */
-int hwstub_get_desc(struct hwstub_device_t *dev, uint16_t desc, void *info, size_t sz);
-/* Returns number of bytes filled */
-int hwstub_get_log(struct hwstub_device_t *dev, void *buf, size_t sz);
-/* Returns number of bytes written/read or <0 on error */
-int hwstub_read(struct hwstub_device_t *dev, uint32_t addr, void *buf, size_t sz);
-int hwstub_read_atomic(struct hwstub_device_t *dev, uint32_t addr, void *buf, size_t sz);
-int hwstub_write(struct hwstub_device_t *dev, uint32_t addr, const void *buf, size_t sz);
-int hwstub_write_atomic(struct hwstub_device_t *dev, uint32_t addr, const void *buf, size_t sz);
-int hwstub_rw_mem(struct hwstub_device_t *dev, int read, uint32_t addr, void *buf, size_t sz);
-int hwstub_rw_mem_atomic(struct hwstub_device_t *dev, int read, uint32_t addr, void *buf, size_t sz);
-/* Returns <0 on error */
-int hwstub_exec(struct hwstub_device_t *dev, uint32_t addr, uint16_t flags);
-int hwstub_call(struct hwstub_device_t *dev, uint32_t addr);
-int hwstub_jump(struct hwstub_device_t *dev, uint32_t addr);
-
-#ifdef __cplusplus
-} // extern "C"
-#endif
-
-#endif /* __HWSTUB__ */ \ No newline at end of file
diff --git a/utils/hwstub/lib/hwstub_net.cpp b/utils/hwstub/lib/hwstub_net.cpp
new file mode 100644
index 0000000000..f561a1004b
--- /dev/null
+++ b/utils/hwstub/lib/hwstub_net.cpp
@@ -0,0 +1,1351 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2016 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_net.hpp"
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <cstddef>
+#include <arpa/inet.h>
+#include <sys/types.h>
+#include <netdb.h>
+
+namespace hwstub {
+namespace net {
+
+/**
+ * Context
+ */
+context::context()
+ :m_state(state::HELLO), m_error(error::SUCCESS)
+{
+}
+
+context::~context()
+{
+}
+
+std::shared_ptr<context> context::create_socket(int socket_fd)
+{
+ // NOTE: can't use make_shared() because of the protected ctor */
+ return std::shared_ptr<socket_context>(new socket_context(socket_fd));
+}
+
+std::string context::default_unix_path()
+{
+ return "hwstub";
+}
+
+std::string context::default_tcp_domain()
+{
+ return "localhost";
+}
+
+std::string context::default_tcp_port()
+{
+ return "6666";
+}
+
+namespace
+{
+ /* len is the total length, including a 0 character if any */
+ int create_unix_low(bool abstract, const char *path, size_t len, bool conn,
+ std::string *error)
+ {
+ struct sockaddr_un address;
+ if(len > sizeof(address.sun_path))
+ {
+ if(error)
+ *error = "unix path is too long";
+ return -1;
+ }
+ int socket_fd = socket(PF_UNIX, SOCK_STREAM, 0);
+ if(socket_fd < 0)
+ {
+ if(error)
+ *error = "socket() failed";
+ return -1;
+ }
+ memset(&address, 0, sizeof(struct sockaddr_un));
+ address.sun_family = AF_UNIX;
+ /* NOTE memcpy, we don't want to add a extra 0 at the end */
+ memcpy(address.sun_path, path, len);
+ /* for abstract name, replace first character by 0 */
+ if(abstract)
+ address.sun_path[0] = 0;
+ /* NOTE sun_path is the last field of the structure */
+ size_t sz = offsetof(struct sockaddr_un, sun_path) + len;
+ /* NOTE don't give sizeof(address) because for abstract names it would contain
+ * extra garbage */
+ if(conn)
+ {
+ if(connect(socket_fd, (struct sockaddr *)&address, sz) != 0)
+ {
+ close(socket_fd);
+ if(error)
+ *error = "connect() failed";
+ return -1;
+ }
+ else
+ return socket_fd;
+ }
+ else
+ {
+ if(bind(socket_fd, (struct sockaddr *)&address, sz) != 0)
+ {
+ close(socket_fd);
+ if(error)
+ *error = "bind() failed";
+ return -1;
+ }
+ else
+ return socket_fd;
+ }
+ }
+
+ int create_tcp_low(const std::string& domain, const std::string& _port, bool server, std::string *error)
+ {
+ std::string port = _port.size() != 0 ? _port : context::default_tcp_port();
+ int socket_fd = -1;
+ struct addrinfo hints;
+ memset(&hints, 0, sizeof(struct addrinfo));
+ hints.ai_family = AF_UNSPEC; /* allow IPv4 or IPv6 */
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = 0;
+ hints.ai_protocol = 0; /* any protocol */
+
+ struct addrinfo *result;
+ int err = getaddrinfo(domain.c_str(), port.c_str(), &hints, &result);
+ if(err != 0)
+ {
+ if(error)
+ *error = std::string("getaddrinfo failed: ") + gai_strerror(err);
+ return -1;
+ }
+
+ /* getaddrinfo() returns a list of address structures.
+ * Try each address until we successfully connect(2).
+ * If socket(2) (or connect(2)/bind(2)) fails, we (close the socket
+ * and) try the next address. */
+ for(struct addrinfo *rp = result; rp != nullptr; rp = rp->ai_next)
+ {
+ socket_fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+ if(socket_fd == -1)
+ continue;
+
+ int err = 0;
+ if(server)
+ err = bind(socket_fd, rp->ai_addr, rp->ai_addrlen);
+ else
+ err = connect(socket_fd, rp->ai_addr, rp->ai_addrlen);
+ if(err < 0)
+ {
+ close(socket_fd);
+ socket_fd = -1;
+ }
+ else
+ break; /* success */
+ }
+ /* no address was tried */
+ if(socket_fd < 0 && error)
+ *error = "getaddrinfo() returned no usable result (socket()/connect()/bind() failed)";
+ return socket_fd;
+ }
+}
+
+std::shared_ptr<context> context::create_tcp(const std::string& domain,
+ const std::string& port, std::string *error)
+{
+ int fd = create_tcp_low(domain, port, false, error);
+ if(fd >= 0)
+ return context::create_socket(fd);
+ else
+ return std::shared_ptr<context>();
+}
+
+std::shared_ptr<context> context::create_unix(const std::string& path, std::string *error)
+{
+ int fd = create_unix_low(false, path.c_str(), path.size() + 1, true, error);
+ if(fd >= 0)
+ return context::create_socket(fd);
+ else
+ return std::shared_ptr<context>();
+}
+
+std::shared_ptr<context> context::create_unix_abstract(const std::string& path, std::string *error)
+{
+ std::string fake_path = "#" + path; /* the # will be overriden by 0 */
+ int fd = create_unix_low(true, fake_path.c_str(), fake_path.size(), true, error);
+ if(fd >= 0)
+ return context::create_socket(fd);
+ else
+ return std::shared_ptr<context>();
+}
+
+uint32_t context::from_ctx_dev(ctx_dev_t dev)
+{
+ return (uint32_t)(uintptr_t)dev; /* NOTE safe because it was originally a 32-bit int */
+}
+
+hwstub::context::ctx_dev_t context::to_ctx_dev(uint32_t dev)
+{
+ return (ctx_dev_t)(uintptr_t)dev; /* NOTE assume that sizeof(void *)>=sizeof(uint32_t) */
+}
+
+error context::fetch_device_list(std::vector<ctx_dev_t>& list, void*& ptr)
+{
+ (void) ptr;
+ delayed_init();
+ if(m_state == state::DEAD)
+ return m_error;
+ uint32_t args[HWSTUB_NET_ARGS] = {0};
+ uint8_t *data = nullptr;
+ size_t data_sz = 0;
+ debug() << "[net::ctx] --> GET_DEV_LIST\n";
+ error err = send_cmd(HWSERVER_GET_DEV_LIST, args, nullptr, 0, &data, &data_sz);
+ debug() << "[net::ctx] <-- GET_DEV_LIST ";
+ if(err != error::SUCCESS)
+ {
+ debug() << "failed: " << error_string(err) << "\n";
+ return err;
+ }
+ /* sanity check on size */
+ if(data_sz % 4)
+ {
+ debug() << "failed: invalid list size\n";
+ delete[] data;
+ return error::PROTOCOL_ERROR;
+ }
+ debug() << "\n";
+ list.clear();
+ /* each entry is a 32-bit ID in network order size */
+ uint32_t *data_list = (uint32_t *)data;
+ for(size_t i = 0; i < data_sz / 4; i++)
+ list.push_back(to_ctx_dev(from_net_order(data_list[i])));
+ delete[] data;
+ return error::SUCCESS;
+}
+
+void context::destroy_device_list(void *ptr)
+{
+ (void)ptr;
+}
+
+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->device_id() == from_ctx_dev(dev);
+}
+
+uint32_t context::to_net_order(uint32_t u)
+{
+ return htonl(u);
+}
+
+uint32_t context::from_net_order(uint32_t u)
+{
+ return ntohl(u);
+}
+
+error context::send_cmd(uint32_t cmd, uint32_t args[HWSTUB_NET_ARGS], uint8_t *send_data,
+ size_t send_size, uint8_t **recv_data, size_t *recv_size)
+{
+ /* make sure with have the lock, this function might be called concurrently
+ * by the different threads */
+ std::unique_lock<std::recursive_mutex> lock(m_mutex);
+
+ if(m_state == state::DEAD)
+ return m_error;
+ /* do a delayed init, unless with are doing a HELLO */
+ if(m_state == state::HELLO && cmd != HWSERVER_HELLO)
+ delayed_init();
+ /* build header */
+ struct hwstub_net_hdr_t hdr;
+ hdr.magic = to_net_order(HWSERVER_MAGIC);
+ hdr.cmd = to_net_order(cmd);
+ for(size_t i = 0; i < HWSTUB_NET_ARGS; i++)
+ hdr.args[i] = to_net_order(args[i]);
+ hdr.length = to_net_order((uint32_t)send_size);
+ /* send header */
+ size_t sz = sizeof(hdr);
+ error err = send((void *)&hdr, sz);
+ if(err != error::SUCCESS)
+ {
+ m_state = state::DEAD;
+ m_error = err;
+ return err;
+ }
+ if(sz != sizeof(hdr))
+ {
+ m_state = state::DEAD;
+ m_error = error::PROTOCOL_ERROR;
+ }
+ /* send data */
+ if(send_size > 0)
+ {
+ sz = send_size;
+ err = send((void *)send_data, sz);
+ if(err != error::SUCCESS)
+ {
+ m_state = state::DEAD;
+ m_error = err;
+ return err;
+ }
+ if(sz != send_size)
+ {
+ m_state = state::DEAD;
+ m_error = error::PROTOCOL_ERROR;
+ }
+ }
+ /* receive header */
+ sz = sizeof(hdr);
+ err = recv((void *)&hdr, sz);
+ if(err != error::SUCCESS)
+ {
+ m_state = state::DEAD;
+ m_error = err;
+ return err;
+ }
+ if(sz != sizeof(hdr))
+ {
+ m_state = state::DEAD;
+ m_error = error::PROTOCOL_ERROR;
+ return m_error;
+ }
+ /* correct byte order */
+ hdr.magic = from_net_order(hdr.magic);
+ hdr.cmd = from_net_order(hdr.cmd);
+ hdr.length = from_net_order(hdr.length);
+ /* copy arguments */
+ for(size_t i = 0; i < HWSTUB_NET_ARGS; i++)
+ args[i] = from_net_order(hdr.args[i]);
+ /* check header */
+ if(hdr.magic != HWSERVER_MAGIC)
+ {
+ m_state = state::DEAD;
+ m_error = error::PROTOCOL_ERROR;
+ return m_error;
+ }
+ /* check NACK */
+ if(hdr.cmd == HWSERVER_NACK(cmd))
+ {
+ /* translate error */
+ switch(args[0])
+ {
+ case HWERR_FAIL: err = error::ERROR; break;
+ case HWERR_INVALID_ID: err = error::ERROR; break; /* should not happen */
+ case HWERR_DISCONNECTED: err = error::DISCONNECTED; break;
+ }
+ return err;
+ }
+ /* check not ACK */
+ if(hdr.cmd != HWSERVER_ACK(cmd))
+ {
+ m_state = state::DEAD;
+ m_error = error::PROTOCOL_ERROR;
+ return m_error;
+ }
+ /* receive additional data */
+ uint8_t *data = nullptr;
+ if(hdr.length > 0)
+ {
+ data = new uint8_t[hdr.length];
+ sz = hdr.length;
+ err = recv((void *)data, sz);
+ if(err != error::SUCCESS)
+ {
+ m_state = state::DEAD;
+ m_error = err;
+ return err;
+ }
+ if(sz != hdr.length)
+ {
+ m_state = state::DEAD;
+ m_error = error::PROTOCOL_ERROR;
+ return m_error;
+ }
+ }
+ /* copy data if user want it */
+ if(recv_data)
+ {
+ if(*recv_data == nullptr)
+ {
+ *recv_data = data;
+ *recv_size = hdr.length;
+ }
+ else if(*recv_size < hdr.length)
+ {
+ delete[] data;
+ return error::OVERFLW;
+ }
+ else
+ {
+ *recv_size = hdr.length;
+ memcpy(*recv_data, data, *recv_size);
+ delete[] data;
+ }
+ }
+ /* throw it away otherwise */
+ else
+ {
+ delete[] data;
+ }
+ return error::SUCCESS;
+}
+
+void context::delayed_init()
+{
+ /* only do HELLO if we haven't do it yet */
+ if(m_state != state::HELLO)
+ return;
+ debug() << "[net::ctx] --> HELLO " << HWSTUB_VERSION_MAJOR << "."
+ << HWSTUB_VERSION_MINOR << "\n";
+ /* send HELLO with our version and see what the server is up to */
+ uint32_t args[HWSTUB_NET_ARGS] = {0};
+ args[0] = HWSTUB_VERSION_MAJOR << 8 | HWSTUB_VERSION_MINOR;
+ error err = send_cmd(HWSERVER_HELLO, args, nullptr, 0, nullptr, nullptr);
+ if(err != error::SUCCESS)
+ {
+ debug() << "[net::ctx] <-- HELLO failed: " << error_string(err) << "\n";
+ m_state = state::DEAD;
+ m_error = err;
+ return;
+ }
+ /* check the server is running the same version */
+ debug() << "[net::ctx] <-- HELLO " << ((args[0] & 0xff00) >> 8) << "." << (args[0] & 0xff) << "";
+ if(args[0] != (HWSTUB_VERSION_MAJOR << 8 | HWSTUB_VERSION_MINOR))
+ {
+ debug() << " (mismatch)\n";
+ m_state = state::DEAD;
+ m_error = error::SERVER_MISMATCH;
+ }
+ debug() << " (good)\n";
+ /* good, we can now send commands */
+ m_state = state::IDLE;
+}
+
+void context::stop_context()
+{
+ /* make sure with have the lock, this function might be call asynchronously */
+ std::unique_lock<std::recursive_mutex> lock(m_mutex);
+ /* if dead, don't do anything */
+ if(m_state == state::DEAD)
+ return;
+ /* only send BYE if we are initialized */
+ if(m_state == state::IDLE)
+ {
+ debug() << "[net::ctx] --> BYE\n";
+ /* send BYE */
+ uint32_t args[HWSTUB_NET_ARGS] = {0};
+ error err = send_cmd(HWSERVER_BYE, args, nullptr, 0, nullptr, nullptr);
+ if(err != error::SUCCESS)
+ {
+ debug() << "[net::ctx] <-- BYE failed: " << error_string(err) << "\n";
+ m_state = state::DEAD;
+ m_error = err;
+ return;
+ }
+ debug() << "[net::ctx] <-- BYE\n";
+ }
+ /* now we are dead */
+ m_state = state::DEAD;
+ m_error = error::SERVER_DISCONNECTED;
+}
+
+/**
+ * Socket context
+ */
+socket_context::socket_context(int socket_fd)
+ :m_socketfd(socket_fd)
+{
+ set_timeout(std::chrono::milliseconds(1000));
+}
+
+socket_context::~socket_context()
+{
+ stop_context();
+ close(m_socketfd);
+}
+
+void socket_context::set_timeout(std::chrono::milliseconds ms)
+{
+ struct timeval tv;
+ tv.tv_usec = 1000 * (ms.count() % 1000);
+ tv.tv_sec = ms.count() / 1000;
+ /* set timeout for the client operations */
+ setsockopt(m_socketfd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv));
+ setsockopt(m_socketfd, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof(tv));
+}
+
+error socket_context::send(void *buffer, size_t& sz)
+{
+ debug() << "[net::ctx::sock] send(" << sz << "): ";
+ int ret = ::send(m_socketfd, buffer, sz, MSG_NOSIGNAL);
+ if(ret >= 0)
+ {
+ debug() << "good(" << ret << ")\n";
+ sz = (size_t)ret;
+ return error::SUCCESS;
+ }
+ /* convert some errors */
+ debug() << "fail(" << errno << "," << strerror(errno) << ")\n";
+ switch(errno)
+ {
+#if EAGAIN != EWOULDBLOCK
+ case EAGAIN:
+#endif
+ case EWOULDBLOCK: return error::TIMEOUT;
+ case ECONNRESET: case EPIPE: return error::SERVER_DISCONNECTED;
+ default: return error::NET_ERROR;
+ }
+}
+
+error socket_context::recv(void *buffer, size_t& sz)
+{
+ debug() << "[net::ctx::sock] recv(" << sz << "): ";
+ int ret = ::recv(m_socketfd, buffer, sz, MSG_WAITALL);
+ if(ret > 0)
+ {
+ debug() << "good(" << ret << ")\n";
+ sz = (size_t)ret;
+ return error::SUCCESS;
+ }
+ if(ret == 0)
+ {
+ debug() << "disconnected\n";
+ return error::SERVER_DISCONNECTED;
+ }
+ debug() << "fail(" << errno << "," << strerror(errno) << ")\n";
+ switch(errno)
+ {
+#if EAGAIN != EWOULDBLOCK
+ case EAGAIN:
+#endif
+ case EWOULDBLOCK: return error::TIMEOUT;
+ default: return error::NET_ERROR;
+ }
+}
+
+/**
+ * Device
+ */
+device::device(std::shared_ptr<hwstub::context> ctx, uint32_t devid)
+ :hwstub::device(ctx), m_device_id(devid)
+{
+}
+
+device::~device()
+{
+}
+
+uint32_t device::device_id()
+{
+ return m_device_id;
+}
+
+error device::open_dev(std::shared_ptr<hwstub::handle>& handle)
+{
+ std::shared_ptr<hwstub::context> hctx = get_context();
+ if(!hctx)
+ return error::NO_CONTEXT;
+ context *ctx = dynamic_cast<context*>(hctx.get());
+ ctx->debug() << "[net::dev] --> DEV_OPEN(" << m_device_id << ")\n";
+ /* ask the server to open the device, note that the device ID may not exists
+ * anymore */
+ uint32_t args[HWSTUB_NET_ARGS] = {0};
+ args[0] = m_device_id;
+ error err = ctx->send_cmd(HWSERVER_DEV_OPEN, args, nullptr, 0, nullptr, nullptr);
+ if(err != error::SUCCESS)
+ {
+ ctx->debug() << "[net::ctx::dev] <-- DEV_OPEN failed: " << error_string(err) << "\n";
+ return err;
+ }
+ ctx->debug() << "[net::ctx::dev] <-- DEV_OPEN: handle = " << args[0] << "\n";
+ // NOTE: can't use make_shared() because of the protected ctor */
+ handle.reset(new hwstub::net::handle(shared_from_this(), args[0]));
+ return error::SUCCESS;
+}
+
+bool device::has_multiple_open() const
+{
+ return false;
+}
+
+/**
+ * Handle
+ */
+handle::handle(std::shared_ptr<hwstub::device> dev, uint32_t hid)
+ :hwstub::handle(dev), m_handle_id(hid)
+{
+}
+
+handle::~handle()
+{
+ /* try to close the handle, if context is still accessible */
+ std::shared_ptr<hwstub::context> hctx = get_device()->get_context();
+ if(hctx)
+ {
+ context *ctx = dynamic_cast<context*>(hctx.get());
+ ctx->debug() << "[net::handle] --> DEV_CLOSE(" << m_handle_id << ")\n";
+ uint32_t args[HWSTUB_NET_ARGS] = {0};
+ args[0] = m_handle_id;
+ error err = ctx->send_cmd(HWSERVER_DEV_CLOSE, args, nullptr, 0, nullptr, nullptr);
+ if(err != error::SUCCESS)
+ ctx->debug() << "[net::handle] <-- DEV_CLOSE failed: " << error_string(err) << "\n";
+ else
+ ctx->debug() << "[net::handle] <-- DEV_CLOSE\n";
+ }
+}
+
+error handle::read_dev(uint32_t addr, void *buf, size_t& sz, bool atomic)
+{
+ std::shared_ptr<hwstub::context> hctx = get_device()->get_context();
+ if(!hctx)
+ return error::NO_CONTEXT;
+
+ context *ctx = dynamic_cast<context*>(hctx.get());
+ ctx->debug() << "[net::handle] --> READ(" << m_handle_id << ",0x" << std::hex
+ << addr << "," << sz << "," << atomic << ")\n";
+ uint32_t args[HWSTUB_NET_ARGS] = {0};
+ args[0] = m_handle_id;
+ args[1] = addr;
+ args[2] = sz;
+ args[3] = atomic ? HWSERVER_RW_ATOMIC : 0;
+ error err = ctx->send_cmd(HWSERVER_READ, args, nullptr, 0, (uint8_t **)&buf, &sz);
+ if(err != error::SUCCESS)
+ {
+ ctx->debug() << "[net::handle] <-- READ failed: " << error_string(err) << "\n";
+ return err;
+ }
+ ctx->debug() << "[net::handle] <-- READ\n";
+ return error::SUCCESS;
+}
+
+error handle::write_dev(uint32_t addr, const void *buf, size_t& sz, bool atomic)
+{
+ std::shared_ptr<hwstub::context> hctx = get_device()->get_context();
+ if(!hctx)
+ return error::NO_CONTEXT;
+
+ context *ctx = dynamic_cast<context*>(hctx.get());
+ ctx->debug() << "[net::handle] --> WRITE(" << m_handle_id << ",0x" << std::hex
+ << addr << "," << sz << "," << atomic << ")\n";
+ uint32_t args[HWSTUB_NET_ARGS] = {0};
+ args[0] = m_handle_id;
+ args[1] = addr;
+ args[2] = atomic ? HWSERVER_RW_ATOMIC : 0;
+ error err = ctx->send_cmd(HWSERVER_WRITE, args, (uint8_t *)buf, sz, nullptr, nullptr);
+ if(err != error::SUCCESS)
+ {
+ ctx->debug() << "[net::handle] <-- WRITE failed: " << error_string(err) << "\n";
+ return err;
+ }
+ ctx->debug() << "[net::handle] <-- WRITE\n";
+ return error::SUCCESS;
+}
+
+error handle::get_dev_desc(uint16_t desc, void *buf, size_t& buf_sz)
+{
+ std::shared_ptr<hwstub::context> hctx = get_device()->get_context();
+ if(!hctx)
+ return error::NO_CONTEXT;
+
+ context *ctx = dynamic_cast<context*>(hctx.get());
+ ctx->debug() << "[net::handle] --> GET_DESC(" << m_handle_id << ",0x" << std::hex
+ << desc << "," << buf_sz << ")\n";
+ uint32_t args[HWSTUB_NET_ARGS] = {0};
+ args[0] = m_handle_id;
+ args[1] = desc;
+ args[2] = buf_sz;
+ error err = ctx->send_cmd(HWSERVER_GET_DESC, args, nullptr, 0, (uint8_t **)&buf, &buf_sz);
+ if(err != error::SUCCESS)
+ {
+ ctx->debug() << "[net::handle] <-- GET_DESC failed: " << error_string(err) << "\n";
+ return err;
+ }
+ ctx->debug() << "[net::handle] <-- GET_DESC\n";
+ return error::SUCCESS;
+}
+
+error handle::get_dev_log(void *buf, size_t& buf_sz)
+{
+ std::shared_ptr<hwstub::context> hctx = get_device()->get_context();
+ if(!hctx)
+ return error::NO_CONTEXT;
+
+ context *ctx = dynamic_cast<context*>(hctx.get());
+ ctx->debug() << "[net::handle] --> GET_LOG(" << buf_sz << ")\n";
+ uint32_t args[HWSTUB_NET_ARGS] = {0};
+ args[0] = m_handle_id;
+ args[1] = buf_sz;
+ error err = ctx->send_cmd(HWSERVER_GET_LOG, args, nullptr, 0, (uint8_t **)&buf, &buf_sz);
+ if(err != error::SUCCESS)
+ {
+ ctx->debug() << "[net::handle] <-- GET_LOG failed: " << error_string(err) << "\n";
+ return err;
+ }
+ ctx->debug() << "[net::handle] <-- GET_LOG\n";
+ return error::SUCCESS;
+}
+
+error handle::exec_dev(uint32_t addr, uint16_t flags)
+{
+ (void) addr;
+ (void) flags;
+ return error::DUMMY;
+}
+
+error handle::status() const
+{
+ return hwstub::handle::status();
+}
+
+size_t handle::get_buffer_size()
+{
+ return 2048;
+}
+
+/**
+ * Server
+ */
+server::server(std::shared_ptr<hwstub::context> ctx)
+ :m_context(ctx)
+{
+ clear_debug();
+}
+
+server::~server()
+{
+}
+
+void server::stop_server()
+{
+ std::unique_lock<std::recursive_mutex> lock(m_mutex);
+ /* ask all client threads to stop */
+ for(auto& cl : m_client)
+ cl.exit = true;
+ /* wait for each thread to stop */
+ for(auto& cl : m_client)
+ cl.future.wait();
+}
+
+std::shared_ptr<server> server::create_unix(std::shared_ptr<hwstub::context> ctx,
+ const std::string& path, std::string *error)
+{
+ int fd = create_unix_low(false, path.c_str(), path.size() + 1, false, error);
+ if(fd >= 0)
+ return socket_server::create_socket(ctx, fd);
+ else
+ return std::shared_ptr<server>();
+}
+
+std::shared_ptr<server> server::create_unix_abstract(std::shared_ptr<hwstub::context> ctx,
+ const std::string& path, std::string *error)
+{
+ std::string fake_path = "#" + path; /* the # will be overriden by 0 */
+ int fd = create_unix_low(true, fake_path.c_str(), fake_path.size(), false, error);
+ if(fd >= 0)
+ return socket_server::create_socket(ctx, fd);
+ else
+ return std::shared_ptr<server>();
+}
+
+std::shared_ptr<server> server::create_socket(std::shared_ptr<hwstub::context> ctx,
+ int socket_fd)
+{
+ return socket_server::create(ctx, socket_fd);
+}
+
+std::shared_ptr<server> server::create_tcp(std::shared_ptr<hwstub::context> ctx,
+ const std::string& domain, const std::string& port, std::string *error)
+{
+ int fd = create_tcp_low(domain, port, true, error);
+ if(fd >= 0)
+ return socket_server::create_socket(ctx, fd);
+ else
+ return std::shared_ptr<server>();
+}
+
+void server::set_debug(std::ostream& os)
+{
+ m_debug = &os;
+}
+
+std::ostream& server::debug()
+{
+ return *m_debug;
+}
+
+server::client_state::client_state(srv_client_t cl, std::future<void>&& f)
+ :client(cl), future(std::move(f)), exit(false), next_dev_id(42),
+ next_handle_id(19)
+{
+}
+
+void server::client_thread2(server *s, client_state *cs)
+{
+ s->client_thread(cs);
+}
+
+uint32_t server::to_net_order(uint32_t u)
+{
+ return htonl(u);
+}
+
+uint32_t server::from_net_order(uint32_t u)
+{
+ return ntohl(u);
+}
+
+void server::client_thread(client_state *state)
+{
+ debug() << "[net::srv::client] start: " << state->client << "\n";
+ while(!state->exit)
+ {
+ /* wait for some header */
+ struct hwstub_net_hdr_t hdr;
+ size_t sz = sizeof(hdr);
+ error err;
+ /* wait for some command, or exit flag */
+ do
+ err = recv(state->client, (void *)&hdr, sz);
+ while(err == error::TIMEOUT && !state->exit);
+ if(state->exit || err != error::SUCCESS || sz != sizeof(hdr))
+ break;
+ /* convert to host order */
+ hdr.magic = from_net_order(hdr.magic);
+ hdr.cmd = from_net_order(hdr.cmd);
+ hdr.length = from_net_order(hdr.length);
+ /* copy arguments */
+ for(size_t i = 0; i < HWSTUB_NET_ARGS; i++)
+ hdr.args[i] = from_net_order(hdr.args[i]);
+ /* check header */
+ if(hdr.magic != HWSERVER_MAGIC)
+ break;
+ /* receive data
+ * FIXME check length here */
+ uint8_t *data = nullptr;
+ if(hdr.length > 0)
+ {
+ data = new uint8_t[hdr.length];
+ sz = hdr.length;
+ /* wait for some command, or exit flag */
+ do
+ err = recv(state->client, (void *)data, sz);
+ while(err == error::TIMEOUT && !state->exit);
+ if(state->exit || err != error::SUCCESS || sz != hdr.length)
+ {
+ delete[] data;
+ break;
+ }
+ }
+ /* hande command */
+ uint8_t *send_data = nullptr;
+ size_t send_size = 0;
+ err = handle_cmd(state, hdr.cmd, hdr.args, data, hdr.length,
+ send_data, send_size);
+ /* free data */
+ delete[] data;
+ /* construct header */
+ if(err != error::SUCCESS)
+ {
+ hdr.magic = to_net_order(HWSERVER_MAGIC);
+ hdr.cmd = to_net_order(HWSERVER_NACK(hdr.cmd));
+ hdr.length = to_net_order(0);
+ hdr.args[0] = to_net_order(HWERR_FAIL);
+ for(size_t i = 1; i < HWSTUB_NET_ARGS; i++)
+ hdr.args[i] = to_net_order(0);
+ send_size = 0;
+ }
+ else
+ {
+ hdr.magic = to_net_order(HWSERVER_MAGIC);
+ hdr.cmd = to_net_order(HWSERVER_ACK(hdr.cmd));
+ hdr.length = to_net_order(send_size);
+ for(size_t i = 0; i < HWSTUB_NET_ARGS; i++)
+ hdr.args[i] = to_net_order(hdr.args[i]);
+ }
+ /* send header */
+ sz = sizeof(hdr);
+ do
+ err = send(state->client, (void *)&hdr, sz);
+ while(err == error::TIMEOUT && !state->exit);
+ if(state->exit || err != error::SUCCESS || sz != sizeof(hdr))
+ {
+ delete[] send_data;
+ break;
+ }
+ /* send data if there is some */
+ if(send_size > 0)
+ {
+ sz = send_size;
+ do
+ err = send(state->client, (void *)send_data, sz);
+ while(err == error::TIMEOUT && !state->exit);
+ delete[] send_data;
+ if(state->exit || err != error::SUCCESS || sz != send_size)
+ break;
+ }
+ }
+ debug() << "[net::srv::client] stop: " << state->client << "\n";
+ /* clean client state to avoiding keeping references to objets */
+ state->dev_map.clear();
+ state->handle_map.clear();
+ /* kill client */
+ terminate_client(state->client);
+}
+
+void server::client_arrived(srv_client_t client)
+{
+ std::unique_lock<std::recursive_mutex> lock(m_mutex);
+ debug() << "[net::srv] client arrived: " << client << "\n";
+ /* the naive way would be to use a std::thread but this class is annoying
+ * because it is impossible to check if a thread has exited, except by calling
+ * join() which is blocking. Fortunately, std::packaged_task and std::future
+ * provide a way around this */
+
+ std::packaged_task<void(server*, client_state*)> task(&server::client_thread2);
+ m_client.emplace_back(client, task.get_future());
+ std::thread(std::move(task), this, &m_client.back()).detach();
+}
+
+void server::client_left(srv_client_t client)
+{
+ std::unique_lock<std::recursive_mutex> lock(m_mutex);
+ debug() << "[net::srv] client left: " << client << "\n";
+ /* find thread and set its exit flag, also cleanup threads that finished */
+ for(auto it = m_client.begin(); it != m_client.end();)
+ {
+ /* check if thread has finished */
+ if(it->future.wait_for(std::chrono::milliseconds(0)) == std::future_status::ready)
+ {
+ it = m_client.erase(it);
+ continue;
+ }
+ /* set exit flag if this our thread */
+ if(it->client == client)
+ it->exit = true;
+ ++it;
+ }
+}
+
+error server::handle_cmd(client_state *state, uint32_t cmd, uint32_t args[HWSTUB_NET_ARGS],
+ uint8_t *recv_data, size_t recv_size, uint8_t*& send_data, size_t& send_size)
+{
+ send_data = nullptr;
+ send_size = 0;
+ /* NOTE: commands are serialized by the client thread, this function is thus
+ * thread safe WITH RESPECT TO CLIENT DATA. If you need to use global data here,
+ * protect it by a mutex or make sure it is safe (hwstub context is thread-safe) */
+
+ /* HELLO */
+ if(cmd == HWSERVER_HELLO)
+ {
+ debug() << "[net::srv::cmd] --> HELLO " << ((args[0] & 0xff00) >> 8)
+ << "." << (args[0] & 0xff);
+ if(args[0] != (HWSTUB_VERSION_MAJOR << 8 | HWSTUB_VERSION_MINOR))
+ {
+ debug() << " (mismatch)\n";
+ return error::ERROR;
+ }
+ debug() << " (good)\n";
+ debug() << "[net::srv::cmd] <-- HELLO " << HWSTUB_VERSION_MAJOR << "."
+ << HWSTUB_VERSION_MINOR << "\n";
+ /* send HELLO with our version */
+ args[0] = HWSTUB_VERSION_MAJOR << 8 | HWSTUB_VERSION_MINOR;
+ return error::SUCCESS;
+ }
+ /* BYE */
+ else if(cmd == HWSERVER_BYE)
+ {
+ debug() << "[net::srv::cmd] --> BYE\n";
+ /* ask client thread to exit after this */
+ state->exit = true;
+ debug() << "[net::srv::cmd] <-- BYE\n";
+ return error::SUCCESS;
+ }
+ /* GET_DEV_LIST */
+ else if(cmd == HWSERVER_GET_DEV_LIST)
+ {
+ debug() << "[net::srv::cmd] --> GET_DEV_LIST\n";
+ /* fetch list again */
+ std::vector<std::shared_ptr<hwstub::device>> list;
+ error err = m_context->get_device_list(list);
+ if(err != error::SUCCESS)
+ {
+ debug() << "[net::srv::cmd] cannot fetch list: " << hwstub::error_string(err) << "\n";
+ debug() << "[net::srv::cmd] <-- GET_DEV_LIST (error)\n";
+ return err;
+ }
+ /* update list: drop device that left */
+ std::vector<uint32_t> to_drop;
+ for(auto it : state->dev_map)
+ {
+ bool still_there = false;
+ /* this has quadratic complexity, optimize this if needed */
+ for(auto dev : list)
+ if(it.second == dev)
+ still_there = true;
+ if(!still_there)
+ to_drop.push_back(it.first);
+ }
+ for(auto id : to_drop)
+ state->dev_map.erase(state->dev_map.find(id));
+ /* add new devices */
+ std::vector<std::shared_ptr<hwstub::device>> to_add;
+ for(auto dev : list)
+ {
+ bool already_there = false;
+ for(auto it : state->dev_map)
+ if(it.second == dev)
+ already_there = true;
+ if(!already_there)
+ to_add.push_back(dev);
+ }
+ for(auto dev : to_add)
+ state->dev_map[state->next_dev_id++] = dev;
+ /* create response list */
+ send_size = sizeof(uint32_t) * state->dev_map.size();
+ send_data = new uint8_t[send_size];
+ uint32_t *p = (uint32_t *)send_data;
+ for(auto it : state->dev_map)
+ *p++ = to_net_order(it.first);
+ debug() << "[net::srv::cmd] <-- GET_DEV_LIST\n";
+ return error::SUCCESS;
+ }
+ /* DEV_OPEN */
+ else if(cmd == HWSERVER_DEV_OPEN)
+ {
+ uint32_t devid = args[0];
+ debug() << "[net::srv::cmd] --> DEV_OPEN(" << devid << ")\n";
+ /* check ID is valid */
+ auto it = state->dev_map.find(devid);
+ if(it == state->dev_map.end())
+ {
+ debug() << "[net::srv::cmd] unknwon device ID\n";
+ debug() << "[net::srv::cmd] <-- DEV_OPEN (error)\n";
+ return error::ERROR;
+ }
+ /* good, now try to get a handle */
+ std::shared_ptr<hwstub::handle> handle;
+ error err = it->second->open(handle);
+ if(err != error::SUCCESS)
+ {
+ debug() << "[net::srv::cmd] cannot open device: " << hwstub::error_string(err) << "\n";
+ return err;
+ }
+ /* record ID and return it */
+ args[0] = state->next_handle_id;
+ state->handle_map[state->next_handle_id++] = handle;
+ return error::SUCCESS;
+ }
+ /* DEV_CLOSE */
+ else if(cmd == HWSERVER_DEV_CLOSE)
+ {
+ uint32_t hid = args[0];
+ debug() << "[net::srv::cmd] --> DEV_CLOSE(" << hid << ")\n";
+ /* check ID is valid */
+ auto it = state->handle_map.find(hid);
+ if(it == state->handle_map.end())
+ {
+ debug() << "[net::srv::cmd] unknwon handle ID\n";
+ debug() << "[net::srv::cmd] <-- DEV_CLOSE (error)\n";
+ return error::ERROR;
+ }
+ /* release ID and handle */
+ state->handle_map.erase(it);
+ debug() << "[net::srv::cmd] <-- DEV_CLOSE\n";
+ return error::SUCCESS;
+ }
+ /* HWSERVER_GET_DESC */
+ else if(cmd == HWSERVER_GET_DESC)
+ {
+ uint32_t hid = args[0];
+ uint32_t did = args[1];
+ uint32_t len = args[2];
+ debug() << "[net::srv::cmd] --> GET_DESC(" << hid << ",0x" << std::hex << did
+ << "," << len << ")\n";
+ /* check ID is valid */
+ auto it = state->handle_map.find(hid);
+ if(it == state->handle_map.end())
+ {
+ debug() << "[net::srv::cmd] unknown handle ID\n";
+ debug() << "[net::srv::cmd] <-- GET_DESC (error)\n";
+ return error::ERROR;
+ }
+ /* query desc */
+ send_size = len;
+ send_data = new uint8_t[send_size];
+ error err = it->second->get_desc(did, send_data, send_size);
+ if(err != error::SUCCESS)
+ {
+ delete[] send_data;
+ debug() << "[net::srv::cmd] cannot get descriptor: " << error_string(err) << "\n";
+ debug() << "[net::srv::cmd] <-- GET_DESC (error)\n";
+ return err;
+ }
+ debug() << "[net::srv::cmd] <-- GET_DESC\n";
+ return error::SUCCESS;
+ }
+ /* HWSERVER_GET_LOG */
+ else if(cmd == HWSERVER_GET_LOG)
+ {
+ uint32_t hid = args[0];
+ uint32_t len = args[1];
+ debug() << "[net::srv::cmd] --> GET_LOG(" << hid << "," << len << ")\n";
+ /* check ID is valid */
+ auto it = state->handle_map.find(hid);
+ if(it == state->handle_map.end())
+ {
+ debug() << "[net::srv::cmd] unknown handle ID\n";
+ debug() << "[net::srv::cmd] <-- GET_DESC (error)\n";
+ return error::ERROR;
+ }
+ /* query log */
+ send_size = len;
+ send_data = new uint8_t[send_size];
+ error err = it->second->get_log(send_data, send_size);
+ if(err != error::SUCCESS)
+ {
+ delete[] send_data;
+ debug() << "[net::srv::cmd] cannot get log: " << error_string(err) << "\n";
+ debug() << "[net::srv::cmd] <-- GET_LOG (error)\n";
+ return err;
+ }
+ if(send_size == 0)
+ delete[] send_data;
+ debug() << "[net::srv::cmd] <-- GET_LOG\n";
+ return error::SUCCESS;
+ }
+ /* HWSERVER_READ */
+ else if(cmd == HWSERVER_READ)
+ {
+ uint32_t hid = args[0];
+ uint32_t addr = args[1];
+ uint32_t len = args[2];
+ uint32_t flags = args[3];
+ debug() << "[net::srv::cmd] --> READ(" << hid << ",0x" << std::hex << addr << ","
+ << len << ",0x" << std::hex << flags << ")\n";
+ /* check ID is valid */
+ auto it = state->handle_map.find(hid);
+ if(it == state->handle_map.end())
+ {
+ debug() << "[net::srv::cmd] unknown handle ID\n";
+ debug() << "[net::srv::cmd] <-- READ (error)\n";
+ return error::ERROR;
+ }
+ /* read */
+ send_size = len;
+ send_data = new uint8_t[send_size];
+ error err = it->second->read(addr, send_data, send_size, !!(flags & HWSERVER_RW_ATOMIC));
+ if(err != error::SUCCESS)
+ {
+ delete[] send_data;
+ debug() << "[net::srv::cmd] cannot read: " << error_string(err) << "\n";
+ debug() << "[net::srv::cmd] <-- READ (error)\n";
+ return err;
+ }
+ debug() << "[net::srv::cmd] <-- READ\n";
+ return error::SUCCESS;
+ }
+ /* HWSERVER_WRITE */
+ else if(cmd == HWSERVER_WRITE)
+ {
+ uint32_t hid = args[0];
+ uint32_t addr = args[1];
+ uint32_t flags = args[2];
+ debug() << "[net::srv::cmd] --> WRITE(" << hid << ",0x" << std::hex << addr << ","
+ << recv_size << ",0x" << std::hex << flags << ")\n";
+ /* check ID is valid */
+ auto it = state->handle_map.find(hid);
+ if(it == state->handle_map.end())
+ {
+ debug() << "[net::srv::cmd] unknown handle ID\n";
+ debug() << "[net::srv::cmd] <-- WRITE (error)\n";
+ return error::ERROR;
+ }
+ /* write */
+ error err = it->second->write(addr, recv_data, recv_size, !!(flags & HWSERVER_RW_ATOMIC));
+ if(err != error::SUCCESS)
+ {
+ delete[] send_data;
+ debug() << "[net::srv::cmd] cannot write: " << error_string(err) << "\n";
+ debug() << "[net::srv::cmd] <-- WRITE (error)\n";
+ return err;
+ }
+ debug() << "[net::srv::cmd] <-- WRITE\n";
+ return error::SUCCESS;
+ }
+ else
+ {
+ debug() << "[net::srv::cmd] <-> unknown cmd (0x" << std::hex << cmd << ")\n";
+ return error::ERROR;
+ }
+}
+
+/*
+ * Socket server
+ */
+socket_server::socket_server(std::shared_ptr<hwstub::context> contex, int socket_fd)
+ :server(contex), m_socketfd(socket_fd)
+{
+ m_discovery_exit = false;
+ set_timeout(std::chrono::milliseconds(1000));
+ m_discovery_thread = std::thread(&socket_server::discovery_thread1, this);
+}
+
+socket_server::~socket_server()
+{
+ /* first stop discovery thread to make sure no more clients are created */
+ m_discovery_exit = true;
+ m_discovery_thread.join();
+ close(m_socketfd);
+ /* ask server to do a clean stop */
+ stop_server();
+}
+
+std::shared_ptr<server> socket_server::create(std::shared_ptr<hwstub::context> ctx,
+ int socket_fd)
+{
+ // NOTE: can't use make_shared() because of the protected ctor */
+ return std::shared_ptr<socket_server>(new socket_server(ctx, socket_fd));
+}
+
+void socket_server::set_timeout(std::chrono::milliseconds ms)
+{
+ m_timeout.tv_usec = 1000 * (ms.count() % 1000);
+ m_timeout.tv_sec = ms.count() / 1000;
+}
+
+int socket_server::from_srv_client(srv_client_t cli)
+{
+ return (int)(intptr_t)cli;
+}
+
+socket_server::srv_client_t socket_server::to_srv_client(int fd)
+{
+ return (srv_client_t)(intptr_t)fd;
+}
+
+void socket_server::discovery_thread1(socket_server *s)
+{
+ s->discovery_thread();
+}
+
+void socket_server::terminate_client(srv_client_t client)
+{
+ debug() << "[net::srv::sock] terminate client: " << client << "\n";
+ /* simply close connection */
+ close(from_srv_client(client));
+}
+
+error socket_server::send(srv_client_t client, void *buffer, size_t& sz)
+{
+ debug() << "[net::ctx::sock] send(" << client << ", " << sz << "): ";
+ int ret = ::send(from_srv_client(client), buffer, sz, MSG_NOSIGNAL);
+ if(ret >= 0)
+ {
+ debug() << "good(" << ret << ")\n";
+ sz = (size_t)ret;
+ return error::SUCCESS;
+ }
+ /* convert some errors */
+ debug() << "fail(" << errno << "," << strerror(errno) << ")\n";
+ switch(errno)
+ {
+#if EAGAIN != EWOULDBLOCK
+ case EAGAIN:
+#endif
+ case EWOULDBLOCK: return error::TIMEOUT;
+ case ECONNRESET: case EPIPE: return error::SERVER_DISCONNECTED;
+ default: return error::NET_ERROR;
+ }
+}
+
+error socket_server::recv(srv_client_t client, void *buffer, size_t& sz)
+{
+ debug() << "[net::ctx::sock] recv(" << client << ", " << sz << "): ";
+ int ret = ::recv(from_srv_client(client), buffer, sz, MSG_WAITALL);
+ if(ret > 0)
+ {
+ debug() << "good(" << ret << ")\n";
+ sz = (size_t)ret;
+ return error::SUCCESS;
+ }
+ if(ret == 0)
+ {
+ debug() << "disconnected\n";
+ return error::SERVER_DISCONNECTED;
+ }
+ debug() << "fail(" << errno << "," << strerror(errno) << ")\n";
+ switch(errno)
+ {
+#if EAGAIN != EWOULDBLOCK
+ case EAGAIN:
+#endif
+ case EWOULDBLOCK: return error::TIMEOUT;
+ default: return error::NET_ERROR;
+ }
+}
+
+void socket_server::discovery_thread()
+{
+ debug() << "[net::srv:sock::discovery] start\n";
+ /* begin listening to incoming connections */
+ if(listen(m_socketfd, LISTEN_QUEUE_SIZE) < 0)
+ {
+ debug() << "[net::srv::sock::discovery] listen() failed: " << errno << "\n";
+ return;
+ }
+
+ /* handle connections */
+ while(!m_discovery_exit)
+ {
+ /* since accept() is blocking, use select to ensure a timeout */
+ struct timeval tmo = m_timeout; /* NOTE select() can overwrite timeout */
+ fd_set set;
+ FD_ZERO(&set);
+ FD_SET(m_socketfd, &set);
+ /* wait for some activity */
+ int ret = select(m_socketfd + 1, &set, nullptr, nullptr, &tmo);
+ if(ret < 0 || !FD_ISSET(m_socketfd, &set))
+ continue;
+ int clifd = accept(m_socketfd, nullptr, nullptr);
+ if(clifd >= 0)
+ {
+ debug() << "[net::srv::sock::discovery] new client\n";
+ /* set timeout for the client operations */
+ setsockopt(clifd, SOL_SOCKET, SO_RCVTIMEO, (char *)&m_timeout, sizeof(m_timeout));
+ setsockopt(clifd, SOL_SOCKET, SO_SNDTIMEO, (char *)&m_timeout, sizeof(m_timeout));
+ client_arrived(to_srv_client(clifd));
+ }
+ }
+ debug() << "[net::srv:sock::discovery] stop\n";
+}
+
+} // namespace uri
+} // namespace net
diff --git a/utils/hwstub/lib/hwstub_protocol.h b/utils/hwstub/lib/hwstub_protocol.h
deleted file mode 100644
index 35510fa9b2..0000000000
--- a/utils/hwstub/lib/hwstub_protocol.h
+++ /dev/null
@@ -1 +0,0 @@
-#include "../hwstub_protocol.h"
diff --git a/utils/hwstub/lib/hwstub_uri.cpp b/utils/hwstub/lib/hwstub_uri.cpp
new file mode 100644
index 0000000000..e2f252f3dc
--- /dev/null
+++ b/utils/hwstub/lib/hwstub_uri.cpp
@@ -0,0 +1,332 @@
+/***************************************************************************
+ * __________ __ ___.
+ * 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_uri.hpp"
+#include "hwstub_usb.hpp"
+#include "hwstub_virtual.hpp"
+#include "hwstub_net.hpp"
+#include <cctype>
+
+namespace hwstub {
+namespace uri {
+
+void print_usage(FILE *f, bool client, bool server)
+{
+ if(client && server)
+ fprintf(f, "A context/server");
+ else if(client)
+ fprintf(f, "A context");
+ else
+ fprintf(f, "A server");
+ fprintf(f, " URI has the following format:\n");
+ fprintf(f, " scheme:[//domain[:port]][/]\n");
+ fprintf(f, "The scheme is mandatory and specifies the type of context to create.\n");
+ fprintf(f, "The following scheme are recognized:\n");
+ if(client)
+ fprintf(f, " usb USB context (using libusb)\n");
+ fprintf(f, " tcp TCP context\n");
+ fprintf(f, " unix Local unix domain context\n");
+ if(client)
+ fprintf(f, " virt A virtual context (testing and debugging mostly)\n");
+ fprintf(f, " default Default choice made by the library\n");
+ if(client)
+ {
+ fprintf(f, "When creating a USB context, the domain and port must be empty:\n");
+ fprintf(f, " usb:\n");
+ }
+ fprintf(f, "When creating a TCP context, the domain and port are the usual TCP parameters:\n");
+ fprintf(f, " tcp://localhost\n");
+ fprintf(f, " tcp://localhost:6666\n");
+ fprintf(f, "The port is optional, in which the default port %s is used.\n", hwstub::net::context::default_tcp_port().c_str());
+ fprintf(f, "When creating a unix domain context, the port must be empty and there are two type of unix domains.\n");
+ fprintf(f, "Normal domains are specified by filesystem paths. Abstract domains (Linux-only) can be arbitrary strings,\n");
+ fprintf(f, "in which case the domain must start with a '#':\n");
+ fprintf(f, " unix:///path/to/socket\n");
+ fprintf(f, " unix://#hwstub\n");
+ if(client)
+ {
+ fprintf(f, "When creating a virtual context, the domain must contain a specification of the devices.\n");
+ fprintf(f, "The device list is of the form type(param);type(param);... where the only supported type\n");
+ fprintf(f, "at the moment is 'dummy' with a single parameter which is the device name:\n");
+ fprintf(f, " virt://dummy(Device A);dummy(Device B);dummy(Super device C)\n");
+ }
+ if(server && client)
+ fprintf(f, "Note that usb and virt schemes are not supported for server URIs.\n");
+}
+
+uri::uri(const std::string& uri)
+ :m_uri(uri), m_valid(false)
+{
+ parse();
+}
+
+bool uri::validate_scheme()
+{
+ /* scheme must be nonempty */
+ if(m_scheme.empty())
+ {
+ m_error = "empty scheme";
+ return false;
+ }
+ /* and contain alphanumeric characters or '+', '-' or '.' */
+ for(auto c : m_scheme)
+ if(!isalnum(c) && c != '+' && c != '-' && c != '.')
+ {
+ m_error = std::string("invalid character '") + c + "' in scheme";
+ return false;
+ }
+ return true;
+}
+
+bool uri::validate_domain()
+{
+ return true;
+}
+
+bool uri::validate_port()
+{
+ return true;
+}
+
+void uri::parse()
+{
+ std::string str = m_uri;
+ /* try to find the first ':' */
+ size_t scheme_colon = str.find(':');
+ if(scheme_colon == std::string::npos)
+ {
+ m_error = "URI contains no scheme";
+ return;
+ }
+ /* found scheme */
+ m_scheme = str.substr(0, scheme_colon);
+ /* validate scheme */
+ if(!validate_scheme())
+ return;
+ /* remove scheme: */
+ str = str.substr(scheme_colon + 1);
+ /* check if it begins with // */
+ if(str.size() >= 2 && str[0] == '/' && str[1] == '/')
+ {
+ /* remove it */
+ str = str.substr(2);
+ /* find next '/' */
+ std::string path;
+ size_t next_slash = str.find('/');
+ /* if none, we can assume the path is empty, otherwise split path */
+ if(next_slash != std::string::npos)
+ {
+ path = str.substr(next_slash);
+ str = str.substr(0, next_slash);
+ }
+ /* find last ':' if any, indicating a port */
+ size_t port_colon = str.rfind(':');
+ if(port_colon != std::string::npos)
+ {
+ m_domain = str.substr(0, port_colon);
+ m_port = str.substr(port_colon + 1);
+ }
+ else
+ m_domain = str;
+ if(!validate_domain() || !validate_port())
+ return;
+ /* pursue with path */
+ str = path;
+ }
+ /* next is path */
+ m_path = str;
+ m_valid = true;
+}
+
+bool uri::valid() const
+{
+ return m_valid;
+}
+
+std::string uri::error() const
+{
+ return m_error;
+}
+
+std::string uri::full_uri() const
+{
+ return m_uri;
+}
+
+std::string uri::scheme() const
+{
+ return m_scheme;
+}
+
+std::string uri::domain() const
+{
+ return m_domain;
+}
+
+std::string uri::port() const
+{
+ return m_port;
+}
+
+std::string uri::path() const
+{
+ return m_path;
+}
+
+/** Context creator */
+
+std::shared_ptr<context> create_default_context(std::string *error)
+{
+ /* first try to create a unix context with abstract name 'hwstub' */
+ std::shared_ptr<context> ctx = hwstub::net::context::create_unix_abstract(
+ hwstub::net::context::default_unix_path(), error);
+ if(ctx)
+ return ctx;
+ /* otherwise try default tcp */
+ ctx = hwstub::net::context::create_tcp(hwstub::net::context::default_tcp_domain(),
+ hwstub::net::context::default_tcp_port(), error);
+ if(ctx)
+ return ctx;
+ /* otherwise default to usb */
+ return hwstub::usb::context::create(nullptr, false, error);
+}
+
+std::shared_ptr<context> create_context(const uri& uri, std::string *error)
+{
+ /* check URI is valid */
+ if(!uri.valid())
+ {
+ if(error)
+ *error = "invalid URI: " + uri.error();
+ return std::shared_ptr<context>();
+ }
+ /* handle different types of contexts */
+ if(uri.scheme() == "usb")
+ {
+ /* domain and port must be empty */
+ if(!uri.domain().empty() || !uri.port().empty())
+ {
+ if(error)
+ *error = "USB URI cannot contain a domain or a port";
+ return std::shared_ptr<context>();
+ }
+ /* in doubt, create a new libusb context and let the context destroy it */
+ libusb_context *ctx;
+ libusb_init(&ctx);
+ return hwstub::usb::context::create(ctx, true);
+ }
+ else if(uri.scheme() == "virt")
+ {
+ /* port must be empty */
+ if(!uri.port().empty())
+ {
+ if(error)
+ *error = "virt URI cannot contain a port";
+ return std::shared_ptr<context>();
+ }
+ return hwstub::virt::context::create_spec(uri.domain(), error);
+ }
+ else if(uri.scheme() == "tcp")
+ {
+ return hwstub::net::context::create_tcp(uri.domain(), uri.port(), error);
+ }
+ else if(uri.scheme() == "unix")
+ {
+ /* port must be empty */
+ if(!uri.port().empty())
+ {
+ if(error)
+ *error = "unix URI cannot contain a port";
+ return std::shared_ptr<context>();
+ }
+ if(!uri.domain().empty() && uri.domain()[0] == '#')
+ return hwstub::net::context::create_unix_abstract(uri.domain().substr(1), error);
+ else
+ return hwstub::net::context::create_unix(uri.domain(), error);
+ }
+ else if(uri.scheme() == "default")
+ {
+ /* domain and port must be empty */
+ if(!uri.domain().empty() || !uri.port().empty())
+ {
+ if(error)
+ *error = "Default URI cannot contain a domain or a port";
+ return std::shared_ptr<context>();
+ }
+ return create_default_context(error);
+ }
+ else
+ {
+ if(error)
+ *error = "unknown scheme '" + uri.scheme() + "'";
+ return std::shared_ptr<context>();
+ }
+}
+
+std::shared_ptr<net::server> create_server(std::shared_ptr<context> ctx,
+ const uri& uri, std::string *error)
+{
+ /* check URI is valid */
+ if(!uri.valid())
+ {
+ if(error)
+ *error = "invalid URI: " + uri.error();
+ return std::shared_ptr<net::server>();
+ }
+ /* handle different types of contexts */
+ if(uri.scheme() == "unix")
+ {
+ /* port must be empty */
+ if(!uri.port().empty())
+ {
+ if(error)
+ *error = "unix URI cannot contain a port";
+ return std::shared_ptr<net::server>();
+ }
+ if(!uri.domain().empty() && uri.domain()[0] == '#')
+ return hwstub::net::server::create_unix_abstract(ctx, uri.domain().substr(1), error);
+ else
+ return hwstub::net::server::create_unix(ctx, uri.domain(), error);
+ }
+ else if(uri.scheme() == "tcp")
+ {
+ return hwstub::net::server::create_tcp(ctx, uri.domain(), uri.port(), error);
+ }
+ else
+ {
+ if(error)
+ *error = "unknown scheme '" + uri.scheme() + "'";
+ return std::shared_ptr<net::server>();
+ }
+}
+
+uri default_uri()
+{
+ return uri("default:");
+}
+
+uri default_server_uri()
+{
+ return uri("unix://#" + hwstub::net::context::default_unix_path());
+}
+
+} // namespace uri
+} // namespace hwstub
+
diff --git a/utils/hwstub/lib/hwstub_usb.cpp b/utils/hwstub/lib/hwstub_usb.cpp
new file mode 100644
index 0000000000..28c64d9df3
--- /dev/null
+++ b/utils/hwstub/lib/hwstub_usb.cpp
@@ -0,0 +1,728 @@
+/***************************************************************************
+ * __________ __ ___.
+ * 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
diff --git a/utils/hwstub/lib/hwstub_virtual.cpp b/utils/hwstub/lib/hwstub_virtual.cpp
new file mode 100644
index 0000000000..fada56fb83
--- /dev/null
+++ b/utils/hwstub/lib/hwstub_virtual.cpp
@@ -0,0 +1,335 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2016 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_virtual.hpp"
+#include <algorithm>
+#include <cstring>
+
+namespace hwstub {
+namespace virt {
+
+/**
+ * Context
+ */
+context::context()
+{
+}
+
+context::~context()
+{
+}
+
+std::shared_ptr<context> context::create()
+{
+ // NOTE: can't use make_shared() because of the protected ctor */
+ return std::shared_ptr<context>(new context());
+}
+
+std::shared_ptr<context> context::create_spec(const std::string& spec, std::string *error)
+{
+ std::shared_ptr<context> ctx = create();
+ /* parse spec */
+ std::string str = spec;
+ while(!str.empty())
+ {
+ /* find next separator, if any */
+ std::string dev_spec;
+ size_t dev_sep = str.find(';');
+ if(dev_sep == std::string::npos)
+ {
+ dev_spec = str;
+ str.clear();
+ }
+ else
+ {
+ dev_spec = str.substr(0, dev_sep);
+ str = str.substr(dev_sep + 1);
+ }
+ /* handle dev spec: find ( and )*/
+ size_t lparen = dev_spec.find('(');
+ if(lparen == std::string::npos)
+ {
+ if(error)
+ *error = "invalid device spec '" + dev_spec + "': missing (";
+ return std::shared_ptr<context>();
+ }
+ if(dev_spec.back() != ')')
+ {
+ if(error)
+ *error = "invalid device spec '" + dev_spec + "': missing )";
+ return std::shared_ptr<context>();
+ }
+ std::string args = dev_spec.substr(lparen + 1, dev_spec.size() - lparen - 2);
+ std::string type = dev_spec.substr(0, lparen);
+ if(type == "dummy")
+ {
+ ctx->connect(std::make_shared<dummy_hardware>(args));
+ }
+ else
+ {
+ if(error)
+ *error = "invalid device spec '" + dev_spec + "': unknown device type";
+ return std::shared_ptr<context>();
+ }
+ }
+ return ctx;
+}
+
+bool context::connect(std::shared_ptr<hardware> hw)
+{
+ std::unique_lock<std::recursive_mutex> lock(m_mutex);
+ if(std::find(m_hwlist.begin(), m_hwlist.end(), hw) != m_hwlist.end())
+ return false;
+ m_hwlist.push_back(hw);
+ return true;
+}
+
+bool context::disconnect(std::shared_ptr<hardware> hw)
+{
+ std::unique_lock<std::recursive_mutex> lock(m_mutex);
+ auto it = std::find(m_hwlist.begin(), m_hwlist.end(), hw);
+ if(it == m_hwlist.end())
+ return false;
+ m_hwlist.erase(it);
+ return true;
+}
+
+std::shared_ptr<hardware> context::from_ctx_dev(ctx_dev_t dev)
+{
+ return ((hardware *)dev)->shared_from_this();
+}
+
+hwstub::context::ctx_dev_t context::to_ctx_dev(std::shared_ptr<hardware>& dev)
+{
+ return (ctx_dev_t)dev.get();
+}
+
+error context::fetch_device_list(std::vector<ctx_dev_t>& list, void*& ptr)
+{
+ (void) ptr;
+ list.resize(m_hwlist.size());
+ for(size_t i = 0; i < m_hwlist.size(); i++)
+ list[i] = to_ctx_dev(m_hwlist[i]);
+ return error::SUCCESS;
+}
+
+void context::destroy_device_list(void *ptr)
+{
+ (void) ptr;
+}
+
+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().get() == from_ctx_dev(dev).get();
+}
+
+/**
+ * Device
+ */
+device::device(std::shared_ptr<hwstub::context> ctx, std::shared_ptr<hardware> dev)
+ :hwstub::device(ctx), m_hwdev(dev)
+{
+}
+
+device::~device()
+{
+}
+
+std::shared_ptr<hardware> device::native_device()
+{
+ return m_hwdev.lock();
+}
+
+error device::open_dev(std::shared_ptr<hwstub::handle>& h)
+{
+ // NOTE: can't use make_shared() because of the protected ctor */
+ h.reset(new handle(shared_from_this()));
+ return error::SUCCESS;
+}
+
+bool device::has_multiple_open() const
+{
+ return true;
+}
+
+/**
+ * Handle
+ */
+handle::handle(std::shared_ptr<hwstub::device> dev)
+ :hwstub::handle(dev)
+{
+ m_hwdev = dynamic_cast<device*>(dev.get())->native_device();
+}
+
+handle::~handle()
+{
+}
+
+
+error handle::read_dev(uint32_t addr, void *buf, size_t& sz, bool atomic)
+{
+ auto p = m_hwdev.lock();
+ return p ? p->read_dev(addr, buf, sz, atomic) : error::DISCONNECTED;
+}
+
+error handle::write_dev(uint32_t addr, const void *buf, size_t& sz, bool atomic)
+{
+ auto p = m_hwdev.lock();
+ return p ? p->write_dev(addr, buf, sz, atomic) : error::DISCONNECTED;
+}
+
+error handle::get_dev_desc(uint16_t desc, void *buf, size_t& buf_sz)
+{
+ auto p = m_hwdev.lock();
+ return p ? p->get_dev_desc(desc, buf, buf_sz) : error::DISCONNECTED;
+}
+
+error handle::get_dev_log(void *buf, size_t& buf_sz)
+{
+ auto p = m_hwdev.lock();
+ return p ? p->get_dev_log(buf, buf_sz) : error::DISCONNECTED;
+}
+
+error handle::exec_dev(uint32_t addr, uint16_t flags)
+{
+ auto p = m_hwdev.lock();
+ return p ? p->exec_dev(addr, flags) : error::DISCONNECTED;
+}
+
+error handle::status() const
+{
+ return hwstub::handle::status();
+}
+
+size_t handle::get_buffer_size()
+{
+ auto p = m_hwdev.lock();
+ return p ? p->get_buffer_size() : 1;
+}
+
+/**
+ * Hardware
+ */
+hardware::hardware()
+{
+}
+
+hardware::~hardware()
+{
+}
+
+/**
+ * Dummy hardware
+ */
+dummy_hardware::dummy_hardware(const std::string& name)
+{
+ 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;
+ strncpy(m_desc_target.bName, name.c_str(), sizeof(m_desc_target.bName));
+ m_desc_target.bName[sizeof(m_desc_target.bName) - 1] = 0;
+}
+
+dummy_hardware::~dummy_hardware()
+{
+}
+
+error dummy_hardware::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_hardware::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_hardware::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_hardware::get_dev_log(void *buf, size_t& buf_sz)
+{
+ (void) buf;
+ (void) buf_sz;
+ return error::DUMMY;
+}
+
+error dummy_hardware::exec_dev(uint32_t addr, uint16_t flags)
+{
+ (void) addr;
+ (void) flags;
+ return error::DUMMY;
+}
+
+size_t dummy_hardware::get_buffer_size()
+{
+ return 1;
+}
+
+} // namespace virt
+} // namespace net
+