summaryrefslogtreecommitdiffstats
path: root/utils/hwstub/include
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/include
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/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__ */