diff options
author | Amaury Pouly <amaury.pouly@gmail.com> | 2016-02-07 21:42:15 +0000 |
---|---|---|
committer | Amaury Pouly <amaury.pouly@gmail.com> | 2016-04-08 19:37:30 +0200 |
commit | 3d8a08ca25c3041ac677335e51341d966a9b370b (patch) | |
tree | 1bf06dea354e3ae95c1ec91b6ee259d0ac21659c | |
parent | 56dc54d38ac6c1d47ea6dbae88b1e5f7fee9f3ec (diff) | |
download | rockbox-3d8a08c.tar.gz rockbox-3d8a08c.zip |
hwstub: rewrite and expand library
Rewrite the hwstub library in C++, with a clean and modular design.
The library was designed from the ground up to be aware of multithreading
issues and to handle memory allocation nicely with shared pointers.
Compared to the original library, it brings the following major features:
- support for JZ boot devices, it is very easy to add support for others
- support for network transparent operations (through sockets): both tcp
and unix domains are support
Change-Id: I75899cb9c7aa938c17ede2bb3f468e7a55d625b4
-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; +} + |