summaryrefslogtreecommitdiffstats
path: root/utils/hwstub/include
diff options
context:
space:
mode:
Diffstat (limited to 'utils/hwstub/include')
-rw-r--r--utils/hwstub/include/hwstub.h70
-rw-r--r--utils/hwstub/include/hwstub.hpp351
-rw-r--r--utils/hwstub/include/hwstub_net.hpp334
-rw-r--r--utils/hwstub/include/hwstub_protocol.h318
-rw-r--r--utils/hwstub/include/hwstub_uri.hpp131
-rw-r--r--utils/hwstub/include/hwstub_usb.hpp194
-rw-r--r--utils/hwstub/include/hwstub_virtual.hpp159
7 files changed, 1557 insertions, 0 deletions
diff --git a/utils/hwstub/include/hwstub.h b/utils/hwstub/include/hwstub.h
new file mode 100644
index 0000000000..4d12de8eda
--- /dev/null
+++ b/utils/hwstub/include/hwstub.h
@@ -0,0 +1,70 @@
+/***************************************************************************
+ * __________ __ ___.
+ * 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/include/hwstub.hpp b/utils/hwstub/include/hwstub.hpp
new file mode 100644
index 0000000000..deac976240
--- /dev/null
+++ b/utils/hwstub/include/hwstub.hpp
@@ -0,0 +1,351 @@
+/***************************************************************************
+ * __________ __ ___.
+ * 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.
+ *
+ ****************************************************************************/
+#ifndef __HWSTUB_HPP__
+#define __HWSTUB_HPP__
+
+#include "hwstub_protocol.h"
+#include <string>
+#include <mutex>
+#include <vector>
+#include <cstdint>
+#include <atomic>
+#include <memory>
+#include <chrono>
+#include <thread>
+#include <mutex>
+#include <condition_variable>
+#include <ostream>
+
+namespace hwstub {
+
+class context;
+class device;
+class handle;
+class context_poller;
+
+/** C++ equivalent of /dev/null for streams */
+extern std::ostream cnull;
+extern std::wostream wcnull;
+
+/** Errors */
+enum class error
+{
+ SUCCESS, /** Success */
+ ERROR, /** Unspecified error */
+ DISCONNECTED, /** Device has been disconnected */
+ PROBE_FAILURE, /** Device did not pass probing */
+ NO_CONTEXT, /** The context has been destroyed */
+ USB_ERROR, /** Unspecified USB error */
+ DUMMY, /** Call on dummy device/handle */
+ NO_SERVER, /** The server could not be reached */
+ SERVER_DISCONNECTED, /** Context got disconnected from the server */
+ SERVER_MISMATCH, /** The server is not compatible with hwstub */
+ PROTOCOL_ERROR, /** Network protocol error */
+ NET_ERROR, /** Network error */
+ TIMEOUT, /** Operation timed out */
+ OVERFLW, /** Operation stopped to prevent buffer overflow */
+};
+
+/** Return a string explaining the error */
+std::string error_string(error err);
+
+/** NOTE Multithreading:
+ * Unless specified, all methods are thread-safe
+ */
+
+/** Context
+ *
+ * A context provides a list of available devices and may notify devices
+ * arrival and departure.
+ *
+ * A context provides a way to regularly poll for derive changes. There are two
+ * ways to manually force an update:
+ * - on call to get_device_list(), the list is already refetched
+ * - on call to update_list() to force list update
+ * Note that automatic polling is disabled by default.
+ */
+class context : public std::enable_shared_from_this<context>
+{
+protected:
+ context();
+public:
+ /** On destruction, the context will destroy all the devices. */
+ virtual ~context();
+ /** Get device list, clears the list in argument first. All devices in the list
+ * are still connected (or believe to be). This function will update the device
+ * list. */
+ error get_device_list(std::vector<std::shared_ptr<device>>& list);
+ /** Force the context to update its internal list of devices. */
+ error update_list();
+ /** Ask the context to automatically poll for device changes.
+ * Note that this might spawn a new thread to do so, in which case it will
+ * be destroyed/stop on deletetion or when stop_polling() is called. If
+ * polling is already enabled, this function will change the polling interval. */
+ void start_polling(std::chrono::milliseconds interval = std::chrono::milliseconds(250));
+ /** Stop polling. */
+ void stop_polling();
+ /** Register a notification callback with arguments (context,arrived,device)
+ * WARNING the callback may be called asynchronously ! */
+ typedef std::function<void(std::shared_ptr<context>, bool, std::shared_ptr<device>)> notification_callback_t;
+ typedef size_t callback_ref_t;
+ callback_ref_t register_callback(const notification_callback_t& fn);
+ void unregister_callback(callback_ref_t ref);
+ /** Return a dummy device that does nothing. A dummy device might be useful
+ * in cases where one still wants a valid pointer to no device. This dummy
+ * device does not appear in the list, it can be opened and will fail all requests. */
+ error get_dummy_device(std::shared_ptr<device>& dev);
+ /** Set/clear debug output for this context */
+ void set_debug(std::ostream& os);
+ inline void clear_debug() { set_debug(cnull); }
+ /** Get debug output for this context */
+ std::ostream& debug();
+
+protected:
+ /** Notify the context about a device. If arrived is true, the device is
+ * added to the list and a reference will be added to it. If arrived is false,
+ * the device is marked as disconnected(), removed from the list and a
+ * reference will be removed from it. Adding a device that matches an
+ * existing one will do nothing. */
+ void change_device(bool arrived, std::shared_ptr<device> dev);
+ /** Do device notification */
+ void notify_device(bool arrived, std::shared_ptr<device> dev);
+ /** Opaque device type */
+ typedef void* ctx_dev_t;
+ /** Fetch the device list. Each item in the list is an opaque pointer. The function
+ * can also provide a pointer that will be used to free the list resources
+ * if necessary. Return <0 on error. */
+ virtual error fetch_device_list(std::vector<ctx_dev_t>& list, void*& ptr) = 0;
+ /** Destroy the resources created to get the list. */
+ virtual void destroy_device_list(void *ptr) = 0;
+ /** Create a new hwstub device from the opaque pointer. Return <0 on error.
+ * This function needs not add a reference to the newly created device. */
+ virtual error create_device(ctx_dev_t dev, std::shared_ptr<device>& hwdev) = 0;
+ /** Return true if the opaque pointer corresponds to the device. Only called
+ * from map_device(). */
+ virtual bool match_device(ctx_dev_t dev, std::shared_ptr<device> hwdev) = 0;
+ /** Check if a device matches another one in the list */
+ bool contains_dev(const std::vector<device*>& list, ctx_dev_t dev);
+
+ struct callback_t
+ {
+ notification_callback_t callback;
+ callback_ref_t ref;
+ };
+
+ std::shared_ptr<context_poller> m_poller; /* poller object */
+ std::recursive_mutex m_mutex; /* list mutex */
+ std::vector<std::shared_ptr<device>> m_devlist; /* list of devices */
+ std::vector<callback_t> m_callbacks; /* list of callbacks */
+ callback_ref_t m_next_cb_ref; /* next callback reference */
+ std::ostream *m_debug; /* debug stream */
+};
+
+/** Context Poller
+ *
+ * This class provides a way to regularly poll a context for device changes.
+ * NOTE this class is not meant to be used directly since context already
+ * provides access to it via start_polling() and stop_polling() */
+class context_poller
+{
+public:
+ context_poller(std::weak_ptr<context> ctx, std::chrono::milliseconds interval = std::chrono::milliseconds(250));
+ ~context_poller();
+ /** Set polling interval (in milliseconds) (works even if polling already enabled) */
+ void set_interval(std::chrono::milliseconds interval);
+ /** Start polling */
+ void start();
+ /** Stop polling. After return, no function will be made. */
+ void stop();
+
+protected:
+ static void thread(context_poller *poller);
+ void poll();
+
+ std::weak_ptr<context> m_ctx; /* context */
+ bool m_running; /* are we running ? */
+ bool m_exit; /* exit flag for the thread */
+ std::thread m_thread; /* polling thread */
+ std::mutex m_mutex; /* mutex lock */
+ std::condition_variable m_cond; /* signalling condition */
+ std::chrono::milliseconds m_interval; /* Interval */
+};
+
+/** Device
+ *
+ * A device belongs to a context.
+ * Note that a device only keeps a weak pointer to the context, so it is possible
+ * for the context to be destroyed during the life of the device, in which case
+ * all operations on it will fail. */
+class device : public std::enable_shared_from_this<device>
+{
+protected:
+ device(std::shared_ptr<context> ctx);
+public:
+ virtual ~device();
+ /** Open a handle to the device. Several handles may be opened concurrently. */
+ error open(std::shared_ptr<handle>& handle);
+ /** Disconnect the device. This will notify the context that the device is gone. */
+ void disconnect();
+ /** Returns true if the device is still connected. */
+ bool connected();
+ /** Get context (might be empty) */
+ std::shared_ptr<context> get_context();
+
+protected:
+ /** Some subsystems allow for hardware to be open several times and so do not.
+ * For example, libusb only allows one handle per device. To workaround this issue,
+ * open() will do some magic to allow for several open() even when the hardware
+ * supports only one. If the device does not support multiple
+ * handles (as reported by has_multiple_open()), open() will only call open_dev()
+ * the first time the device is opened and will redirect other open() calls to
+ * this handle using proxy handles. If the device supports multiple handles,
+ * open() will simply call open_dev() each time.
+ * The open_dev() does not need to care about this magic and only needs to
+ * open the device and returns the handle to it.
+ * NOTE this function is always called with the mutex locked already. */
+ virtual error open_dev(std::shared_ptr<handle>& handle) = 0;
+ /** Return true if device can be opened multiple times. In this case, each
+ * call to open() will generate a call to do_open(). Otherwise, proxy handles
+ * will be created for each open() and do_open() will only be called the first
+ * time. */
+ virtual bool has_multiple_open() const = 0;
+
+ std::weak_ptr<context> m_ctx; /* pointer to context */
+ std::recursive_mutex m_mutex; /* device state mutex: ref count, connection status */
+ bool m_connected; /* false once device is disconnected */
+ std::weak_ptr<handle> m_handle; /* weak pointer to the opened handle (if !has_multiple_open()) */
+};
+
+/** Handle
+ *
+ * A handle is tied to a device and provides access to the stub operation.
+ * The handle is reference counted and is destroyed
+ * when its reference count decreased to zero.
+ */
+class handle : public std::enable_shared_from_this<handle>
+{
+protected:
+ /** A handle will always hold a reference to the device */
+ handle(std::shared_ptr<device> dev);
+public:
+ /** When destroyed, the handle will release its reference to the device */
+ virtual ~handle();
+ /** Return associated device */
+ std::shared_ptr<device> get_device();
+ /** Fetch a descriptor, buf_sz is the size of the buffer and is updated to
+ * reflect the number of bytes written to the buffer. */
+ error get_desc(uint16_t desc, void *buf, size_t& buf_sz);
+ /** Fetch part of the log, buf_sz is the size of the buffer and is updated to
+ * reflect the number of bytes written to the buffer. */
+ error get_log(void *buf, size_t& buf_sz);
+ /** Ask the stub to execute some code.
+ * NOTE: this may kill the stub */
+ error exec(uint32_t addr, uint16_t flags);
+ /** Read/write some device memory. sz is the size of the buffer and is updated to
+ * reflect the number of bytes written to the buffer.
+ * NOTE: the stub may or may not recover from bad read/write, so this may kill it.
+ * NOTE: the default implemtentation of read() and write() will split transfers
+ * according to the buffer size and call read_dev() and write_dev() */
+ error read(uint32_t addr, void *buf, size_t& sz, bool atomic);
+ error write(uint32_t addr, const void *buf, size_t& sz, bool atomic);
+ /** Get device buffer size: any read() or write() greater than this size
+ * will be split into several transaction to avoid overflowing device
+ * buffer. */
+ virtual size_t get_buffer_size() = 0;
+ /** Check a handle status. A successful handle does not guarantee successful
+ * operations. An invalid handle will typically report probing failure (the
+ * device did not pass probing) or disconnection.
+ * The default implemtentation will test if context still exists and connection status. */
+ virtual error status() const;
+ /** Shorthand for status() == HWSTUB_SUCCESS */
+ inline bool valid() const { return status() == error::SUCCESS; }
+
+ /** Helper functions */
+ error get_version_desc(hwstub_version_desc_t& desc);
+ error get_layout_desc(hwstub_layout_desc_t& desc);
+ error get_stmp_desc(hwstub_stmp_desc_t& desc);
+ error get_pp_desc(hwstub_pp_desc_t& desc);
+ error get_jz_desc(hwstub_jz_desc_t& desc);
+ error get_target_desc(hwstub_target_desc_t& desc);
+
+protected:
+ /** The get_desc(), get_log(), exec(), read() and write() function
+ * take care of details so that each implementation can safely assume that
+ * the hwstub context exists and will not be destroyed during the execution
+ * of the function. It will also return early if the device has been disconnected.
+ *
+ * NOTE on read() and write():
+ * Since devices have a limited buffer, big transfers must be split into
+ * smaller ones. The high-level read() and write() functions perform this
+ * splitting in a generic way, based on get_buffer_size(), and calling read_dev()
+ * and write_dev() which do the actual operation.
+ * These function can safely assume that sz <= get_buffer_size(). */
+ virtual error read_dev(uint32_t addr, void *buf, size_t& sz, bool atomic) = 0;
+ virtual error write_dev(uint32_t addr, const void *buf, size_t& sz, bool atomic) = 0;
+ virtual error get_dev_desc(uint16_t desc, void *buf, size_t& buf_sz) = 0;
+ virtual error get_dev_log(void *buf, size_t& buf_sz) = 0;
+ virtual error exec_dev(uint32_t addr, uint16_t flags) = 0;
+
+ std::shared_ptr<device> m_dev; /* pointer to device */
+ std::atomic<int> m_refcnt; /* reference count */
+ std::recursive_mutex m_mutex; /* operation mutex to serialise operations */
+};
+
+/** Dummy device */
+class dummy_device : public device
+{
+ friend class context; /* for ctor */
+protected:
+ dummy_device(std::shared_ptr<context> ctx);
+public:
+ virtual ~dummy_device();
+
+protected:
+ virtual error open_dev(std::shared_ptr<handle>& handle);
+ virtual bool has_multiple_open() const;
+};
+
+/** Dummy handle */
+class dummy_handle : public handle
+{
+ friend class dummy_device;
+protected:
+ dummy_handle(std::shared_ptr<device> dev);
+public:
+ virtual ~dummy_handle();
+
+protected:
+ virtual error read_dev(uint32_t addr, void *buf, size_t& sz, bool atomic);
+ virtual error write_dev(uint32_t addr, const void *buf, size_t& sz, bool atomic);
+ virtual error get_dev_desc(uint16_t desc, void *buf, size_t& buf_sz);
+ virtual error get_dev_log(void *buf, size_t& buf_sz);
+ virtual error exec_dev(uint32_t addr, uint16_t flags);
+ virtual error status() const;
+ virtual size_t get_buffer_size();
+
+ struct hwstub_version_desc_t m_desc_version;
+ struct hwstub_layout_desc_t m_desc_layout;
+ struct hwstub_target_desc_t m_desc_target;
+};
+
+} // namespace hwstub
+
+#endif /* __HWSTUB_HPP__ */
diff --git a/utils/hwstub/include/hwstub_net.hpp b/utils/hwstub/include/hwstub_net.hpp
new file mode 100644
index 0000000000..2cf6e07ccb
--- /dev/null
+++ b/utils/hwstub/include/hwstub_net.hpp
@@ -0,0 +1,334 @@
+/***************************************************************************
+ * __________ __ ___.
+ * 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.
+ *
+ ****************************************************************************/
+#ifndef __HWSTUB_NET_HPP__
+#define __HWSTUB_NET_HPP__
+
+#include "hwstub.hpp"
+#include <map>
+#include <thread>
+#include <future>
+#include <list>
+
+namespace hwstub {
+namespace net {
+
+/** Net context
+ *
+ * A socket context provides access to another context through a network. This
+ * is particularly useful to have another program create a USB context and provide
+ * access to it via some network. The two most useful types of network are TCP
+ * and Unix domains */
+class context : public hwstub::context
+{
+ friend class device;
+ friend class handle;
+protected:
+ context();
+public:
+ virtual ~context();
+ /** Create a socket context with an existing file descriptor. Note that the
+ * file descriptor will be closed when the context will be destroyed. */
+ static std::shared_ptr<context> create_socket(int socket_fd);
+ /** Create a TCP socket context with a domain name and a port. If port is empty,
+ * a default port is used. */
+ static std::shared_ptr<context> create_tcp(const std::string& domain,
+ const std::string& port, std::string *error = nullptr);
+ /** Create a UNIX socket context with a file system path (see man for details) */
+ static std::shared_ptr<context> create_unix(const std::string& path,
+ std::string *error = nullptr);
+ /** Create a UNIX socket context with an abstract name (see man for details) */
+ static std::shared_ptr<context> create_unix_abstract(const std::string& path,
+ std::string *error = nullptr);
+ /** Useful functions for network byte order conversion */
+ uint32_t to_net_order(uint32_t u);
+ uint32_t from_net_order(uint32_t u);
+
+ /** Default parameters */
+ static std::string default_unix_path();
+ static std::string default_tcp_domain();
+ static std::string default_tcp_port();
+
+protected:
+ /** Send a message to the server. Context will always serialize calls to send()
+ * so there is no need to worry about concurrency issues. */
+ virtual error send(void *buffer, size_t& sz) = 0;
+ /** Receive a message from the server, sz is updated with the received size.
+ * Context will always serialize calls to recv() so there is no need to
+ * worry about concurrency issues. */
+ virtual error recv(void *buffer, size_t& sz) = 0;
+ /** Perform a standard command: send a header with optional data and wait for
+ * an answer. In case of an underlying network error, the corresponding error
+ * code will be reported. If the server responds correctly, the argument array
+ * is overwritten with the servers's response. If the requests has been NACK'ed
+ * the error code will be parsed and returned as a standard error code (see details below)
+ * (note that the original error code can still be found in args[0]). No data
+ * is transmitted in case of NACK.
+ * If the server ACKs the request, this function will also perform reception of
+ * the data. In recv_data is not NULL, the receive data will be put there and the
+ * size will be written in in_size. There are two cases: either *recv_data is NULL
+ * and the function will allocate the memory based on much data is sent by the server.
+ * Or *recv_data is not NULL, in which case the function NOT allocate memory
+ * and put the data at *recv_data; in this case, *recv_size should be the set
+ * to the size of the buffer and will be updated to the received size. If the
+ * server sents more data than the buffer size, OVERFLOW will be returned.
+ * If no data was received but recv_data is not null, *recv_size will be set to
+ * zero. It is the caller's responsability to delete *recv_data. Note that if
+ * server sends data but recv_data is null, the data will still be received and
+ * thrown away.
+ * This function takes care of network byte order for cmd and arguments
+ * but not for data. */
+ error 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);
+ /** Ask the context to stop any communication with the server and do a clean
+ * shutdown if possible. This is a blocking call. When this function returns,
+ * there will no more calls to the underlying communication functions.
+ * This function should be called in the destructor to prevent the context from
+ * calling children functions after the object has been deconstructed. */
+ void stop_context();
+
+ /** Perform delayed init (aka HELLO stage), do nothing is not needed */
+ void delayed_init();
+ /* NOTE ctx_dev_t = uint32_t (device id) */
+ uint32_t from_ctx_dev(ctx_dev_t dev);
+ ctx_dev_t to_ctx_dev(uint32_t dev);
+ virtual error fetch_device_list(std::vector<ctx_dev_t>& list, void*& ptr);
+ virtual void destroy_device_list(void *ptr);
+ virtual error create_device(ctx_dev_t dev, std::shared_ptr<hwstub::device>& hwdev);
+ virtual bool match_device(ctx_dev_t dev, std::shared_ptr<hwstub::device> hwdev);
+
+ enum class state
+ {
+ HELLO, /* client is initialising, server has not been contacted yet */
+ IDLE, /* not doing anything */
+ DEAD, /* died on unrecoverable error */
+ };
+
+ state m_state; /* client state */
+ error m_error; /* error state for DEAD */
+};
+
+/** Socket based net context
+ *
+ * Don't use this class directly, use context::create_* calls. This class
+ * provides send()/recv() for any socket based network. */
+class socket_context : public context
+{
+ friend class context;
+protected:
+ socket_context(int socket_fd);
+public:
+ virtual ~socket_context();
+ /** set operation timeout */
+ void set_timeout(std::chrono::milliseconds ms);
+
+protected:
+ virtual error send(void *buffer, size_t& sz);
+ virtual error recv(void *buffer, size_t& sz);
+
+ int m_socketfd; /* socket file descriptor */
+};
+
+
+/** Net device
+ *
+ * Device accessed through a network */
+class device : public hwstub::device
+{
+ friend class context; /* for ctor */
+protected:
+ device(std::shared_ptr<hwstub::context> ctx, uint32_t devid);
+public:
+ virtual ~device();
+
+protected:
+ /** Return device ID */
+ uint32_t device_id();
+ virtual error open_dev(std::shared_ptr<hwstub::handle>& handle);
+ virtual bool has_multiple_open() const;
+
+ int32_t m_device_id; /* device id */
+};
+
+/** Net handle
+ *
+ * Handle used to talk to a distant device. */
+class handle : public hwstub::handle
+{
+ friend class device;
+protected:
+ handle(std::shared_ptr<hwstub::device> dev, uint32_t hid);
+public:
+ virtual ~handle();
+
+protected:
+ virtual error read_dev(uint32_t addr, void *buf, size_t& sz, bool atomic);
+ virtual error write_dev(uint32_t addr, const void *buf, size_t& sz, bool atomic);
+ virtual error get_dev_desc(uint16_t desc, void *buf, size_t& buf_sz);
+ virtual error get_dev_log(void *buf, size_t& buf_sz);
+ virtual error exec_dev(uint32_t addr, uint16_t flags);
+ virtual error status() const;
+ virtual size_t get_buffer_size();
+
+ uint32_t m_handle_id; /* handle id */
+};
+
+/** Net server
+ *
+ * A server that forwards requests from net clients to a context */
+class server
+{
+protected:
+ server(std::shared_ptr<hwstub::context> contex);
+public:
+ virtual ~server();
+
+ /** Create a socket server with an existing file descriptor. Note that the
+ * file descriptor will be closed when the context will be destroyed. */
+ static std::shared_ptr<server> create_socket(std::shared_ptr<hwstub::context> contex,
+ int socket_fd);
+ /** Create a TCP socket server with a domain name and a port. If port is empty,
+ * a default port is used. */
+ static std::shared_ptr<server> create_tcp(std::shared_ptr<hwstub::context> contex,
+ const std::string& domain, const std::string& port, std::string *error = nullptr);
+ /** Create a UNIX socket server with a file system path (see man for details) */
+ static std::shared_ptr<server> create_unix(std::shared_ptr<hwstub::context> contex,
+ const std::string& path, std::string *error = nullptr);
+ /** Create a UNIX socket server with an abstract name (see man for details) */
+ static std::shared_ptr<server> create_unix_abstract(
+ std::shared_ptr<hwstub::context> contex, const std::string& path,
+ std::string *error = nullptr);
+ /** Useful functions for network byte order conversion */
+ uint32_t to_net_order(uint32_t u);
+ uint32_t from_net_order(uint32_t u);
+
+ /** Set/clear debug output for this context */
+ void set_debug(std::ostream& os);
+ inline void clear_debug() { set_debug(cnull); }
+ /** Get debug output for this context */
+ std::ostream& debug();
+protected:
+ struct client_state;
+ /** Opaque client type */
+ typedef void* srv_client_t;
+ /** The client discovery implementation must call this function when a new
+ * client wants to talk to the server. If the server is unhappy with the
+ * request, it will immediately call terminate_client() */
+ void client_arrived(srv_client_t client);
+ /** The client discovery implementation can notify asychronously about a client
+ * that left. Note that the implementation does not need to provide a mechanism,
+ * but should in this case return CLIENT_DISCONNECTED when the server performs
+ * a send() or recv() on a disconnected client. The server will always call
+ * after receiving client_left() but since this call is asychronous, the
+ * implementation must be prepared to deal with extra send()/recv() in the mean
+ * time. */
+ void client_left(srv_client_t client);
+ /** The client discovery implementation can ask the server to stop all client
+ * threads. This is a blocking call. When this function returns, there will no
+ * more calls to the underlying communication functions. Note that the server
+ * will normally call terminate_client() on each active client at this point.
+ * This function should be called in the destructor to prevent the server from
+ * calling children functions after the object has been deconstructed. */
+ void stop_server();
+ /** Notify that the connection to a client is now finished. After this call, no
+ * more send()/recv() will be made to the client and the associated data will
+ * be freed. After this call, the implementation is not allowed to call client_left()
+ * for this client (assuming it did not previously). The implementation should close
+ * the communication channel at this point and free any associated data. */
+ virtual void terminate_client(srv_client_t client) = 0;
+ /** Send a message to the client. Server will always serialize calls to send()
+ * for a given client so there is no need to worry about concurrency issues. */
+ virtual error send(srv_client_t client, void *buffer, size_t& sz) = 0;
+ /** Receive a message from the client, sz is updated with the received size.
+ * Server will always serialize calls to recv() for a given client so there
+ * is no need to worry about concurrency issues. See comment about client_left(). */
+ virtual error recv(srv_client_t client, void *buffer, size_t& sz) = 0;
+ /** handle command: cmd and arguments are in host order, the function should
+ * either return an error (command will be NACKed) or must fill the arguments
+ * and data for the answer. Note that the data is still in network byte order.
+ * If the funtion wants to send data back, it must set *send_data to a valid
+ * pointer, this pointer will be freed after the data is sent back. */
+ error 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);
+
+ /* complete state of a client */
+ struct client_state
+ {
+ client_state(srv_client_t cl, std::future<void>&& f);
+ srv_client_t client; /* client */
+ std::future<void> future; /* thread (see .cpp for explaination) */
+ volatile bool exit; /* exit flag */
+ uint32_t next_dev_id; /* next device ID */
+ uint32_t next_handle_id; /* next handle ID */
+ /* dev ID <-> hwstub dev map */
+ std::map<uint32_t, std::shared_ptr<hwstub::device>> dev_map;
+ /* handle ID -> hwstub handle map */
+ std::map<uint32_t, std::shared_ptr<hwstub::handle>> handle_map;
+ };
+
+ /** Client thread */
+ static void client_thread2(server *s, client_state *cs);
+ void client_thread(client_state *cs);
+
+ std::shared_ptr<hwstub::context> m_context; /* context to perform operation */
+ std::list<client_state> m_client; /* client list */
+ std::recursive_mutex m_mutex; /* server mutex */
+ std::ostream *m_debug; /* debug stream */
+};
+
+/** Socket based net server
+ *
+ */
+class socket_server : public server
+{
+protected:
+ socket_server(std::shared_ptr<hwstub::context> contex, int socket_fd);
+public:
+ virtual ~socket_server();
+ /** create a server */
+ static std::shared_ptr<server> create(std::shared_ptr<hwstub::context> contex,
+ int socket_fd);
+ /** set operation timeout */
+ void set_timeout(std::chrono::milliseconds ms);
+
+protected:
+ virtual void terminate_client(srv_client_t client);
+ virtual error send(srv_client_t client, void *buffer, size_t& sz);
+ virtual error recv(srv_client_t, void *buffer, size_t& sz);
+
+ /* NOTE srv_client_t = int (client file descriptor) */
+ int from_srv_client(srv_client_t cli);
+ srv_client_t to_srv_client(int fd);
+
+ /** Discovery thread */
+ static void discovery_thread1(socket_server *s);
+ void discovery_thread();
+
+ static const int LISTEN_QUEUE_SIZE = 5;
+ struct timeval m_timeout; /* operations timeout */
+ int m_socketfd; /* socket file descriptor */
+ std::thread m_discovery_thread; /* thread handling client discovery */
+ volatile bool m_discovery_exit; /* exit flag */
+};
+
+} // namespace net
+} // namespace hwstub
+
+#endif /* __HWSTUB_NET_HPP__ */
diff --git a/utils/hwstub/include/hwstub_protocol.h b/utils/hwstub/include/hwstub_protocol.h
new file mode 100644
index 0000000000..39d2f2ebfe
--- /dev/null
+++ b/utils/hwstub/include/hwstub_protocol.h
@@ -0,0 +1,318 @@
+/***************************************************************************
+ * __________ __ ___.
+ * 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_PROTOCOL__
+#define __HWSTUB_PROTOCOL__
+
+#include <stdint.h>
+
+/**
+ * This file contains the data structures used in the USB and network protocol.
+ * All USB data uses the standard USB byte order which is little-endian.
+ */
+
+/**
+ * HWStub protocol version
+ */
+
+#define HWSTUB_VERSION_MAJOR 4
+#define HWSTUB_VERSION_MINOR 2
+
+#define HWSTUB_VERSION__(maj, min) #maj"."#min
+#define HWSTUB_VERSION_(maj, min) HWSTUB_VERSION__(maj, min)
+#define HWSTUB_VERSION HWSTUB_VERSION_(HWSTUB_VERSION_MAJOR, HWSTUB_VERSION_MINOR)
+
+/**
+ * A device can use any VID:PID but in case hwstub is in full control of the
+ * device, the preferred VID:PID is the following.
+ */
+
+#define HWSTUB_USB_VID 0xfee1
+#define HWSTUB_USB_PID 0xdead
+
+/**
+ * The device class should be per interface and the hwstub interface must use
+ * the following class, subclass and protocol.
+ */
+
+#define HWSTUB_CLASS 0xff
+#define HWSTUB_SUBCLASS 0xde
+#define HWSTUB_PROTOCOL 0xad
+
+/**********************************
+ * Descriptors
+ **********************************/
+
+/**
+ * Descriptors can be retrieved using configuration descriptor or individually
+ * using the standard GetDescriptor request on the interface.
+ */
+
+#define HWSTUB_DT_VERSION 0x41 /* mandatory */
+#define HWSTUB_DT_LAYOUT 0x42 /* mandatory */
+#define HWSTUB_DT_TARGET 0x43 /* mandatory */
+#define HWSTUB_DT_STMP 0x44 /* mandatory for STMP */
+#define HWSTUB_DT_PP 0x45 /* mandatory for PP */
+#define HWSTUB_DT_JZ 0x46 /* mandatory for JZ */
+
+struct hwstub_version_desc_t
+{
+ uint8_t bLength;
+ uint8_t bDescriptorType;
+ /* full version information */
+ uint8_t bMajor;
+ uint8_t bMinor;
+ uint8_t bRevision;
+} __attribute__((packed));
+
+struct hwstub_layout_desc_t
+{
+ uint8_t bLength;
+ uint8_t bDescriptorType;
+ /* describe the range of memory used by the running code */
+ uint32_t dCodeStart;
+ uint32_t dCodeSize;
+ /* describe the range of memory used by the stack */
+ uint32_t dStackStart;
+ uint32_t dStackSize;
+ /* describe the range of memory available as a buffer */
+ uint32_t dBufferStart;
+ uint32_t dBufferSize;
+} __attribute__((packed));
+
+struct hwstub_stmp_desc_t
+{
+ uint8_t bLength;
+ uint8_t bDescriptorType;
+ /* Chip ID and revision */
+ uint16_t wChipID; /* 0x3780 for STMP3780 for example */
+ uint8_t bRevision; /* 0=TA1 on STMP3780 for example */
+ uint8_t bPackage; /* 0=169BGA for example */
+} __attribute__((packed));
+
+struct hwstub_pp_desc_t
+{
+ uint8_t bLength;
+ uint8_t bDescriptorType;
+ /* Chip ID and revision */
+ uint16_t wChipID; /* 0x5002 for PP5002 for example */
+ uint8_t bRevision[2]; /* 'B1' for B1 for example */
+} __attribute__((packed));
+
+struct hwstub_jz_desc_t
+{
+ uint8_t bLength;
+ uint8_t bDescriptorType;
+ /* Chip ID and revision */
+ uint16_t wChipID; /* 0x4760 for Jz4760 for example */
+ uint8_t bRevision; /* 0 for Jz4760, 'B' for JZ4760B */
+} __attribute__((packed));
+
+#define HWSTUB_TARGET_UNK ('U' | 'N' << 8 | 'K' << 16 | ' ' << 24)
+#define HWSTUB_TARGET_STMP ('S' | 'T' << 8 | 'M' << 16 | 'P' << 24)
+#define HWSTUB_TARGET_RK27 ('R' | 'K' << 8 | '2' << 16 | '7' << 24)
+#define HWSTUB_TARGET_PP ('P' | 'P' << 8 | ' ' << 16 | ' ' << 24)
+#define HWSTUB_TARGET_ATJ ('A' | 'T' << 8 | 'J' << 16 | ' ' << 24)
+#define HWSTUB_TARGET_JZ ('J' | 'Z' << 8 | '4' << 16 | '7' << 24)
+
+struct hwstub_target_desc_t
+{
+ uint8_t bLength;
+ uint8_t bDescriptorType;
+ /* Target ID and name */
+ uint32_t dID;
+ char bName[58];
+} __attribute__((packed));
+
+/**
+ * Socket command packet header: any transfer (in both directions) start with this.
+ * All data is transmitted in network byte order.
+ */
+#define HWSTUB_NET_ARGS 4
+
+struct hwstub_net_hdr_t
+{
+ uint32_t magic; /* magic value (HWSERVER_MAGIC) */
+ uint32_t cmd; /* command (OR'ed with (N)ACK on response) */
+ uint32_t length; /* length of the data following this header */
+ uint32_t args[HWSTUB_NET_ARGS]; /* command arguments */
+} __attribute__((packed));
+
+/**
+ * Control commands
+ *
+ * These commands are sent to the interface, using the standard bRequest field
+ * of the SETUP packet. The wIndex contains the interface number. The wValue
+ * contains an ID which is used for requests requiring several transfers.
+ */
+#define HWSTUB_GET_LOG 0x40
+#define HWSTUB_READ 0x41
+#define HWSTUB_READ2 0x42
+#define HWSTUB_WRITE 0x43
+#define HWSTUB_EXEC 0x44
+#define HWSTUB_READ2_ATOMIC 0x45
+#define HWSTUB_WRITE_ATOMIC 0x46
+
+/* the following commands and the ACK/NACK mechanism are net only */
+#define HWSERVER_ACK(n) (0x100|(n))
+#define HWSERVER_ACK_MASK 0x100
+#define HWSERVER_NACK(n) (0x200|(n))
+#define HWSERVER_NACK_MASK 0x200
+
+#define HWSERVER_MAGIC ('h' << 24 | 'w' << 16 | 's' << 8 | 't')
+
+#define HWSERVER_HELLO 0x400
+#define HWSERVER_GET_DEV_LIST 0x401
+#define HWSERVER_DEV_OPEN 0x402
+#define HWSERVER_DEV_CLOSE 0x403
+#define HWSERVER_BYE 0x404
+#define HWSERVER_GET_DESC 0x405
+#define HWSERVER_GET_LOG 0x406
+#define HWSERVER_READ 0x407
+#define HWSERVER_WRITE 0x408
+#define HWSERVER_EXEC 0x409
+
+/* net errors (always in arg[0] if command is NACKed) */
+#define HWERR_OK 0 /* success */
+#define HWERR_FAIL 1 /* general error from hwstub */
+#define HWERR_INVALID_ID 2 /* invalid id of the device */
+#define HWERR_DISCONNECTED 3 /* device got disconnected */
+
+/* read/write flags */
+#define HWSERVER_RW_ATOMIC 0x1
+
+/**********************************
+ * Control Protocol
+ **********************************/
+
+/**
+ * HWSTUB_GET_LOG:
+ * The log is returned as part of the control transfer.
+ */
+
+/**
+ * HWSTUB_READ and HWSTUB_READ2(_ATOMIC):
+ * Read a range of memory. The request works in two steps: first the host
+ * sends HWSTUB_READ with the parameters (address, length) and then
+ * a HWSTUB_READ2 to retrieve the buffer. Both requests must use the same
+ * ID in wValue, otherwise the second request will be STALLed.
+ * HWSTUB_READ2_ATOMIC behaves the same as HWSTUB_READ2 except that the read
+ * is guaranteed to be atomic (ie performed as a single memory access) and
+ * will be STALLed if atomicity can not be ensured.
+ */
+
+struct hwstub_read_req_t
+{
+ uint32_t dAddress;
+} __attribute__((packed));
+
+/**
+ * HWSTUB_WRITE:
+ * Write a range of memory. The payload starts with the following header, everything
+ * which follows is data.
+ * HWSTUB_WRITE_ATOMIC behaves the same except it is atomic. See HWSTUB_READ2_ATOMIC.
+ */
+struct hwstub_write_req_t
+{
+ uint32_t dAddress;
+} __attribute__((packed));
+
+/**
+ * HWSTUB_EXEC:
+ * Execute code at an address. Several options are available regarding ARM vs Thumb,
+ * jump vs call.
+ */
+
+#define HWSTUB_EXEC_ARM (0 << 0) /* target code is ARM */
+#define HWSTUB_EXEC_THUMB (1 << 0) /* target code is Thumb */
+#define HWSTUB_EXEC_JUMP (0 << 1) /* branch, code will never turn */
+#define HWSTUB_EXEC_CALL (1 << 1) /* call and expect return */
+
+struct hwstub_exec_req_t
+{
+ uint32_t dAddress;
+ uint16_t bmFlags;
+} __attribute__((packed));
+
+/**
+ * HWSERVER_HELLO:
+ * Say hello to the server, give protocol version and get server version.
+ * Send: args[0] = major << 8 | minor, no data
+ * Receive: args[0] = major << 8 | minor, no data
+ */
+
+/**
+ * HWSERVER_GET_DEV_LIST:
+ * Get device list.
+ * Send: no argument, no data.
+ * Receive: no argument, data contains a list of device IDs, each ID is a uint32_t
+ * transmitted in network byte order.
+ */
+
+/**
+ * HWSERVER_DEV_OPEN:
+ * Open a device and return a handle.
+ * Send: args[0] = device ID, no data.
+ * Receive: args[0] = handle ID, no data.
+ */
+
+/**
+ * HWSERVER_DEV_CLOSE:
+ * Close a device handle.
+ * Send: args[0] = handle ID, no data.
+ * Receive: no argument, no data.
+ */
+
+/**
+ * HWSERVER_BYE:
+ * Say bye to the server, closing all devices and effectively stopping the communication.
+ * Send: no argument, no data
+ * Receive: no argument, no data
+ */
+
+/**
+ * HWSERVER_GET_DESC:
+ * Query a descriptor.
+ * Send: args[0] = handle ID, args[1] = desc ID, args[2] = requested length, no data
+ * Receive: no argument, data contains RAW descriptor (ie all fields are in little-endian)
+ */
+
+/**
+ * HWSERVER_GET_LOG:
+ * Query a descriptor.
+ * Send: args[0] = handle ID, args[1] = requested length, no data
+ * Receive: no argument, data contains log data
+ */
+
+/**
+ * HWSERVER_READ:
+ * Read data.
+ * Send: args[0] = handle ID, args[1] = addr, args[2] = length, args[3] = flags
+ * Receive: no argument, data read on device
+ */
+
+/**
+ * HWSERVER_WRITE:
+ * Read data.
+ * Send: args[0] = handle ID, args[1] = addr, args[2] = flags, data to write
+ * Receive: no data
+ */
+
+#endif /* __HWSTUB_PROTOCOL__ */
diff --git a/utils/hwstub/include/hwstub_uri.hpp b/utils/hwstub/include/hwstub_uri.hpp
new file mode 100644
index 0000000000..d461764cd9
--- /dev/null
+++ b/utils/hwstub/include/hwstub_uri.hpp
@@ -0,0 +1,131 @@
+/***************************************************************************
+ * __________ __ ___.
+ * 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.
+ *
+ ****************************************************************************/
+#ifndef __HWSTUB_URI_HPP__
+#define __HWSTUB_URI_HPP__
+
+#include "hwstub.hpp"
+#include "hwstub_net.hpp"
+
+namespace hwstub {
+namespace uri {
+
+/** HWSTUB URIs
+ *
+ * They are of the form:
+ *
+ * scheme:[//domain[:port]][/][path[?query]]
+ *
+ * The scheme is mandatory and controls the type of context that is created.
+ * The following scheme are recognized:
+ * usb USB context
+ * tcp TCP context
+ * unix Unix domain context
+ * virt Virtual context (Testing and debugging)
+ * default Default context (This is the default)
+ *
+ * When creating a USB context, the domain and port must be empty:
+ * usb:
+ *
+ * When creating a TCP context, the domain and port are given as argument to
+ * the context:
+ * tcp://localhost:6666
+ *
+ * When creating a Unix context, the domain is given as argument to
+ * the context, it is invalid to specify a port. There are two types of
+ * unix contexts: the one specified by a filesystem path, or (Linux-only) by
+ * an abstract domain. Abstract names are specified as a domain starting with a '#',
+ * whereas standard path can be any path:
+ * unix:///path/to/socket
+ * unix://#hwstub
+ *
+ * When creating a virtual context, the domain will contain a specification of
+ * the device to create. The device list is of the type(param);type(param);...
+ * where the only supported type at the moment is 'dummy' with a single parameter
+ * which is the device name:
+ * virt://dummy(Device A);dummy(Device B);dummy(Super device C)
+ *
+ *
+ * HWSTUB SERVER URIs
+ *
+ * The same scheme can be used to spawn servers. Server URIs are a subset of
+ * context URIs and only support tcp and unix schemes.
+ */
+
+/** URI
+ *
+ * Represents an URI and allows queries on it */
+class uri
+{
+public:
+ uri(const std::string& uri);
+ /** Return whether the URI is syntactically correct */
+ bool valid() const;
+ /** Return error description if URI is invalid */
+ std::string error() const;
+ /** Return the original URI */
+ std::string full_uri() const;
+ /** Return the scheme */
+ std::string scheme() const;
+ /** Return the domain, or empty is none */
+ std::string domain() const;
+ /** Return the port, or empty is none */
+ std::string port() const;
+ /** Return the path, or empty is none */
+ std::string path() const;
+
+protected:
+ void parse();
+ bool validate_scheme();
+ bool validate_domain();
+ bool validate_port();
+
+ std::string m_uri; /* original uri */
+ bool m_valid; /* did it parse correctly ? */
+ std::string m_scheme; /* scheme (extracted from URI) */
+ std::string m_domain; /* domain (extracted from URI) */
+ std::string m_port; /* port (extracted from URI) */
+ std::string m_path; /* path (extracted from URI) */
+ std::string m_error; /* error string (for invalid URIs) */
+};
+
+/** Create a context based on a URI. This function only uses the scheme/domain/port
+ * parts of the URI. This function may fail and return a empty pointer. An optional
+ * string can receive a description of the error */
+std::shared_ptr<context> create_context(const uri& uri, std::string *error = nullptr);
+/** Return a safe default for a URI */
+uri default_uri();
+/** Special case function for the default function */
+std::shared_ptr<context> create_default_context(std::string *error = nullptr);
+/** Create a server based on a URI. This function only uses the scheme/domain/port
+ * parts of the URI. This function may fail and return a empty pointer. An optional
+ * string can receive a description of the error */
+std::shared_ptr<net::server> create_server(std::shared_ptr<context> ctx,
+ const uri& uri, std::string *error);
+/** Return a safe default for a server URI */
+uri default_server_uri();
+/** Print help for the format of a URI, typically for a command-line help.
+ * The help can be client-only, server-only, or both. */
+void print_usage(FILE *f, bool client, bool server);
+
+} // namespace uri
+} // namespace hwstub
+
+#endif /* __HWSTUB_URI_HPP__ */
diff --git a/utils/hwstub/include/hwstub_usb.hpp b/utils/hwstub/include/hwstub_usb.hpp
new file mode 100644
index 0000000000..6a9d4d8798
--- /dev/null
+++ b/utils/hwstub/include/hwstub_usb.hpp
@@ -0,0 +1,194 @@
+/***************************************************************************
+ * __________ __ ___.
+ * 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.
+ *
+ ****************************************************************************/
+#ifndef __HWSTUB_USB_HPP__
+#define __HWSTUB_USB_HPP__
+
+#include "hwstub_usb.hpp"
+#include <libusb.h>
+
+namespace hwstub {
+namespace usb {
+
+/** USB context
+ *
+ * Context based on libusb. */
+class context : public hwstub::context
+{
+protected:
+ context(libusb_context *ctx, bool cleanup_ctx);
+public:
+ virtual ~context();
+ /** Return native libusb context */
+ libusb_context *native_context();
+ /** Create a USB context. If cleanup_ctx is true, libusb_exit() will be
+ * called on the context on deletion of this class. If ctx is NULL, libusb_init()
+ * will be called with NULL so there is no need to init the default context. */
+ static std::shared_ptr<context> create(libusb_context *ctx, bool cleanup_ctx = false,
+ std::string *error = nullptr);
+
+protected:
+ /* NOTE ctx_dev_t = libusb_device* */
+ libusb_device *from_ctx_dev(ctx_dev_t dev);
+ ctx_dev_t to_ctx_dev(libusb_device *dev);
+ virtual error fetch_device_list(std::vector<ctx_dev_t>& list, void*& ptr);
+ virtual void destroy_device_list(void *ptr);
+ virtual error create_device(ctx_dev_t dev, std::shared_ptr<hwstub::device>& hwdev);
+ virtual bool match_device(ctx_dev_t dev, std::shared_ptr<hwstub::device> hwdev);
+
+ libusb_context *m_usb_ctx; /* libusb context (might be NULL) */
+ bool m_cleanup_ctx; /* cleanup context on delete ? */
+};
+
+/** USB device
+ *
+ * Device based on libusb_device. */
+class device : public hwstub::device
+{
+ friend class context; /* for ctor */
+protected:
+ device(std::shared_ptr<hwstub::context> ctx, libusb_device *dev);
+public:
+ virtual ~device();
+ /** Return native libusb device */
+ libusb_device *native_device();
+ /** Get bus number */
+ uint8_t get_bus_number();
+ /** Get device address */
+ uint8_t get_address();
+ /** Get device VID */
+ uint16_t get_vid();
+ /** Get device PID */
+ uint16_t get_pid();
+
+protected:
+ /** Return true if this might be a hwstub device and should appear in the list */
+ static bool is_hwstub_dev(libusb_device *dev);
+
+ virtual error open_dev(std::shared_ptr<hwstub::handle>& handle);
+ virtual bool has_multiple_open() const;
+
+ libusb_device *m_dev; /* USB device */
+};
+
+/** USB handle
+ *
+ * Handle based on libusb_device_handle. */
+class handle : public hwstub::handle
+{
+protected:
+ handle(std::shared_ptr<hwstub::device> dev, libusb_device_handle *handle);
+public:
+ virtual ~handle();
+ /** set operation timeout */
+ void set_timeout(std::chrono::milliseconds ms);
+
+protected:
+ /* interpret libusb error: >=0 means SUCCESS, others are treated as errors,
+ * LIBUSB_ERROR_NO_DEVICE is treated as DISCONNECTED */
+ error interpret_libusb_error(int err);
+ /* interpret libusb error: <0 returns interpret_libusb_error(err), otherwise
+ * returns SUCCESS if err == expected_value */
+ error interpret_libusb_error(int err, size_t expected_value);
+ /* interpret libusb error: <0 returns interpret_libusb_error(err), otherwise
+ * returns SUCCESS and write size in out_size */
+ error interpret_libusb_size(int err, size_t& out_size);
+
+ libusb_device_handle *m_handle; /* USB handle */
+ unsigned int m_timeout; /* in milliseconds */
+};
+
+/** Rockbox USB handle
+ *
+ * HWSTUB/Rockbox protocol. */
+class rb_handle : public handle
+{
+ friend class device; /* for find_intf() */
+protected:
+ rb_handle(std::shared_ptr<hwstub::device> dev, libusb_device_handle *handle, int intf);
+public:
+ virtual ~rb_handle();
+
+protected:
+ virtual error read_dev(uint32_t addr, void *buf, size_t& sz, bool atomic);
+ virtual error write_dev(uint32_t addr, const void *buf, size_t& sz, bool atomic);
+ virtual error get_dev_desc(uint16_t desc, void *buf, size_t& buf_sz);
+ virtual error get_dev_log(void *buf, size_t& buf_sz);
+ virtual error exec_dev(uint32_t addr, uint16_t flags);
+ virtual error status() const;
+ virtual size_t get_buffer_size();
+ /* Probe a device to check if it is an hwstub device and return the interface
+ * number, or <0 on error. */
+ static bool find_intf(struct libusb_device_descriptor *dev,
+ struct libusb_config_descriptor *config, int& intf);
+
+ error m_probe_status; /* probing status */
+ int m_intf; /* interface number */
+ uint16_t m_transac_id; /* transaction ID */
+ size_t m_buf_size; /* Device buffer size */
+};
+
+/** JZ USB handle
+ *
+ * JZ boot protocol */
+class jz_handle : public handle
+{
+ friend class device; /* for is_boot_dev() */
+protected:
+ jz_handle(std::shared_ptr<hwstub::device> dev, libusb_device_handle *handle);
+public:
+ virtual ~jz_handle();
+
+protected:
+ virtual error read_dev(uint32_t addr, void *buf, size_t& sz, bool atomic);
+ virtual error write_dev(uint32_t addr, const void *buf, size_t& sz, bool atomic);
+ virtual error get_dev_desc(uint16_t desc, void *buf, size_t& buf_sz);
+ virtual error get_dev_log(void *buf, size_t& buf_sz);
+ virtual error exec_dev(uint32_t addr, uint16_t flags);
+ virtual error status() const;
+ virtual size_t get_buffer_size();
+ error probe();
+ error probe_jz4760b();
+ error read_reg32(uint32_t addr, uint32_t& value);
+ error write_reg32(uint32_t addr, uint32_t value);
+
+ error jz_cpuinfo(char cpuinfo[8]);
+ error jz_set_addr(uint32_t addr);
+ error jz_set_length(uint32_t size);
+ error jz_upload(void *data, size_t& length);
+ error jz_download(const void *data, size_t& length);
+ error jz_start1(uint32_t addr);
+ error jz_flush_caches();
+ error jz_start2(uint32_t addr);
+ /* Probe a device to check if it is a jz boot device */
+ static bool is_boot_dev(struct libusb_device_descriptor *dev,
+ struct libusb_config_descriptor *config);
+
+ error m_probe_status; /* probing status */
+ struct hwstub_version_desc_t m_desc_version;
+ struct hwstub_layout_desc_t m_desc_layout;
+ struct hwstub_target_desc_t m_desc_target;
+ struct hwstub_jz_desc_t m_desc_jz;
+};
+
+} // namespace usb
+} // namespace hwstub
+
+#endif /* __HWSTUB_USB_HPP__ */
diff --git a/utils/hwstub/include/hwstub_virtual.hpp b/utils/hwstub/include/hwstub_virtual.hpp
new file mode 100644
index 0000000000..d35f98e0ec
--- /dev/null
+++ b/utils/hwstub/include/hwstub_virtual.hpp
@@ -0,0 +1,159 @@
+/***************************************************************************
+ * __________ __ ___.
+ * 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.
+ *
+ ****************************************************************************/
+#ifndef __HWSTUB_VIRTUAL_HPP__
+#define __HWSTUB_VIRTUAL_HPP__
+
+#include "hwstub.hpp"
+#include <libusb.h>
+
+namespace hwstub {
+namespace virt {
+
+class hardware;
+
+/** Virtual context
+ *
+ * A virtual context hosts a number of virtual devices.
+ * This kind of contexts is mostly useful for testing/debugging purposes */
+class context : public hwstub::context
+{
+protected:
+ context();
+public:
+ virtual ~context();
+ /** Create a virtual context. */
+ static std::shared_ptr<context> create();
+ /** To ease creation, the context can be given a specification of the initial
+ * device list using the following format:
+ * dev1;dev2;...
+ * At the moment the only format support for devi is:
+ * dummy(Device name) */
+ static std::shared_ptr<context> create_spec(const std::string& spec, std::string *error = nullptr);
+
+ /** Connect a device to the context. Return false if device is already connected,
+ * and true otherwise. This method is thread-safe. */
+ bool connect(std::shared_ptr<hardware> hw);
+ /** Disconnect a device from the context. Return false if device is not connected,
+ * and true otheriwse. This method is thread-safe. */
+ bool disconnect(std::shared_ptr<hardware> hw);
+
+protected:
+ /* NOTE ctx_dev_t = hardware* */
+ std::shared_ptr<hardware> from_ctx_dev(ctx_dev_t dev);
+ ctx_dev_t to_ctx_dev(std::shared_ptr<hardware>& dev);
+ virtual error fetch_device_list(std::vector<ctx_dev_t>& list, void*& ptr);
+ virtual void destroy_device_list(void *ptr);
+ virtual error create_device(ctx_dev_t dev, std::shared_ptr<device>& hwdev);
+ virtual bool match_device(ctx_dev_t dev, std::shared_ptr<device> hwdev);
+
+ std::vector<std::shared_ptr<hardware>> m_hwlist; /* List of connected hardware */
+};
+
+/** Virtual hardware device (server/provider side)
+ *
+ * This base class represents a virtual piece of hardware that is being accessed
+ * by the context. Users of virtual contexts must inherit from this class and
+ * implement the requests. All requests are guaranteed to be serialize at the device
+ * level so there is no need to care about concurrency issues */
+class hardware : public std::enable_shared_from_this<hardware>
+{
+public:
+ hardware();
+ virtual ~hardware();
+
+ virtual error read_dev(uint32_t addr, void *buf, size_t& sz, bool atomic) = 0;
+ virtual error write_dev(uint32_t addr, const void *buf, size_t& sz, bool atomic) = 0;
+ virtual error get_dev_desc(uint16_t desc, void *buf, size_t& buf_sz) = 0;
+ virtual error get_dev_log(void *buf, size_t& buf_sz) = 0;
+ virtual error exec_dev(uint32_t addr, uint16_t flags) = 0;
+ virtual size_t get_buffer_size() = 0;
+};
+
+/** Dummy implementation of an hardware.
+ *
+ * This dummy hardware will fail all operations except getting descriptors.
+ * The device description can be customised */
+class dummy_hardware : public hardware
+{
+public:
+ dummy_hardware(const std::string& name);
+ virtual ~dummy_hardware();
+
+ virtual error read_dev(uint32_t addr, void *buf, size_t& sz, bool atomic);
+ virtual error write_dev(uint32_t addr, const void *buf, size_t& sz, bool atomic);
+ virtual error get_dev_desc(uint16_t desc, void *buf, size_t& buf_sz);
+ virtual error get_dev_log(void *buf, size_t& buf_sz);
+ virtual error exec_dev(uint32_t addr, uint16_t flags);
+ virtual size_t get_buffer_size();
+
+protected:
+ struct hwstub_version_desc_t m_desc_version;
+ struct hwstub_layout_desc_t m_desc_layout;
+ struct hwstub_target_desc_t m_desc_target;
+};
+
+/** Virtual device (client/user side)
+ *
+ * Device based on virtual device. */
+class device : public hwstub::device
+{
+ friend class context; /* for ctor */
+protected:
+ device(std::shared_ptr<hwstub::context> ctx, std::shared_ptr<hardware> dev);
+public:
+ virtual ~device();
+ /** Get native device (possibly null) */
+ std::shared_ptr<hardware> native_device();
+
+protected:
+ virtual error open_dev(std::shared_ptr<handle>& handle);
+ virtual bool has_multiple_open() const;
+
+ std::weak_ptr<hardware> m_hwdev; /* pointer to hardware */
+};
+
+/** Virtual handle
+ *
+ * Handle based on virtual device. */
+class handle : public hwstub::handle
+{
+ friend class device; /* for ctor */
+protected:
+ handle(std::shared_ptr<hwstub::device> dev);
+public:
+ virtual ~handle();
+
+protected:
+ virtual error read_dev(uint32_t addr, void *buf, size_t& sz, bool atomic);
+ virtual error write_dev(uint32_t addr, const void *buf, size_t& sz, bool atomic);
+ virtual error get_dev_desc(uint16_t desc, void *buf, size_t& buf_sz);
+ virtual error get_dev_log(void *buf, size_t& buf_sz);
+ virtual error exec_dev(uint32_t addr, uint16_t flags);
+ virtual error status() const;
+ virtual size_t get_buffer_size();
+
+ std::weak_ptr<hardware> m_hwdev; /* pointer to hardware */
+};
+
+} // namespace virt
+} // namespace hwstub
+
+#endif /* __HWSTUB_VIRTUAL_HPP__ */