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