summaryrefslogtreecommitdiffstats
path: root/utils/hwstub
diff options
context:
space:
mode:
Diffstat (limited to 'utils/hwstub')
-rw-r--r--utils/hwstub/include/hwstub.h (renamed from utils/hwstub/lib/hwstub.h)0
-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.h (renamed from utils/hwstub/hwstub_protocol.h)155
-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
-rw-r--r--utils/hwstub/lib/Makefile13
-rw-r--r--utils/hwstub/lib/hwstub.cpp627
-rw-r--r--utils/hwstub/lib/hwstub_net.cpp1351
-rw-r--r--utils/hwstub/lib/hwstub_protocol.h1
-rw-r--r--utils/hwstub/lib/hwstub_uri.cpp332
-rw-r--r--utils/hwstub/lib/hwstub_usb.cpp728
-rw-r--r--utils/hwstub/lib/hwstub_virtual.cpp335
-rw-r--r--utils/hwstub/stub/hwstub.make4
-rw-r--r--utils/hwstub/stub/protocol.h2
-rw-r--r--utils/hwstub/tools/Makefile16
-rw-r--r--utils/hwstub/tools/hwstub_server.cpp127
-rw-r--r--utils/hwstub/tools/hwstub_test.cpp219
19 files changed, 5051 insertions, 28 deletions
diff --git a/utils/hwstub/lib/hwstub.h b/utils/hwstub/include/hwstub.h
index 4d12de8eda..4d12de8eda 100644
--- a/utils/hwstub/lib/hwstub.h
+++ b/utils/hwstub/include/hwstub.h
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/hwstub_protocol.h b/utils/hwstub/include/hwstub_protocol.h
index 1fe982323d..39d2f2ebfe 100644
--- a/utils/hwstub/hwstub_protocol.h
+++ b/utils/hwstub/include/hwstub_protocol.h
@@ -21,12 +21,19 @@
#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 1
+#define HWSTUB_VERSION_MINOR 2
#define HWSTUB_VERSION__(maj, min) #maj"."#min
#define HWSTUB_VERSION_(maj, min) HWSTUB_VERSION__(maj, min)
@@ -49,6 +56,10 @@
#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.
@@ -57,9 +68,9 @@
#define HWSTUB_DT_VERSION 0x41 /* mandatory */
#define HWSTUB_DT_LAYOUT 0x42 /* mandatory */
#define HWSTUB_DT_TARGET 0x43 /* mandatory */
-#define HWSTUB_DT_STMP 0x44 /* optional */
-#define HWSTUB_DT_PP 0x45 /* optional */
-#define HWSTUB_DT_DEVICE 0x46 /* optional */
+#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
{
@@ -105,11 +116,21 @@ struct hwstub_pp_desc_t
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
{
@@ -120,11 +141,18 @@ struct hwstub_target_desc_t
char bName[58];
} __attribute__((packed));
-struct hwstub_device_desc_t
+/**
+ * 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
{
- uint8_t bLength;
- uint8_t bDescriptorType;
- /* Give the bRequest value for */
+ 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));
/**
@@ -134,14 +162,45 @@ struct hwstub_device_desc_t
* 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 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
+#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:
@@ -165,7 +224,7 @@ struct hwstub_read_req_t
} __attribute__((packed));
/**
- * HWSTUB_WRITE
+ * 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.
@@ -192,4 +251,68 @@ struct hwstub_exec_req_t
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__ */
diff --git a/utils/hwstub/lib/Makefile b/utils/hwstub/lib/Makefile
index 7c455e4586..92dd358ce3 100644
--- a/utils/hwstub/lib/Makefile
+++ b/utils/hwstub/lib/Makefile
@@ -1,16 +1,21 @@
-CC=gcc
AR=ar
-CFLAGS=-W -Wall -O2 `pkg-config --cflags libusb-1.0` -std=c99 -g -fPIC
-LDFLAGS=`pkg-config --libs libusb-1.0` -fPIC
+INCLUDE=../include
+CFLAGS=-W -Wall -O2 `pkg-config --cflags libusb-1.0` -std=c99 -g -fPIC -D_XOPEN_SOURCE=700 -I$(INCLUDE)
+CXXFLAGS=-W -Wall -O2 `pkg-config --cflags libusb-1.0` -std=c++11 -g -fPIC -D_XOPEN_SOURCE=700 -I$(INCLUDE)
+LDFLAGS=`pkg-config --libs libusb-1.0` -fPIC -lpthread
LIB=libhwstub.a
SRC=$(wildcard *.c)
-OBJ=$(SRC:.c=.o)
+SRCXX=$(wildcard *.cpp)
+OBJ=$(SRCXX:.cpp=.oxx) $(SRCXX:.cpp=.o)
all: $(LIB)
%.o: %.c
$(CC) $(CFLAGS) -c -o $@ $<
+%.oxx: %.cpp
+ $(CXX) $(CXXFLAGS) -c -o $@ $<
+
$(LIB): $(OBJ)
$(AR) rcs $@ $^
diff --git a/utils/hwstub/lib/hwstub.cpp b/utils/hwstub/lib/hwstub.cpp
new file mode 100644
index 0000000000..7c81146c77
--- /dev/null
+++ b/utils/hwstub/lib/hwstub.cpp
@@ -0,0 +1,627 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2015 by Amaury Pouly
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include "hwstub.hpp"
+#include <algorithm>
+#include <cstring>
+
+namespace hwstub {
+
+std::ostream cnull(0);
+std::wostream wcnull(0);
+
+std::string error_string(error err)
+{
+ switch(err)
+ {
+ case error::SUCCESS: return "success";
+ case error::ERROR: return "unspecified error";
+ case error::DISCONNECTED: return "device was disconnected";
+ case error::PROBE_FAILURE: return "probing failed";
+ case error::NO_CONTEXT: return "context was destroyed";
+ case error::USB_ERROR: return "unspecified USB error";
+ case error::DUMMY: return "operation on dummy device";
+ case error::NO_SERVER: return "server could not be reached";
+ case error::SERVER_DISCONNECTED: return "server disconnected";
+ case error::SERVER_MISMATCH: return "incompatible server";
+ case error::NET_ERROR: return "network error";
+ case error::PROTOCOL_ERROR: return "network protocol error";
+ case error::TIMEOUT: return "timeout";
+ case error::OVERFLW: return "overflow";
+ default: return "unknown error";
+ }
+}
+
+/**
+ * Context
+ */
+
+context::context()
+ :m_next_cb_ref(0)
+{
+ clear_debug();
+}
+
+context::~context()
+{
+}
+
+void context::set_debug(std::ostream& os)
+{
+ m_debug = &os;
+}
+
+std::ostream& context::debug()
+{
+ return *m_debug;
+}
+
+error context::get_device_list(std::vector<std::shared_ptr<device>>& list)
+{
+ std::unique_lock<std::recursive_mutex> lock(m_mutex);
+ error err = update_list();
+ if(err != error::SUCCESS)
+ return err;
+ list.resize(m_devlist.size());
+ for(size_t i = 0; i < m_devlist.size(); i++)
+ list[i] = m_devlist[i];
+ return error::SUCCESS;
+}
+
+error context::get_dummy_device(std::shared_ptr<device>& dev)
+{
+ // NOTE: can't use make_shared() because of the protected ctor */
+ dev.reset(new dummy_device(shared_from_this()));
+ return error::SUCCESS;
+}
+
+void context::notify_device(bool arrived, std::shared_ptr<device> dev)
+{
+ for(auto cb : m_callbacks)
+ cb.callback(shared_from_this(), arrived, dev);
+}
+
+void context::change_device(bool arrived, std::shared_ptr<device> dev)
+{
+ std::unique_lock<std::recursive_mutex> lock(m_mutex);
+ if(arrived)
+ {
+ /* add to the list (assumed it's not already there) */
+ m_devlist.push_back(dev);
+ /* notify */
+ notify_device(arrived, dev);
+ }
+ else
+ {
+ dev->disconnect();
+ /* notify first */
+ notify_device(arrived, dev);
+ auto it = std::find(m_devlist.begin(), m_devlist.end(), dev);
+ if(it != m_devlist.end())
+ m_devlist.erase(it);
+ }
+}
+
+error context::update_list()
+{
+ std::unique_lock<std::recursive_mutex> lock(m_mutex);
+ /* fetch new list */
+ std::vector<ctx_dev_t> new_list;
+ void* ptr;
+ error err = fetch_device_list(new_list, ptr);
+ if(err != error::SUCCESS)
+ return err;
+ /* determine devices that have left */
+ std::vector<std::shared_ptr<device>> to_del;
+ for(auto dev : m_devlist)
+ {
+ bool still_there = false;
+ for(auto new_dev : new_list)
+ if(match_device(new_dev, dev))
+ still_there = true;
+ if(!still_there)
+ to_del.push_back(dev);
+ }
+ for(auto dev : to_del)
+ change_device(false, dev);
+ /* determine new devices */
+ std::vector<ctx_dev_t> to_add;
+ for(auto new_dev : new_list)
+ {
+ bool exists = false;
+ for(auto dev : m_devlist)
+ if(match_device(new_dev, dev))
+ exists = true;
+ if(!exists)
+ to_add.push_back(new_dev);
+ }
+ /* create new devices */
+ for(auto dev : to_add)
+ {
+ std::shared_ptr<device> new_dev;
+ err = create_device(dev, new_dev);
+ if(err == error::SUCCESS)
+ change_device(true, new_dev);
+ }
+ /* destroy list */
+ destroy_device_list(ptr);
+ return error::SUCCESS;
+}
+
+context::callback_ref_t context::register_callback(const notification_callback_t& fn)
+{
+ std::unique_lock<std::recursive_mutex> lock(m_mutex);
+ struct callback_t cb;
+ cb.callback = fn;
+ cb.ref = m_next_cb_ref++;
+ m_callbacks.push_back(cb);
+ return cb.ref;
+}
+
+void context::unregister_callback(callback_ref_t ref)
+{
+ std::unique_lock<std::recursive_mutex> lock(m_mutex);
+ for(auto it = m_callbacks.begin(); it != m_callbacks.end(); ++it)
+ {
+ if((*it).ref == ref)
+ {
+ m_callbacks.erase(it);
+ return;
+ }
+ }
+}
+
+void context::start_polling(std::chrono::milliseconds interval)
+{
+ std::unique_lock<std::recursive_mutex> lock(m_mutex);
+ /* create poller on demand */
+ if(!m_poller)
+ m_poller.reset(new context_poller(shared_from_this(), interval));
+ else
+ m_poller->set_interval(interval);
+ m_poller->start();
+}
+
+void context::stop_polling()
+{
+ std::unique_lock<std::recursive_mutex> lock(m_mutex);
+ m_poller->stop();
+}
+
+/**
+ * Context Poller
+ */
+
+context_poller::context_poller(std::weak_ptr<context> ctx, std::chrono::milliseconds interval)
+ :m_ctx(ctx), m_running(false), m_exit(false), m_interval(interval)
+{
+ m_thread = std::thread(context_poller::thread, this);
+}
+
+context_poller::~context_poller()
+{
+ /* set exit flag, wakeup thread, wait for exit */
+ m_mutex.lock();
+ m_exit = true;
+ m_mutex.unlock();
+ m_cond.notify_one();
+ m_thread.join();
+}
+
+void context_poller::set_interval(std::chrono::milliseconds interval)
+{
+ std::unique_lock<std::mutex> lock(m_mutex);
+ /* change interval, wakeup thread to take new interval into account */
+ m_interval = interval;
+ m_cond.notify_one();
+}
+
+void context_poller::start()
+{
+ std::unique_lock<std::mutex> lock(m_mutex);
+ /* change running flag, wakeup thread to start polling */
+ m_running = true;
+ m_cond.notify_one();
+}
+
+void context_poller::stop()
+{
+ std::unique_lock<std::mutex> lock(m_mutex);
+ /* change running flag, wakeup thread to stop polling */
+ m_running = false;
+ m_cond.notify_one();
+}
+
+void context_poller::poll()
+{
+ std::unique_lock<std::mutex> lock(m_mutex);
+ while(true)
+ {
+ /* if asked, exit */
+ if(m_exit)
+ break;
+ /* if running, poll and then sleep for some time */
+ if(m_running)
+ {
+ std::shared_ptr<context> ctx = m_ctx.lock();
+ if(ctx)
+ ctx->update_list();
+ ctx.reset();
+ m_cond.wait_for(lock, m_interval);
+ }
+ /* if not, sleep until awaken */
+ else
+ m_cond.wait(lock);
+ }
+}
+
+void context_poller::thread(context_poller *poller)
+{
+ poller->poll();
+}
+
+/**
+ * Device
+ */
+device::device(std::shared_ptr<context> ctx)
+ :m_ctx(ctx), m_connected(true)
+{
+}
+
+device::~device()
+{
+}
+
+error device::open(std::shared_ptr<handle>& handle)
+{
+ std::unique_lock<std::recursive_mutex> lock(m_mutex);
+ /* get a pointer so that it's not destroyed during the runtime of the function,
+ * the pointer will be released at the end of the function */
+ std::shared_ptr<context> ctx = get_context();
+ if(!ctx)
+ return error::NO_CONTEXT;
+ /* do not even try if device is disconnected */
+ if(!connected())
+ return error::DISCONNECTED;
+ /* NOTE at the moment handle is state-less which means that we can
+ * safely give the same handle each time open() is called without callers
+ * interfering with each other. If handle eventually get a state,
+ * one will need to create a proxy class to encapsulate the state */
+ handle = m_handle.lock();
+ if(has_multiple_open() || !handle)
+ {
+ error err = open_dev(handle);
+ m_handle = handle;
+ return err;
+ }
+ else
+ return error::SUCCESS;
+}
+
+void device::disconnect()
+{
+ std::unique_lock<std::recursive_mutex> lock(m_mutex);
+ m_connected = false;
+}
+
+bool device::connected()
+{
+ return m_connected;
+}
+
+std::shared_ptr<context> device::get_context()
+{
+ return m_ctx.lock();
+}
+
+/**
+ * Handle
+ */
+
+handle::handle(std::shared_ptr<device > dev)
+ :m_dev(dev)
+{
+}
+
+handle::~handle()
+{
+}
+
+std::shared_ptr<device> handle::get_device()
+{
+ return m_dev;
+}
+
+error handle::get_desc(uint16_t desc, void *buf, size_t& buf_sz)
+{
+ std::unique_lock<std::recursive_mutex> lock(m_mutex);
+ /* get a pointer so that it's not destroyed during the runtime of the function,
+ * the pointer will be released at the end of the function */
+ std::shared_ptr<context> ctx = m_dev->get_context();
+ if(!ctx)
+ return error::NO_CONTEXT;
+ /* ensure valid status */
+ error err = status();
+ if(err != error::SUCCESS)
+ return err;
+ return get_dev_desc(desc, buf, buf_sz);
+}
+
+error handle::get_log(void *buf, size_t& buf_sz)
+{
+ std::unique_lock<std::recursive_mutex> lock(m_mutex);
+ /* get a pointer so that it's not destroyed during the runtime of the function,
+ * the pointer will be released at the end of the function */
+ std::shared_ptr<context> ctx = m_dev->get_context();
+ if(!ctx)
+ return error::NO_CONTEXT;
+ /* ensure valid status */
+ error err = status();
+ if(err != error::SUCCESS)
+ return err;
+ return get_dev_log(buf, buf_sz);
+}
+
+error handle::exec(uint32_t addr, uint16_t flags)
+{
+ std::unique_lock<std::recursive_mutex> lock(m_mutex);
+ /* get a pointer so that it's not destroyed during the runtime of the function,
+ * the pointer will be released at the end of the function */
+ std::shared_ptr<context> ctx = m_dev->get_context();
+ if(!ctx)
+ return error::NO_CONTEXT;
+ /* ensure valid status */
+ error err = status();
+ if(err != error::SUCCESS)
+ return err;
+ return exec_dev(addr, flags);
+}
+
+error handle::read(uint32_t addr, void *buf, size_t& sz, bool atomic)
+{
+ std::unique_lock<std::recursive_mutex> lock(m_mutex);
+ /* get a pointer so that it's not destroyed during the runtime of the function,
+ * the pointer will be released at the end of the function */
+ std::shared_ptr<context> ctx = m_dev->get_context();
+ if(!ctx)
+ return error::NO_CONTEXT;
+ /* ensure valid status */
+ error err = status();
+ if(err != error::SUCCESS)
+ return err;
+ /* split transfer as needed */
+ size_t cnt = 0;
+ uint8_t *bufp = (uint8_t *)buf;
+ while(sz > 0)
+ {
+ size_t xfer = std::min(sz, get_buffer_size());
+ err = read_dev(addr, buf, xfer, atomic);
+ if(err != error::SUCCESS)
+ return err;
+ sz -= xfer;
+ bufp += xfer;
+ addr += xfer;
+ cnt += xfer;
+ }
+ sz = cnt;
+ return error::SUCCESS;
+}
+
+error handle::write(uint32_t addr, const void *buf, size_t& sz, bool atomic)
+{
+ std::unique_lock<std::recursive_mutex> lock(m_mutex);
+ /* get a pointer so that it's not destroyed during the runtime of the function,
+ * the pointer will be released at the end of the function */
+ std::shared_ptr<context> ctx = m_dev->get_context();
+ if(!ctx)
+ return error::NO_CONTEXT;
+ /* ensure valid status */
+ error err = status();
+ if(err != error::SUCCESS)
+ return err;
+ /* split transfer as needed */
+ size_t cnt = 0;
+ const uint8_t *bufp = (uint8_t *)buf;
+ while(sz > 0)
+ {
+ size_t xfer = std::min(sz, get_buffer_size());
+ err = write_dev(addr, buf, xfer, atomic);
+ if(err != error::SUCCESS)
+ return err;
+ sz -= xfer;
+ bufp += xfer;
+ addr += xfer;
+ cnt += xfer;
+ }
+ sz = cnt;
+ return error::SUCCESS;
+}
+
+error handle::status() const
+{
+ /* check context */
+ if(!m_dev->get_context())
+ return error::NO_CONTEXT;
+ if(!m_dev->connected())
+ return error::DISCONNECTED;
+ else
+ return error::SUCCESS;
+}
+
+namespace
+{
+ template<typename T>
+ error helper_get_desc(handle *h, uint8_t type, T& desc)
+ {
+ size_t sz = sizeof(desc);
+ error ret = h->get_desc(type, &desc, sz);
+ if(ret != error::SUCCESS)
+ return ret;
+ if(sz != sizeof(desc) || desc.bDescriptorType != type ||
+ desc.bLength != sizeof(desc))
+ return error::ERROR;
+ else
+ return error::SUCCESS;
+ }
+}
+
+error handle::get_version_desc(hwstub_version_desc_t& desc)
+{
+ return helper_get_desc(this, HWSTUB_DT_VERSION, desc);
+}
+
+error handle::get_layout_desc(hwstub_layout_desc_t& desc)
+{
+ return helper_get_desc(this, HWSTUB_DT_LAYOUT, desc);
+}
+
+error handle::get_stmp_desc(hwstub_stmp_desc_t& desc)
+{
+ return helper_get_desc(this, HWSTUB_DT_STMP, desc);
+}
+
+error handle::get_pp_desc(hwstub_pp_desc_t& desc)
+{
+ return helper_get_desc(this, HWSTUB_DT_PP, desc);
+}
+
+error handle::get_jz_desc(hwstub_jz_desc_t& desc)
+{
+ return helper_get_desc(this, HWSTUB_DT_JZ, desc);
+}
+
+error handle::get_target_desc(hwstub_target_desc_t& desc)
+{
+ return helper_get_desc(this, HWSTUB_DT_TARGET, desc);
+}
+
+/** Dummy device */
+dummy_device::dummy_device(std::shared_ptr<context> ctx)
+ :device(ctx)
+{
+}
+
+dummy_device::~dummy_device()
+{
+}
+
+error dummy_device::open_dev(std::shared_ptr<handle>& handle)
+{
+ handle.reset(new dummy_handle(shared_from_this()));
+ return error::SUCCESS;
+}
+
+bool dummy_device::has_multiple_open() const
+{
+ return true;
+}
+
+/** Dummy handle */
+dummy_handle::dummy_handle(std::shared_ptr<device> dev)
+ :handle(dev)
+{
+ m_desc_version.bLength = sizeof(m_desc_version);
+ m_desc_version.bDescriptorType = HWSTUB_DT_VERSION;
+ m_desc_version.bMajor = HWSTUB_VERSION_MAJOR;
+ m_desc_version.bMinor = HWSTUB_VERSION_MINOR;
+ m_desc_version.bRevision = 0;
+
+ m_desc_layout.bLength = sizeof(m_desc_layout);
+ m_desc_layout.bDescriptorType = HWSTUB_DT_LAYOUT;
+ m_desc_layout.dCodeStart = 0;
+ m_desc_layout.dCodeSize = 0;
+ m_desc_layout.dStackStart = 0;
+ m_desc_layout.dStackSize = 0;
+ m_desc_layout.dBufferStart = 0;
+ m_desc_layout.dBufferSize = 1;
+
+ m_desc_target.bLength = sizeof(m_desc_target);
+ m_desc_target.bDescriptorType = HWSTUB_DT_TARGET;
+ m_desc_target.dID = HWSTUB_TARGET_UNK;
+ strcpy(m_desc_target.bName, "Dummy target");
+}
+
+dummy_handle::~dummy_handle()
+{
+}
+
+error dummy_handle::read_dev(uint32_t addr, void *buf, size_t& sz, bool atomic)
+{
+ (void) addr;
+ (void) buf;
+ (void) sz;
+ (void) atomic;
+ return error::DUMMY;
+}
+
+error dummy_handle::write_dev(uint32_t addr, const void *buf, size_t& sz, bool atomic)
+{
+ (void) addr;
+ (void) buf;
+ (void) sz;
+ (void) atomic;
+ return error::DUMMY;
+}
+
+error dummy_handle::get_dev_desc(uint16_t desc, void *buf, size_t& buf_sz)
+{
+ void *p = nullptr;
+ switch(desc)
+ {
+ case HWSTUB_DT_VERSION: p = &m_desc_version; break;
+ case HWSTUB_DT_LAYOUT: p = &m_desc_layout; break;
+ case HWSTUB_DT_TARGET: p = &m_desc_target; break;
+ default: break;
+ }
+ if(p == nullptr)
+ return error::ERROR;
+ /* size is in the bLength field of the descriptor */
+ size_t desc_sz = *(uint8_t *)p;
+ buf_sz = std::min(buf_sz, desc_sz);
+ memcpy(buf, p, buf_sz);
+ return error::SUCCESS;
+}
+
+error dummy_handle::get_dev_log(void *buf, size_t& buf_sz)
+{
+ (void) buf;
+ (void) buf_sz;
+ return error::DUMMY;
+}
+
+error dummy_handle::exec_dev(uint32_t addr, uint16_t flags)
+{
+ (void) addr;
+ (void) flags;
+ return error::DUMMY;
+}
+
+error dummy_handle::status() const
+{
+ error err = handle::status();
+ return err == error::SUCCESS ? error::DUMMY : err;
+}
+
+size_t dummy_handle::get_buffer_size()
+{
+ return 1;
+}
+
+} // namespace hwstub
diff --git a/utils/hwstub/lib/hwstub_net.cpp b/utils/hwstub/lib/hwstub_net.cpp
new file mode 100644
index 0000000000..f561a1004b
--- /dev/null
+++ b/utils/hwstub/lib/hwstub_net.cpp
@@ -0,0 +1,1351 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2016 by Amaury Pouly
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include "hwstub_net.hpp"
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <cstddef>
+#include <arpa/inet.h>
+#include <sys/types.h>
+#include <netdb.h>
+
+namespace hwstub {
+namespace net {
+
+/**
+ * Context
+ */
+context::context()
+ :m_state(state::HELLO), m_error(error::SUCCESS)
+{
+}
+
+context::~context()
+{
+}
+
+std::shared_ptr<context> context::create_socket(int socket_fd)
+{
+ // NOTE: can't use make_shared() because of the protected ctor */
+ return std::shared_ptr<socket_context>(new socket_context(socket_fd));
+}
+
+std::string context::default_unix_path()
+{
+ return "hwstub";
+}
+
+std::string context::default_tcp_domain()
+{
+ return "localhost";
+}
+
+std::string context::default_tcp_port()
+{
+ return "6666";
+}
+
+namespace
+{
+ /* len is the total length, including a 0 character if any */
+ int create_unix_low(bool abstract, const char *path, size_t len, bool conn,
+ std::string *error)
+ {
+ struct sockaddr_un address;
+ if(len > sizeof(address.sun_path))
+ {
+ if(error)
+ *error = "unix path is too long";
+ return -1;
+ }
+ int socket_fd = socket(PF_UNIX, SOCK_STREAM, 0);
+ if(socket_fd < 0)
+ {
+ if(error)
+ *error = "socket() failed";
+ return -1;
+ }
+ memset(&address, 0, sizeof(struct sockaddr_un));
+ address.sun_family = AF_UNIX;
+ /* NOTE memcpy, we don't want to add a extra 0 at the end */
+ memcpy(address.sun_path, path, len);
+ /* for abstract name, replace first character by 0 */
+ if(abstract)
+ address.sun_path[0] = 0;
+ /* NOTE sun_path is the last field of the structure */
+ size_t sz = offsetof(struct sockaddr_un, sun_path) + len;
+ /* NOTE don't give sizeof(address) because for abstract names it would contain
+ * extra garbage */
+ if(conn)
+ {
+ if(connect(socket_fd, (struct sockaddr *)&address, sz) != 0)
+ {
+ close(socket_fd);
+ if(error)
+ *error = "connect() failed";
+ return -1;
+ }
+ else
+ return socket_fd;
+ }
+ else
+ {
+ if(bind(socket_fd, (struct sockaddr *)&address, sz) != 0)
+ {
+ close(socket_fd);
+ if(error)
+ *error = "bind() failed";
+ return -1;
+ }
+ else
+ return socket_fd;
+ }
+ }
+
+ int create_tcp_low(const std::string& domain, const std::string& _port, bool server, std::string *error)
+ {
+ std::string port = _port.size() != 0 ? _port : context::default_tcp_port();
+ int socket_fd = -1;
+ struct addrinfo hints;
+ memset(&hints, 0, sizeof(struct addrinfo));
+ hints.ai_family = AF_UNSPEC; /* allow IPv4 or IPv6 */
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = 0;
+ hints.ai_protocol = 0; /* any protocol */
+
+ struct addrinfo *result;
+ int err = getaddrinfo(domain.c_str(), port.c_str(), &hints, &result);
+ if(err != 0)
+ {
+ if(error)
+ *error = std::string("getaddrinfo failed: ") + gai_strerror(err);
+ return -1;
+ }
+
+ /* getaddrinfo() returns a list of address structures.
+ * Try each address until we successfully connect(2).
+ * If socket(2) (or connect(2)/bind(2)) fails, we (close the socket
+ * and) try the next address. */
+ for(struct addrinfo *rp = result; rp != nullptr; rp = rp->ai_next)
+ {
+ socket_fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+ if(socket_fd == -1)
+ continue;
+
+ int err = 0;
+ if(server)
+ err = bind(socket_fd, rp->ai_addr, rp->ai_addrlen);
+ else
+ err = connect(socket_fd, rp->ai_addr, rp->ai_addrlen);
+ if(err < 0)
+ {
+ close(socket_fd);
+ socket_fd = -1;
+ }
+ else
+ break; /* success */
+ }
+ /* no address was tried */
+ if(socket_fd < 0 && error)
+ *error = "getaddrinfo() returned no usable result (socket()/connect()/bind() failed)";
+ return socket_fd;
+ }
+}
+
+std::shared_ptr<context> context::create_tcp(const std::string& domain,
+ const std::string& port, std::string *error)
+{
+ int fd = create_tcp_low(domain, port, false, error);
+ if(fd >= 0)
+ return context::create_socket(fd);
+ else
+ return std::shared_ptr<context>();
+}
+
+std::shared_ptr<context> context::create_unix(const std::string& path, std::string *error)
+{
+ int fd = create_unix_low(false, path.c_str(), path.size() + 1, true, error);
+ if(fd >= 0)
+ return context::create_socket(fd);
+ else
+ return std::shared_ptr<context>();
+}
+
+std::shared_ptr<context> context::create_unix_abstract(const std::string& path, std::string *error)
+{
+ std::string fake_path = "#" + path; /* the # will be overriden by 0 */
+ int fd = create_unix_low(true, fake_path.c_str(), fake_path.size(), true, error);
+ if(fd >= 0)
+ return context::create_socket(fd);
+ else
+ return std::shared_ptr<context>();
+}
+
+uint32_t context::from_ctx_dev(ctx_dev_t dev)
+{
+ return (uint32_t)(uintptr_t)dev; /* NOTE safe because it was originally a 32-bit int */
+}
+
+hwstub::context::ctx_dev_t context::to_ctx_dev(uint32_t dev)
+{
+ return (ctx_dev_t)(uintptr_t)dev; /* NOTE assume that sizeof(void *)>=sizeof(uint32_t) */
+}
+
+error context::fetch_device_list(std::vector<ctx_dev_t>& list, void*& ptr)
+{
+ (void) ptr;
+ delayed_init();
+ if(m_state == state::DEAD)
+ return m_error;
+ uint32_t args[HWSTUB_NET_ARGS] = {0};
+ uint8_t *data = nullptr;
+ size_t data_sz = 0;
+ debug() << "[net::ctx] --> GET_DEV_LIST\n";
+ error err = send_cmd(HWSERVER_GET_DEV_LIST, args, nullptr, 0, &data, &data_sz);
+ debug() << "[net::ctx] <-- GET_DEV_LIST ";
+ if(err != error::SUCCESS)
+ {
+ debug() << "failed: " << error_string(err) << "\n";
+ return err;
+ }
+ /* sanity check on size */
+ if(data_sz % 4)
+ {
+ debug() << "failed: invalid list size\n";
+ delete[] data;
+ return error::PROTOCOL_ERROR;
+ }
+ debug() << "\n";
+ list.clear();
+ /* each entry is a 32-bit ID in network order size */
+ uint32_t *data_list = (uint32_t *)data;
+ for(size_t i = 0; i < data_sz / 4; i++)
+ list.push_back(to_ctx_dev(from_net_order(data_list[i])));
+ delete[] data;
+ return error::SUCCESS;
+}
+
+void context::destroy_device_list(void *ptr)
+{
+ (void)ptr;
+}
+
+error context::create_device(ctx_dev_t dev, std::shared_ptr<hwstub::device>& hwdev)
+{
+ // NOTE: can't use make_shared() because of the protected ctor */
+ hwdev.reset(new device(shared_from_this(), from_ctx_dev(dev)));
+ return error::SUCCESS;
+}
+
+bool context::match_device(ctx_dev_t dev, std::shared_ptr<hwstub::device> hwdev)
+{
+ device *udev = dynamic_cast<device*>(hwdev.get());
+ return udev != nullptr && udev->device_id() == from_ctx_dev(dev);
+}
+
+uint32_t context::to_net_order(uint32_t u)
+{
+ return htonl(u);
+}
+
+uint32_t context::from_net_order(uint32_t u)
+{
+ return ntohl(u);
+}
+
+error context::send_cmd(uint32_t cmd, uint32_t args[HWSTUB_NET_ARGS], uint8_t *send_data,
+ size_t send_size, uint8_t **recv_data, size_t *recv_size)
+{
+ /* make sure with have the lock, this function might be called concurrently
+ * by the different threads */
+ std::unique_lock<std::recursive_mutex> lock(m_mutex);
+
+ if(m_state == state::DEAD)
+ return m_error;
+ /* do a delayed init, unless with are doing a HELLO */
+ if(m_state == state::HELLO && cmd != HWSERVER_HELLO)
+ delayed_init();
+ /* build header */
+ struct hwstub_net_hdr_t hdr;
+ hdr.magic = to_net_order(HWSERVER_MAGIC);
+ hdr.cmd = to_net_order(cmd);
+ for(size_t i = 0; i < HWSTUB_NET_ARGS; i++)
+ hdr.args[i] = to_net_order(args[i]);
+ hdr.length = to_net_order((uint32_t)send_size);
+ /* send header */
+ size_t sz = sizeof(hdr);
+ error err = send((void *)&hdr, sz);
+ if(err != error::SUCCESS)
+ {
+ m_state = state::DEAD;
+ m_error = err;
+ return err;
+ }
+ if(sz != sizeof(hdr))
+ {
+ m_state = state::DEAD;
+ m_error = error::PROTOCOL_ERROR;
+ }
+ /* send data */
+ if(send_size > 0)
+ {
+ sz = send_size;
+ err = send((void *)send_data, sz);
+ if(err != error::SUCCESS)
+ {
+ m_state = state::DEAD;
+ m_error = err;
+ return err;
+ }
+ if(sz != send_size)
+ {
+ m_state = state::DEAD;
+ m_error = error::PROTOCOL_ERROR;
+ }
+ }
+ /* receive header */
+ sz = sizeof(hdr);
+ err = recv((void *)&hdr, sz);
+ if(err != error::SUCCESS)
+ {
+ m_state = state::DEAD;
+ m_error = err;
+ return err;
+ }
+ if(sz != sizeof(hdr))
+ {
+ m_state = state::DEAD;
+ m_error = error::PROTOCOL_ERROR;
+ return m_error;
+ }
+ /* correct byte order */
+ hdr.magic = from_net_order(hdr.magic);
+ hdr.cmd = from_net_order(hdr.cmd);
+ hdr.length = from_net_order(hdr.length);
+ /* copy arguments */
+ for(size_t i = 0; i < HWSTUB_NET_ARGS; i++)
+ args[i] = from_net_order(hdr.args[i]);
+ /* check header */
+ if(hdr.magic != HWSERVER_MAGIC)
+ {
+ m_state = state::DEAD;
+ m_error = error::PROTOCOL_ERROR;
+ return m_error;
+ }
+ /* check NACK */
+ if(hdr.cmd == HWSERVER_NACK(cmd))
+ {
+ /* translate error */
+ switch(args[0])
+ {
+ case HWERR_FAIL: err = error::ERROR; break;
+ case HWERR_INVALID_ID: err = error::ERROR; break; /* should not happen */
+ case HWERR_DISCONNECTED: err = error::DISCONNECTED; break;
+ }
+ return err;
+ }
+ /* check not ACK */
+ if(hdr.cmd != HWSERVER_ACK(cmd))
+ {
+ m_state = state::DEAD;
+ m_error = error::PROTOCOL_ERROR;
+ return m_error;
+ }
+ /* receive additional data */
+ uint8_t *data = nullptr;
+ if(hdr.length > 0)
+ {
+ data = new uint8_t[hdr.length];
+ sz = hdr.length;
+ err = recv((void *)data, sz);
+ if(err != error::SUCCESS)
+ {
+ m_state = state::DEAD;
+ m_error = err;
+ return err;
+ }
+ if(sz != hdr.length)
+ {
+ m_state = state::DEAD;
+ m_error = error::PROTOCOL_ERROR;
+ return m_error;
+ }
+ }
+ /* copy data if user want it */
+ if(recv_data)
+ {
+ if(*recv_data == nullptr)
+ {
+ *recv_data = data;
+ *recv_size = hdr.length;
+ }
+ else if(*recv_size < hdr.length)
+ {
+ delete[] data;
+ return error::OVERFLW;
+ }
+ else
+ {
+ *recv_size = hdr.length;
+ memcpy(*recv_data, data, *recv_size);
+ delete[] data;
+ }
+ }
+ /* throw it away otherwise */
+ else
+ {
+ delete[] data;
+ }
+ return error::SUCCESS;
+}
+
+void context::delayed_init()
+{
+ /* only do HELLO if we haven't do it yet */
+ if(m_state != state::HELLO)
+ return;
+ debug() << "[net::ctx] --> HELLO " << HWSTUB_VERSION_MAJOR << "."
+ << HWSTUB_VERSION_MINOR << "\n";
+ /* send HELLO with our version and see what the server is up to */
+ uint32_t args[HWSTUB_NET_ARGS] = {0};
+ args[0] = HWSTUB_VERSION_MAJOR << 8 | HWSTUB_VERSION_MINOR;
+ error err = send_cmd(HWSERVER_HELLO, args, nullptr, 0, nullptr, nullptr);
+ if(err != error::SUCCESS)
+ {
+ debug() << "[net::ctx] <-- HELLO failed: " << error_string(err) << "\n";
+ m_state = state::DEAD;
+ m_error = err;
+ return;
+ }
+ /* check the server is running the same version */
+ debug() << "[net::ctx] <-- HELLO " << ((args[0] & 0xff00) >> 8) << "." << (args[0] & 0xff) << "";
+ if(args[0] != (HWSTUB_VERSION_MAJOR << 8 | HWSTUB_VERSION_MINOR))
+ {
+ debug() << " (mismatch)\n";
+ m_state = state::DEAD;
+ m_error = error::SERVER_MISMATCH;
+ }
+ debug() << " (good)\n";
+ /* good, we can now send commands */
+ m_state = state::IDLE;
+}
+
+void context::stop_context()
+{
+ /* make sure with have the lock, this function might be call asynchronously */
+ std::unique_lock<std::recursive_mutex> lock(m_mutex);
+ /* if dead, don't do anything */
+ if(m_state == state::DEAD)
+ return;
+ /* only send BYE if we are initialized */
+ if(m_state == state::IDLE)
+ {
+ debug() << "[net::ctx] --> BYE\n";
+ /* send BYE */
+ uint32_t args[HWSTUB_NET_ARGS] = {0};
+ error err = send_cmd(HWSERVER_BYE, args, nullptr, 0, nullptr, nullptr);
+ if(err != error::SUCCESS)
+ {
+ debug() << "[net::ctx] <-- BYE failed: " << error_string(err) << "\n";
+ m_state = state::DEAD;
+ m_error = err;
+ return;
+ }
+ debug() << "[net::ctx] <-- BYE\n";
+ }
+ /* now we are dead */
+ m_state = state::DEAD;
+ m_error = error::SERVER_DISCONNECTED;
+}
+
+/**
+ * Socket context
+ */
+socket_context::socket_context(int socket_fd)
+ :m_socketfd(socket_fd)
+{
+ set_timeout(std::chrono::milliseconds(1000));
+}
+
+socket_context::~socket_context()
+{
+ stop_context();
+ close(m_socketfd);
+}
+
+void socket_context::set_timeout(std::chrono::milliseconds ms)
+{
+ struct timeval tv;
+ tv.tv_usec = 1000 * (ms.count() % 1000);
+ tv.tv_sec = ms.count() / 1000;
+ /* set timeout for the client operations */
+ setsockopt(m_socketfd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv));
+ setsockopt(m_socketfd, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof(tv));
+}
+
+error socket_context::send(void *buffer, size_t& sz)
+{
+ debug() << "[net::ctx::sock] send(" << sz << "): ";
+ int ret = ::send(m_socketfd, buffer, sz, MSG_NOSIGNAL);
+ if(ret >= 0)
+ {
+ debug() << "good(" << ret << ")\n";
+ sz = (size_t)ret;
+ return error::SUCCESS;
+ }
+ /* convert some errors */
+ debug() << "fail(" << errno << "," << strerror(errno) << ")\n";
+ switch(errno)
+ {
+#if EAGAIN != EWOULDBLOCK
+ case EAGAIN:
+#endif
+ case EWOULDBLOCK: return error::TIMEOUT;
+ case ECONNRESET: case EPIPE: return error::SERVER_DISCONNECTED;
+ default: return error::NET_ERROR;
+ }
+}
+
+error socket_context::recv(void *buffer, size_t& sz)
+{
+ debug() << "[net::ctx::sock] recv(" << sz << "): ";
+ int ret = ::recv(m_socketfd, buffer, sz, MSG_WAITALL);
+ if(ret > 0)
+ {
+ debug() << "good(" << ret << ")\n";
+ sz = (size_t)ret;
+ return error::SUCCESS;
+ }
+ if(ret == 0)
+ {
+ debug() << "disconnected\n";
+ return error::SERVER_DISCONNECTED;
+ }
+ debug() << "fail(" << errno << "," << strerror(errno) << ")\n";
+ switch(errno)
+ {
+#if EAGAIN != EWOULDBLOCK
+ case EAGAIN:
+#endif
+ case EWOULDBLOCK: return error::TIMEOUT;
+ default: return error::NET_ERROR;
+ }
+}
+
+/**
+ * Device
+ */
+device::device(std::shared_ptr<hwstub::context> ctx, uint32_t devid)
+ :hwstub::device(ctx), m_device_id(devid)
+{
+}
+
+device::~device()
+{
+}
+
+uint32_t device::device_id()
+{
+ return m_device_id;
+}
+
+error device::open_dev(std::shared_ptr<hwstub::handle>& handle)
+{
+ std::shared_ptr<hwstub::context> hctx = get_context();
+ if(!hctx)
+ return error::NO_CONTEXT;
+ context *ctx = dynamic_cast<context*>(hctx.get());
+ ctx->debug() << "[net::dev] --> DEV_OPEN(" << m_device_id << ")\n";
+ /* ask the server to open the device, note that the device ID may not exists
+ * anymore */
+ uint32_t args[HWSTUB_NET_ARGS] = {0};
+ args[0] = m_device_id;
+ error err = ctx->send_cmd(HWSERVER_DEV_OPEN, args, nullptr, 0, nullptr, nullptr);
+ if(err != error::SUCCESS)
+ {
+ ctx->debug() << "[net::ctx::dev] <-- DEV_OPEN failed: " << error_string(err) << "\n";
+ return err;
+ }
+ ctx->debug() << "[net::ctx::dev] <-- DEV_OPEN: handle = " << args[0] << "\n";
+ // NOTE: can't use make_shared() because of the protected ctor */
+ handle.reset(new hwstub::net::handle(shared_from_this(), args[0]));
+ return error::SUCCESS;
+}
+
+bool device::has_multiple_open() const
+{
+ return false;
+}
+
+/**
+ * Handle
+ */
+handle::handle(std::shared_ptr<hwstub::device> dev, uint32_t hid)
+ :hwstub::handle(dev), m_handle_id(hid)
+{
+}
+
+handle::~handle()
+{
+ /* try to close the handle, if context is still accessible */
+ std::shared_ptr<hwstub::context> hctx = get_device()->get_context();
+ if(hctx)
+ {
+ context *ctx = dynamic_cast<context*>(hctx.get());
+ ctx->debug() << "[net::handle] --> DEV_CLOSE(" << m_handle_id << ")\n";
+ uint32_t args[HWSTUB_NET_ARGS] = {0};
+ args[0] = m_handle_id;
+ error err = ctx->send_cmd(HWSERVER_DEV_CLOSE, args, nullptr, 0, nullptr, nullptr);
+ if(err != error::SUCCESS)
+ ctx->debug() << "[net::handle] <-- DEV_CLOSE failed: " << error_string(err) << "\n";
+ else
+ ctx->debug() << "[net::handle] <-- DEV_CLOSE\n";
+ }
+}
+
+error handle::read_dev(uint32_t addr, void *buf, size_t& sz, bool atomic)
+{
+ std::shared_ptr<hwstub::context> hctx = get_device()->get_context();
+ if(!hctx)
+ return error::NO_CONTEXT;
+
+ context *ctx = dynamic_cast<context*>(hctx.get());
+ ctx->debug() << "[net::handle] --> READ(" << m_handle_id << ",0x" << std::hex
+ << addr << "," << sz << "," << atomic << ")\n";
+ uint32_t args[HWSTUB_NET_ARGS] = {0};
+ args[0] = m_handle_id;
+ args[1] = addr;
+ args[2] = sz;
+ args[3] = atomic ? HWSERVER_RW_ATOMIC : 0;
+ error err = ctx->send_cmd(HWSERVER_READ, args, nullptr, 0, (uint8_t **)&buf, &sz);
+ if(err != error::SUCCESS)
+ {
+ ctx->debug() << "[net::handle] <-- READ failed: " << error_string(err) << "\n";
+ return err;
+ }
+ ctx->debug() << "[net::handle] <-- READ\n";
+ return error::SUCCESS;
+}
+
+error handle::write_dev(uint32_t addr, const void *buf, size_t& sz, bool atomic)
+{
+ std::shared_ptr<hwstub::context> hctx = get_device()->get_context();
+ if(!hctx)
+ return error::NO_CONTEXT;
+
+ context *ctx = dynamic_cast<context*>(hctx.get());
+ ctx->debug() << "[net::handle] --> WRITE(" << m_handle_id << ",0x" << std::hex
+ << addr << "," << sz << "," << atomic << ")\n";
+ uint32_t args[HWSTUB_NET_ARGS] = {0};
+ args[0] = m_handle_id;
+ args[1] = addr;
+ args[2] = atomic ? HWSERVER_RW_ATOMIC : 0;
+ error err = ctx->send_cmd(HWSERVER_WRITE, args, (uint8_t *)buf, sz, nullptr, nullptr);
+ if(err != error::SUCCESS)
+ {
+ ctx->debug() << "[net::handle] <-- WRITE failed: " << error_string(err) << "\n";
+ return err;
+ }
+ ctx->debug() << "[net::handle] <-- WRITE\n";
+ return error::SUCCESS;
+}
+
+error handle::get_dev_desc(uint16_t desc, void *buf, size_t& buf_sz)
+{
+ std::shared_ptr<hwstub::context> hctx = get_device()->get_context();
+ if(!hctx)
+ return error::NO_CONTEXT;
+
+ context *ctx = dynamic_cast<context*>(hctx.get());
+ ctx->debug() << "[net::handle] --> GET_DESC(" << m_handle_id << ",0x" << std::hex
+ << desc << "," << buf_sz << ")\n";
+ uint32_t args[HWSTUB_NET_ARGS] = {0};
+ args[0] = m_handle_id;
+ args[1] = desc;
+ args[2] = buf_sz;
+ error err = ctx->send_cmd(HWSERVER_GET_DESC, args, nullptr, 0, (uint8_t **)&buf, &buf_sz);
+ if(err != error::SUCCESS)
+ {
+ ctx->debug() << "[net::handle] <-- GET_DESC failed: " << error_string(err) << "\n";
+ return err;
+ }
+ ctx->debug() << "[net::handle] <-- GET_DESC\n";
+ return error::SUCCESS;
+}
+
+error handle::get_dev_log(void *buf, size_t& buf_sz)
+{
+ std::shared_ptr<hwstub::context> hctx = get_device()->get_context();
+ if(!hctx)
+ return error::NO_CONTEXT;
+
+ context *ctx = dynamic_cast<context*>(hctx.get());
+ ctx->debug() << "[net::handle] --> GET_LOG(" << buf_sz << ")\n";
+ uint32_t args[HWSTUB_NET_ARGS] = {0};
+ args[0] = m_handle_id;
+ args[1] = buf_sz;
+ error err = ctx->send_cmd(HWSERVER_GET_LOG, args, nullptr, 0, (uint8_t **)&buf, &buf_sz);
+ if(err != error::SUCCESS)
+ {
+ ctx->debug() << "[net::handle] <-- GET_LOG failed: " << error_string(err) << "\n";
+ return err;
+ }
+ ctx->debug() << "[net::handle] <-- GET_LOG\n";
+ return error::SUCCESS;
+}
+
+error handle::exec_dev(uint32_t addr, uint16_t flags)
+{
+ (void) addr;
+ (void) flags;
+ return error::DUMMY;
+}
+
+error handle::status() const
+{
+ return hwstub::handle::status();
+}
+
+size_t handle::get_buffer_size()
+{
+ return 2048;
+}
+
+/**
+ * Server
+ */
+server::server(std::shared_ptr<hwstub::context> ctx)
+ :m_context(ctx)
+{
+ clear_debug();
+}
+
+server::~server()
+{
+}
+
+void server::stop_server()
+{
+ std::unique_lock<std::recursive_mutex> lock(m_mutex);
+ /* ask all client threads to stop */
+ for(auto& cl : m_client)
+ cl.exit = true;
+ /* wait for each thread to stop */
+ for(auto& cl : m_client)
+ cl.future.wait();
+}
+
+std::shared_ptr<server> server::create_unix(std::shared_ptr<hwstub::context> ctx,
+ const std::string& path, std::string *error)
+{
+ int fd = create_unix_low(false, path.c_str(), path.size() + 1, false, error);
+ if(fd >= 0)
+ return socket_server::create_socket(ctx, fd);
+ else
+ return std::shared_ptr<server>();
+}
+
+std::shared_ptr<server> server::create_unix_abstract(std::shared_ptr<hwstub::context> ctx,
+ const std::string& path, std::string *error)
+{
+ std::string fake_path = "#" + path; /* the # will be overriden by 0 */
+ int fd = create_unix_low(true, fake_path.c_str(), fake_path.size(), false, error);
+ if(fd >= 0)
+ return socket_server::create_socket(ctx, fd);
+ else
+ return std::shared_ptr<server>();
+}
+
+std::shared_ptr<server> server::create_socket(std::shared_ptr<hwstub::context> ctx,
+ int socket_fd)
+{
+ return socket_server::create(ctx, socket_fd);
+}
+
+std::shared_ptr<server> server::create_tcp(std::shared_ptr<hwstub::context> ctx,
+ const std::string& domain, const std::string& port, std::string *error)
+{
+ int fd = create_tcp_low(domain, port, true, error);
+ if(fd >= 0)
+ return socket_server::create_socket(ctx, fd);
+ else
+ return std::shared_ptr<server>();
+}
+
+void server::set_debug(std::ostream& os)
+{
+ m_debug = &os;
+}
+
+std::ostream& server::debug()
+{
+ return *m_debug;
+}
+
+server::client_state::client_state(srv_client_t cl, std::future<void>&& f)
+ :client(cl), future(std::move(f)), exit(false), next_dev_id(42),
+ next_handle_id(19)
+{
+}
+
+void server::client_thread2(server *s, client_state *cs)
+{
+ s->client_thread(cs);
+}
+
+uint32_t server::to_net_order(uint32_t u)
+{
+ return htonl(u);
+}
+
+uint32_t server::from_net_order(uint32_t u)
+{
+ return ntohl(u);
+}
+
+void server::client_thread(client_state *state)
+{
+ debug() << "[net::srv::client] start: " << state->client << "\n";
+ while(!state->exit)
+ {
+ /* wait for some header */
+ struct hwstub_net_hdr_t hdr;
+ size_t sz = sizeof(hdr);
+ error err;
+ /* wait for some command, or exit flag */
+ do
+ err = recv(state->client, (void *)&hdr, sz);
+ while(err == error::TIMEOUT && !state->exit);
+ if(state->exit || err != error::SUCCESS || sz != sizeof(hdr))
+ break;
+ /* convert to host order */
+ hdr.magic = from_net_order(hdr.magic);
+ hdr.cmd = from_net_order(hdr.cmd);
+ hdr.length = from_net_order(hdr.length);
+ /* copy arguments */
+ for(size_t i = 0; i < HWSTUB_NET_ARGS; i++)
+ hdr.args[i] = from_net_order(hdr.args[i]);
+ /* check header */
+ if(hdr.magic != HWSERVER_MAGIC)
+ break;
+ /* receive data
+ * FIXME check length here */
+ uint8_t *data = nullptr;
+ if(hdr.length > 0)
+ {
+ data = new uint8_t[hdr.length];
+ sz = hdr.length;
+ /* wait for some command, or exit flag */
+ do
+ err = recv(state->client, (void *)data, sz);
+ while(err == error::TIMEOUT && !state->exit);
+ if(state->exit || err != error::SUCCESS || sz != hdr.length)
+ {
+ delete[] data;
+ break;
+ }
+ }
+ /* hande command */
+ uint8_t *send_data = nullptr;
+ size_t send_size = 0;
+ err = handle_cmd(state, hdr.cmd, hdr.args, data, hdr.length,
+ send_data, send_size);
+ /* free data */
+ delete[] data;
+ /* construct header */
+ if(err != error::SUCCESS)
+ {
+ hdr.magic = to_net_order(HWSERVER_MAGIC);
+ hdr.cmd = to_net_order(HWSERVER_NACK(hdr.cmd));
+ hdr.length = to_net_order(0);
+ hdr.args[0] = to_net_order(HWERR_FAIL);
+ for(size_t i = 1; i < HWSTUB_NET_ARGS; i++)
+ hdr.args[i] = to_net_order(0);
+ send_size = 0;
+ }
+ else
+ {
+ hdr.magic = to_net_order(HWSERVER_MAGIC);
+ hdr.cmd = to_net_order(HWSERVER_ACK(hdr.cmd));
+ hdr.length = to_net_order(send_size);
+ for(size_t i = 0; i < HWSTUB_NET_ARGS; i++)
+ hdr.args[i] = to_net_order(hdr.args[i]);
+ }
+ /* send header */
+ sz = sizeof(hdr);
+ do
+ err = send(state->client, (void *)&hdr, sz);
+ while(err == error::TIMEOUT && !state->exit);
+ if(state->exit || err != error::SUCCESS || sz != sizeof(hdr))
+ {
+ delete[] send_data;
+ break;
+ }
+ /* send data if there is some */
+ if(send_size > 0)
+ {
+ sz = send_size;
+ do
+ err = send(state->client, (void *)send_data, sz);
+ while(err == error::TIMEOUT && !state->exit);
+ delete[] send_data;
+ if(state->exit || err != error::SUCCESS || sz != send_size)
+ break;
+ }
+ }
+ debug() << "[net::srv::client] stop: " << state->client << "\n";
+ /* clean client state to avoiding keeping references to objets */
+ state->dev_map.clear();
+ state->handle_map.clear();
+ /* kill client */
+ terminate_client(state->client);
+}
+
+void server::client_arrived(srv_client_t client)
+{
+ std::unique_lock<std::recursive_mutex> lock(m_mutex);
+ debug() << "[net::srv] client arrived: " << client << "\n";
+ /* the naive way would be to use a std::thread but this class is annoying
+ * because it is impossible to check if a thread has exited, except by calling
+ * join() which is blocking. Fortunately, std::packaged_task and std::future
+ * provide a way around this */
+
+ std::packaged_task<void(server*, client_state*)> task(&server::client_thread2);
+ m_client.emplace_back(client, task.get_future());
+ std::thread(std::move(task), this, &m_client.back()).detach();
+}
+
+void server::client_left(srv_client_t client)
+{
+ std::unique_lock<std::recursive_mutex> lock(m_mutex);
+ debug() << "[net::srv] client left: " << client << "\n";
+ /* find thread and set its exit flag, also cleanup threads that finished */
+ for(auto it = m_client.begin(); it != m_client.end();)
+ {
+ /* check if thread has finished */
+ if(it->future.wait_for(std::chrono::milliseconds(0)) == std::future_status::ready)
+ {
+ it = m_client.erase(it);
+ continue;
+ }
+ /* set exit flag if this our thread */
+ if(it->client == client)
+ it->exit = true;
+ ++it;
+ }
+}
+
+error server::handle_cmd(client_state *state, uint32_t cmd, uint32_t args[HWSTUB_NET_ARGS],
+ uint8_t *recv_data, size_t recv_size, uint8_t*& send_data, size_t& send_size)
+{
+ send_data = nullptr;
+ send_size = 0;
+ /* NOTE: commands are serialized by the client thread, this function is thus
+ * thread safe WITH RESPECT TO CLIENT DATA. If you need to use global data here,
+ * protect it by a mutex or make sure it is safe (hwstub context is thread-safe) */
+
+ /* HELLO */
+ if(cmd == HWSERVER_HELLO)
+ {
+ debug() << "[net::srv::cmd] --> HELLO " << ((args[0] & 0xff00) >> 8)
+ << "." << (args[0] & 0xff);
+ if(args[0] != (HWSTUB_VERSION_MAJOR << 8 | HWSTUB_VERSION_MINOR))
+ {
+ debug() << " (mismatch)\n";
+ return error::ERROR;
+ }
+ debug() << " (good)\n";
+ debug() << "[net::srv::cmd] <-- HELLO " << HWSTUB_VERSION_MAJOR << "."
+ << HWSTUB_VERSION_MINOR << "\n";
+ /* send HELLO with our version */
+ args[0] = HWSTUB_VERSION_MAJOR << 8 | HWSTUB_VERSION_MINOR;
+ return error::SUCCESS;
+ }
+ /* BYE */
+ else if(cmd == HWSERVER_BYE)
+ {
+ debug() << "[net::srv::cmd] --> BYE\n";
+ /* ask client thread to exit after this */
+ state->exit = true;
+ debug() << "[net::srv::cmd] <-- BYE\n";
+ return error::SUCCESS;
+ }
+ /* GET_DEV_LIST */
+ else if(cmd == HWSERVER_GET_DEV_LIST)
+ {
+ debug() << "[net::srv::cmd] --> GET_DEV_LIST\n";
+ /* fetch list again */
+ std::vector<std::shared_ptr<hwstub::device>> list;
+ error err = m_context->get_device_list(list);
+ if(err != error::SUCCESS)
+ {
+ debug() << "[net::srv::cmd] cannot fetch list: " << hwstub::error_string(err) << "\n";
+ debug() << "[net::srv::cmd] <-- GET_DEV_LIST (error)\n";
+ return err;
+ }
+ /* update list: drop device that left */
+ std::vector<uint32_t> to_drop;
+ for(auto it : state->dev_map)
+ {
+ bool still_there = false;
+ /* this has quadratic complexity, optimize this if needed */
+ for(auto dev : list)
+ if(it.second == dev)
+ still_there = true;
+ if(!still_there)
+ to_drop.push_back(it.first);
+ }
+ for(auto id : to_drop)
+ state->dev_map.erase(state->dev_map.find(id));
+ /* add new devices */
+ std::vector<std::shared_ptr<hwstub::device>> to_add;
+ for(auto dev : list)
+ {
+ bool already_there = false;
+ for(auto it : state->dev_map)
+ if(it.second == dev)
+ already_there = true;
+ if(!already_there)
+ to_add.push_back(dev);
+ }
+ for(auto dev : to_add)
+ state->dev_map[state->next_dev_id++] = dev;
+ /* create response list */
+ send_size = sizeof(uint32_t) * state->dev_map.size();
+ send_data = new uint8_t[send_size];
+ uint32_t *p = (uint32_t *)send_data;
+ for(auto it : state->dev_map)
+ *p++ = to_net_order(it.first);
+ debug() << "[net::srv::cmd] <-- GET_DEV_LIST\n";
+ return error::SUCCESS;
+ }
+ /* DEV_OPEN */
+ else if(cmd == HWSERVER_DEV_OPEN)
+ {
+ uint32_t devid = args[0];
+ debug() << "[net::srv::cmd] --> DEV_OPEN(" << devid << ")\n";
+ /* check ID is valid */
+ auto it = state->dev_map.find(devid);
+ if(it == state->dev_map.end())
+ {
+ debug() << "[net::srv::cmd] unknwon device ID\n";
+ debug() << "[net::srv::cmd] <-- DEV_OPEN (error)\n";
+ return error::ERROR;
+ }
+ /* good, now try to get a handle */
+ std::shared_ptr<hwstub::handle> handle;
+ error err = it->second->open(handle);
+ if(err != error::SUCCESS)
+ {
+ debug() << "[net::srv::cmd] cannot open device: " << hwstub::error_string(err) << "\n";
+ return err;
+ }
+ /* record ID and return it */
+ args[0] = state->next_handle_id;
+ state->handle_map[state->next_handle_id++] = handle;
+ return error::SUCCESS;
+ }
+ /* DEV_CLOSE */
+ else if(cmd == HWSERVER_DEV_CLOSE)
+ {
+ uint32_t hid = args[0];
+ debug() << "[net::srv::cmd] --> DEV_CLOSE(" << hid << ")\n";
+ /* check ID is valid */
+ auto it = state->handle_map.find(hid);
+ if(it == state->handle_map.end())
+ {
+ debug() << "[net::srv::cmd] unknwon handle ID\n";
+ debug() << "[net::srv::cmd] <-- DEV_CLOSE (error)\n";
+ return error::ERROR;
+ }
+ /* release ID and handle */
+ state->handle_map.erase(it);
+ debug() << "[net::srv::cmd] <-- DEV_CLOSE\n";
+ return error::SUCCESS;
+ }
+ /* HWSERVER_GET_DESC */
+ else if(cmd == HWSERVER_GET_DESC)
+ {
+ uint32_t hid = args[0];
+ uint32_t did = args[1];
+ uint32_t len = args[2];
+ debug() << "[net::srv::cmd] --> GET_DESC(" << hid << ",0x" << std::hex << did
+ << "," << len << ")\n";
+ /* check ID is valid */
+ auto it = state->handle_map.find(hid);
+ if(it == state->handle_map.end())
+ {
+ debug() << "[net::srv::cmd] unknown handle ID\n";
+ debug() << "[net::srv::cmd] <-- GET_DESC (error)\n";
+ return error::ERROR;
+ }
+ /* query desc */
+ send_size = len;
+ send_data = new uint8_t[send_size];
+ error err = it->second->get_desc(did, send_data, send_size);
+ if(err != error::SUCCESS)
+ {
+ delete[] send_data;
+ debug() << "[net::srv::cmd] cannot get descriptor: " << error_string(err) << "\n";
+ debug() << "[net::srv::cmd] <-- GET_DESC (error)\n";
+ return err;
+ }
+ debug() << "[net::srv::cmd] <-- GET_DESC\n";
+ return error::SUCCESS;
+ }
+ /* HWSERVER_GET_LOG */
+ else if(cmd == HWSERVER_GET_LOG)
+ {
+ uint32_t hid = args[0];
+ uint32_t len = args[1];
+ debug() << "[net::srv::cmd] --> GET_LOG(" << hid << "," << len << ")\n";
+ /* check ID is valid */
+ auto it = state->handle_map.find(hid);
+ if(it == state->handle_map.end())
+ {
+ debug() << "[net::srv::cmd] unknown handle ID\n";
+ debug() << "[net::srv::cmd] <-- GET_DESC (error)\n";
+ return error::ERROR;
+ }
+ /* query log */
+ send_size = len;
+ send_data = new uint8_t[send_size];
+ error err = it->second->get_log(send_data, send_size);
+ if(err != error::SUCCESS)
+ {
+ delete[] send_data;
+ debug() << "[net::srv::cmd] cannot get log: " << error_string(err) << "\n";
+ debug() << "[net::srv::cmd] <-- GET_LOG (error)\n";
+ return err;
+ }
+ if(send_size == 0)
+ delete[] send_data;
+ debug() << "[net::srv::cmd] <-- GET_LOG\n";
+ return error::SUCCESS;
+ }
+ /* HWSERVER_READ */
+ else if(cmd == HWSERVER_READ)
+ {
+ uint32_t hid = args[0];
+ uint32_t addr = args[1];
+ uint32_t len = args[2];
+ uint32_t flags = args[3];
+ debug() << "[net::srv::cmd] --> READ(" << hid << ",0x" << std::hex << addr << ","
+ << len << ",0x" << std::hex << flags << ")\n";
+ /* check ID is valid */
+ auto it = state->handle_map.find(hid);
+ if(it == state->handle_map.end())
+ {
+ debug() << "[net::srv::cmd] unknown handle ID\n";
+ debug() << "[net::srv::cmd] <-- READ (error)\n";
+ return error::ERROR;
+ }
+ /* read */
+ send_size = len;
+ send_data = new uint8_t[send_size];
+ error err = it->second->read(addr, send_data, send_size, !!(flags & HWSERVER_RW_ATOMIC));
+ if(err != error::SUCCESS)
+ {
+ delete[] send_data;
+ debug() << "[net::srv::cmd] cannot read: " << error_string(err) << "\n";
+ debug() << "[net::srv::cmd] <-- READ (error)\n";
+ return err;
+ }
+ debug() << "[net::srv::cmd] <-- READ\n";
+ return error::SUCCESS;
+ }
+ /* HWSERVER_WRITE */
+ else if(cmd == HWSERVER_WRITE)
+ {
+ uint32_t hid = args[0];
+ uint32_t addr = args[1];
+ uint32_t flags = args[2];
+ debug() << "[net::srv::cmd] --> WRITE(" << hid << ",0x" << std::hex << addr << ","
+ << recv_size << ",0x" << std::hex << flags << ")\n";
+ /* check ID is valid */
+ auto it = state->handle_map.find(hid);
+ if(it == state->handle_map.end())
+ {
+ debug() << "[net::srv::cmd] unknown handle ID\n";
+ debug() << "[net::srv::cmd] <-- WRITE (error)\n";
+ return error::ERROR;
+ }
+ /* write */
+ error err = it->second->write(addr, recv_data, recv_size, !!(flags & HWSERVER_RW_ATOMIC));
+ if(err != error::SUCCESS)
+ {
+ delete[] send_data;
+ debug() << "[net::srv::cmd] cannot write: " << error_string(err) << "\n";
+ debug() << "[net::srv::cmd] <-- WRITE (error)\n";
+ return err;
+ }
+ debug() << "[net::srv::cmd] <-- WRITE\n";
+ return error::SUCCESS;
+ }
+ else
+ {
+ debug() << "[net::srv::cmd] <-> unknown cmd (0x" << std::hex << cmd << ")\n";
+ return error::ERROR;
+ }
+}
+
+/*
+ * Socket server
+ */
+socket_server::socket_server(std::shared_ptr<hwstub::context> contex, int socket_fd)
+ :server(contex), m_socketfd(socket_fd)
+{
+ m_discovery_exit = false;
+ set_timeout(std::chrono::milliseconds(1000));
+ m_discovery_thread = std::thread(&socket_server::discovery_thread1, this);
+}
+
+socket_server::~socket_server()
+{
+ /* first stop discovery thread to make sure no more clients are created */
+ m_discovery_exit = true;
+ m_discovery_thread.join();
+ close(m_socketfd);
+ /* ask server to do a clean stop */
+ stop_server();
+}
+
+std::shared_ptr<server> socket_server::create(std::shared_ptr<hwstub::context> ctx,
+ int socket_fd)
+{
+ // NOTE: can't use make_shared() because of the protected ctor */
+ return std::shared_ptr<socket_server>(new socket_server(ctx, socket_fd));
+}
+
+void socket_server::set_timeout(std::chrono::milliseconds ms)
+{
+ m_timeout.tv_usec = 1000 * (ms.count() % 1000);
+ m_timeout.tv_sec = ms.count() / 1000;
+}
+
+int socket_server::from_srv_client(srv_client_t cli)
+{
+ return (int)(intptr_t)cli;
+}
+
+socket_server::srv_client_t socket_server::to_srv_client(int fd)
+{
+ return (srv_client_t)(intptr_t)fd;
+}
+
+void socket_server::discovery_thread1(socket_server *s)
+{
+ s->discovery_thread();
+}
+
+void socket_server::terminate_client(srv_client_t client)
+{
+ debug() << "[net::srv::sock] terminate client: " << client << "\n";
+ /* simply close connection */
+ close(from_srv_client(client));
+}
+
+error socket_server::send(srv_client_t client, void *buffer, size_t& sz)
+{
+ debug() << "[net::ctx::sock] send(" << client << ", " << sz << "): ";
+ int ret = ::send(from_srv_client(client), buffer, sz, MSG_NOSIGNAL);
+ if(ret >= 0)
+ {
+ debug() << "good(" << ret << ")\n";
+ sz = (size_t)ret;
+ return error::SUCCESS;
+ }
+ /* convert some errors */
+ debug() << "fail(" << errno << "," << strerror(errno) << ")\n";
+ switch(errno)
+ {
+#if EAGAIN != EWOULDBLOCK
+ case EAGAIN:
+#endif
+ case EWOULDBLOCK: return error::TIMEOUT;
+ case ECONNRESET: case EPIPE: return error::SERVER_DISCONNECTED;
+ default: return error::NET_ERROR;
+ }
+}
+
+error socket_server::recv(srv_client_t client, void *buffer, size_t& sz)
+{
+ debug() << "[net::ctx::sock] recv(" << client << ", " << sz << "): ";
+ int ret = ::recv(from_srv_client(client), buffer, sz, MSG_WAITALL);
+ if(ret > 0)
+ {
+ debug() << "good(" << ret << ")\n";
+ sz = (size_t)ret;
+ return error::SUCCESS;
+ }
+ if(ret == 0)
+ {
+ debug() << "disconnected\n";
+ return error::SERVER_DISCONNECTED;
+ }
+ debug() << "fail(" << errno << "," << strerror(errno) << ")\n";
+ switch(errno)
+ {
+#if EAGAIN != EWOULDBLOCK
+ case EAGAIN:
+#endif
+ case EWOULDBLOCK: return error::TIMEOUT;
+ default: return error::NET_ERROR;
+ }
+}
+
+void socket_server::discovery_thread()
+{
+ debug() << "[net::srv:sock::discovery] start\n";
+ /* begin listening to incoming connections */
+ if(listen(m_socketfd, LISTEN_QUEUE_SIZE) < 0)
+ {
+ debug() << "[net::srv::sock::discovery] listen() failed: " << errno << "\n";
+ return;
+ }
+
+ /* handle connections */
+ while(!m_discovery_exit)
+ {
+ /* since accept() is blocking, use select to ensure a timeout */
+ struct timeval tmo = m_timeout; /* NOTE select() can overwrite timeout */
+ fd_set set;
+ FD_ZERO(&set);
+ FD_SET(m_socketfd, &set);
+ /* wait for some activity */
+ int ret = select(m_socketfd + 1, &set, nullptr, nullptr, &tmo);
+ if(ret < 0 || !FD_ISSET(m_socketfd, &set))
+ continue;
+ int clifd = accept(m_socketfd, nullptr, nullptr);
+ if(clifd >= 0)
+ {
+ debug() << "[net::srv::sock::discovery] new client\n";
+ /* set timeout for the client operations */
+ setsockopt(clifd, SOL_SOCKET, SO_RCVTIMEO, (char *)&m_timeout, sizeof(m_timeout));
+ setsockopt(clifd, SOL_SOCKET, SO_SNDTIMEO, (char *)&m_timeout, sizeof(m_timeout));
+ client_arrived(to_srv_client(clifd));
+ }
+ }
+ debug() << "[net::srv:sock::discovery] stop\n";
+}
+
+} // namespace uri
+} // namespace net
diff --git a/utils/hwstub/lib/hwstub_protocol.h b/utils/hwstub/lib/hwstub_protocol.h
deleted file mode 100644
index 35510fa9b2..0000000000
--- a/utils/hwstub/lib/hwstub_protocol.h
+++ /dev/null
@@ -1 +0,0 @@
-#include "../hwstub_protocol.h"
diff --git a/utils/hwstub/lib/hwstub_uri.cpp b/utils/hwstub/lib/hwstub_uri.cpp
new file mode 100644
index 0000000000..e2f252f3dc
--- /dev/null
+++ b/utils/hwstub/lib/hwstub_uri.cpp
@@ -0,0 +1,332 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2015 by Amaury Pouly
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include "hwstub_uri.hpp"
+#include "hwstub_usb.hpp"
+#include "hwstub_virtual.hpp"
+#include "hwstub_net.hpp"
+#include <cctype>
+
+namespace hwstub {
+namespace uri {
+
+void print_usage(FILE *f, bool client, bool server)
+{
+ if(client && server)
+ fprintf(f, "A context/server");
+ else if(client)
+ fprintf(f, "A context");
+ else
+ fprintf(f, "A server");
+ fprintf(f, " URI has the following format:\n");
+ fprintf(f, " scheme:[//domain[:port]][/]\n");
+ fprintf(f, "The scheme is mandatory and specifies the type of context to create.\n");
+ fprintf(f, "The following scheme are recognized:\n");
+ if(client)
+ fprintf(f, " usb USB context (using libusb)\n");
+ fprintf(f, " tcp TCP context\n");
+ fprintf(f, " unix Local unix domain context\n");
+ if(client)
+ fprintf(f, " virt A virtual context (testing and debugging mostly)\n");
+ fprintf(f, " default Default choice made by the library\n");
+ if(client)
+ {
+ fprintf(f, "When creating a USB context, the domain and port must be empty:\n");
+ fprintf(f, " usb:\n");
+ }
+ fprintf(f, "When creating a TCP context, the domain and port are the usual TCP parameters:\n");
+ fprintf(f, " tcp://localhost\n");
+ fprintf(f, " tcp://localhost:6666\n");
+ fprintf(f, "The port is optional, in which the default port %s is used.\n", hwstub::net::context::default_tcp_port().c_str());
+ fprintf(f, "When creating a unix domain context, the port must be empty and there are two type of unix domains.\n");
+ fprintf(f, "Normal domains are specified by filesystem paths. Abstract domains (Linux-only) can be arbitrary strings,\n");
+ fprintf(f, "in which case the domain must start with a '#':\n");
+ fprintf(f, " unix:///path/to/socket\n");
+ fprintf(f, " unix://#hwstub\n");
+ if(client)
+ {
+ fprintf(f, "When creating a virtual context, the domain must contain a specification of the devices.\n");
+ fprintf(f, "The device list is of the form type(param);type(param);... where the only supported type\n");
+ fprintf(f, "at the moment is 'dummy' with a single parameter which is the device name:\n");
+ fprintf(f, " virt://dummy(Device A);dummy(Device B);dummy(Super device C)\n");
+ }
+ if(server && client)
+ fprintf(f, "Note that usb and virt schemes are not supported for server URIs.\n");
+}
+
+uri::uri(const std::string& uri)
+ :m_uri(uri), m_valid(false)
+{
+ parse();
+}
+
+bool uri::validate_scheme()
+{
+ /* scheme must be nonempty */
+ if(m_scheme.empty())
+ {
+ m_error = "empty scheme";
+ return false;
+ }
+ /* and contain alphanumeric characters or '+', '-' or '.' */
+ for(auto c : m_scheme)
+ if(!isalnum(c) && c != '+' && c != '-' && c != '.')
+ {
+ m_error = std::string("invalid character '") + c + "' in scheme";
+ return false;
+ }
+ return true;
+}
+
+bool uri::validate_domain()
+{
+ return true;
+}
+
+bool uri::validate_port()
+{
+ return true;
+}
+
+void uri::parse()
+{
+ std::string str = m_uri;
+ /* try to find the first ':' */
+ size_t scheme_colon = str.find(':');
+ if(scheme_colon == std::string::npos)
+ {
+ m_error = "URI contains no scheme";
+ return;
+ }
+ /* found scheme */
+ m_scheme = str.substr(0, scheme_colon);
+ /* validate scheme */
+ if(!validate_scheme())
+ return;
+ /* remove scheme: */
+ str = str.substr(scheme_colon + 1);
+ /* check if it begins with // */
+ if(str.size() >= 2 && str[0] == '/' && str[1] == '/')
+ {
+ /* remove it */
+ str = str.substr(2);
+ /* find next '/' */
+ std::string path;
+ size_t next_slash = str.find('/');
+ /* if none, we can assume the path is empty, otherwise split path */
+ if(next_slash != std::string::npos)
+ {
+ path = str.substr(next_slash);
+ str = str.substr(0, next_slash);
+ }
+ /* find last ':' if any, indicating a port */
+ size_t port_colon = str.rfind(':');
+ if(port_colon != std::string::npos)
+ {
+ m_domain = str.substr(0, port_colon);
+ m_port = str.substr(port_colon + 1);
+ }
+ else
+ m_domain = str;
+ if(!validate_domain() || !validate_port())
+ return;
+ /* pursue with path */
+ str = path;
+ }
+ /* next is path */
+ m_path = str;
+ m_valid = true;
+}
+
+bool uri::valid() const
+{
+ return m_valid;
+}
+
+std::string uri::error() const
+{
+ return m_error;
+}
+
+std::string uri::full_uri() const
+{
+ return m_uri;
+}
+
+std::string uri::scheme() const
+{
+ return m_scheme;
+}
+
+std::string uri::domain() const
+{
+ return m_domain;
+}
+
+std::string uri::port() const
+{
+ return m_port;
+}
+
+std::string uri::path() const
+{
+ return m_path;
+}
+
+/** Context creator */
+
+std::shared_ptr<context> create_default_context(std::string *error)
+{
+ /* first try to create a unix context with abstract name 'hwstub' */
+ std::shared_ptr<context> ctx = hwstub::net::context::create_unix_abstract(
+ hwstub::net::context::default_unix_path(), error);
+ if(ctx)
+ return ctx;
+ /* otherwise try default tcp */
+ ctx = hwstub::net::context::create_tcp(hwstub::net::context::default_tcp_domain(),
+ hwstub::net::context::default_tcp_port(), error);
+ if(ctx)
+ return ctx;
+ /* otherwise default to usb */
+ return hwstub::usb::context::create(nullptr, false, error);
+}
+
+std::shared_ptr<context> create_context(const uri& uri, std::string *error)
+{
+ /* check URI is valid */
+ if(!uri.valid())
+ {
+ if(error)
+ *error = "invalid URI: " + uri.error();
+ return std::shared_ptr<context>();
+ }
+ /* handle different types of contexts */
+ if(uri.scheme() == "usb")
+ {
+ /* domain and port must be empty */
+ if(!uri.domain().empty() || !uri.port().empty())
+ {
+ if(error)
+ *error = "USB URI cannot contain a domain or a port";
+ return std::shared_ptr<context>();
+ }
+ /* in doubt, create a new libusb context and let the context destroy it */
+ libusb_context *ctx;
+ libusb_init(&ctx);
+ return hwstub::usb::context::create(ctx, true);
+ }
+ else if(uri.scheme() == "virt")
+ {
+ /* port must be empty */
+ if(!uri.port().empty())
+ {
+ if(error)
+ *error = "virt URI cannot contain a port";
+ return std::shared_ptr<context>();
+ }
+ return hwstub::virt::context::create_spec(uri.domain(), error);
+ }
+ else if(uri.scheme() == "tcp")
+ {
+ return hwstub::net::context::create_tcp(uri.domain(), uri.port(), error);
+ }
+ else if(uri.scheme() == "unix")
+ {
+ /* port must be empty */
+ if(!uri.port().empty())
+ {
+ if(error)
+ *error = "unix URI cannot contain a port";
+ return std::shared_ptr<context>();
+ }
+ if(!uri.domain().empty() && uri.domain()[0] == '#')
+ return hwstub::net::context::create_unix_abstract(uri.domain().substr(1), error);
+ else
+ return hwstub::net::context::create_unix(uri.domain(), error);
+ }
+ else if(uri.scheme() == "default")
+ {
+ /* domain and port must be empty */
+ if(!uri.domain().empty() || !uri.port().empty())
+ {
+ if(error)
+ *error = "Default URI cannot contain a domain or a port";
+ return std::shared_ptr<context>();
+ }
+ return create_default_context(error);
+ }
+ else
+ {
+ if(error)
+ *error = "unknown scheme '" + uri.scheme() + "'";
+ return std::shared_ptr<context>();
+ }
+}
+
+std::shared_ptr<net::server> create_server(std::shared_ptr<context> ctx,
+ const uri& uri, std::string *error)
+{
+ /* check URI is valid */
+ if(!uri.valid())
+ {
+ if(error)
+ *error = "invalid URI: " + uri.error();
+ return std::shared_ptr<net::server>();
+ }
+ /* handle different types of contexts */
+ if(uri.scheme() == "unix")
+ {
+ /* port must be empty */
+ if(!uri.port().empty())
+ {
+ if(error)
+ *error = "unix URI cannot contain a port";
+ return std::shared_ptr<net::server>();
+ }
+ if(!uri.domain().empty() && uri.domain()[0] == '#')
+ return hwstub::net::server::create_unix_abstract(ctx, uri.domain().substr(1), error);
+ else
+ return hwstub::net::server::create_unix(ctx, uri.domain(), error);
+ }
+ else if(uri.scheme() == "tcp")
+ {
+ return hwstub::net::server::create_tcp(ctx, uri.domain(), uri.port(), error);
+ }
+ else
+ {
+ if(error)
+ *error = "unknown scheme '" + uri.scheme() + "'";
+ return std::shared_ptr<net::server>();
+ }
+}
+
+uri default_uri()
+{
+ return uri("default:");
+}
+
+uri default_server_uri()
+{
+ return uri("unix://#" + hwstub::net::context::default_unix_path());
+}
+
+} // namespace uri
+} // namespace hwstub
+
diff --git a/utils/hwstub/lib/hwstub_usb.cpp b/utils/hwstub/lib/hwstub_usb.cpp
new file mode 100644
index 0000000000..28c64d9df3
--- /dev/null
+++ b/utils/hwstub/lib/hwstub_usb.cpp
@@ -0,0 +1,728 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2015 by Amaury Pouly
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include "hwstub.hpp"
+#include "hwstub_usb.hpp"
+#include <cstring> /* for memcpy */
+
+namespace hwstub {
+namespace usb {
+
+const uint8_t VR_GET_CPU_INFO = 0;
+const uint8_t VR_SET_DATA_ADDRESS = 1;
+const uint8_t VR_SET_DATA_LENGTH = 2;
+const uint8_t VR_FLUSH_CACHES = 3;
+const uint8_t VR_PROGRAM_START1 = 4;
+const uint8_t VR_PROGRAM_START2 = 5;
+
+/**
+ * Context
+ */
+
+context::context(libusb_context *ctx, bool cleanup_ctx)
+ :m_usb_ctx(ctx), m_cleanup_ctx(cleanup_ctx)
+{
+}
+
+context::~context()
+{
+ if(m_cleanup_ctx)
+ libusb_exit(m_usb_ctx);
+}
+
+std::shared_ptr<context> context::create(libusb_context *ctx, bool cleanup_ctx,
+ std::string *error)
+{
+ (void) error;
+ if(ctx == nullptr)
+ libusb_init(nullptr);
+ // NOTE: can't use make_shared() because of the protected ctor */
+ return std::shared_ptr<context>(new context(ctx, cleanup_ctx));
+}
+
+libusb_context *context::native_context()
+{
+ return m_usb_ctx;
+}
+
+libusb_device *context::from_ctx_dev(ctx_dev_t dev)
+{
+ return reinterpret_cast<libusb_device*>(dev);
+}
+
+hwstub::context::ctx_dev_t context::to_ctx_dev(libusb_device *dev)
+{
+ return static_cast<ctx_dev_t>(dev);
+}
+
+error context::fetch_device_list(std::vector<ctx_dev_t>& list, void*& ptr)
+{
+ libusb_device **usb_list;
+ ssize_t ret = libusb_get_device_list(m_usb_ctx, &usb_list);
+ if(ret < 0)
+ return error::ERROR;
+ ptr = (void *)usb_list;
+ list.clear();
+ for(int i = 0; i < ret; i++)
+ if(device::is_hwstub_dev(usb_list[i]))
+ list.push_back(to_ctx_dev(usb_list[i]));
+ return error::SUCCESS;
+}
+
+void context::destroy_device_list(void *ptr)
+{
+ /* remove all references */
+ libusb_free_device_list((libusb_device **)ptr, 1);
+}
+
+error context::create_device(ctx_dev_t dev, std::shared_ptr<hwstub::device>& hwdev)
+{
+ // NOTE: can't use make_shared() because of the protected ctor */
+ hwdev.reset(new device(shared_from_this(), from_ctx_dev(dev)));
+ return error::SUCCESS;
+}
+
+bool context::match_device(ctx_dev_t dev, std::shared_ptr<hwstub::device> hwdev)
+{
+ device *udev = dynamic_cast<device*>(hwdev.get());
+ return udev != nullptr && udev->native_device() == dev;
+}
+
+/**
+ * Device
+ */
+device::device(std::shared_ptr<hwstub::context> ctx, libusb_device *dev)
+ :hwstub::device(ctx), m_dev(dev)
+{
+ libusb_ref_device(dev);
+}
+
+device::~device()
+{
+ libusb_unref_device(m_dev);
+}
+
+libusb_device *device::native_device()
+{
+ return m_dev;
+}
+
+bool device::is_hwstub_dev(libusb_device *dev)
+{
+ struct libusb_device_descriptor dev_desc;
+ struct libusb_config_descriptor *config = nullptr;
+ int intf = 0;
+ if(libusb_get_device_descriptor(dev, &dev_desc) != 0)
+ goto Lend;
+ if(libusb_get_config_descriptor(dev, 0, &config) != 0)
+ goto Lend;
+ /* Try to find Rockbox hwstub interface or a JZ device */
+ if(rb_handle::find_intf(&dev_desc, config, intf) ||
+ jz_handle::is_boot_dev(&dev_desc, config))
+ {
+ libusb_free_config_descriptor(config);
+ return true;
+ }
+Lend:
+ if(config)
+ libusb_free_config_descriptor(config);
+ return false;
+}
+
+error device::open_dev(std::shared_ptr<hwstub::handle>& handle)
+{
+ int intf = -1;
+ /* open the device */
+ libusb_device_handle *h;
+ int err = libusb_open(m_dev, &h);
+ if(err != LIBUSB_SUCCESS)
+ return error::ERROR;
+ /* fetch some descriptors */
+ struct libusb_device_descriptor dev_desc;
+ struct libusb_config_descriptor *config = nullptr;
+ if(libusb_get_device_descriptor(m_dev, &dev_desc) != 0)
+ goto Lend;
+ if(libusb_get_config_descriptor(m_dev, 0, &config) != 0)
+ goto Lend;
+ /* Try to find Rockbox hwstub interface */
+ if(rb_handle::find_intf(&dev_desc, config, intf))
+ {
+ libusb_free_config_descriptor(config);
+ /* create the handle */
+ // NOTE: can't use make_shared() because of the protected ctor */
+ handle.reset(new rb_handle(shared_from_this(), h, intf));
+ }
+ /* Maybe this is a JZ device ? */
+ else if(jz_handle::is_boot_dev(&dev_desc, config))
+ {
+ libusb_free_config_descriptor(config);
+ /* create the handle */
+ // NOTE: can't use make_shared() because of the protected ctor */
+ handle.reset(new jz_handle(shared_from_this(), h));
+ }
+ else
+ {
+ libusb_free_config_descriptor(config);
+ return error::ERROR;
+ }
+ /* the class will perform some probing on creation: check that it actually worked */
+ if(handle->valid())
+ return error::SUCCESS;
+ /* abort */
+ handle.reset(); // will close the libusb handle
+ return error::ERROR;
+
+Lend:
+ if(config)
+ libusb_free_config_descriptor(config);
+ libusb_close(h);
+ return error::ERROR;
+}
+
+bool device::has_multiple_open() const
+{
+ /* libusb only allows one handle per device */
+ return false;
+}
+
+uint8_t device::get_bus_number()
+{
+ return libusb_get_bus_number(native_device());
+}
+
+uint8_t device::get_address()
+{
+ return libusb_get_device_address(native_device());
+}
+
+uint16_t device::get_vid()
+{
+ /* NOTE: doc says it's cached so it should always succeed */
+ struct libusb_device_descriptor dev_desc;
+ libusb_get_device_descriptor(native_device(), &dev_desc);
+ return dev_desc.idVendor;
+}
+
+uint16_t device::get_pid()
+{
+ /* NOTE: doc says it's cached so it should always succeed */
+ struct libusb_device_descriptor dev_desc;
+ libusb_get_device_descriptor(native_device(), &dev_desc);
+ return dev_desc.idProduct;
+}
+
+/**
+ * USB handle
+ */
+handle::handle(std::shared_ptr<hwstub::device> dev, libusb_device_handle *handle)
+ :hwstub::handle(dev), m_handle(handle)
+{
+ set_timeout(std::chrono::milliseconds(100));
+}
+
+handle::~handle()
+{
+ libusb_close(m_handle);
+}
+
+error handle::interpret_libusb_error(int err)
+{
+ if(err >= 0)
+ return error::SUCCESS;
+ if(err == LIBUSB_ERROR_NO_DEVICE)
+ return error::DISCONNECTED;
+ else
+ return error::USB_ERROR;
+}
+
+error handle::interpret_libusb_error(int err, size_t expected_val)
+{
+ if(err < 0)
+ return interpret_libusb_error(err);
+ if((size_t)err != expected_val)
+ return error::ERROR;
+ return error::SUCCESS;
+}
+
+error handle::interpret_libusb_size(int err, size_t& out_siz)
+{
+ if(err < 0)
+ return interpret_libusb_error(err);
+ out_siz = (size_t)err;
+ return error::SUCCESS;
+}
+
+void handle::set_timeout(std::chrono::milliseconds ms)
+{
+ m_timeout = ms.count();
+}
+
+/**
+ * Rockbox Handle
+ */
+
+rb_handle::rb_handle(std::shared_ptr<hwstub::device> dev,
+ libusb_device_handle *handle, int intf)
+ :hwstub::usb::handle(dev, handle), m_intf(intf), m_transac_id(0), m_buf_size(1)
+{
+ m_probe_status = error::SUCCESS;
+ /* claim interface */
+ if(libusb_claim_interface(m_handle, m_intf) != 0)
+ m_probe_status = error::PROBE_FAILURE;
+ /* check version */
+ if(m_probe_status == error::SUCCESS)
+ {
+ struct hwstub_version_desc_t ver_desc;
+ m_probe_status = get_version_desc(ver_desc);
+ if(m_probe_status == error::SUCCESS)
+ {
+ if(ver_desc.bMajor != HWSTUB_VERSION_MAJOR ||
+ ver_desc.bMinor < HWSTUB_VERSION_MINOR)
+ m_probe_status = error::PROBE_FAILURE;
+ }
+ }
+ /* get buffer size */
+ if(m_probe_status == error::SUCCESS)
+ {
+ struct hwstub_layout_desc_t layout_desc;
+ m_probe_status = get_layout_desc(layout_desc);
+ if(m_probe_status == error::SUCCESS)
+ m_buf_size = layout_desc.dBufferSize;
+ }
+}
+
+rb_handle::~rb_handle()
+{
+}
+
+size_t rb_handle::get_buffer_size()
+{
+ return m_buf_size;
+}
+
+error rb_handle::status() const
+{
+ error err = handle::status();
+ if(err == error::SUCCESS)
+ err = m_probe_status;
+ return err;
+}
+
+error rb_handle::get_dev_desc(uint16_t desc, void *buf, size_t& buf_sz)
+{
+ return interpret_libusb_size(libusb_control_transfer(m_handle,
+ LIBUSB_REQUEST_TYPE_STANDARD | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_IN,
+ LIBUSB_REQUEST_GET_DESCRIPTOR, desc << 8, m_intf, (unsigned char *)buf, buf_sz, m_timeout),
+ buf_sz);
+}
+
+error rb_handle::get_dev_log(void *buf, size_t& buf_sz)
+{
+ return interpret_libusb_size(libusb_control_transfer(m_handle,
+ LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_IN,
+ HWSTUB_GET_LOG, 0, m_intf, (unsigned char *)buf, buf_sz, m_timeout), buf_sz);
+}
+
+error rb_handle::exec_dev(uint32_t addr, uint16_t flags)
+{
+ struct hwstub_exec_req_t exec;
+ exec.dAddress = addr;
+ exec.bmFlags = flags;
+ return interpret_libusb_error(libusb_control_transfer(m_handle,
+ LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_OUT,
+ HWSTUB_EXEC, 0, m_intf, (unsigned char *)&exec, sizeof(exec), m_timeout), sizeof(exec));
+}
+
+error rb_handle::read_dev(uint32_t addr, void *buf, size_t& sz, bool atomic)
+{
+ struct hwstub_read_req_t read;
+ read.dAddress = addr;
+ error err = interpret_libusb_error(libusb_control_transfer(m_handle,
+ LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_OUT,
+ HWSTUB_READ, m_transac_id, m_intf, (unsigned char *)&read, sizeof(read), m_timeout),
+ sizeof(read));
+ if(err != error::SUCCESS)
+ return err;
+ return interpret_libusb_size(libusb_control_transfer(m_handle,
+ LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_IN,
+ atomic ? HWSTUB_READ2_ATOMIC : HWSTUB_READ2, m_transac_id++, m_intf,
+ (unsigned char *)buf, sz, m_timeout), sz);
+}
+
+error rb_handle::write_dev(uint32_t addr, const void *buf, size_t& sz, bool atomic)
+{
+ size_t hdr_sz = sizeof(struct hwstub_write_req_t);
+ uint8_t *tmp_buf = new uint8_t[sz + hdr_sz];
+ struct hwstub_write_req_t *req = reinterpret_cast<struct hwstub_write_req_t *>(tmp_buf);
+ req->dAddress = addr;
+ memcpy(tmp_buf + hdr_sz, buf, sz);
+ error ret = interpret_libusb_error(libusb_control_transfer(m_handle,
+ LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_OUT,
+ atomic ? HWSTUB_WRITE_ATOMIC : HWSTUB_WRITE, m_transac_id++, m_intf,
+ (unsigned char *)req, sz + hdr_sz, m_timeout), sz + hdr_sz);
+ delete[] tmp_buf;
+ return ret;
+}
+
+bool rb_handle::find_intf(struct libusb_device_descriptor *dev,
+ struct libusb_config_descriptor *config, int& intf_idx)
+{
+ (void) dev;
+ /* search hwstub interface */
+ for(unsigned i = 0; i < config->bNumInterfaces; i++)
+ {
+ /* hwstub interface has only one setting */
+ if(config->interface[i].num_altsetting != 1)
+ continue;
+ const struct libusb_interface_descriptor *intf = &config->interface[i].altsetting[0];
+ /* check class/subclass/protocol */
+ if(intf->bInterfaceClass == HWSTUB_CLASS &&
+ intf->bInterfaceSubClass == HWSTUB_SUBCLASS &&
+ intf->bInterfaceProtocol == HWSTUB_PROTOCOL)
+ {
+ /* found it ! */
+ intf_idx = i;
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * JZ Handle
+ */
+
+namespace
+{
+ uint16_t jz_bcd(char *bcd)
+ {
+ uint16_t v = 0;
+ for(int i = 0; i < 4; i++)
+ v = (bcd[i] - '0') | v << 4;
+ return v;
+ }
+}
+
+jz_handle::jz_handle(std::shared_ptr<hwstub::device> dev,
+ libusb_device_handle *handle)
+ :hwstub::usb::handle(dev, handle)
+{
+ m_probe_status = probe();
+}
+
+jz_handle::~jz_handle()
+{
+}
+
+error jz_handle::probe()
+{
+ char cpuinfo[8];
+ /* Get CPU info and devise descriptor */
+ error err = jz_cpuinfo(cpuinfo);
+ if(err != error::SUCCESS)
+ return err;
+ struct libusb_device_descriptor dev_desc;
+ err = interpret_libusb_error(libusb_get_device_descriptor(
+ libusb_get_device(m_handle), &dev_desc), 0);
+ if(err != error::SUCCESS)
+ return err;
+ /** parse CPU info */
+ /* if cpuinfo if of the form JZxxxxVy then extract xxxx */
+ if(cpuinfo[0] == 'J' && cpuinfo[1] == 'Z' && cpuinfo[6] == 'V')
+ m_desc_jz.wChipID = jz_bcd(cpuinfo + 2);
+ /* if cpuinfo if of the form Bootxxxx then extract xxxx */
+ else if(strncmp(cpuinfo, "Boot", 4) == 4)
+ m_desc_jz.wChipID = jz_bcd(cpuinfo + 4);
+ /* else use usb id */
+ else
+ m_desc_jz.wChipID = dev_desc.idProduct;
+ m_desc_jz.bRevision = 0;
+
+ /** Retrieve product string */
+ memset(m_desc_target.bName, 0, sizeof(m_desc_target.bName));
+ err = interpret_libusb_error(libusb_get_string_descriptor_ascii(m_handle,
+ dev_desc.iProduct, (unsigned char *)m_desc_target.bName, sizeof(m_desc_target.bName)));
+ if(err != error::SUCCESS)
+ return err;
+ /** The JZ4760 and JZ4760B cannot be distinguished by the above information,
+ * for this the best way I have found is to check the SRAM size: 48KiB vs 16KiB.
+ * This requires to enable AHB1 and SRAM clock and read/write to SRAM, but
+ * this code will leaves registers and ram is the same state as before.
+ * In case of failure, simply assume JZ4760. */
+ if(m_desc_jz.wChipID == 0x4760)
+ probe_jz4760b();
+
+ /** Fill descriptors */
+ m_desc_version.bLength = sizeof(m_desc_version);
+ m_desc_version.bDescriptorType = HWSTUB_DT_VERSION;
+ m_desc_version.bMajor = HWSTUB_VERSION_MAJOR;
+ m_desc_version.bMinor = HWSTUB_VERSION_MINOR;
+ m_desc_version.bRevision = 0;
+
+ m_desc_layout.bLength = sizeof(m_desc_layout);
+ m_desc_layout.bDescriptorType = HWSTUB_DT_LAYOUT;
+ m_desc_layout.dCodeStart = 0xbfc00000; /* ROM */
+ m_desc_layout.dCodeSize = 0x2000; /* 8kB per datasheet */
+ m_desc_layout.dStackStart = 0; /* As far as I can tell, the ROM uses no stack */
+ m_desc_layout.dStackSize = 0;
+ m_desc_layout.dBufferStart = 0x080000000;
+ m_desc_layout.dBufferSize = 0x4000;
+
+ m_desc_target.bLength = sizeof(m_desc_target);
+ m_desc_target.bDescriptorType = HWSTUB_DT_TARGET;
+ m_desc_target.dID = HWSTUB_TARGET_JZ;
+
+ m_desc_jz.bLength = sizeof(m_desc_jz);
+ m_desc_jz.bDescriptorType = HWSTUB_DT_JZ;
+
+ /* claim interface */
+ if(libusb_claim_interface(m_handle, 0) != 0)
+ m_probe_status = error::PROBE_FAILURE;
+
+ return m_probe_status;
+}
+
+error jz_handle::read_reg32(uint32_t addr, uint32_t& value)
+{
+ size_t sz = sizeof(value);
+ error err = read_dev(addr, &value, sz, true);
+ if(err == error::SUCCESS && sz != sizeof(value))
+ err = error::ERROR;
+ return err;
+}
+
+error jz_handle::write_reg32(uint32_t addr, uint32_t value)
+{
+ size_t sz = sizeof(value);
+ error err = write_dev(addr, &value, sz, true);
+ if(err == error::SUCCESS && sz != sizeof(value))
+ err = error::ERROR;
+ return err;
+}
+
+error jz_handle::probe_jz4760b()
+{
+ /* first read CPM_CLKGR1 */
+ const uint32_t cpm_clkgr1_addr = 0xb0000028;
+ uint32_t cpm_clkgr1;
+ error err = read_reg32(cpm_clkgr1_addr, cpm_clkgr1);
+ if(err != error::SUCCESS)
+ return err;
+ /* Bit 7 controls AHB1 clock and bit 5 the SRAM. Note that SRAM is on AHB1.
+ * Only ungate if gated */
+ uint32_t cpm_clkgr1_mask = 1 << 7 | 1 << 5;
+ if(cpm_clkgr1 & cpm_clkgr1_mask)
+ {
+ /* ungate both clocks */
+ err = write_reg32(cpm_clkgr1_addr, cpm_clkgr1 & ~cpm_clkgr1_mask);
+ if(err != error::SUCCESS)
+ return err;
+ }
+ /* read first word of SRAM and then at end (supposedly) */
+ uint32_t sram_addr = 0xb32d0000;
+ uint32_t sram_end_addr = sram_addr + 16 * 1024; /* SRAM is 16KiB on JZ4760B */
+ uint32_t sram_start, sram_end;
+ err = read_reg32(sram_addr, sram_start);
+ if(err != error::SUCCESS)
+ goto Lrestore;
+ err = read_reg32(sram_end_addr, sram_end);
+ if(err != error::SUCCESS)
+ goto Lrestore;
+ /* if start and end are different, clearly the size is not 16KiB and this is
+ * JZ4760 and we have nothing to do */
+ if(sram_start != sram_end)
+ goto Lrestore;
+ /* now reverse all bits of the first word */
+ sram_start ^= 0xffffffff;
+ err = write_reg32(sram_addr, sram_start);
+ if(err != error::SUCCESS)
+ goto Lrestore;
+ /* and read again at end */
+ err = read_reg32(sram_end_addr, sram_end);
+ if(err != error::SUCCESS)
+ goto Lrestore;
+ /* if they are still equal, we identified JZ4760B */
+ if(sram_start == sram_end)
+ m_desc_jz.bRevision = 'B';
+ /* restore SRAM value */
+ sram_start ^= 0xffffffff;
+ err = write_reg32(sram_addr, sram_start);
+ if(err != error::SUCCESS)
+ goto Lrestore;
+
+Lrestore:
+ /* restore gates if needed */
+ if(cpm_clkgr1 & cpm_clkgr1_mask)
+ return write_reg32(cpm_clkgr1_addr, cpm_clkgr1);
+ else
+ return error::SUCCESS;
+}
+
+size_t jz_handle::get_buffer_size()
+{
+ return m_desc_layout.dBufferSize;
+}
+
+error jz_handle::status() const
+{
+ error err = handle::status();
+ if(err == error::SUCCESS)
+ err = m_probe_status;
+ return err;
+}
+
+error jz_handle::get_dev_desc(uint16_t desc, void *buf, size_t& buf_sz)
+{
+ void *p = nullptr;
+ switch(desc)
+ {
+ case HWSTUB_DT_VERSION: p = &m_desc_version; break;
+ case HWSTUB_DT_LAYOUT: p = &m_desc_layout; break;
+ case HWSTUB_DT_TARGET: p = &m_desc_target; break;
+ case HWSTUB_DT_JZ: p = &m_desc_jz; break;
+ default: break;
+ }
+ if(p == nullptr)
+ return error::ERROR;
+ /* size is in the bLength field of the descriptor */
+ size_t desc_sz = *(uint8_t *)p;
+ buf_sz = std::min(buf_sz, desc_sz);
+ memcpy(buf, p, buf_sz);
+ return error::SUCCESS;
+}
+
+error jz_handle::get_dev_log(void *buf, size_t& buf_sz)
+{
+ (void) buf;
+ buf_sz = 0;
+ return error::SUCCESS;
+}
+
+error jz_handle::exec_dev(uint32_t addr, uint16_t flags)
+{
+ (void) flags;
+ /* FIXME the ROM always do call so the stub can always return, this behaviour
+ * cannot be changed */
+ /* NOTE assume that exec at 0x80000000 is a first stage load with START1,
+ * otherwise flush cache and use START2 */
+ if(addr == 0x80000000)
+ return jz_start1(addr);
+ error ret = jz_flush_caches();
+ if(ret == error::SUCCESS)
+ return jz_start2(addr);
+ else
+ return ret;
+}
+
+error jz_handle::read_dev(uint32_t addr, void *buf, size_t& sz, bool atomic)
+{
+ (void) atomic;
+ /* NOTE disassembly shows that the ROM will do atomic read on aligned words */
+ error ret = jz_set_addr(addr);
+ if(ret == error::SUCCESS)
+ ret = jz_set_length(sz);
+ if(ret == error::SUCCESS)
+ ret = jz_upload(buf, sz);
+ return ret;
+}
+
+error jz_handle::write_dev(uint32_t addr, const void *buf, size_t& sz, bool atomic)
+{
+ (void) atomic;
+ /* NOTE disassembly shows that the ROM will do atomic read on aligned words */
+ /* IMPORTANT BUG Despite what the manual suggest, one must absolutely NOT send
+ * a VR_SET_DATA_LENGTH request for a write, otherwise it will have completely
+ * random effects */
+ error ret = jz_set_addr(addr);
+ if(ret == error::SUCCESS)
+ ret = jz_download(buf, sz);
+ return ret;
+}
+
+bool jz_handle::is_boot_dev(struct libusb_device_descriptor *dev,
+ struct libusb_config_descriptor *config)
+{
+ (void) config;
+ /* don't bother checking the config descriptor and use the device ID only */
+ return dev->idVendor == 0x601a && dev->idProduct >= 0x4740 && dev->idProduct <= 0x4780;
+}
+
+error jz_handle::jz_cpuinfo(char cpuinfo[8])
+{
+ return interpret_libusb_error(libusb_control_transfer(m_handle,
+ LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ VR_GET_CPU_INFO, 0, 0, (unsigned char *)cpuinfo, 8, m_timeout), 8);
+}
+
+error jz_handle::jz_set_addr(uint32_t addr)
+{
+ return interpret_libusb_error(libusb_control_transfer(m_handle,
+ LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ VR_SET_DATA_ADDRESS, addr >> 16, addr & 0xffff, NULL, 0, m_timeout), 0);
+}
+
+error jz_handle::jz_set_length(uint32_t size)
+{
+ return interpret_libusb_error(libusb_control_transfer(m_handle,
+ LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ VR_SET_DATA_LENGTH, size >> 16, size & 0xffff, NULL, 0, m_timeout), 0);
+}
+
+error jz_handle::jz_upload(void *data, size_t& length)
+{
+ int xfer = 0;
+ error err = interpret_libusb_error(libusb_bulk_transfer(m_handle,
+ LIBUSB_ENDPOINT_IN | 1, (unsigned char *)data, length, &xfer, m_timeout));
+ length = xfer;
+ return err;
+}
+
+error jz_handle::jz_download(const void *data, size_t& length)
+{
+ int xfer = 0;
+ error err = interpret_libusb_error(libusb_bulk_transfer(m_handle,
+ LIBUSB_ENDPOINT_OUT | 1, (unsigned char *)data, length, &xfer, m_timeout));
+ length = xfer;
+ return err;
+}
+
+error jz_handle::jz_start1(uint32_t addr)
+{
+ return interpret_libusb_error(libusb_control_transfer(m_handle,
+ LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ VR_PROGRAM_START1, addr >> 16, addr & 0xffff, NULL, 0, m_timeout), 0);
+}
+
+error jz_handle::jz_flush_caches()
+{
+ return interpret_libusb_error(libusb_control_transfer(m_handle,
+ LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ VR_FLUSH_CACHES, 0, 0, NULL, 0, m_timeout), 0);
+}
+
+error jz_handle::jz_start2(uint32_t addr)
+{
+ return interpret_libusb_error(libusb_control_transfer(m_handle,
+ LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ VR_PROGRAM_START2, addr >> 16, addr & 0xffff, NULL, 0, m_timeout), 0);
+}
+
+} // namespace usb
+} // namespace hwstub
diff --git a/utils/hwstub/lib/hwstub_virtual.cpp b/utils/hwstub/lib/hwstub_virtual.cpp
new file mode 100644
index 0000000000..fada56fb83
--- /dev/null
+++ b/utils/hwstub/lib/hwstub_virtual.cpp
@@ -0,0 +1,335 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2016 by Amaury Pouly
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include "hwstub_virtual.hpp"
+#include <algorithm>
+#include <cstring>
+
+namespace hwstub {
+namespace virt {
+
+/**
+ * Context
+ */
+context::context()
+{
+}
+
+context::~context()
+{
+}
+
+std::shared_ptr<context> context::create()
+{
+ // NOTE: can't use make_shared() because of the protected ctor */
+ return std::shared_ptr<context>(new context());
+}
+
+std::shared_ptr<context> context::create_spec(const std::string& spec, std::string *error)
+{
+ std::shared_ptr<context> ctx = create();
+ /* parse spec */
+ std::string str = spec;
+ while(!str.empty())
+ {
+ /* find next separator, if any */
+ std::string dev_spec;
+ size_t dev_sep = str.find(';');
+ if(dev_sep == std::string::npos)
+ {
+ dev_spec = str;
+ str.clear();
+ }
+ else
+ {
+ dev_spec = str.substr(0, dev_sep);
+ str = str.substr(dev_sep + 1);
+ }
+ /* handle dev spec: find ( and )*/
+ size_t lparen = dev_spec.find('(');
+ if(lparen == std::string::npos)
+ {
+ if(error)
+ *error = "invalid device spec '" + dev_spec + "': missing (";
+ return std::shared_ptr<context>();
+ }
+ if(dev_spec.back() != ')')
+ {
+ if(error)
+ *error = "invalid device spec '" + dev_spec + "': missing )";
+ return std::shared_ptr<context>();
+ }
+ std::string args = dev_spec.substr(lparen + 1, dev_spec.size() - lparen - 2);
+ std::string type = dev_spec.substr(0, lparen);
+ if(type == "dummy")
+ {
+ ctx->connect(std::make_shared<dummy_hardware>(args));
+ }
+ else
+ {
+ if(error)
+ *error = "invalid device spec '" + dev_spec + "': unknown device type";
+ return std::shared_ptr<context>();
+ }
+ }
+ return ctx;
+}
+
+bool context::connect(std::shared_ptr<hardware> hw)
+{
+ std::unique_lock<std::recursive_mutex> lock(m_mutex);
+ if(std::find(m_hwlist.begin(), m_hwlist.end(), hw) != m_hwlist.end())
+ return false;
+ m_hwlist.push_back(hw);
+ return true;
+}
+
+bool context::disconnect(std::shared_ptr<hardware> hw)
+{
+ std::unique_lock<std::recursive_mutex> lock(m_mutex);
+ auto it = std::find(m_hwlist.begin(), m_hwlist.end(), hw);
+ if(it == m_hwlist.end())
+ return false;
+ m_hwlist.erase(it);
+ return true;
+}
+
+std::shared_ptr<hardware> context::from_ctx_dev(ctx_dev_t dev)
+{
+ return ((hardware *)dev)->shared_from_this();
+}
+
+hwstub::context::ctx_dev_t context::to_ctx_dev(std::shared_ptr<hardware>& dev)
+{
+ return (ctx_dev_t)dev.get();
+}
+
+error context::fetch_device_list(std::vector<ctx_dev_t>& list, void*& ptr)
+{
+ (void) ptr;
+ list.resize(m_hwlist.size());
+ for(size_t i = 0; i < m_hwlist.size(); i++)
+ list[i] = to_ctx_dev(m_hwlist[i]);
+ return error::SUCCESS;
+}
+
+void context::destroy_device_list(void *ptr)
+{
+ (void) ptr;
+}
+
+error context::create_device(ctx_dev_t dev, std::shared_ptr<hwstub::device>& hwdev)
+{
+ // NOTE: can't use make_shared() because of the protected ctor */
+ hwdev.reset(new device(shared_from_this(), from_ctx_dev(dev)));
+ return error::SUCCESS;
+}
+
+bool context::match_device(ctx_dev_t dev, std::shared_ptr<hwstub::device> hwdev)
+{
+ device *udev = dynamic_cast<device*>(hwdev.get());
+ return udev != nullptr && udev->native_device().get() == from_ctx_dev(dev).get();
+}
+
+/**
+ * Device
+ */
+device::device(std::shared_ptr<hwstub::context> ctx, std::shared_ptr<hardware> dev)
+ :hwstub::device(ctx), m_hwdev(dev)
+{
+}
+
+device::~device()
+{
+}
+
+std::shared_ptr<hardware> device::native_device()
+{
+ return m_hwdev.lock();
+}
+
+error device::open_dev(std::shared_ptr<hwstub::handle>& h)
+{
+ // NOTE: can't use make_shared() because of the protected ctor */
+ h.reset(new handle(shared_from_this()));
+ return error::SUCCESS;
+}
+
+bool device::has_multiple_open() const
+{
+ return true;
+}
+
+/**
+ * Handle
+ */
+handle::handle(std::shared_ptr<hwstub::device> dev)
+ :hwstub::handle(dev)
+{
+ m_hwdev = dynamic_cast<device*>(dev.get())->native_device();
+}
+
+handle::~handle()
+{
+}
+
+
+error handle::read_dev(uint32_t addr, void *buf, size_t& sz, bool atomic)
+{
+ auto p = m_hwdev.lock();
+ return p ? p->read_dev(addr, buf, sz, atomic) : error::DISCONNECTED;
+}
+
+error handle::write_dev(uint32_t addr, const void *buf, size_t& sz, bool atomic)
+{
+ auto p = m_hwdev.lock();
+ return p ? p->write_dev(addr, buf, sz, atomic) : error::DISCONNECTED;
+}
+
+error handle::get_dev_desc(uint16_t desc, void *buf, size_t& buf_sz)
+{
+ auto p = m_hwdev.lock();
+ return p ? p->get_dev_desc(desc, buf, buf_sz) : error::DISCONNECTED;
+}
+
+error handle::get_dev_log(void *buf, size_t& buf_sz)
+{
+ auto p = m_hwdev.lock();
+ return p ? p->get_dev_log(buf, buf_sz) : error::DISCONNECTED;
+}
+
+error handle::exec_dev(uint32_t addr, uint16_t flags)
+{
+ auto p = m_hwdev.lock();
+ return p ? p->exec_dev(addr, flags) : error::DISCONNECTED;
+}
+
+error handle::status() const
+{
+ return hwstub::handle::status();
+}
+
+size_t handle::get_buffer_size()
+{
+ auto p = m_hwdev.lock();
+ return p ? p->get_buffer_size() : 1;
+}
+
+/**
+ * Hardware
+ */
+hardware::hardware()
+{
+}
+
+hardware::~hardware()
+{
+}
+
+/**
+ * Dummy hardware
+ */
+dummy_hardware::dummy_hardware(const std::string& name)
+{
+ m_desc_version.bLength = sizeof(m_desc_version);
+ m_desc_version.bDescriptorType = HWSTUB_DT_VERSION;
+ m_desc_version.bMajor = HWSTUB_VERSION_MAJOR;
+ m_desc_version.bMinor = HWSTUB_VERSION_MINOR;
+ m_desc_version.bRevision = 0;
+
+ m_desc_layout.bLength = sizeof(m_desc_layout);
+ m_desc_layout.bDescriptorType = HWSTUB_DT_LAYOUT;
+ m_desc_layout.dCodeStart = 0;
+ m_desc_layout.dCodeSize = 0;
+ m_desc_layout.dStackStart = 0;
+ m_desc_layout.dStackSize = 0;
+ m_desc_layout.dBufferStart = 0;
+ m_desc_layout.dBufferSize = 1;
+
+ m_desc_target.bLength = sizeof(m_desc_target);
+ m_desc_target.bDescriptorType = HWSTUB_DT_TARGET;
+ m_desc_target.dID = HWSTUB_TARGET_UNK;
+ strncpy(m_desc_target.bName, name.c_str(), sizeof(m_desc_target.bName));
+ m_desc_target.bName[sizeof(m_desc_target.bName) - 1] = 0;
+}
+
+dummy_hardware::~dummy_hardware()
+{
+}
+
+error dummy_hardware::read_dev(uint32_t addr, void *buf, size_t& sz, bool atomic)
+{
+ (void) addr;
+ (void) buf;
+ (void) sz;
+ (void) atomic;
+ return error::DUMMY;
+}
+
+error dummy_hardware::write_dev(uint32_t addr, const void *buf, size_t& sz, bool atomic)
+{
+ (void) addr;
+ (void) buf;
+ (void) sz;
+ (void) atomic;
+ return error::DUMMY;
+}
+
+error dummy_hardware::get_dev_desc(uint16_t desc, void *buf, size_t& buf_sz)
+{
+ void *p = nullptr;
+ switch(desc)
+ {
+ case HWSTUB_DT_VERSION: p = &m_desc_version; break;
+ case HWSTUB_DT_LAYOUT: p = &m_desc_layout; break;
+ case HWSTUB_DT_TARGET: p = &m_desc_target; break;
+ default: break;
+ }
+ if(p == nullptr)
+ return error::ERROR;
+ /* size is in the bLength field of the descriptor */
+ size_t desc_sz = *(uint8_t *)p;
+ buf_sz = std::min(buf_sz, desc_sz);
+ memcpy(buf, p, buf_sz);
+ return error::SUCCESS;
+}
+
+error dummy_hardware::get_dev_log(void *buf, size_t& buf_sz)
+{
+ (void) buf;
+ (void) buf_sz;
+ return error::DUMMY;
+}
+
+error dummy_hardware::exec_dev(uint32_t addr, uint16_t flags)
+{
+ (void) addr;
+ (void) flags;
+ return error::DUMMY;
+}
+
+size_t dummy_hardware::get_buffer_size()
+{
+ return 1;
+}
+
+} // namespace virt
+} // namespace net
+
diff --git a/utils/hwstub/stub/hwstub.make b/utils/hwstub/stub/hwstub.make
index c1dd9f0f0f..d19594eae0 100644
--- a/utils/hwstub/stub/hwstub.make
+++ b/utils/hwstub/stub/hwstub.make
@@ -1,4 +1,4 @@
-INCLUDES+=-I$(ROOT_DIR)
+INCLUDES+=-I$(ROOT_DIR) -I$(ROOT_DIR)/../include/
LINKER_FILE=hwstub.lds
TMP_LDS=$(BUILD_DIR)/link.lds
TMP_MAP=$(BUILD_DIR)/hwstub.map
@@ -45,7 +45,7 @@ $(TMP_LDS): $(LINKER_FILE)
$(EXEC_ELF): $(OBJ) $(TMP_LDS)
$(call PRINTS,LD $(@F))
$(SILENT)$(LD) $(LDFLAGS) -o $@ $(OBJ_EXCEPT_CRT0)
-
+
$(EXEC_BIN): $(EXEC_ELF)
$(call PRINTS,OC $(@F))
$(SILENT)$(OC) -O binary $< $@
diff --git a/utils/hwstub/stub/protocol.h b/utils/hwstub/stub/protocol.h
index 61d6ae02a4..7c244275b7 100644
--- a/utils/hwstub/stub/protocol.h
+++ b/utils/hwstub/stub/protocol.h
@@ -1,4 +1,4 @@
-#include "../hwstub_protocol.h"
+#include "hwstub_protocol.h"
#define HWSTUB_VERSION_REV 0
diff --git a/utils/hwstub/tools/Makefile b/utils/hwstub/tools/Makefile
index 868ddcca79..079933ad25 100644
--- a/utils/hwstub/tools/Makefile
+++ b/utils/hwstub/tools/Makefile
@@ -1,13 +1,15 @@
CC=gcc
CXX=g++
LD=g++
+HWSTUB_INCLUDE_DIR=../include
HWSTUB_LIB_DIR=../lib
REGTOOLS_INCLUDE_DIR=../../regtools/include
REGTOOLS_LIB_DIR=../../regtools/lib
-CFLAGS=-Wall -O2 `pkg-config --cflags libusb-1.0` -std=c99 -g -I$(HWSTUB_LIB_DIR) -I$(REGTOOLS_INCLUDE_DIR) `pkg-config --cflags lua5.2`
-CXXFLAGS=-Wall -O2 `pkg-config --cflags libusb-1.0` -g -I$(HWSTUB_LIB_DIR) -I$(REGTOOLS_INCLUDE_DIR) `pkg-config --cflags lua5.2`
-LDFLAGS=`pkg-config --libs libusb-1.0` `pkg-config --libs lua5.2` -lreadline -L$(HWSTUB_LIB_DIR) -L$(REGTOOLS_LIB_DIR) -lsocdesc -lhwstub `xml2-config --libs`
-EXEC=hwstub_shell hwstub_load
+INCLUDES=-I$(HWSTUB_INCLUDE_DIR) -I$(REGTOOLS_INCLUDE_DIR) `pkg-config --cflags lua5.2` `pkg-config --cflags libusb-1.0`
+CFLAGS=-Wall -O2 -std=c99 -g $(INCLUDES) -D_XOPEN_SOURCE=600
+CXXFLAGS=-Wall -O2 -std=c++11 -g $(INCLUDES)
+LDFLAGS=`pkg-config --libs libusb-1.0` `pkg-config --libs lua5.2` -lreadline -L$(HWSTUB_LIB_DIR) -L$(REGTOOLS_LIB_DIR) -lsocdesc -lhwstub `xml2-config --libs` -pthread
+EXEC=hwstub_shell hwstub_load hwstub_server hwstub_test
SRC=$(wildcard *.c)
SRCXX=$(wildcard *.cpp)
OBJ=$(SRC:.c=.o) $(SRCXX:.cpp=.o)
@@ -33,6 +35,12 @@ hwstub_shell: hwstub_shell.o prompt.o $(LIBS)
hwstub_load: hwstub_load.o $(LIBS)
$(LD) -o $@ $^ $(LDFLAGS)
+hwstub_server: hwstub_server.o $(LIBS)
+ $(LD) -o $@ $^ $(LDFLAGS)
+
+hwstub_test: hwstub_test.o $(LIBS)
+ $(LD) -o $@ $^ $(LDFLAGS)
+
clean:
rm -rf $(OBJ) $(LIB) $(EXEC)
diff --git a/utils/hwstub/tools/hwstub_server.cpp b/utils/hwstub/tools/hwstub_server.cpp
new file mode 100644
index 0000000000..6cb8010897
--- /dev/null
+++ b/utils/hwstub/tools/hwstub_server.cpp
@@ -0,0 +1,127 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2016 by Amaury Pouly
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include <cstdio>
+#include <thread>
+#include <chrono>
+#include <cstring>
+#include <iostream>
+#include "hwstub.hpp"
+#include "hwstub_usb.hpp"
+#include "hwstub_uri.hpp"
+#include "hwstub_net.hpp"
+#include <signal.h>
+#include <getopt.h>
+
+/* capture CTRL+C */
+volatile sig_atomic_t g_exit_loop = 0;
+
+void do_signal(int sig)
+{
+ g_exit_loop = 1;
+}
+
+std::shared_ptr<hwstub::context> g_ctx;
+std::shared_ptr<hwstub::net::server> g_srv;
+
+int usage()
+{
+ printf("usage: hwstub_server [options]\n");
+ printf(" --help/-h Display this help\n");
+ printf(" --verbose/-v Verbose output\n");
+ printf(" --context/-c <uri> Context URI (see below)\n");
+ printf(" --server/-s <uri> Server URI (see below)\n");
+ printf("\n");
+ hwstub::uri::print_usage(stdout, true, true);
+ return 1;
+}
+
+int main(int argc, char **argv)
+{
+ hwstub::uri::uri ctx_uri = hwstub::uri::default_uri();
+ hwstub::uri::uri srv_uri = hwstub::uri::default_server_uri();
+ bool verbose = false;
+
+ while(1)
+ {
+ static struct option long_options[] =
+ {
+ {"help", no_argument, 0, 'h'},
+ {"verbose", no_argument, 0, 'v'},
+ {"context", required_argument, 0, 'c'},
+ {"server", required_argument, 0, 's'},
+ {0, 0, 0, 0}
+ };
+
+ int c = getopt_long(argc, argv, "hvc:s:", long_options, NULL);
+ if(c == -1)
+ break;
+ switch(c)
+ {
+ case -1:
+ break;
+ case 'v':
+ verbose = true;
+ break;
+ case 'h':
+ return usage();
+ case 'c':
+ ctx_uri = hwstub::uri::uri(optarg);
+ break;
+ case 's':
+ srv_uri = hwstub::uri::uri(optarg);
+ break;
+ default:
+ abort();
+ }
+ }
+
+ if(optind != argc)
+ return usage();
+
+ /* intercept CTRL+C */
+ signal(SIGINT, do_signal);
+
+ std::string error;
+ g_ctx = hwstub::uri::create_context(ctx_uri, &error);
+ if(!g_ctx)
+ {
+ printf("Cannot create context: %s\n", error.c_str());
+ return 1;
+ }
+ g_ctx->start_polling();
+
+ g_srv = hwstub::uri::create_server(g_ctx, srv_uri, &error);
+ if(!g_srv)
+ {
+ printf("Cannot create server: %s\n", error.c_str());
+ return 1;
+ }
+ if(verbose)
+ g_srv->set_debug(std::cout);
+
+ while(!g_exit_loop)
+ std::this_thread::sleep_for(std::chrono::seconds(1));
+ printf("Shutting down...\n");
+ g_srv.reset(); /* will cleanup */
+ g_ctx.reset(); /* will cleanup */
+
+ return 0;
+}
diff --git a/utils/hwstub/tools/hwstub_test.cpp b/utils/hwstub/tools/hwstub_test.cpp
new file mode 100644
index 0000000000..c93e601c36
--- /dev/null
+++ b/utils/hwstub/tools/hwstub_test.cpp
@@ -0,0 +1,219 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2015 by Amaury Pouly
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include <cstdio>
+#include <thread>
+#include <chrono>
+#include <cstring>
+#include <iostream>
+#include "hwstub.hpp"
+#include "hwstub_usb.hpp"
+#include "hwstub_uri.hpp"
+#include <signal.h>
+#include <getopt.h>
+
+/* capture CTRL+C */
+volatile sig_atomic_t g_exit_loop = 0;
+
+void do_signal(int sig)
+{
+ g_exit_loop = 1;
+}
+
+std::shared_ptr<hwstub::context> g_ctx;
+
+void print_error(hwstub::error err, bool nl = true)
+{
+ switch(err)
+ {
+ case hwstub::error::SUCCESS: printf("success"); break;
+ case hwstub::error::ERROR: printf("error"); break;
+ default: printf("unknown(%d)", (int)err); break;
+ }
+ if(nl)
+ printf("\n");
+}
+
+const char *target_string(uint32_t id)
+{
+ switch(id)
+ {
+ case HWSTUB_TARGET_UNK: return "unknown";
+ case HWSTUB_TARGET_STMP: return "stmp";
+ case HWSTUB_TARGET_RK27: return "rk27";
+ case HWSTUB_TARGET_PP: return "pp";
+ case HWSTUB_TARGET_ATJ: return "atj";
+ case HWSTUB_TARGET_JZ: return "jz";
+ default: return "unknown";
+ }
+}
+
+void print_dev_details(std::shared_ptr<hwstub::device> dev)
+{
+ std::shared_ptr<hwstub::handle> h;
+ hwstub::error err = dev->open(h);
+ if(err != hwstub::error::SUCCESS)
+ {
+ printf(" [cannot open dev: %s]\n", error_string(err).c_str());
+ return;
+ }
+ /* version */
+ struct hwstub_version_desc_t ver_desc;
+ err = h->get_version_desc(ver_desc);
+ if(err != hwstub::error::SUCCESS)
+ {
+ printf(" [cannot get version descriptor: %s]\n", error_string(err).c_str());
+ return;
+ }
+ printf(" [version %d.%d.%d]\n", ver_desc.bMajor, ver_desc.bMinor, ver_desc.bRevision);
+ /* target */
+ struct hwstub_target_desc_t target_desc;
+ err = h->get_target_desc(target_desc);
+ if(err != hwstub::error::SUCCESS)
+ {
+ printf(" [cannot get target descriptor: %s]\n", error_string(err).c_str());
+ return;
+ }
+ std::string name(target_desc.bName, sizeof(target_desc.bName));
+ printf(" [target %s: %s]\n", target_string(target_desc.dID), name.c_str());
+ /* layout */
+ struct hwstub_layout_desc_t layout_desc;
+ err = h->get_layout_desc(layout_desc);
+ if(err != hwstub::error::SUCCESS)
+ {
+ printf(" [cannot get layout descriptor: %s]\n", error_string(err).c_str());
+ return;
+ }
+ printf(" [code layout %#x bytes @ %#x]\n", layout_desc.dCodeSize, layout_desc.dCodeStart);
+ printf(" [stack layout %#x bytes @ %#x]\n", layout_desc.dStackSize, layout_desc.dStackStart);
+ printf(" [buffer layout %#x bytes @ %#x]\n", layout_desc.dBufferSize, layout_desc.dBufferStart);
+}
+
+void print_device(std::shared_ptr<hwstub::device> dev, bool arrived = true)
+{
+ hwstub::usb::device *udev = dynamic_cast< hwstub::usb::device* >(dev.get());
+ if(arrived)
+ printf("--> ");
+ else
+ printf("<-- ");
+ if(udev)
+ {
+ libusb_device *uudev = udev->native_device();
+ struct libusb_device_descriptor dev_desc;
+ libusb_get_device_descriptor(uudev, &dev_desc);
+ printf("USB device @ %d.%u: ID %04x:%04x\n", udev->get_bus_number(),
+ udev->get_address(), dev_desc.idVendor, dev_desc.idProduct);
+ }
+ else
+ printf("Unknown device\n");
+ if(arrived)
+ print_dev_details(dev);
+}
+
+void dev_changed(std::shared_ptr<hwstub::context> ctx, bool arrived, std::shared_ptr<hwstub::device> dev)
+{
+ print_device(dev, arrived);
+}
+
+void print_list()
+{
+ std::vector<std::shared_ptr<hwstub::device>> list;
+ hwstub::error ret = g_ctx->get_device_list(list);
+ if(ret != hwstub::error::SUCCESS)
+ {
+ printf("Cannot get device list: %s\n", error_string(ret).c_str());
+ return;
+ }
+ for(auto d : list)
+ print_device(d);
+}
+
+int usage()
+{
+ printf("usage: hwstub_test [options]\n");
+ printf(" --help/-h Display this help\n");
+ printf(" --verbose/-v Verbose output\n");
+ printf(" --context/-c <uri> Context URI (see below)\n");
+ printf("\n");
+ hwstub::uri::print_usage(stdout, true, false);
+ return 1;
+}
+
+int main(int argc, char **argv)
+{
+ hwstub::uri::uri uri = hwstub::uri::default_uri();
+ bool verbose = false;
+
+ while(1)
+ {
+ static struct option long_options[] =
+ {
+ {"help", no_argument, 0, 'h'},
+ {"verbose", no_argument, 0, 'v'},
+ {"context", required_argument, 0, 'c'},
+ {0, 0, 0, 0}
+ };
+
+ int c = getopt_long(argc, argv, "hvc:", long_options, NULL);
+ if(c == -1)
+ break;
+ switch(c)
+ {
+ case -1:
+ break;
+ case 'v':
+ verbose = true;
+ break;
+ case 'h':
+ return usage();
+ case 'c':
+ uri = hwstub::uri::uri(optarg);
+ break;
+ default:
+ abort();
+ }
+ }
+
+ if(optind != argc)
+ return usage();
+
+ /* intercept CTRL+C */
+ signal(SIGINT, do_signal);
+
+ std::string error;
+ g_ctx = hwstub::uri::create_context(uri, &error);
+ if(!g_ctx)
+ {
+ printf("Cannot create context: %s\n", error.c_str());
+ return 1;
+ }
+ if(verbose)
+ g_ctx->set_debug(std::cout);
+ print_list();
+ g_ctx->register_callback(dev_changed);
+ g_ctx->start_polling();
+ while(!g_exit_loop)
+ std::this_thread::sleep_for(std::chrono::seconds(1));
+ printf("Shutting down...\n");
+ g_ctx.reset(); /* will cleanup */
+
+ return 0;
+}
+