diff options
-rw-r--r-- | utils/hwstub/include/hwstub.h (renamed from utils/hwstub/lib/hwstub.h) | 0 | ||||
-rw-r--r-- | utils/hwstub/include/hwstub.hpp | 351 | ||||
-rw-r--r-- | utils/hwstub/include/hwstub_net.hpp | 334 | ||||
-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.hpp | 131 | ||||
-rw-r--r-- | utils/hwstub/include/hwstub_usb.hpp | 194 | ||||
-rw-r--r-- | utils/hwstub/include/hwstub_virtual.hpp | 159 | ||||
-rw-r--r-- | utils/hwstub/lib/Makefile | 13 | ||||
-rw-r--r-- | utils/hwstub/lib/hwstub.cpp | 627 | ||||
-rw-r--r-- | utils/hwstub/lib/hwstub_net.cpp | 1351 | ||||
-rw-r--r-- | utils/hwstub/lib/hwstub_protocol.h | 1 | ||||
-rw-r--r-- | utils/hwstub/lib/hwstub_uri.cpp | 332 | ||||
-rw-r--r-- | utils/hwstub/lib/hwstub_usb.cpp | 728 | ||||
-rw-r--r-- | utils/hwstub/lib/hwstub_virtual.cpp | 335 | ||||
-rw-r--r-- | utils/hwstub/stub/hwstub.make | 4 | ||||
-rw-r--r-- | utils/hwstub/stub/protocol.h | 2 | ||||
-rw-r--r-- | utils/hwstub/tools/Makefile | 16 | ||||
-rw-r--r-- | utils/hwstub/tools/hwstub_server.cpp | 127 | ||||
-rw-r--r-- | utils/hwstub/tools/hwstub_test.cpp | 219 |
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; +} + |