summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAmaury Pouly <amaury.pouly@gmail.com>2016-02-07 21:42:15 +0000
committerAmaury Pouly <amaury.pouly@gmail.com>2016-04-08 19:37:30 +0200
commit3d8a08ca25c3041ac677335e51341d966a9b370b (patch)
tree1bf06dea354e3ae95c1ec91b6ee259d0ac21659c
parent56dc54d38ac6c1d47ea6dbae88b1e5f7fee9f3ec (diff)
downloadrockbox-3d8a08ca25c3041ac677335e51341d966a9b370b.tar.gz
rockbox-3d8a08ca25c3041ac677335e51341d966a9b370b.zip
hwstub: rewrite and expand library
Rewrite the hwstub library in C++, with a clean and modular design. The library was designed from the ground up to be aware of multithreading issues and to handle memory allocation nicely with shared pointers. Compared to the original library, it brings the following major features: - support for JZ boot devices, it is very easy to add support for others - support for network transparent operations (through sockets): both tcp and unix domains are support Change-Id: I75899cb9c7aa938c17ede2bb3f468e7a55d625b4
-rw-r--r--utils/hwstub/include/hwstub.h (renamed from utils/hwstub/lib/hwstub.h)0
-rw-r--r--utils/hwstub/include/hwstub.hpp351
-rw-r--r--utils/hwstub/include/hwstub_net.hpp334
-rw-r--r--utils/hwstub/include/hwstub_protocol.h (renamed from utils/hwstub/hwstub_protocol.h)155
-rw-r--r--utils/hwstub/include/hwstub_uri.hpp131
-rw-r--r--utils/hwstub/include/hwstub_usb.hpp194
-rw-r--r--utils/hwstub/include/hwstub_virtual.hpp159
-rw-r--r--utils/hwstub/lib/Makefile13
-rw-r--r--utils/hwstub/lib/hwstub.cpp627
-rw-r--r--utils/hwstub/lib/hwstub_net.cpp1351
-rw-r--r--utils/hwstub/lib/hwstub_protocol.h1
-rw-r--r--utils/hwstub/lib/hwstub_uri.cpp332
-rw-r--r--utils/hwstub/lib/hwstub_usb.cpp728
-rw-r--r--utils/hwstub/lib/hwstub_virtual.cpp335
-rw-r--r--utils/hwstub/stub/hwstub.make4
-rw-r--r--utils/hwstub/stub/protocol.h2
-rw-r--r--utils/hwstub/tools/Makefile16
-rw-r--r--utils/hwstub/tools/hwstub_server.cpp127
-rw-r--r--utils/hwstub/tools/hwstub_test.cpp219
19 files changed, 5051 insertions, 28 deletions
diff --git a/utils/hwstub/lib/hwstub.h b/utils/hwstub/include/hwstub.h
index 4d12de8eda..4d12de8eda 100644
--- a/utils/hwstub/lib/hwstub.h
+++ b/utils/hwstub/include/hwstub.h
diff --git a/utils/hwstub/include/hwstub.hpp b/utils/hwstub/include/hwstub.hpp
new file mode 100644
index 0000000000..deac976240
--- /dev/null
+++ b/utils/hwstub/include/hwstub.hpp
@@ -0,0 +1,351 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2015 by Amaury Pouly
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#ifndef __HWSTUB_HPP__
22#define __HWSTUB_HPP__
23
24#include "hwstub_protocol.h"
25#include <string>
26#include <mutex>
27#include <vector>
28#include <cstdint>
29#include <atomic>
30#include <memory>
31#include <chrono>
32#include <thread>
33#include <mutex>
34#include <condition_variable>
35#include <ostream>
36
37namespace hwstub {
38
39class context;
40class device;
41class handle;
42class context_poller;
43
44/** C++ equivalent of /dev/null for streams */
45extern std::ostream cnull;
46extern std::wostream wcnull;
47
48/** Errors */
49enum class error
50{
51 SUCCESS, /** Success */
52 ERROR, /** Unspecified error */
53 DISCONNECTED, /** Device has been disconnected */
54 PROBE_FAILURE, /** Device did not pass probing */
55 NO_CONTEXT, /** The context has been destroyed */
56 USB_ERROR, /** Unspecified USB error */
57 DUMMY, /** Call on dummy device/handle */
58 NO_SERVER, /** The server could not be reached */
59 SERVER_DISCONNECTED, /** Context got disconnected from the server */
60 SERVER_MISMATCH, /** The server is not compatible with hwstub */
61 PROTOCOL_ERROR, /** Network protocol error */
62 NET_ERROR, /** Network error */
63 TIMEOUT, /** Operation timed out */
64 OVERFLW, /** Operation stopped to prevent buffer overflow */
65};
66
67/** Return a string explaining the error */
68std::string error_string(error err);
69
70/** NOTE Multithreading:
71 * Unless specified, all methods are thread-safe
72 */
73
74/** Context
75 *
76 * A context provides a list of available devices and may notify devices
77 * arrival and departure.
78 *
79 * A context provides a way to regularly poll for derive changes. There are two
80 * ways to manually force an update:
81 * - on call to get_device_list(), the list is already refetched
82 * - on call to update_list() to force list update
83 * Note that automatic polling is disabled by default.
84 */
85class context : public std::enable_shared_from_this<context>
86{
87protected:
88 context();
89public:
90 /** On destruction, the context will destroy all the devices. */
91 virtual ~context();
92 /** Get device list, clears the list in argument first. All devices in the list
93 * are still connected (or believe to be). This function will update the device
94 * list. */
95 error get_device_list(std::vector<std::shared_ptr<device>>& list);
96 /** Force the context to update its internal list of devices. */
97 error update_list();
98 /** Ask the context to automatically poll for device changes.
99 * Note that this might spawn a new thread to do so, in which case it will
100 * be destroyed/stop on deletetion or when stop_polling() is called. If
101 * polling is already enabled, this function will change the polling interval. */
102 void start_polling(std::chrono::milliseconds interval = std::chrono::milliseconds(250));
103 /** Stop polling. */
104 void stop_polling();
105 /** Register a notification callback with arguments (context,arrived,device)
106 * WARNING the callback may be called asynchronously ! */
107 typedef std::function<void(std::shared_ptr<context>, bool, std::shared_ptr<device>)> notification_callback_t;
108 typedef size_t callback_ref_t;
109 callback_ref_t register_callback(const notification_callback_t& fn);
110 void unregister_callback(callback_ref_t ref);
111 /** Return a dummy device that does nothing. A dummy device might be useful
112 * in cases where one still wants a valid pointer to no device. This dummy
113 * device does not appear in the list, it can be opened and will fail all requests. */
114 error get_dummy_device(std::shared_ptr<device>& dev);
115 /** Set/clear debug output for this context */
116 void set_debug(std::ostream& os);
117 inline void clear_debug() { set_debug(cnull); }
118 /** Get debug output for this context */
119 std::ostream& debug();
120
121protected:
122 /** Notify the context about a device. If arrived is true, the device is
123 * added to the list and a reference will be added to it. If arrived is false,
124 * the device is marked as disconnected(), removed from the list and a
125 * reference will be removed from it. Adding a device that matches an
126 * existing one will do nothing. */
127 void change_device(bool arrived, std::shared_ptr<device> dev);
128 /** Do device notification */
129 void notify_device(bool arrived, std::shared_ptr<device> dev);
130 /** Opaque device type */
131 typedef void* ctx_dev_t;
132 /** Fetch the device list. Each item in the list is an opaque pointer. The function
133 * can also provide a pointer that will be used to free the list resources
134 * if necessary. Return <0 on error. */
135 virtual error fetch_device_list(std::vector<ctx_dev_t>& list, void*& ptr) = 0;
136 /** Destroy the resources created to get the list. */
137 virtual void destroy_device_list(void *ptr) = 0;
138 /** Create a new hwstub device from the opaque pointer. Return <0 on error.
139 * This function needs not add a reference to the newly created device. */
140 virtual error create_device(ctx_dev_t dev, std::shared_ptr<device>& hwdev) = 0;
141 /** Return true if the opaque pointer corresponds to the device. Only called
142 * from map_device(). */
143 virtual bool match_device(ctx_dev_t dev, std::shared_ptr<device> hwdev) = 0;
144 /** Check if a device matches another one in the list */
145 bool contains_dev(const std::vector<device*>& list, ctx_dev_t dev);
146
147 struct callback_t
148 {
149 notification_callback_t callback;
150 callback_ref_t ref;
151 };
152
153 std::shared_ptr<context_poller> m_poller; /* poller object */
154 std::recursive_mutex m_mutex; /* list mutex */
155 std::vector<std::shared_ptr<device>> m_devlist; /* list of devices */
156 std::vector<callback_t> m_callbacks; /* list of callbacks */
157 callback_ref_t m_next_cb_ref; /* next callback reference */
158 std::ostream *m_debug; /* debug stream */
159};
160
161/** Context Poller
162 *
163 * This class provides a way to regularly poll a context for device changes.
164 * NOTE this class is not meant to be used directly since context already
165 * provides access to it via start_polling() and stop_polling() */
166class context_poller
167{
168public:
169 context_poller(std::weak_ptr<context> ctx, std::chrono::milliseconds interval = std::chrono::milliseconds(250));
170 ~context_poller();
171 /** Set polling interval (in milliseconds) (works even if polling already enabled) */
172 void set_interval(std::chrono::milliseconds interval);
173 /** Start polling */
174 void start();
175 /** Stop polling. After return, no function will be made. */
176 void stop();
177
178protected:
179 static void thread(context_poller *poller);
180 void poll();
181
182 std::weak_ptr<context> m_ctx; /* context */
183 bool m_running; /* are we running ? */
184 bool m_exit; /* exit flag for the thread */
185 std::thread m_thread; /* polling thread */
186 std::mutex m_mutex; /* mutex lock */
187 std::condition_variable m_cond; /* signalling condition */
188 std::chrono::milliseconds m_interval; /* Interval */
189};
190
191/** Device
192 *
193 * A device belongs to a context.
194 * Note that a device only keeps a weak pointer to the context, so it is possible
195 * for the context to be destroyed during the life of the device, in which case
196 * all operations on it will fail. */
197class device : public std::enable_shared_from_this<device>
198{
199protected:
200 device(std::shared_ptr<context> ctx);
201public:
202 virtual ~device();
203 /** Open a handle to the device. Several handles may be opened concurrently. */
204 error open(std::shared_ptr<handle>& handle);
205 /** Disconnect the device. This will notify the context that the device is gone. */
206 void disconnect();
207 /** Returns true if the device is still connected. */
208 bool connected();
209 /** Get context (might be empty) */
210 std::shared_ptr<context> get_context();
211
212protected:
213 /** Some subsystems allow for hardware to be open several times and so do not.
214 * For example, libusb only allows one handle per device. To workaround this issue,
215 * open() will do some magic to allow for several open() even when the hardware
216 * supports only one. If the device does not support multiple
217 * handles (as reported by has_multiple_open()), open() will only call open_dev()
218 * the first time the device is opened and will redirect other open() calls to
219 * this handle using proxy handles. If the device supports multiple handles,
220 * open() will simply call open_dev() each time.
221 * The open_dev() does not need to care about this magic and only needs to
222 * open the device and returns the handle to it.
223 * NOTE this function is always called with the mutex locked already. */
224 virtual error open_dev(std::shared_ptr<handle>& handle) = 0;
225 /** Return true if device can be opened multiple times. In this case, each
226 * call to open() will generate a call to do_open(). Otherwise, proxy handles
227 * will be created for each open() and do_open() will only be called the first
228 * time. */
229 virtual bool has_multiple_open() const = 0;
230
231 std::weak_ptr<context> m_ctx; /* pointer to context */
232 std::recursive_mutex m_mutex; /* device state mutex: ref count, connection status */
233 bool m_connected; /* false once device is disconnected */
234 std::weak_ptr<handle> m_handle; /* weak pointer to the opened handle (if !has_multiple_open()) */
235};
236
237/** Handle
238 *
239 * A handle is tied to a device and provides access to the stub operation.
240 * The handle is reference counted and is destroyed
241 * when its reference count decreased to zero.
242 */
243class handle : public std::enable_shared_from_this<handle>
244{
245protected:
246 /** A handle will always hold a reference to the device */
247 handle(std::shared_ptr<device> dev);
248public:
249 /** When destroyed, the handle will release its reference to the device */
250 virtual ~handle();
251 /** Return associated device */
252 std::shared_ptr<device> get_device();
253 /** Fetch a descriptor, buf_sz is the size of the buffer and is updated to
254 * reflect the number of bytes written to the buffer. */
255 error get_desc(uint16_t desc, void *buf, size_t& buf_sz);
256 /** Fetch part of the log, buf_sz is the size of the buffer and is updated to
257 * reflect the number of bytes written to the buffer. */
258 error get_log(void *buf, size_t& buf_sz);
259 /** Ask the stub to execute some code.
260 * NOTE: this may kill the stub */
261 error exec(uint32_t addr, uint16_t flags);
262 /** Read/write some device memory. sz is the size of the buffer and is updated to
263 * reflect the number of bytes written to the buffer.
264 * NOTE: the stub may or may not recover from bad read/write, so this may kill it.
265 * NOTE: the default implemtentation of read() and write() will split transfers
266 * according to the buffer size and call read_dev() and write_dev() */
267 error read(uint32_t addr, void *buf, size_t& sz, bool atomic);
268 error write(uint32_t addr, const void *buf, size_t& sz, bool atomic);
269 /** Get device buffer size: any read() or write() greater than this size
270 * will be split into several transaction to avoid overflowing device
271 * buffer. */
272 virtual size_t get_buffer_size() = 0;
273 /** Check a handle status. A successful handle does not guarantee successful
274 * operations. An invalid handle will typically report probing failure (the
275 * device did not pass probing) or disconnection.
276 * The default implemtentation will test if context still exists and connection status. */
277 virtual error status() const;
278 /** Shorthand for status() == HWSTUB_SUCCESS */
279 inline bool valid() const { return status() == error::SUCCESS; }
280
281 /** Helper functions */
282 error get_version_desc(hwstub_version_desc_t& desc);
283 error get_layout_desc(hwstub_layout_desc_t& desc);
284 error get_stmp_desc(hwstub_stmp_desc_t& desc);
285 error get_pp_desc(hwstub_pp_desc_t& desc);
286 error get_jz_desc(hwstub_jz_desc_t& desc);
287 error get_target_desc(hwstub_target_desc_t& desc);
288
289protected:
290 /** The get_desc(), get_log(), exec(), read() and write() function
291 * take care of details so that each implementation can safely assume that
292 * the hwstub context exists and will not be destroyed during the execution
293 * of the function. It will also return early if the device has been disconnected.
294 *
295 * NOTE on read() and write():
296 * Since devices have a limited buffer, big transfers must be split into
297 * smaller ones. The high-level read() and write() functions perform this
298 * splitting in a generic way, based on get_buffer_size(), and calling read_dev()
299 * and write_dev() which do the actual operation.
300 * These function can safely assume that sz <= get_buffer_size(). */
301 virtual error read_dev(uint32_t addr, void *buf, size_t& sz, bool atomic) = 0;
302 virtual error write_dev(uint32_t addr, const void *buf, size_t& sz, bool atomic) = 0;
303 virtual error get_dev_desc(uint16_t desc, void *buf, size_t& buf_sz) = 0;
304 virtual error get_dev_log(void *buf, size_t& buf_sz) = 0;
305 virtual error exec_dev(uint32_t addr, uint16_t flags) = 0;
306
307 std::shared_ptr<device> m_dev; /* pointer to device */
308 std::atomic<int> m_refcnt; /* reference count */
309 std::recursive_mutex m_mutex; /* operation mutex to serialise operations */
310};
311
312/** Dummy device */
313class dummy_device : public device
314{
315 friend class context; /* for ctor */
316protected:
317 dummy_device(std::shared_ptr<context> ctx);
318public:
319 virtual ~dummy_device();
320
321protected:
322 virtual error open_dev(std::shared_ptr<handle>& handle);
323 virtual bool has_multiple_open() const;
324};
325
326/** Dummy handle */
327class dummy_handle : public handle
328{
329 friend class dummy_device;
330protected:
331 dummy_handle(std::shared_ptr<device> dev);
332public:
333 virtual ~dummy_handle();
334
335protected:
336 virtual error read_dev(uint32_t addr, void *buf, size_t& sz, bool atomic);
337 virtual error write_dev(uint32_t addr, const void *buf, size_t& sz, bool atomic);
338 virtual error get_dev_desc(uint16_t desc, void *buf, size_t& buf_sz);
339 virtual error get_dev_log(void *buf, size_t& buf_sz);
340 virtual error exec_dev(uint32_t addr, uint16_t flags);
341 virtual error status() const;
342 virtual size_t get_buffer_size();
343
344 struct hwstub_version_desc_t m_desc_version;
345 struct hwstub_layout_desc_t m_desc_layout;
346 struct hwstub_target_desc_t m_desc_target;
347};
348
349} // namespace hwstub
350
351#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 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2016 by Amaury Pouly
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#ifndef __HWSTUB_NET_HPP__
22#define __HWSTUB_NET_HPP__
23
24#include "hwstub.hpp"
25#include <map>
26#include <thread>
27#include <future>
28#include <list>
29
30namespace hwstub {
31namespace net {
32
33/** Net context
34 *
35 * A socket context provides access to another context through a network. This
36 * is particularly useful to have another program create a USB context and provide
37 * access to it via some network. The two most useful types of network are TCP
38 * and Unix domains */
39class context : public hwstub::context
40{
41 friend class device;
42 friend class handle;
43protected:
44 context();
45public:
46 virtual ~context();
47 /** Create a socket context with an existing file descriptor. Note that the
48 * file descriptor will be closed when the context will be destroyed. */
49 static std::shared_ptr<context> create_socket(int socket_fd);
50 /** Create a TCP socket context with a domain name and a port. If port is empty,
51 * a default port is used. */
52 static std::shared_ptr<context> create_tcp(const std::string& domain,
53 const std::string& port, std::string *error = nullptr);
54 /** Create a UNIX socket context with a file system path (see man for details) */
55 static std::shared_ptr<context> create_unix(const std::string& path,
56 std::string *error = nullptr);
57 /** Create a UNIX socket context with an abstract name (see man for details) */
58 static std::shared_ptr<context> create_unix_abstract(const std::string& path,
59 std::string *error = nullptr);
60 /** Useful functions for network byte order conversion */
61 uint32_t to_net_order(uint32_t u);
62 uint32_t from_net_order(uint32_t u);
63
64 /** Default parameters */
65 static std::string default_unix_path();
66 static std::string default_tcp_domain();
67 static std::string default_tcp_port();
68
69protected:
70 /** Send a message to the server. Context will always serialize calls to send()
71 * so there is no need to worry about concurrency issues. */
72 virtual error send(void *buffer, size_t& sz) = 0;
73 /** Receive a message from the server, sz is updated with the received size.
74 * Context will always serialize calls to recv() so there is no need to
75 * worry about concurrency issues. */
76 virtual error recv(void *buffer, size_t& sz) = 0;
77 /** Perform a standard command: send a header with optional data and wait for
78 * an answer. In case of an underlying network error, the corresponding error
79 * code will be reported. If the server responds correctly, the argument array
80 * is overwritten with the servers's response. If the requests has been NACK'ed
81 * the error code will be parsed and returned as a standard error code (see details below)
82 * (note that the original error code can still be found in args[0]). No data
83 * is transmitted in case of NACK.
84 * If the server ACKs the request, this function will also perform reception of
85 * the data. In recv_data is not NULL, the receive data will be put there and the
86 * size will be written in in_size. There are two cases: either *recv_data is NULL
87 * and the function will allocate the memory based on much data is sent by the server.
88 * Or *recv_data is not NULL, in which case the function NOT allocate memory
89 * and put the data at *recv_data; in this case, *recv_size should be the set
90 * to the size of the buffer and will be updated to the received size. If the
91 * server sents more data than the buffer size, OVERFLOW will be returned.
92 * If no data was received but recv_data is not null, *recv_size will be set to
93 * zero. It is the caller's responsability to delete *recv_data. Note that if
94 * server sends data but recv_data is null, the data will still be received and
95 * thrown away.
96 * This function takes care of network byte order for cmd and arguments
97 * but not for data. */
98 error send_cmd(uint32_t cmd, uint32_t args[HWSTUB_NET_ARGS], uint8_t *send_data,
99 size_t send_size, uint8_t **recv_data, size_t *recv_size);
100 /** Ask the context to stop any communication with the server and do a clean
101 * shutdown if possible. This is a blocking call. When this function returns,
102 * there will no more calls to the underlying communication functions.
103 * This function should be called in the destructor to prevent the context from
104 * calling children functions after the object has been deconstructed. */
105 void stop_context();
106
107 /** Perform delayed init (aka HELLO stage), do nothing is not needed */
108 void delayed_init();
109 /* NOTE ctx_dev_t = uint32_t (device id) */
110 uint32_t from_ctx_dev(ctx_dev_t dev);
111 ctx_dev_t to_ctx_dev(uint32_t dev);
112 virtual error fetch_device_list(std::vector<ctx_dev_t>& list, void*& ptr);
113 virtual void destroy_device_list(void *ptr);
114 virtual error create_device(ctx_dev_t dev, std::shared_ptr<hwstub::device>& hwdev);
115 virtual bool match_device(ctx_dev_t dev, std::shared_ptr<hwstub::device> hwdev);
116
117 enum class state
118 {
119 HELLO, /* client is initialising, server has not been contacted yet */
120 IDLE, /* not doing anything */
121 DEAD, /* died on unrecoverable error */
122 };
123
124 state m_state; /* client state */
125 error m_error; /* error state for DEAD */
126};
127
128/** Socket based net context
129 *
130 * Don't use this class directly, use context::create_* calls. This class
131 * provides send()/recv() for any socket based network. */
132class socket_context : public context
133{
134 friend class context;
135protected:
136 socket_context(int socket_fd);
137public:
138 virtual ~socket_context();
139 /** set operation timeout */
140 void set_timeout(std::chrono::milliseconds ms);
141
142protected:
143 virtual error send(void *buffer, size_t& sz);
144 virtual error recv(void *buffer, size_t& sz);
145
146 int m_socketfd; /* socket file descriptor */
147};
148
149
150/** Net device
151 *
152 * Device accessed through a network */
153class device : public hwstub::device
154{
155 friend class context; /* for ctor */
156protected:
157 device(std::shared_ptr<hwstub::context> ctx, uint32_t devid);
158public:
159 virtual ~device();
160
161protected:
162 /** Return device ID */
163 uint32_t device_id();
164 virtual error open_dev(std::shared_ptr<hwstub::handle>& handle);
165 virtual bool has_multiple_open() const;
166
167 int32_t m_device_id; /* device id */
168};
169
170/** Net handle
171 *
172 * Handle used to talk to a distant device. */
173class handle : public hwstub::handle
174{
175 friend class device;
176protected:
177 handle(std::shared_ptr<hwstub::device> dev, uint32_t hid);
178public:
179 virtual ~handle();
180
181protected:
182 virtual error read_dev(uint32_t addr, void *buf, size_t& sz, bool atomic);
183 virtual error write_dev(uint32_t addr, const void *buf, size_t& sz, bool atomic);
184 virtual error get_dev_desc(uint16_t desc, void *buf, size_t& buf_sz);
185 virtual error get_dev_log(void *buf, size_t& buf_sz);
186 virtual error exec_dev(uint32_t addr, uint16_t flags);
187 virtual error status() const;
188 virtual size_t get_buffer_size();
189
190 uint32_t m_handle_id; /* handle id */
191};
192
193/** Net server
194 *
195 * A server that forwards requests from net clients to a context */
196class server
197{
198protected:
199 server(std::shared_ptr<hwstub::context> contex);
200public:
201 virtual ~server();
202
203 /** Create a socket server with an existing file descriptor. Note that the
204 * file descriptor will be closed when the context will be destroyed. */
205 static std::shared_ptr<server> create_socket(std::shared_ptr<hwstub::context> contex,
206 int socket_fd);
207 /** Create a TCP socket server with a domain name and a port. If port is empty,
208 * a default port is used. */
209 static std::shared_ptr<server> create_tcp(std::shared_ptr<hwstub::context> contex,
210 const std::string& domain, const std::string& port, std::string *error = nullptr);
211 /** Create a UNIX socket server with a file system path (see man for details) */
212 static std::shared_ptr<server> create_unix(std::shared_ptr<hwstub::context> contex,
213 const std::string& path, std::string *error = nullptr);
214 /** Create a UNIX socket server with an abstract name (see man for details) */
215 static std::shared_ptr<server> create_unix_abstract(
216 std::shared_ptr<hwstub::context> contex, const std::string& path,
217 std::string *error = nullptr);
218 /** Useful functions for network byte order conversion */
219 uint32_t to_net_order(uint32_t u);
220 uint32_t from_net_order(uint32_t u);
221
222 /** Set/clear debug output for this context */
223 void set_debug(std::ostream& os);
224 inline void clear_debug() { set_debug(cnull); }
225 /** Get debug output for this context */
226 std::ostream& debug();
227protected:
228 struct client_state;
229 /** Opaque client type */
230 typedef void* srv_client_t;
231 /** The client discovery implementation must call this function when a new
232 * client wants to talk to the server. If the server is unhappy with the
233 * request, it will immediately call terminate_client() */
234 void client_arrived(srv_client_t client);
235 /** The client discovery implementation can notify asychronously about a client
236 * that left. Note that the implementation does not need to provide a mechanism,
237 * but should in this case return CLIENT_DISCONNECTED when the server performs
238 * a send() or recv() on a disconnected client. The server will always call
239 * after receiving client_left() but since this call is asychronous, the
240 * implementation must be prepared to deal with extra send()/recv() in the mean
241 * time. */
242 void client_left(srv_client_t client);
243 /** The client discovery implementation can ask the server to stop all client
244 * threads. This is a blocking call. When this function returns, there will no
245 * more calls to the underlying communication functions. Note that the server
246 * will normally call terminate_client() on each active client at this point.
247 * This function should be called in the destructor to prevent the server from
248 * calling children functions after the object has been deconstructed. */
249 void stop_server();
250 /** Notify that the connection to a client is now finished. After this call, no
251 * more send()/recv() will be made to the client and the associated data will
252 * be freed. After this call, the implementation is not allowed to call client_left()
253 * for this client (assuming it did not previously). The implementation should close
254 * the communication channel at this point and free any associated data. */
255 virtual void terminate_client(srv_client_t client) = 0;
256 /** Send a message to the client. Server will always serialize calls to send()
257 * for a given client so there is no need to worry about concurrency issues. */
258 virtual error send(srv_client_t client, void *buffer, size_t& sz) = 0;
259 /** Receive a message from the client, sz is updated with the received size.
260 * Server will always serialize calls to recv() for a given client so there
261 * is no need to worry about concurrency issues. See comment about client_left(). */
262 virtual error recv(srv_client_t client, void *buffer, size_t& sz) = 0;
263 /** handle command: cmd and arguments are in host order, the function should
264 * either return an error (command will be NACKed) or must fill the arguments
265 * and data for the answer. Note that the data is still in network byte order.
266 * If the funtion wants to send data back, it must set *send_data to a valid
267 * pointer, this pointer will be freed after the data is sent back. */
268 error handle_cmd(client_state *state, uint32_t cmd, uint32_t args[HWSTUB_NET_ARGS],
269 uint8_t *recv_data, size_t recv_size, uint8_t*& send_data, size_t& send_size);
270
271 /* complete state of a client */
272 struct client_state
273 {
274 client_state(srv_client_t cl, std::future<void>&& f);
275 srv_client_t client; /* client */
276 std::future<void> future; /* thread (see .cpp for explaination) */
277 volatile bool exit; /* exit flag */
278 uint32_t next_dev_id; /* next device ID */
279 uint32_t next_handle_id; /* next handle ID */
280 /* dev ID <-> hwstub dev map */
281 std::map<uint32_t, std::shared_ptr<hwstub::device>> dev_map;
282 /* handle ID -> hwstub handle map */
283 std::map<uint32_t, std::shared_ptr<hwstub::handle>> handle_map;
284 };
285
286 /** Client thread */
287 static void client_thread2(server *s, client_state *cs);
288 void client_thread(client_state *cs);
289
290 std::shared_ptr<hwstub::context> m_context; /* context to perform operation */
291 std::list<client_state> m_client; /* client list */
292 std::recursive_mutex m_mutex; /* server mutex */
293 std::ostream *m_debug; /* debug stream */
294};
295
296/** Socket based net server
297 *
298 */
299class socket_server : public server
300{
301protected:
302 socket_server(std::shared_ptr<hwstub::context> contex, int socket_fd);
303public:
304 virtual ~socket_server();
305 /** create a server */
306 static std::shared_ptr<server> create(std::shared_ptr<hwstub::context> contex,
307 int socket_fd);
308 /** set operation timeout */
309 void set_timeout(std::chrono::milliseconds ms);
310
311protected:
312 virtual void terminate_client(srv_client_t client);
313 virtual error send(srv_client_t client, void *buffer, size_t& sz);
314 virtual error recv(srv_client_t, void *buffer, size_t& sz);
315
316 /* NOTE srv_client_t = int (client file descriptor) */
317 int from_srv_client(srv_client_t cli);
318 srv_client_t to_srv_client(int fd);
319
320 /** Discovery thread */
321 static void discovery_thread1(socket_server *s);
322 void discovery_thread();
323
324 static const int LISTEN_QUEUE_SIZE = 5;
325 struct timeval m_timeout; /* operations timeout */
326 int m_socketfd; /* socket file descriptor */
327 std::thread m_discovery_thread; /* thread handling client discovery */
328 volatile bool m_discovery_exit; /* exit flag */
329};
330
331} // namespace net
332} // namespace hwstub
333
334#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 @@
21#ifndef __HWSTUB_PROTOCOL__ 21#ifndef __HWSTUB_PROTOCOL__
22#define __HWSTUB_PROTOCOL__ 22#define __HWSTUB_PROTOCOL__
23 23
24#include <stdint.h>
25
26/**
27 * This file contains the data structures used in the USB and network protocol.
28 * All USB data uses the standard USB byte order which is little-endian.
29 */
30
24/** 31/**
25 * HWStub protocol version 32 * HWStub protocol version
26 */ 33 */
27 34
28#define HWSTUB_VERSION_MAJOR 4 35#define HWSTUB_VERSION_MAJOR 4
29#define HWSTUB_VERSION_MINOR 1 36#define HWSTUB_VERSION_MINOR 2
30 37
31#define HWSTUB_VERSION__(maj, min) #maj"."#min 38#define HWSTUB_VERSION__(maj, min) #maj"."#min
32#define HWSTUB_VERSION_(maj, min) HWSTUB_VERSION__(maj, min) 39#define HWSTUB_VERSION_(maj, min) HWSTUB_VERSION__(maj, min)
@@ -49,6 +56,10 @@
49#define HWSTUB_SUBCLASS 0xde 56#define HWSTUB_SUBCLASS 0xde
50#define HWSTUB_PROTOCOL 0xad 57#define HWSTUB_PROTOCOL 0xad
51 58
59/**********************************
60 * Descriptors
61 **********************************/
62
52/** 63/**
53 * Descriptors can be retrieved using configuration descriptor or individually 64 * Descriptors can be retrieved using configuration descriptor or individually
54 * using the standard GetDescriptor request on the interface. 65 * using the standard GetDescriptor request on the interface.
@@ -57,9 +68,9 @@
57#define HWSTUB_DT_VERSION 0x41 /* mandatory */ 68#define HWSTUB_DT_VERSION 0x41 /* mandatory */
58#define HWSTUB_DT_LAYOUT 0x42 /* mandatory */ 69#define HWSTUB_DT_LAYOUT 0x42 /* mandatory */
59#define HWSTUB_DT_TARGET 0x43 /* mandatory */ 70#define HWSTUB_DT_TARGET 0x43 /* mandatory */
60#define HWSTUB_DT_STMP 0x44 /* optional */ 71#define HWSTUB_DT_STMP 0x44 /* mandatory for STMP */
61#define HWSTUB_DT_PP 0x45 /* optional */ 72#define HWSTUB_DT_PP 0x45 /* mandatory for PP */
62#define HWSTUB_DT_DEVICE 0x46 /* optional */ 73#define HWSTUB_DT_JZ 0x46 /* mandatory for JZ */
63 74
64struct hwstub_version_desc_t 75struct hwstub_version_desc_t
65{ 76{
@@ -105,11 +116,21 @@ struct hwstub_pp_desc_t
105 uint8_t bRevision[2]; /* 'B1' for B1 for example */ 116 uint8_t bRevision[2]; /* 'B1' for B1 for example */
106} __attribute__((packed)); 117} __attribute__((packed));
107 118
119struct hwstub_jz_desc_t
120{
121 uint8_t bLength;
122 uint8_t bDescriptorType;
123 /* Chip ID and revision */
124 uint16_t wChipID; /* 0x4760 for Jz4760 for example */
125 uint8_t bRevision; /* 0 for Jz4760, 'B' for JZ4760B */
126} __attribute__((packed));
127
108#define HWSTUB_TARGET_UNK ('U' | 'N' << 8 | 'K' << 16 | ' ' << 24) 128#define HWSTUB_TARGET_UNK ('U' | 'N' << 8 | 'K' << 16 | ' ' << 24)
109#define HWSTUB_TARGET_STMP ('S' | 'T' << 8 | 'M' << 16 | 'P' << 24) 129#define HWSTUB_TARGET_STMP ('S' | 'T' << 8 | 'M' << 16 | 'P' << 24)
110#define HWSTUB_TARGET_RK27 ('R' | 'K' << 8 | '2' << 16 | '7' << 24) 130#define HWSTUB_TARGET_RK27 ('R' | 'K' << 8 | '2' << 16 | '7' << 24)
111#define HWSTUB_TARGET_PP ('P' | 'P' << 8 | ' ' << 16 | ' ' << 24) 131#define HWSTUB_TARGET_PP ('P' | 'P' << 8 | ' ' << 16 | ' ' << 24)
112#define HWSTUB_TARGET_ATJ ('A' | 'T' << 8 | 'J' << 16 | ' ' << 24) 132#define HWSTUB_TARGET_ATJ ('A' | 'T' << 8 | 'J' << 16 | ' ' << 24)
133#define HWSTUB_TARGET_JZ ('J' | 'Z' << 8 | '4' << 16 | '7' << 24)
113 134
114struct hwstub_target_desc_t 135struct hwstub_target_desc_t
115{ 136{
@@ -120,11 +141,18 @@ struct hwstub_target_desc_t
120 char bName[58]; 141 char bName[58];
121} __attribute__((packed)); 142} __attribute__((packed));
122 143
123struct hwstub_device_desc_t 144/**
145 * Socket command packet header: any transfer (in both directions) start with this.
146 * All data is transmitted in network byte order.
147 */
148#define HWSTUB_NET_ARGS 4
149
150struct hwstub_net_hdr_t
124{ 151{
125 uint8_t bLength; 152 uint32_t magic; /* magic value (HWSERVER_MAGIC) */
126 uint8_t bDescriptorType; 153 uint32_t cmd; /* command (OR'ed with (N)ACK on response) */
127 /* Give the bRequest value for */ 154 uint32_t length; /* length of the data following this header */
155 uint32_t args[HWSTUB_NET_ARGS]; /* command arguments */
128} __attribute__((packed)); 156} __attribute__((packed));
129 157
130/** 158/**
@@ -134,14 +162,45 @@ struct hwstub_device_desc_t
134 * of the SETUP packet. The wIndex contains the interface number. The wValue 162 * of the SETUP packet. The wIndex contains the interface number. The wValue
135 * contains an ID which is used for requests requiring several transfers. 163 * contains an ID which is used for requests requiring several transfers.
136 */ 164 */
165#define HWSTUB_GET_LOG 0x40
166#define HWSTUB_READ 0x41
167#define HWSTUB_READ2 0x42
168#define HWSTUB_WRITE 0x43
169#define HWSTUB_EXEC 0x44
170#define HWSTUB_READ2_ATOMIC 0x45
171#define HWSTUB_WRITE_ATOMIC 0x46
172
173/* the following commands and the ACK/NACK mechanism are net only */
174#define HWSERVER_ACK(n) (0x100|(n))
175#define HWSERVER_ACK_MASK 0x100
176#define HWSERVER_NACK(n) (0x200|(n))
177#define HWSERVER_NACK_MASK 0x200
178
179#define HWSERVER_MAGIC ('h' << 24 | 'w' << 16 | 's' << 8 | 't')
137 180
138#define HWSTUB_GET_LOG 0x40 181#define HWSERVER_HELLO 0x400
139#define HWSTUB_READ 0x41 182#define HWSERVER_GET_DEV_LIST 0x401
140#define HWSTUB_READ2 0x42 183#define HWSERVER_DEV_OPEN 0x402
141#define HWSTUB_WRITE 0x43 184#define HWSERVER_DEV_CLOSE 0x403
142#define HWSTUB_EXEC 0x44 185#define HWSERVER_BYE 0x404
143#define HWSTUB_READ2_ATOMIC 0x45 186#define HWSERVER_GET_DESC 0x405
144#define HWSTUB_WRITE_ATOMIC 0x46 187#define HWSERVER_GET_LOG 0x406
188#define HWSERVER_READ 0x407
189#define HWSERVER_WRITE 0x408
190#define HWSERVER_EXEC 0x409
191
192/* net errors (always in arg[0] if command is NACKed) */
193#define HWERR_OK 0 /* success */
194#define HWERR_FAIL 1 /* general error from hwstub */
195#define HWERR_INVALID_ID 2 /* invalid id of the device */
196#define HWERR_DISCONNECTED 3 /* device got disconnected */
197
198/* read/write flags */
199#define HWSERVER_RW_ATOMIC 0x1
200
201/**********************************
202 * Control Protocol
203 **********************************/
145 204
146/** 205/**
147 * HWSTUB_GET_LOG: 206 * HWSTUB_GET_LOG:
@@ -165,7 +224,7 @@ struct hwstub_read_req_t
165} __attribute__((packed)); 224} __attribute__((packed));
166 225
167/** 226/**
168 * HWSTUB_WRITE 227 * HWSTUB_WRITE:
169 * Write a range of memory. The payload starts with the following header, everything 228 * Write a range of memory. The payload starts with the following header, everything
170 * which follows is data. 229 * which follows is data.
171 * HWSTUB_WRITE_ATOMIC behaves the same except it is atomic. See HWSTUB_READ2_ATOMIC. 230 * HWSTUB_WRITE_ATOMIC behaves the same except it is atomic. See HWSTUB_READ2_ATOMIC.
@@ -192,4 +251,68 @@ struct hwstub_exec_req_t
192 uint16_t bmFlags; 251 uint16_t bmFlags;
193} __attribute__((packed)); 252} __attribute__((packed));
194 253
254/**
255 * HWSERVER_HELLO:
256 * Say hello to the server, give protocol version and get server version.
257 * Send: args[0] = major << 8 | minor, no data
258 * Receive: args[0] = major << 8 | minor, no data
259 */
260
261/**
262 * HWSERVER_GET_DEV_LIST:
263 * Get device list.
264 * Send: no argument, no data.
265 * Receive: no argument, data contains a list of device IDs, each ID is a uint32_t
266 * transmitted in network byte order.
267 */
268
269/**
270 * HWSERVER_DEV_OPEN:
271 * Open a device and return a handle.
272 * Send: args[0] = device ID, no data.
273 * Receive: args[0] = handle ID, no data.
274 */
275
276/**
277 * HWSERVER_DEV_CLOSE:
278 * Close a device handle.
279 * Send: args[0] = handle ID, no data.
280 * Receive: no argument, no data.
281 */
282
283/**
284 * HWSERVER_BYE:
285 * Say bye to the server, closing all devices and effectively stopping the communication.
286 * Send: no argument, no data
287 * Receive: no argument, no data
288 */
289
290/**
291 * HWSERVER_GET_DESC:
292 * Query a descriptor.
293 * Send: args[0] = handle ID, args[1] = desc ID, args[2] = requested length, no data
294 * Receive: no argument, data contains RAW descriptor (ie all fields are in little-endian)
295 */
296
297/**
298 * HWSERVER_GET_LOG:
299 * Query a descriptor.
300 * Send: args[0] = handle ID, args[1] = requested length, no data
301 * Receive: no argument, data contains log data
302 */
303
304/**
305 * HWSERVER_READ:
306 * Read data.
307 * Send: args[0] = handle ID, args[1] = addr, args[2] = length, args[3] = flags
308 * Receive: no argument, data read on device
309 */
310
311/**
312 * HWSERVER_WRITE:
313 * Read data.
314 * Send: args[0] = handle ID, args[1] = addr, args[2] = flags, data to write
315 * Receive: no data
316 */
317
195#endif /* __HWSTUB_PROTOCOL__ */ 318#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 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2016 by Amaury Pouly
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#ifndef __HWSTUB_URI_HPP__
22#define __HWSTUB_URI_HPP__
23
24#include "hwstub.hpp"
25#include "hwstub_net.hpp"
26
27namespace hwstub {
28namespace uri {
29
30/** HWSTUB URIs
31 *
32 * They are of the form:
33 *
34 * scheme:[//domain[:port]][/][path[?query]]
35 *
36 * The scheme is mandatory and controls the type of context that is created.
37 * The following scheme are recognized:
38 * usb USB context
39 * tcp TCP context
40 * unix Unix domain context
41 * virt Virtual context (Testing and debugging)
42 * default Default context (This is the default)
43 *
44 * When creating a USB context, the domain and port must be empty:
45 * usb:
46 *
47 * When creating a TCP context, the domain and port are given as argument to
48 * the context:
49 * tcp://localhost:6666
50 *
51 * When creating a Unix context, the domain is given as argument to
52 * the context, it is invalid to specify a port. There are two types of
53 * unix contexts: the one specified by a filesystem path, or (Linux-only) by
54 * an abstract domain. Abstract names are specified as a domain starting with a '#',
55 * whereas standard path can be any path:
56 * unix:///path/to/socket
57 * unix://#hwstub
58 *
59 * When creating a virtual context, the domain will contain a specification of
60 * the device to create. The device list is of the type(param);type(param);...
61 * where the only supported type at the moment is 'dummy' with a single parameter
62 * which is the device name:
63 * virt://dummy(Device A);dummy(Device B);dummy(Super device C)
64 *
65 *
66 * HWSTUB SERVER URIs
67 *
68 * The same scheme can be used to spawn servers. Server URIs are a subset of
69 * context URIs and only support tcp and unix schemes.
70 */
71
72/** URI
73 *
74 * Represents an URI and allows queries on it */
75class uri
76{
77public:
78 uri(const std::string& uri);
79 /** Return whether the URI is syntactically correct */
80 bool valid() const;
81 /** Return error description if URI is invalid */
82 std::string error() const;
83 /** Return the original URI */
84 std::string full_uri() const;
85 /** Return the scheme */
86 std::string scheme() const;
87 /** Return the domain, or empty is none */
88 std::string domain() const;
89 /** Return the port, or empty is none */
90 std::string port() const;
91 /** Return the path, or empty is none */
92 std::string path() const;
93
94protected:
95 void parse();
96 bool validate_scheme();
97 bool validate_domain();
98 bool validate_port();
99
100 std::string m_uri; /* original uri */
101 bool m_valid; /* did it parse correctly ? */
102 std::string m_scheme; /* scheme (extracted from URI) */
103 std::string m_domain; /* domain (extracted from URI) */
104 std::string m_port; /* port (extracted from URI) */
105 std::string m_path; /* path (extracted from URI) */
106 std::string m_error; /* error string (for invalid URIs) */
107};
108
109/** Create a context based on a URI. This function only uses the scheme/domain/port
110 * parts of the URI. This function may fail and return a empty pointer. An optional
111 * string can receive a description of the error */
112std::shared_ptr<context> create_context(const uri& uri, std::string *error = nullptr);
113/** Return a safe default for a URI */
114uri default_uri();
115/** Special case function for the default function */
116std::shared_ptr<context> create_default_context(std::string *error = nullptr);
117/** Create a server based on a URI. This function only uses the scheme/domain/port
118 * parts of the URI. This function may fail and return a empty pointer. An optional
119 * string can receive a description of the error */
120std::shared_ptr<net::server> create_server(std::shared_ptr<context> ctx,
121 const uri& uri, std::string *error);
122/** Return a safe default for a server URI */
123uri default_server_uri();
124/** Print help for the format of a URI, typically for a command-line help.
125 * The help can be client-only, server-only, or both. */
126void print_usage(FILE *f, bool client, bool server);
127
128} // namespace uri
129} // namespace hwstub
130
131#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 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2015 by Amaury Pouly
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#ifndef __HWSTUB_USB_HPP__
22#define __HWSTUB_USB_HPP__
23
24#include "hwstub_usb.hpp"
25#include <libusb.h>
26
27namespace hwstub {
28namespace usb {
29
30/** USB context
31 *
32 * Context based on libusb. */
33class context : public hwstub::context
34{
35protected:
36 context(libusb_context *ctx, bool cleanup_ctx);
37public:
38 virtual ~context();
39 /** Return native libusb context */
40 libusb_context *native_context();
41 /** Create a USB context. If cleanup_ctx is true, libusb_exit() will be
42 * called on the context on deletion of this class. If ctx is NULL, libusb_init()
43 * will be called with NULL so there is no need to init the default context. */
44 static std::shared_ptr<context> create(libusb_context *ctx, bool cleanup_ctx = false,
45 std::string *error = nullptr);
46
47protected:
48 /* NOTE ctx_dev_t = libusb_device* */
49 libusb_device *from_ctx_dev(ctx_dev_t dev);
50 ctx_dev_t to_ctx_dev(libusb_device *dev);
51 virtual error fetch_device_list(std::vector<ctx_dev_t>& list, void*& ptr);
52 virtual void destroy_device_list(void *ptr);
53 virtual error create_device(ctx_dev_t dev, std::shared_ptr<hwstub::device>& hwdev);
54 virtual bool match_device(ctx_dev_t dev, std::shared_ptr<hwstub::device> hwdev);
55
56 libusb_context *m_usb_ctx; /* libusb context (might be NULL) */
57 bool m_cleanup_ctx; /* cleanup context on delete ? */
58};
59
60/** USB device
61 *
62 * Device based on libusb_device. */
63class device : public hwstub::device
64{
65 friend class context; /* for ctor */
66protected:
67 device(std::shared_ptr<hwstub::context> ctx, libusb_device *dev);
68public:
69 virtual ~device();
70 /** Return native libusb device */
71 libusb_device *native_device();
72 /** Get bus number */
73 uint8_t get_bus_number();
74 /** Get device address */
75 uint8_t get_address();
76 /** Get device VID */
77 uint16_t get_vid();
78 /** Get device PID */
79 uint16_t get_pid();
80
81protected:
82 /** Return true if this might be a hwstub device and should appear in the list */
83 static bool is_hwstub_dev(libusb_device *dev);
84
85 virtual error open_dev(std::shared_ptr<hwstub::handle>& handle);
86 virtual bool has_multiple_open() const;
87
88 libusb_device *m_dev; /* USB device */
89};
90
91/** USB handle
92 *
93 * Handle based on libusb_device_handle. */
94class handle : public hwstub::handle
95{
96protected:
97 handle(std::shared_ptr<hwstub::device> dev, libusb_device_handle *handle);
98public:
99 virtual ~handle();
100 /** set operation timeout */
101 void set_timeout(std::chrono::milliseconds ms);
102
103protected:
104 /* interpret libusb error: >=0 means SUCCESS, others are treated as errors,
105 * LIBUSB_ERROR_NO_DEVICE is treated as DISCONNECTED */
106 error interpret_libusb_error(int err);
107 /* interpret libusb error: <0 returns interpret_libusb_error(err), otherwise
108 * returns SUCCESS if err == expected_value */
109 error interpret_libusb_error(int err, size_t expected_value);
110 /* interpret libusb error: <0 returns interpret_libusb_error(err), otherwise
111 * returns SUCCESS and write size in out_size */
112 error interpret_libusb_size(int err, size_t& out_size);
113
114 libusb_device_handle *m_handle; /* USB handle */
115 unsigned int m_timeout; /* in milliseconds */
116};
117
118/** Rockbox USB handle
119 *
120 * HWSTUB/Rockbox protocol. */
121class rb_handle : public handle
122{
123 friend class device; /* for find_intf() */
124protected:
125 rb_handle(std::shared_ptr<hwstub::device> dev, libusb_device_handle *handle, int intf);
126public:
127 virtual ~rb_handle();
128
129protected:
130 virtual error read_dev(uint32_t addr, void *buf, size_t& sz, bool atomic);
131 virtual error write_dev(uint32_t addr, const void *buf, size_t& sz, bool atomic);
132 virtual error get_dev_desc(uint16_t desc, void *buf, size_t& buf_sz);
133 virtual error get_dev_log(void *buf, size_t& buf_sz);
134 virtual error exec_dev(uint32_t addr, uint16_t flags);
135 virtual error status() const;
136 virtual size_t get_buffer_size();
137 /* Probe a device to check if it is an hwstub device and return the interface
138 * number, or <0 on error. */
139 static bool find_intf(struct libusb_device_descriptor *dev,
140 struct libusb_config_descriptor *config, int& intf);
141
142 error m_probe_status; /* probing status */
143 int m_intf; /* interface number */
144 uint16_t m_transac_id; /* transaction ID */
145 size_t m_buf_size; /* Device buffer size */
146};
147
148/** JZ USB handle
149 *
150 * JZ boot protocol */
151class jz_handle : public handle
152{
153 friend class device; /* for is_boot_dev() */
154protected:
155 jz_handle(std::shared_ptr<hwstub::device> dev, libusb_device_handle *handle);
156public:
157 virtual ~jz_handle();
158
159protected:
160 virtual error read_dev(uint32_t addr, void *buf, size_t& sz, bool atomic);
161 virtual error write_dev(uint32_t addr, const void *buf, size_t& sz, bool atomic);
162 virtual error get_dev_desc(uint16_t desc, void *buf, size_t& buf_sz);
163 virtual error get_dev_log(void *buf, size_t& buf_sz);
164 virtual error exec_dev(uint32_t addr, uint16_t flags);
165 virtual error status() const;
166 virtual size_t get_buffer_size();
167 error probe();
168 error probe_jz4760b();
169 error read_reg32(uint32_t addr, uint32_t& value);
170 error write_reg32(uint32_t addr, uint32_t value);
171
172 error jz_cpuinfo(char cpuinfo[8]);
173 error jz_set_addr(uint32_t addr);
174 error jz_set_length(uint32_t size);
175 error jz_upload(void *data, size_t& length);
176 error jz_download(const void *data, size_t& length);
177 error jz_start1(uint32_t addr);
178 error jz_flush_caches();
179 error jz_start2(uint32_t addr);
180 /* Probe a device to check if it is a jz boot device */
181 static bool is_boot_dev(struct libusb_device_descriptor *dev,
182 struct libusb_config_descriptor *config);
183
184 error m_probe_status; /* probing status */
185 struct hwstub_version_desc_t m_desc_version;
186 struct hwstub_layout_desc_t m_desc_layout;
187 struct hwstub_target_desc_t m_desc_target;
188 struct hwstub_jz_desc_t m_desc_jz;
189};
190
191} // namespace usb
192} // namespace hwstub
193
194#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 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2016 by Amaury Pouly
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#ifndef __HWSTUB_VIRTUAL_HPP__
22#define __HWSTUB_VIRTUAL_HPP__
23
24#include "hwstub.hpp"
25#include <libusb.h>
26
27namespace hwstub {
28namespace virt {
29
30class hardware;
31
32/** Virtual context
33 *
34 * A virtual context hosts a number of virtual devices.
35 * This kind of contexts is mostly useful for testing/debugging purposes */
36class context : public hwstub::context
37{
38protected:
39 context();
40public:
41 virtual ~context();
42 /** Create a virtual context. */
43 static std::shared_ptr<context> create();
44 /** To ease creation, the context can be given a specification of the initial
45 * device list using the following format:
46 * dev1;dev2;...
47 * At the moment the only format support for devi is:
48 * dummy(Device name) */
49 static std::shared_ptr<context> create_spec(const std::string& spec, std::string *error = nullptr);
50
51 /** Connect a device to the context. Return false if device is already connected,
52 * and true otherwise. This method is thread-safe. */
53 bool connect(std::shared_ptr<hardware> hw);
54 /** Disconnect a device from the context. Return false if device is not connected,
55 * and true otheriwse. This method is thread-safe. */
56 bool disconnect(std::shared_ptr<hardware> hw);
57
58protected:
59 /* NOTE ctx_dev_t = hardware* */
60 std::shared_ptr<hardware> from_ctx_dev(ctx_dev_t dev);
61 ctx_dev_t to_ctx_dev(std::shared_ptr<hardware>& dev);
62 virtual error fetch_device_list(std::vector<ctx_dev_t>& list, void*& ptr);
63 virtual void destroy_device_list(void *ptr);
64 virtual error create_device(ctx_dev_t dev, std::shared_ptr<device>& hwdev);
65 virtual bool match_device(ctx_dev_t dev, std::shared_ptr<device> hwdev);
66
67 std::vector<std::shared_ptr<hardware>> m_hwlist; /* List of connected hardware */
68};
69
70/** Virtual hardware device (server/provider side)
71 *
72 * This base class represents a virtual piece of hardware that is being accessed
73 * by the context. Users of virtual contexts must inherit from this class and
74 * implement the requests. All requests are guaranteed to be serialize at the device
75 * level so there is no need to care about concurrency issues */
76class hardware : public std::enable_shared_from_this<hardware>
77{
78public:
79 hardware();
80 virtual ~hardware();
81
82 virtual error read_dev(uint32_t addr, void *buf, size_t& sz, bool atomic) = 0;
83 virtual error write_dev(uint32_t addr, const void *buf, size_t& sz, bool atomic) = 0;
84 virtual error get_dev_desc(uint16_t desc, void *buf, size_t& buf_sz) = 0;
85 virtual error get_dev_log(void *buf, size_t& buf_sz) = 0;
86 virtual error exec_dev(uint32_t addr, uint16_t flags) = 0;
87 virtual size_t get_buffer_size() = 0;
88};
89
90/** Dummy implementation of an hardware.
91 *
92 * This dummy hardware will fail all operations except getting descriptors.
93 * The device description can be customised */
94class dummy_hardware : public hardware
95{
96public:
97 dummy_hardware(const std::string& name);
98 virtual ~dummy_hardware();
99
100 virtual error read_dev(uint32_t addr, void *buf, size_t& sz, bool atomic);
101 virtual error write_dev(uint32_t addr, const void *buf, size_t& sz, bool atomic);
102 virtual error get_dev_desc(uint16_t desc, void *buf, size_t& buf_sz);
103 virtual error get_dev_log(void *buf, size_t& buf_sz);
104 virtual error exec_dev(uint32_t addr, uint16_t flags);
105 virtual size_t get_buffer_size();
106
107protected:
108 struct hwstub_version_desc_t m_desc_version;
109 struct hwstub_layout_desc_t m_desc_layout;
110 struct hwstub_target_desc_t m_desc_target;
111};
112
113/** Virtual device (client/user side)
114 *
115 * Device based on virtual device. */
116class device : public hwstub::device
117{
118 friend class context; /* for ctor */
119protected:
120 device(std::shared_ptr<hwstub::context> ctx, std::shared_ptr<hardware> dev);
121public:
122 virtual ~device();
123 /** Get native device (possibly null) */
124 std::shared_ptr<hardware> native_device();
125
126protected:
127 virtual error open_dev(std::shared_ptr<handle>& handle);
128 virtual bool has_multiple_open() const;
129
130 std::weak_ptr<hardware> m_hwdev; /* pointer to hardware */
131};
132
133/** Virtual handle
134 *
135 * Handle based on virtual device. */
136class handle : public hwstub::handle
137{
138 friend class device; /* for ctor */
139protected:
140 handle(std::shared_ptr<hwstub::device> dev);
141public:
142 virtual ~handle();
143
144protected:
145 virtual error read_dev(uint32_t addr, void *buf, size_t& sz, bool atomic);
146 virtual error write_dev(uint32_t addr, const void *buf, size_t& sz, bool atomic);
147 virtual error get_dev_desc(uint16_t desc, void *buf, size_t& buf_sz);
148 virtual error get_dev_log(void *buf, size_t& buf_sz);
149 virtual error exec_dev(uint32_t addr, uint16_t flags);
150 virtual error status() const;
151 virtual size_t get_buffer_size();
152
153 std::weak_ptr<hardware> m_hwdev; /* pointer to hardware */
154};
155
156} // namespace virt
157} // namespace hwstub
158
159#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 @@
1CC=gcc
2AR=ar 1AR=ar
3CFLAGS=-W -Wall -O2 `pkg-config --cflags libusb-1.0` -std=c99 -g -fPIC 2INCLUDE=../include
4LDFLAGS=`pkg-config --libs libusb-1.0` -fPIC 3CFLAGS=-W -Wall -O2 `pkg-config --cflags libusb-1.0` -std=c99 -g -fPIC -D_XOPEN_SOURCE=700 -I$(INCLUDE)
4CXXFLAGS=-W -Wall -O2 `pkg-config --cflags libusb-1.0` -std=c++11 -g -fPIC -D_XOPEN_SOURCE=700 -I$(INCLUDE)
5LDFLAGS=`pkg-config --libs libusb-1.0` -fPIC -lpthread
5LIB=libhwstub.a 6LIB=libhwstub.a
6SRC=$(wildcard *.c) 7SRC=$(wildcard *.c)
7OBJ=$(SRC:.c=.o) 8SRCXX=$(wildcard *.cpp)
9OBJ=$(SRCXX:.cpp=.oxx) $(SRCXX:.cpp=.o)
8 10
9all: $(LIB) 11all: $(LIB)
10 12
11%.o: %.c 13%.o: %.c
12 $(CC) $(CFLAGS) -c -o $@ $< 14 $(CC) $(CFLAGS) -c -o $@ $<
13 15
16%.oxx: %.cpp
17 $(CXX) $(CXXFLAGS) -c -o $@ $<
18
14$(LIB): $(OBJ) 19$(LIB): $(OBJ)
15 $(AR) rcs $@ $^ 20 $(AR) rcs $@ $^
16 21
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 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2015 by Amaury Pouly
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#include "hwstub.hpp"
22#include <algorithm>
23#include <cstring>
24
25namespace hwstub {
26
27std::ostream cnull(0);
28std::wostream wcnull(0);
29
30std::string error_string(error err)
31{
32 switch(err)
33 {
34 case error::SUCCESS: return "success";
35 case error::ERROR: return "unspecified error";
36 case error::DISCONNECTED: return "device was disconnected";
37 case error::PROBE_FAILURE: return "probing failed";
38 case error::NO_CONTEXT: return "context was destroyed";
39 case error::USB_ERROR: return "unspecified USB error";
40 case error::DUMMY: return "operation on dummy device";
41 case error::NO_SERVER: return "server could not be reached";
42 case error::SERVER_DISCONNECTED: return "server disconnected";
43 case error::SERVER_MISMATCH: return "incompatible server";
44 case error::NET_ERROR: return "network error";
45 case error::PROTOCOL_ERROR: return "network protocol error";
46 case error::TIMEOUT: return "timeout";
47 case error::OVERFLW: return "overflow";
48 default: return "unknown error";
49 }
50}
51
52/**
53 * Context
54 */
55
56context::context()
57 :m_next_cb_ref(0)
58{
59 clear_debug();
60}
61
62context::~context()
63{
64}
65
66void context::set_debug(std::ostream& os)
67{
68 m_debug = &os;
69}
70
71std::ostream& context::debug()
72{
73 return *m_debug;
74}
75
76error context::get_device_list(std::vector<std::shared_ptr<device>>& list)
77{
78 std::unique_lock<std::recursive_mutex> lock(m_mutex);
79 error err = update_list();
80 if(err != error::SUCCESS)
81 return err;
82 list.resize(m_devlist.size());
83 for(size_t i = 0; i < m_devlist.size(); i++)
84 list[i] = m_devlist[i];
85 return error::SUCCESS;
86}
87
88error context::get_dummy_device(std::shared_ptr<device>& dev)
89{
90 // NOTE: can't use make_shared() because of the protected ctor */
91 dev.reset(new dummy_device(shared_from_this()));
92 return error::SUCCESS;
93}
94
95void context::notify_device(bool arrived, std::shared_ptr<device> dev)
96{
97 for(auto cb : m_callbacks)
98 cb.callback(shared_from_this(), arrived, dev);
99}
100
101void context::change_device(bool arrived, std::shared_ptr<device> dev)
102{
103 std::unique_lock<std::recursive_mutex> lock(m_mutex);
104 if(arrived)
105 {
106 /* add to the list (assumed it's not already there) */
107 m_devlist.push_back(dev);
108 /* notify */
109 notify_device(arrived, dev);
110 }
111 else
112 {
113 dev->disconnect();
114 /* notify first */
115 notify_device(arrived, dev);
116 auto it = std::find(m_devlist.begin(), m_devlist.end(), dev);
117 if(it != m_devlist.end())
118 m_devlist.erase(it);
119 }
120}
121
122error context::update_list()
123{
124 std::unique_lock<std::recursive_mutex> lock(m_mutex);
125 /* fetch new list */
126 std::vector<ctx_dev_t> new_list;
127 void* ptr;
128 error err = fetch_device_list(new_list, ptr);
129 if(err != error::SUCCESS)
130 return err;
131 /* determine devices that have left */
132 std::vector<std::shared_ptr<device>> to_del;
133 for(auto dev : m_devlist)
134 {
135 bool still_there = false;
136 for(auto new_dev : new_list)
137 if(match_device(new_dev, dev))
138 still_there = true;
139 if(!still_there)
140 to_del.push_back(dev);
141 }
142 for(auto dev : to_del)
143 change_device(false, dev);
144 /* determine new devices */
145 std::vector<ctx_dev_t> to_add;
146 for(auto new_dev : new_list)
147 {
148 bool exists = false;
149 for(auto dev : m_devlist)
150 if(match_device(new_dev, dev))
151 exists = true;
152 if(!exists)
153 to_add.push_back(new_dev);
154 }
155 /* create new devices */
156 for(auto dev : to_add)
157 {
158 std::shared_ptr<device> new_dev;
159 err = create_device(dev, new_dev);
160 if(err == error::SUCCESS)
161 change_device(true, new_dev);
162 }
163 /* destroy list */
164 destroy_device_list(ptr);
165 return error::SUCCESS;
166}
167
168context::callback_ref_t context::register_callback(const notification_callback_t& fn)
169{
170 std::unique_lock<std::recursive_mutex> lock(m_mutex);
171 struct callback_t cb;
172 cb.callback = fn;
173 cb.ref = m_next_cb_ref++;
174 m_callbacks.push_back(cb);
175 return cb.ref;
176}
177
178void context::unregister_callback(callback_ref_t ref)
179{
180 std::unique_lock<std::recursive_mutex> lock(m_mutex);
181 for(auto it = m_callbacks.begin(); it != m_callbacks.end(); ++it)
182 {
183 if((*it).ref == ref)
184 {
185 m_callbacks.erase(it);
186 return;
187 }
188 }
189}
190
191void context::start_polling(std::chrono::milliseconds interval)
192{
193 std::unique_lock<std::recursive_mutex> lock(m_mutex);
194 /* create poller on demand */
195 if(!m_poller)
196 m_poller.reset(new context_poller(shared_from_this(), interval));
197 else
198 m_poller->set_interval(interval);
199 m_poller->start();
200}
201
202void context::stop_polling()
203{
204 std::unique_lock<std::recursive_mutex> lock(m_mutex);
205 m_poller->stop();
206}
207
208/**
209 * Context Poller
210 */
211
212context_poller::context_poller(std::weak_ptr<context> ctx, std::chrono::milliseconds interval)
213 :m_ctx(ctx), m_running(false), m_exit(false), m_interval(interval)
214{
215 m_thread = std::thread(context_poller::thread, this);
216}
217
218context_poller::~context_poller()
219{
220 /* set exit flag, wakeup thread, wait for exit */
221 m_mutex.lock();
222 m_exit = true;
223 m_mutex.unlock();
224 m_cond.notify_one();
225 m_thread.join();
226}
227
228void context_poller::set_interval(std::chrono::milliseconds interval)
229{
230 std::unique_lock<std::mutex> lock(m_mutex);
231 /* change interval, wakeup thread to take new interval into account */
232 m_interval = interval;
233 m_cond.notify_one();
234}
235
236void context_poller::start()
237{
238 std::unique_lock<std::mutex> lock(m_mutex);
239 /* change running flag, wakeup thread to start polling */
240 m_running = true;
241 m_cond.notify_one();
242}
243
244void context_poller::stop()
245{
246 std::unique_lock<std::mutex> lock(m_mutex);
247 /* change running flag, wakeup thread to stop polling */
248 m_running = false;
249 m_cond.notify_one();
250}
251
252void context_poller::poll()
253{
254 std::unique_lock<std::mutex> lock(m_mutex);
255 while(true)
256 {
257 /* if asked, exit */
258 if(m_exit)
259 break;
260 /* if running, poll and then sleep for some time */
261 if(m_running)
262 {
263 std::shared_ptr<context> ctx = m_ctx.lock();
264 if(ctx)
265 ctx->update_list();
266 ctx.reset();
267 m_cond.wait_for(lock, m_interval);
268 }
269 /* if not, sleep until awaken */
270 else
271 m_cond.wait(lock);
272 }
273}
274
275void context_poller::thread(context_poller *poller)
276{
277 poller->poll();
278}
279
280/**
281 * Device
282 */
283device::device(std::shared_ptr<context> ctx)
284 :m_ctx(ctx), m_connected(true)
285{
286}
287
288device::~device()
289{
290}
291
292error device::open(std::shared_ptr<handle>& handle)
293{
294 std::unique_lock<std::recursive_mutex> lock(m_mutex);
295 /* get a pointer so that it's not destroyed during the runtime of the function,
296 * the pointer will be released at the end of the function */
297 std::shared_ptr<context> ctx = get_context();
298 if(!ctx)
299 return error::NO_CONTEXT;
300 /* do not even try if device is disconnected */
301 if(!connected())
302 return error::DISCONNECTED;
303 /* NOTE at the moment handle is state-less which means that we can
304 * safely give the same handle each time open() is called without callers
305 * interfering with each other. If handle eventually get a state,
306 * one will need to create a proxy class to encapsulate the state */
307 handle = m_handle.lock();
308 if(has_multiple_open() || !handle)
309 {
310 error err = open_dev(handle);
311 m_handle = handle;
312 return err;
313 }
314 else
315 return error::SUCCESS;
316}
317
318void device::disconnect()
319{
320 std::unique_lock<std::recursive_mutex> lock(m_mutex);
321 m_connected = false;
322}
323
324bool device::connected()
325{
326 return m_connected;
327}
328
329std::shared_ptr<context> device::get_context()
330{
331 return m_ctx.lock();
332}
333
334/**
335 * Handle
336 */
337
338handle::handle(std::shared_ptr<device > dev)
339 :m_dev(dev)
340{
341}
342
343handle::~handle()
344{
345}
346
347std::shared_ptr<device> handle::get_device()
348{
349 return m_dev;
350}
351
352error handle::get_desc(uint16_t desc, void *buf, size_t& buf_sz)
353{
354 std::unique_lock<std::recursive_mutex> lock(m_mutex);
355 /* get a pointer so that it's not destroyed during the runtime of the function,
356 * the pointer will be released at the end of the function */
357 std::shared_ptr<context> ctx = m_dev->get_context();
358 if(!ctx)
359 return error::NO_CONTEXT;
360 /* ensure valid status */
361 error err = status();
362 if(err != error::SUCCESS)
363 return err;
364 return get_dev_desc(desc, buf, buf_sz);
365}
366
367error handle::get_log(void *buf, size_t& buf_sz)
368{
369 std::unique_lock<std::recursive_mutex> lock(m_mutex);
370 /* get a pointer so that it's not destroyed during the runtime of the function,
371 * the pointer will be released at the end of the function */
372 std::shared_ptr<context> ctx = m_dev->get_context();
373 if(!ctx)
374 return error::NO_CONTEXT;
375 /* ensure valid status */
376 error err = status();
377 if(err != error::SUCCESS)
378 return err;
379 return get_dev_log(buf, buf_sz);
380}
381
382error handle::exec(uint32_t addr, uint16_t flags)
383{
384 std::unique_lock<std::recursive_mutex> lock(m_mutex);
385 /* get a pointer so that it's not destroyed during the runtime of the function,
386 * the pointer will be released at the end of the function */
387 std::shared_ptr<context> ctx = m_dev->get_context();
388 if(!ctx)
389 return error::NO_CONTEXT;
390 /* ensure valid status */
391 error err = status();
392 if(err != error::SUCCESS)
393 return err;
394 return exec_dev(addr, flags);
395}
396
397error handle::read(uint32_t addr, void *buf, size_t& sz, bool atomic)
398{
399 std::unique_lock<std::recursive_mutex> lock(m_mutex);
400 /* get a pointer so that it's not destroyed during the runtime of the function,
401 * the pointer will be released at the end of the function */
402 std::shared_ptr<context> ctx = m_dev->get_context();
403 if(!ctx)
404 return error::NO_CONTEXT;
405 /* ensure valid status */
406 error err = status();
407 if(err != error::SUCCESS)
408 return err;
409 /* split transfer as needed */
410 size_t cnt = 0;
411 uint8_t *bufp = (uint8_t *)buf;
412 while(sz > 0)
413 {
414 size_t xfer = std::min(sz, get_buffer_size());
415 err = read_dev(addr, buf, xfer, atomic);
416 if(err != error::SUCCESS)
417 return err;
418 sz -= xfer;
419 bufp += xfer;
420 addr += xfer;
421 cnt += xfer;
422 }
423 sz = cnt;
424 return error::SUCCESS;
425}
426
427error handle::write(uint32_t addr, const void *buf, size_t& sz, bool atomic)
428{
429 std::unique_lock<std::recursive_mutex> lock(m_mutex);
430 /* get a pointer so that it's not destroyed during the runtime of the function,
431 * the pointer will be released at the end of the function */
432 std::shared_ptr<context> ctx = m_dev->get_context();
433 if(!ctx)
434 return error::NO_CONTEXT;
435 /* ensure valid status */
436 error err = status();
437 if(err != error::SUCCESS)
438 return err;
439 /* split transfer as needed */
440 size_t cnt = 0;
441 const uint8_t *bufp = (uint8_t *)buf;
442 while(sz > 0)
443 {
444 size_t xfer = std::min(sz, get_buffer_size());
445 err = write_dev(addr, buf, xfer, atomic);
446 if(err != error::SUCCESS)
447 return err;
448 sz -= xfer;
449 bufp += xfer;
450 addr += xfer;
451 cnt += xfer;
452 }
453 sz = cnt;
454 return error::SUCCESS;
455}
456
457error handle::status() const
458{
459 /* check context */
460 if(!m_dev->get_context())
461 return error::NO_CONTEXT;
462 if(!m_dev->connected())
463 return error::DISCONNECTED;
464 else
465 return error::SUCCESS;
466}
467
468namespace
469{
470 template<typename T>
471 error helper_get_desc(handle *h, uint8_t type, T& desc)
472 {
473 size_t sz = sizeof(desc);
474 error ret = h->get_desc(type, &desc, sz);
475 if(ret != error::SUCCESS)
476 return ret;
477 if(sz != sizeof(desc) || desc.bDescriptorType != type ||
478 desc.bLength != sizeof(desc))
479 return error::ERROR;
480 else
481 return error::SUCCESS;
482 }
483}
484
485error handle::get_version_desc(hwstub_version_desc_t& desc)
486{
487 return helper_get_desc(this, HWSTUB_DT_VERSION, desc);
488}
489
490error handle::get_layout_desc(hwstub_layout_desc_t& desc)
491{
492 return helper_get_desc(this, HWSTUB_DT_LAYOUT, desc);
493}
494
495error handle::get_stmp_desc(hwstub_stmp_desc_t& desc)
496{
497 return helper_get_desc(this, HWSTUB_DT_STMP, desc);
498}
499
500error handle::get_pp_desc(hwstub_pp_desc_t& desc)
501{
502 return helper_get_desc(this, HWSTUB_DT_PP, desc);
503}
504
505error handle::get_jz_desc(hwstub_jz_desc_t& desc)
506{
507 return helper_get_desc(this, HWSTUB_DT_JZ, desc);
508}
509
510error handle::get_target_desc(hwstub_target_desc_t& desc)
511{
512 return helper_get_desc(this, HWSTUB_DT_TARGET, desc);
513}
514
515/** Dummy device */
516dummy_device::dummy_device(std::shared_ptr<context> ctx)
517 :device(ctx)
518{
519}
520
521dummy_device::~dummy_device()
522{
523}
524
525error dummy_device::open_dev(std::shared_ptr<handle>& handle)
526{
527 handle.reset(new dummy_handle(shared_from_this()));
528 return error::SUCCESS;
529}
530
531bool dummy_device::has_multiple_open() const
532{
533 return true;
534}
535
536/** Dummy handle */
537dummy_handle::dummy_handle(std::shared_ptr<device> dev)
538 :handle(dev)
539{
540 m_desc_version.bLength = sizeof(m_desc_version);
541 m_desc_version.bDescriptorType = HWSTUB_DT_VERSION;
542 m_desc_version.bMajor = HWSTUB_VERSION_MAJOR;
543 m_desc_version.bMinor = HWSTUB_VERSION_MINOR;
544 m_desc_version.bRevision = 0;
545
546 m_desc_layout.bLength = sizeof(m_desc_layout);
547 m_desc_layout.bDescriptorType = HWSTUB_DT_LAYOUT;
548 m_desc_layout.dCodeStart = 0;
549 m_desc_layout.dCodeSize = 0;
550 m_desc_layout.dStackStart = 0;
551 m_desc_layout.dStackSize = 0;
552 m_desc_layout.dBufferStart = 0;
553 m_desc_layout.dBufferSize = 1;
554
555 m_desc_target.bLength = sizeof(m_desc_target);
556 m_desc_target.bDescriptorType = HWSTUB_DT_TARGET;
557 m_desc_target.dID = HWSTUB_TARGET_UNK;
558 strcpy(m_desc_target.bName, "Dummy target");
559}
560
561dummy_handle::~dummy_handle()
562{
563}
564
565error dummy_handle::read_dev(uint32_t addr, void *buf, size_t& sz, bool atomic)
566{
567 (void) addr;
568 (void) buf;
569 (void) sz;
570 (void) atomic;
571 return error::DUMMY;
572}
573
574error dummy_handle::write_dev(uint32_t addr, const void *buf, size_t& sz, bool atomic)
575{
576 (void) addr;
577 (void) buf;
578 (void) sz;
579 (void) atomic;
580 return error::DUMMY;
581}
582
583error dummy_handle::get_dev_desc(uint16_t desc, void *buf, size_t& buf_sz)
584{
585 void *p = nullptr;
586 switch(desc)
587 {
588 case HWSTUB_DT_VERSION: p = &m_desc_version; break;
589 case HWSTUB_DT_LAYOUT: p = &m_desc_layout; break;
590 case HWSTUB_DT_TARGET: p = &m_desc_target; break;
591 default: break;
592 }
593 if(p == nullptr)
594 return error::ERROR;
595 /* size is in the bLength field of the descriptor */
596 size_t desc_sz = *(uint8_t *)p;
597 buf_sz = std::min(buf_sz, desc_sz);
598 memcpy(buf, p, buf_sz);
599 return error::SUCCESS;
600}
601
602error dummy_handle::get_dev_log(void *buf, size_t& buf_sz)
603{
604 (void) buf;
605 (void) buf_sz;
606 return error::DUMMY;
607}
608
609error dummy_handle::exec_dev(uint32_t addr, uint16_t flags)
610{
611 (void) addr;
612 (void) flags;
613 return error::DUMMY;
614}
615
616error dummy_handle::status() const
617{
618 error err = handle::status();
619 return err == error::SUCCESS ? error::DUMMY : err;
620}
621
622size_t dummy_handle::get_buffer_size()
623{
624 return 1;
625}
626
627} // 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 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2016 by Amaury Pouly
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#include "hwstub_net.hpp"
22#include <sys/socket.h>
23#include <sys/un.h>
24#include <unistd.h>
25#include <cstddef>
26#include <arpa/inet.h>
27#include <sys/types.h>
28#include <netdb.h>
29
30namespace hwstub {
31namespace net {
32
33/**
34 * Context
35 */
36context::context()
37 :m_state(state::HELLO), m_error(error::SUCCESS)
38{
39}
40
41context::~context()
42{
43}
44
45std::shared_ptr<context> context::create_socket(int socket_fd)
46{
47 // NOTE: can't use make_shared() because of the protected ctor */
48 return std::shared_ptr<socket_context>(new socket_context(socket_fd));
49}
50
51std::string context::default_unix_path()
52{
53 return "hwstub";
54}
55
56std::string context::default_tcp_domain()
57{
58 return "localhost";
59}
60
61std::string context::default_tcp_port()
62{
63 return "6666";
64}
65
66namespace
67{
68 /* len is the total length, including a 0 character if any */
69 int create_unix_low(bool abstract, const char *path, size_t len, bool conn,
70 std::string *error)
71 {
72 struct sockaddr_un address;
73 if(len > sizeof(address.sun_path))
74 {
75 if(error)
76 *error = "unix path is too long";
77 return -1;
78 }
79 int socket_fd = socket(PF_UNIX, SOCK_STREAM, 0);
80 if(socket_fd < 0)
81 {
82 if(error)
83 *error = "socket() failed";
84 return -1;
85 }
86 memset(&address, 0, sizeof(struct sockaddr_un));
87 address.sun_family = AF_UNIX;
88 /* NOTE memcpy, we don't want to add a extra 0 at the end */
89 memcpy(address.sun_path, path, len);
90 /* for abstract name, replace first character by 0 */
91 if(abstract)
92 address.sun_path[0] = 0;
93 /* NOTE sun_path is the last field of the structure */
94 size_t sz = offsetof(struct sockaddr_un, sun_path) + len;
95 /* NOTE don't give sizeof(address) because for abstract names it would contain
96 * extra garbage */
97 if(conn)
98 {
99 if(connect(socket_fd, (struct sockaddr *)&address, sz) != 0)
100 {
101 close(socket_fd);
102 if(error)
103 *error = "connect() failed";
104 return -1;
105 }
106 else
107 return socket_fd;
108 }
109 else
110 {
111 if(bind(socket_fd, (struct sockaddr *)&address, sz) != 0)
112 {
113 close(socket_fd);
114 if(error)
115 *error = "bind() failed";
116 return -1;
117 }
118 else
119 return socket_fd;
120 }
121 }
122
123 int create_tcp_low(const std::string& domain, const std::string& _port, bool server, std::string *error)
124 {
125 std::string port = _port.size() != 0 ? _port : context::default_tcp_port();
126 int socket_fd = -1;
127 struct addrinfo hints;
128 memset(&hints, 0, sizeof(struct addrinfo));
129 hints.ai_family = AF_UNSPEC; /* allow IPv4 or IPv6 */
130 hints.ai_socktype = SOCK_STREAM;
131 hints.ai_flags = 0;
132 hints.ai_protocol = 0; /* any protocol */
133
134 struct addrinfo *result;
135 int err = getaddrinfo(domain.c_str(), port.c_str(), &hints, &result);
136 if(err != 0)
137 {
138 if(error)
139 *error = std::string("getaddrinfo failed: ") + gai_strerror(err);
140 return -1;
141 }
142
143 /* getaddrinfo() returns a list of address structures.
144 * Try each address until we successfully connect(2).
145 * If socket(2) (or connect(2)/bind(2)) fails, we (close the socket
146 * and) try the next address. */
147 for(struct addrinfo *rp = result; rp != nullptr; rp = rp->ai_next)
148 {
149 socket_fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
150 if(socket_fd == -1)
151 continue;
152
153 int err = 0;
154 if(server)
155 err = bind(socket_fd, rp->ai_addr, rp->ai_addrlen);
156 else
157 err = connect(socket_fd, rp->ai_addr, rp->ai_addrlen);
158 if(err < 0)
159 {
160 close(socket_fd);
161 socket_fd = -1;
162 }
163 else
164 break; /* success */
165 }
166 /* no address was tried */
167 if(socket_fd < 0 && error)
168 *error = "getaddrinfo() returned no usable result (socket()/connect()/bind() failed)";
169 return socket_fd;
170 }
171}
172
173std::shared_ptr<context> context::create_tcp(const std::string& domain,
174 const std::string& port, std::string *error)
175{
176 int fd = create_tcp_low(domain, port, false, error);
177 if(fd >= 0)
178 return context::create_socket(fd);
179 else
180 return std::shared_ptr<context>();
181}
182
183std::shared_ptr<context> context::create_unix(const std::string& path, std::string *error)
184{
185 int fd = create_unix_low(false, path.c_str(), path.size() + 1, true, error);
186 if(fd >= 0)
187 return context::create_socket(fd);
188 else
189 return std::shared_ptr<context>();
190}
191
192std::shared_ptr<context> context::create_unix_abstract(const std::string& path, std::string *error)
193{
194 std::string fake_path = "#" + path; /* the # will be overriden by 0 */
195 int fd = create_unix_low(true, fake_path.c_str(), fake_path.size(), true, error);
196 if(fd >= 0)
197 return context::create_socket(fd);
198 else
199 return std::shared_ptr<context>();
200}
201
202uint32_t context::from_ctx_dev(ctx_dev_t dev)
203{
204 return (uint32_t)(uintptr_t)dev; /* NOTE safe because it was originally a 32-bit int */
205}
206
207hwstub::context::ctx_dev_t context::to_ctx_dev(uint32_t dev)
208{
209 return (ctx_dev_t)(uintptr_t)dev; /* NOTE assume that sizeof(void *)>=sizeof(uint32_t) */
210}
211
212error context::fetch_device_list(std::vector<ctx_dev_t>& list, void*& ptr)
213{
214 (void) ptr;
215 delayed_init();
216 if(m_state == state::DEAD)
217 return m_error;
218 uint32_t args[HWSTUB_NET_ARGS] = {0};
219 uint8_t *data = nullptr;
220 size_t data_sz = 0;
221 debug() << "[net::ctx] --> GET_DEV_LIST\n";
222 error err = send_cmd(HWSERVER_GET_DEV_LIST, args, nullptr, 0, &data, &data_sz);
223 debug() << "[net::ctx] <-- GET_DEV_LIST ";
224 if(err != error::SUCCESS)
225 {
226 debug() << "failed: " << error_string(err) << "\n";
227 return err;
228 }
229 /* sanity check on size */
230 if(data_sz % 4)
231 {
232 debug() << "failed: invalid list size\n";
233 delete[] data;
234 return error::PROTOCOL_ERROR;
235 }
236 debug() << "\n";
237 list.clear();
238 /* each entry is a 32-bit ID in network order size */
239 uint32_t *data_list = (uint32_t *)data;
240 for(size_t i = 0; i < data_sz / 4; i++)
241 list.push_back(to_ctx_dev(from_net_order(data_list[i])));
242 delete[] data;
243 return error::SUCCESS;
244}
245
246void context::destroy_device_list(void *ptr)
247{
248 (void)ptr;
249}
250
251error context::create_device(ctx_dev_t dev, std::shared_ptr<hwstub::device>& hwdev)
252{
253 // NOTE: can't use make_shared() because of the protected ctor */
254 hwdev.reset(new device(shared_from_this(), from_ctx_dev(dev)));
255 return error::SUCCESS;
256}
257
258bool context::match_device(ctx_dev_t dev, std::shared_ptr<hwstub::device> hwdev)
259{
260 device *udev = dynamic_cast<device*>(hwdev.get());
261 return udev != nullptr && udev->device_id() == from_ctx_dev(dev);
262}
263
264uint32_t context::to_net_order(uint32_t u)
265{
266 return htonl(u);
267}
268
269uint32_t context::from_net_order(uint32_t u)
270{
271 return ntohl(u);
272}
273
274error context::send_cmd(uint32_t cmd, uint32_t args[HWSTUB_NET_ARGS], uint8_t *send_data,
275 size_t send_size, uint8_t **recv_data, size_t *recv_size)
276{
277 /* make sure with have the lock, this function might be called concurrently
278 * by the different threads */
279 std::unique_lock<std::recursive_mutex> lock(m_mutex);
280
281 if(m_state == state::DEAD)
282 return m_error;
283 /* do a delayed init, unless with are doing a HELLO */
284 if(m_state == state::HELLO && cmd != HWSERVER_HELLO)
285 delayed_init();
286 /* build header */
287 struct hwstub_net_hdr_t hdr;
288 hdr.magic = to_net_order(HWSERVER_MAGIC);
289 hdr.cmd = to_net_order(cmd);
290 for(size_t i = 0; i < HWSTUB_NET_ARGS; i++)
291 hdr.args[i] = to_net_order(args[i]);
292 hdr.length = to_net_order((uint32_t)send_size);
293 /* send header */
294 size_t sz = sizeof(hdr);
295 error err = send((void *)&hdr, sz);
296 if(err != error::SUCCESS)
297 {
298 m_state = state::DEAD;
299 m_error = err;
300 return err;
301 }
302 if(sz != sizeof(hdr))
303 {
304 m_state = state::DEAD;
305 m_error = error::PROTOCOL_ERROR;
306 }
307 /* send data */
308 if(send_size > 0)
309 {
310 sz = send_size;
311 err = send((void *)send_data, sz);
312 if(err != error::SUCCESS)
313 {
314 m_state = state::DEAD;
315 m_error = err;
316 return err;
317 }
318 if(sz != send_size)
319 {
320 m_state = state::DEAD;
321 m_error = error::PROTOCOL_ERROR;
322 }
323 }
324 /* receive header */
325 sz = sizeof(hdr);
326 err = recv((void *)&hdr, sz);
327 if(err != error::SUCCESS)
328 {
329 m_state = state::DEAD;
330 m_error = err;
331 return err;
332 }
333 if(sz != sizeof(hdr))
334 {
335 m_state = state::DEAD;
336 m_error = error::PROTOCOL_ERROR;
337 return m_error;
338 }
339 /* correct byte order */
340 hdr.magic = from_net_order(hdr.magic);
341 hdr.cmd = from_net_order(hdr.cmd);
342 hdr.length = from_net_order(hdr.length);
343 /* copy arguments */
344 for(size_t i = 0; i < HWSTUB_NET_ARGS; i++)
345 args[i] = from_net_order(hdr.args[i]);
346 /* check header */
347 if(hdr.magic != HWSERVER_MAGIC)
348 {
349 m_state = state::DEAD;
350 m_error = error::PROTOCOL_ERROR;
351 return m_error;
352 }
353 /* check NACK */
354 if(hdr.cmd == HWSERVER_NACK(cmd))
355 {
356 /* translate error */
357 switch(args[0])
358 {
359 case HWERR_FAIL: err = error::ERROR; break;
360 case HWERR_INVALID_ID: err = error::ERROR; break; /* should not happen */
361 case HWERR_DISCONNECTED: err = error::DISCONNECTED; break;
362 }
363 return err;
364 }
365 /* check not ACK */
366 if(hdr.cmd != HWSERVER_ACK(cmd))
367 {
368 m_state = state::DEAD;
369 m_error = error::PROTOCOL_ERROR;
370 return m_error;
371 }
372 /* receive additional data */
373 uint8_t *data = nullptr;
374 if(hdr.length > 0)
375 {
376 data = new uint8_t[hdr.length];
377 sz = hdr.length;
378 err = recv((void *)data, sz);
379 if(err != error::SUCCESS)
380 {
381 m_state = state::DEAD;
382 m_error = err;
383 return err;
384 }
385 if(sz != hdr.length)
386 {
387 m_state = state::DEAD;
388 m_error = error::PROTOCOL_ERROR;
389 return m_error;
390 }
391 }
392 /* copy data if user want it */
393 if(recv_data)
394 {
395 if(*recv_data == nullptr)
396 {
397 *recv_data = data;
398 *recv_size = hdr.length;
399 }
400 else if(*recv_size < hdr.length)
401 {
402 delete[] data;
403 return error::OVERFLW;
404 }
405 else
406 {
407 *recv_size = hdr.length;
408 memcpy(*recv_data, data, *recv_size);
409 delete[] data;
410 }
411 }
412 /* throw it away otherwise */
413 else
414 {
415 delete[] data;
416 }
417 return error::SUCCESS;
418}
419
420void context::delayed_init()
421{
422 /* only do HELLO if we haven't do it yet */
423 if(m_state != state::HELLO)
424 return;
425 debug() << "[net::ctx] --> HELLO " << HWSTUB_VERSION_MAJOR << "."
426 << HWSTUB_VERSION_MINOR << "\n";
427 /* send HELLO with our version and see what the server is up to */
428 uint32_t args[HWSTUB_NET_ARGS] = {0};
429 args[0] = HWSTUB_VERSION_MAJOR << 8 | HWSTUB_VERSION_MINOR;
430 error err = send_cmd(HWSERVER_HELLO, args, nullptr, 0, nullptr, nullptr);
431 if(err != error::SUCCESS)
432 {
433 debug() << "[net::ctx] <-- HELLO failed: " << error_string(err) << "\n";
434 m_state = state::DEAD;
435 m_error = err;
436 return;
437 }
438 /* check the server is running the same version */
439 debug() << "[net::ctx] <-- HELLO " << ((args[0] & 0xff00) >> 8) << "." << (args[0] & 0xff) << "";
440 if(args[0] != (HWSTUB_VERSION_MAJOR << 8 | HWSTUB_VERSION_MINOR))
441 {
442 debug() << " (mismatch)\n";
443 m_state = state::DEAD;
444 m_error = error::SERVER_MISMATCH;
445 }
446 debug() << " (good)\n";
447 /* good, we can now send commands */
448 m_state = state::IDLE;
449}
450
451void context::stop_context()
452{
453 /* make sure with have the lock, this function might be call asynchronously */
454 std::unique_lock<std::recursive_mutex> lock(m_mutex);
455 /* if dead, don't do anything */
456 if(m_state == state::DEAD)
457 return;
458 /* only send BYE if we are initialized */
459 if(m_state == state::IDLE)
460 {
461 debug() << "[net::ctx] --> BYE\n";
462 /* send BYE */
463 uint32_t args[HWSTUB_NET_ARGS] = {0};
464 error err = send_cmd(HWSERVER_BYE, args, nullptr, 0, nullptr, nullptr);
465 if(err != error::SUCCESS)
466 {
467 debug() << "[net::ctx] <-- BYE failed: " << error_string(err) << "\n";
468 m_state = state::DEAD;
469 m_error = err;
470 return;
471 }
472 debug() << "[net::ctx] <-- BYE\n";
473 }
474 /* now we are dead */
475 m_state = state::DEAD;
476 m_error = error::SERVER_DISCONNECTED;
477}
478
479/**
480 * Socket context
481 */
482socket_context::socket_context(int socket_fd)
483 :m_socketfd(socket_fd)
484{
485 set_timeout(std::chrono::milliseconds(1000));
486}
487
488socket_context::~socket_context()
489{
490 stop_context();
491 close(m_socketfd);
492}
493
494void socket_context::set_timeout(std::chrono::milliseconds ms)
495{
496 struct timeval tv;
497 tv.tv_usec = 1000 * (ms.count() % 1000);
498 tv.tv_sec = ms.count() / 1000;
499 /* set timeout for the client operations */
500 setsockopt(m_socketfd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv));
501 setsockopt(m_socketfd, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof(tv));
502}
503
504error socket_context::send(void *buffer, size_t& sz)
505{
506 debug() << "[net::ctx::sock] send(" << sz << "): ";
507 int ret = ::send(m_socketfd, buffer, sz, MSG_NOSIGNAL);
508 if(ret >= 0)
509 {
510 debug() << "good(" << ret << ")\n";
511 sz = (size_t)ret;
512 return error::SUCCESS;
513 }
514 /* convert some errors */
515 debug() << "fail(" << errno << "," << strerror(errno) << ")\n";
516 switch(errno)
517 {
518#if EAGAIN != EWOULDBLOCK
519 case EAGAIN:
520#endif
521 case EWOULDBLOCK: return error::TIMEOUT;
522 case ECONNRESET: case EPIPE: return error::SERVER_DISCONNECTED;
523 default: return error::NET_ERROR;
524 }
525}
526
527error socket_context::recv(void *buffer, size_t& sz)
528{
529 debug() << "[net::ctx::sock] recv(" << sz << "): ";
530 int ret = ::recv(m_socketfd, buffer, sz, MSG_WAITALL);
531 if(ret > 0)
532 {
533 debug() << "good(" << ret << ")\n";
534 sz = (size_t)ret;
535 return error::SUCCESS;
536 }
537 if(ret == 0)
538 {
539 debug() << "disconnected\n";
540 return error::SERVER_DISCONNECTED;
541 }
542 debug() << "fail(" << errno << "," << strerror(errno) << ")\n";
543 switch(errno)
544 {
545#if EAGAIN != EWOULDBLOCK
546 case EAGAIN:
547#endif
548 case EWOULDBLOCK: return error::TIMEOUT;
549 default: return error::NET_ERROR;
550 }
551}
552
553/**
554 * Device
555 */
556device::device(std::shared_ptr<hwstub::context> ctx, uint32_t devid)
557 :hwstub::device(ctx), m_device_id(devid)
558{
559}
560
561device::~device()
562{
563}
564
565uint32_t device::device_id()
566{
567 return m_device_id;
568}
569
570error device::open_dev(std::shared_ptr<hwstub::handle>& handle)
571{
572 std::shared_ptr<hwstub::context> hctx = get_context();
573 if(!hctx)
574 return error::NO_CONTEXT;
575 context *ctx = dynamic_cast<context*>(hctx.get());
576 ctx->debug() << "[net::dev] --> DEV_OPEN(" << m_device_id << ")\n";
577 /* ask the server to open the device, note that the device ID may not exists
578 * anymore */
579 uint32_t args[HWSTUB_NET_ARGS] = {0};
580 args[0] = m_device_id;
581 error err = ctx->send_cmd(HWSERVER_DEV_OPEN, args, nullptr, 0, nullptr, nullptr);
582 if(err != error::SUCCESS)
583 {
584 ctx->debug() << "[net::ctx::dev] <-- DEV_OPEN failed: " << error_string(err) << "\n";
585 return err;
586 }
587 ctx->debug() << "[net::ctx::dev] <-- DEV_OPEN: handle = " << args[0] << "\n";
588 // NOTE: can't use make_shared() because of the protected ctor */
589 handle.reset(new hwstub::net::handle(shared_from_this(), args[0]));
590 return error::SUCCESS;
591}
592
593bool device::has_multiple_open() const
594{
595 return false;
596}
597
598/**
599 * Handle
600 */
601handle::handle(std::shared_ptr<hwstub::device> dev, uint32_t hid)
602 :hwstub::handle(dev), m_handle_id(hid)
603{
604}
605
606handle::~handle()
607{
608 /* try to close the handle, if context is still accessible */
609 std::shared_ptr<hwstub::context> hctx = get_device()->get_context();
610 if(hctx)
611 {
612 context *ctx = dynamic_cast<context*>(hctx.get());
613 ctx->debug() << "[net::handle] --> DEV_CLOSE(" << m_handle_id << ")\n";
614 uint32_t args[HWSTUB_NET_ARGS] = {0};
615 args[0] = m_handle_id;
616 error err = ctx->send_cmd(HWSERVER_DEV_CLOSE, args, nullptr, 0, nullptr, nullptr);
617 if(err != error::SUCCESS)
618 ctx->debug() << "[net::handle] <-- DEV_CLOSE failed: " << error_string(err) << "\n";
619 else
620 ctx->debug() << "[net::handle] <-- DEV_CLOSE\n";
621 }
622}
623
624error handle::read_dev(uint32_t addr, void *buf, size_t& sz, bool atomic)
625{
626 std::shared_ptr<hwstub::context> hctx = get_device()->get_context();
627 if(!hctx)
628 return error::NO_CONTEXT;
629
630 context *ctx = dynamic_cast<context*>(hctx.get());
631 ctx->debug() << "[net::handle] --> READ(" << m_handle_id << ",0x" << std::hex
632 << addr << "," << sz << "," << atomic << ")\n";
633 uint32_t args[HWSTUB_NET_ARGS] = {0};
634 args[0] = m_handle_id;
635 args[1] = addr;
636 args[2] = sz;
637 args[3] = atomic ? HWSERVER_RW_ATOMIC : 0;
638 error err = ctx->send_cmd(HWSERVER_READ, args, nullptr, 0, (uint8_t **)&buf, &sz);
639 if(err != error::SUCCESS)
640 {
641 ctx->debug() << "[net::handle] <-- READ failed: " << error_string(err) << "\n";
642 return err;
643 }
644 ctx->debug() << "[net::handle] <-- READ\n";
645 return error::SUCCESS;
646}
647
648error handle::write_dev(uint32_t addr, const void *buf, size_t& sz, bool atomic)
649{
650 std::shared_ptr<hwstub::context> hctx = get_device()->get_context();
651 if(!hctx)
652 return error::NO_CONTEXT;
653
654 context *ctx = dynamic_cast<context*>(hctx.get());
655 ctx->debug() << "[net::handle] --> WRITE(" << m_handle_id << ",0x" << std::hex
656 << addr << "," << sz << "," << atomic << ")\n";
657 uint32_t args[HWSTUB_NET_ARGS] = {0};
658 args[0] = m_handle_id;
659 args[1] = addr;
660 args[2] = atomic ? HWSERVER_RW_ATOMIC : 0;
661 error err = ctx->send_cmd(HWSERVER_WRITE, args, (uint8_t *)buf, sz, nullptr, nullptr);
662 if(err != error::SUCCESS)
663 {
664 ctx->debug() << "[net::handle] <-- WRITE failed: " << error_string(err) << "\n";
665 return err;
666 }
667 ctx->debug() << "[net::handle] <-- WRITE\n";
668 return error::SUCCESS;
669}
670
671error handle::get_dev_desc(uint16_t desc, void *buf, size_t& buf_sz)
672{
673 std::shared_ptr<hwstub::context> hctx = get_device()->get_context();
674 if(!hctx)
675 return error::NO_CONTEXT;
676
677 context *ctx = dynamic_cast<context*>(hctx.get());
678 ctx->debug() << "[net::handle] --> GET_DESC(" << m_handle_id << ",0x" << std::hex
679 << desc << "," << buf_sz << ")\n";
680 uint32_t args[HWSTUB_NET_ARGS] = {0};
681 args[0] = m_handle_id;
682 args[1] = desc;
683 args[2] = buf_sz;
684 error err = ctx->send_cmd(HWSERVER_GET_DESC, args, nullptr, 0, (uint8_t **)&buf, &buf_sz);
685 if(err != error::SUCCESS)
686 {
687 ctx->debug() << "[net::handle] <-- GET_DESC failed: " << error_string(err) << "\n";
688 return err;
689 }
690 ctx->debug() << "[net::handle] <-- GET_DESC\n";
691 return error::SUCCESS;
692}
693
694error handle::get_dev_log(void *buf, size_t& buf_sz)
695{
696 std::shared_ptr<hwstub::context> hctx = get_device()->get_context();
697 if(!hctx)
698 return error::NO_CONTEXT;
699
700 context *ctx = dynamic_cast<context*>(hctx.get());
701 ctx->debug() << "[net::handle] --> GET_LOG(" << buf_sz << ")\n";
702 uint32_t args[HWSTUB_NET_ARGS] = {0};
703 args[0] = m_handle_id;
704 args[1] = buf_sz;
705 error err = ctx->send_cmd(HWSERVER_GET_LOG, args, nullptr, 0, (uint8_t **)&buf, &buf_sz);
706 if(err != error::SUCCESS)
707 {
708 ctx->debug() << "[net::handle] <-- GET_LOG failed: " << error_string(err) << "\n";
709 return err;
710 }
711 ctx->debug() << "[net::handle] <-- GET_LOG\n";
712 return error::SUCCESS;
713}
714
715error handle::exec_dev(uint32_t addr, uint16_t flags)
716{
717 (void) addr;
718 (void) flags;
719 return error::DUMMY;
720}
721
722error handle::status() const
723{
724 return hwstub::handle::status();
725}
726
727size_t handle::get_buffer_size()
728{
729 return 2048;
730}
731
732/**
733 * Server
734 */
735server::server(std::shared_ptr<hwstub::context> ctx)
736 :m_context(ctx)
737{
738 clear_debug();
739}
740
741server::~server()
742{
743}
744
745void server::stop_server()
746{
747 std::unique_lock<std::recursive_mutex> lock(m_mutex);
748 /* ask all client threads to stop */
749 for(auto& cl : m_client)
750 cl.exit = true;
751 /* wait for each thread to stop */
752 for(auto& cl : m_client)
753 cl.future.wait();
754}
755
756std::shared_ptr<server> server::create_unix(std::shared_ptr<hwstub::context> ctx,
757 const std::string& path, std::string *error)
758{
759 int fd = create_unix_low(false, path.c_str(), path.size() + 1, false, error);
760 if(fd >= 0)
761 return socket_server::create_socket(ctx, fd);
762 else
763 return std::shared_ptr<server>();
764}
765
766std::shared_ptr<server> server::create_unix_abstract(std::shared_ptr<hwstub::context> ctx,
767 const std::string& path, std::string *error)
768{
769 std::string fake_path = "#" + path; /* the # will be overriden by 0 */
770 int fd = create_unix_low(true, fake_path.c_str(), fake_path.size(), false, error);
771 if(fd >= 0)
772 return socket_server::create_socket(ctx, fd);
773 else
774 return std::shared_ptr<server>();
775}
776
777std::shared_ptr<server> server::create_socket(std::shared_ptr<hwstub::context> ctx,
778 int socket_fd)
779{
780 return socket_server::create(ctx, socket_fd);
781}
782
783std::shared_ptr<server> server::create_tcp(std::shared_ptr<hwstub::context> ctx,
784 const std::string& domain, const std::string& port, std::string *error)
785{
786 int fd = create_tcp_low(domain, port, true, error);
787 if(fd >= 0)
788 return socket_server::create_socket(ctx, fd);
789 else
790 return std::shared_ptr<server>();
791}
792
793void server::set_debug(std::ostream& os)
794{
795 m_debug = &os;
796}
797
798std::ostream& server::debug()
799{
800 return *m_debug;
801}
802
803server::client_state::client_state(srv_client_t cl, std::future<void>&& f)
804 :client(cl), future(std::move(f)), exit(false), next_dev_id(42),
805 next_handle_id(19)
806{
807}
808
809void server::client_thread2(server *s, client_state *cs)
810{
811 s->client_thread(cs);
812}
813
814uint32_t server::to_net_order(uint32_t u)
815{
816 return htonl(u);
817}
818
819uint32_t server::from_net_order(uint32_t u)
820{
821 return ntohl(u);
822}
823
824void server::client_thread(client_state *state)
825{
826 debug() << "[net::srv::client] start: " << state->client << "\n";
827 while(!state->exit)
828 {
829 /* wait for some header */
830 struct hwstub_net_hdr_t hdr;
831 size_t sz = sizeof(hdr);
832 error err;
833 /* wait for some command, or exit flag */
834 do
835 err = recv(state->client, (void *)&hdr, sz);
836 while(err == error::TIMEOUT && !state->exit);
837 if(state->exit || err != error::SUCCESS || sz != sizeof(hdr))
838 break;
839 /* convert to host order */
840 hdr.magic = from_net_order(hdr.magic);
841 hdr.cmd = from_net_order(hdr.cmd);
842 hdr.length = from_net_order(hdr.length);
843 /* copy arguments */
844 for(size_t i = 0; i < HWSTUB_NET_ARGS; i++)
845 hdr.args[i] = from_net_order(hdr.args[i]);
846 /* check header */
847 if(hdr.magic != HWSERVER_MAGIC)
848 break;
849 /* receive data
850 * FIXME check length here */
851 uint8_t *data = nullptr;
852 if(hdr.length > 0)
853 {
854 data = new uint8_t[hdr.length];
855 sz = hdr.length;
856 /* wait for some command, or exit flag */
857 do
858 err = recv(state->client, (void *)data, sz);
859 while(err == error::TIMEOUT && !state->exit);
860 if(state->exit || err != error::SUCCESS || sz != hdr.length)
861 {
862 delete[] data;
863 break;
864 }
865 }
866 /* hande command */
867 uint8_t *send_data = nullptr;
868 size_t send_size = 0;
869 err = handle_cmd(state, hdr.cmd, hdr.args, data, hdr.length,
870 send_data, send_size);
871 /* free data */
872 delete[] data;
873 /* construct header */
874 if(err != error::SUCCESS)
875 {
876 hdr.magic = to_net_order(HWSERVER_MAGIC);
877 hdr.cmd = to_net_order(HWSERVER_NACK(hdr.cmd));
878 hdr.length = to_net_order(0);
879 hdr.args[0] = to_net_order(HWERR_FAIL);
880 for(size_t i = 1; i < HWSTUB_NET_ARGS; i++)
881 hdr.args[i] = to_net_order(0);
882 send_size = 0;
883 }
884 else
885 {
886 hdr.magic = to_net_order(HWSERVER_MAGIC);
887 hdr.cmd = to_net_order(HWSERVER_ACK(hdr.cmd));
888 hdr.length = to_net_order(send_size);
889 for(size_t i = 0; i < HWSTUB_NET_ARGS; i++)
890 hdr.args[i] = to_net_order(hdr.args[i]);
891 }
892 /* send header */
893 sz = sizeof(hdr);
894 do
895 err = send(state->client, (void *)&hdr, sz);
896 while(err == error::TIMEOUT && !state->exit);
897 if(state->exit || err != error::SUCCESS || sz != sizeof(hdr))
898 {
899 delete[] send_data;
900 break;
901 }
902 /* send data if there is some */
903 if(send_size > 0)
904 {
905 sz = send_size;
906 do
907 err = send(state->client, (void *)send_data, sz);
908 while(err == error::TIMEOUT && !state->exit);
909 delete[] send_data;
910 if(state->exit || err != error::SUCCESS || sz != send_size)
911 break;
912 }
913 }
914 debug() << "[net::srv::client] stop: " << state->client << "\n";
915 /* clean client state to avoiding keeping references to objets */
916 state->dev_map.clear();
917 state->handle_map.clear();
918 /* kill client */
919 terminate_client(state->client);
920}
921
922void server::client_arrived(srv_client_t client)
923{
924 std::unique_lock<std::recursive_mutex> lock(m_mutex);
925 debug() << "[net::srv] client arrived: " << client << "\n";
926 /* the naive way would be to use a std::thread but this class is annoying
927 * because it is impossible to check if a thread has exited, except by calling
928 * join() which is blocking. Fortunately, std::packaged_task and std::future
929 * provide a way around this */
930
931 std::packaged_task<void(server*, client_state*)> task(&server::client_thread2);
932 m_client.emplace_back(client, task.get_future());
933 std::thread(std::move(task), this, &m_client.back()).detach();
934}
935
936void server::client_left(srv_client_t client)
937{
938 std::unique_lock<std::recursive_mutex> lock(m_mutex);
939 debug() << "[net::srv] client left: " << client << "\n";
940 /* find thread and set its exit flag, also cleanup threads that finished */
941 for(auto it = m_client.begin(); it != m_client.end();)
942 {
943 /* check if thread has finished */
944 if(it->future.wait_for(std::chrono::milliseconds(0)) == std::future_status::ready)
945 {
946 it = m_client.erase(it);
947 continue;
948 }
949 /* set exit flag if this our thread */
950 if(it->client == client)
951 it->exit = true;
952 ++it;
953 }
954}
955
956error server::handle_cmd(client_state *state, uint32_t cmd, uint32_t args[HWSTUB_NET_ARGS],
957 uint8_t *recv_data, size_t recv_size, uint8_t*& send_data, size_t& send_size)
958{
959 send_data = nullptr;
960 send_size = 0;
961 /* NOTE: commands are serialized by the client thread, this function is thus
962 * thread safe WITH RESPECT TO CLIENT DATA. If you need to use global data here,
963 * protect it by a mutex or make sure it is safe (hwstub context is thread-safe) */
964
965 /* HELLO */
966 if(cmd == HWSERVER_HELLO)
967 {
968 debug() << "[net::srv::cmd] --> HELLO " << ((args[0] & 0xff00) >> 8)
969 << "." << (args[0] & 0xff);
970 if(args[0] != (HWSTUB_VERSION_MAJOR << 8 | HWSTUB_VERSION_MINOR))
971 {
972 debug() << " (mismatch)\n";
973 return error::ERROR;
974 }
975 debug() << " (good)\n";
976 debug() << "[net::srv::cmd] <-- HELLO " << HWSTUB_VERSION_MAJOR << "."
977 << HWSTUB_VERSION_MINOR << "\n";
978 /* send HELLO with our version */
979 args[0] = HWSTUB_VERSION_MAJOR << 8 | HWSTUB_VERSION_MINOR;
980 return error::SUCCESS;
981 }
982 /* BYE */
983 else if(cmd == HWSERVER_BYE)
984 {
985 debug() << "[net::srv::cmd] --> BYE\n";
986 /* ask client thread to exit after this */
987 state->exit = true;
988 debug() << "[net::srv::cmd] <-- BYE\n";
989 return error::SUCCESS;
990 }
991 /* GET_DEV_LIST */
992 else if(cmd == HWSERVER_GET_DEV_LIST)
993 {
994 debug() << "[net::srv::cmd] --> GET_DEV_LIST\n";
995 /* fetch list again */
996 std::vector<std::shared_ptr<hwstub::device>> list;
997 error err = m_context->get_device_list(list);
998 if(err != error::SUCCESS)
999 {
1000 debug() << "[net::srv::cmd] cannot fetch list: " << hwstub::error_string(err) << "\n";
1001 debug() << "[net::srv::cmd] <-- GET_DEV_LIST (error)\n";
1002 return err;
1003 }
1004 /* update list: drop device that left */
1005 std::vector<uint32_t> to_drop;
1006 for(auto it : state->dev_map)
1007 {
1008 bool still_there = false;
1009 /* this has quadratic complexity, optimize this if needed */
1010 for(auto dev : list)
1011 if(it.second == dev)
1012 still_there = true;
1013 if(!still_there)
1014 to_drop.push_back(it.first);
1015 }
1016 for(auto id : to_drop)
1017 state->dev_map.erase(state->dev_map.find(id));
1018 /* add new devices */
1019 std::vector<std::shared_ptr<hwstub::device>> to_add;
1020 for(auto dev : list)
1021 {
1022 bool already_there = false;
1023 for(auto it : state->dev_map)
1024 if(it.second == dev)
1025 already_there = true;
1026 if(!already_there)
1027 to_add.push_back(dev);
1028 }
1029 for(auto dev : to_add)
1030 state->dev_map[state->next_dev_id++] = dev;
1031 /* create response list */
1032 send_size = sizeof(uint32_t) * state->dev_map.size();
1033 send_data = new uint8_t[send_size];
1034 uint32_t *p = (uint32_t *)send_data;
1035 for(auto it : state->dev_map)
1036 *p++ = to_net_order(it.first);
1037 debug() << "[net::srv::cmd] <-- GET_DEV_LIST\n";
1038 return error::SUCCESS;
1039 }
1040 /* DEV_OPEN */
1041 else if(cmd == HWSERVER_DEV_OPEN)
1042 {
1043 uint32_t devid = args[0];
1044 debug() << "[net::srv::cmd] --> DEV_OPEN(" << devid << ")\n";
1045 /* check ID is valid */
1046 auto it = state->dev_map.find(devid);
1047 if(it == state->dev_map.end())
1048 {
1049 debug() << "[net::srv::cmd] unknwon device ID\n";
1050 debug() << "[net::srv::cmd] <-- DEV_OPEN (error)\n";
1051 return error::ERROR;
1052 }
1053 /* good, now try to get a handle */
1054 std::shared_ptr<hwstub::handle> handle;
1055 error err = it->second->open(handle);
1056 if(err != error::SUCCESS)
1057 {
1058 debug() << "[net::srv::cmd] cannot open device: " << hwstub::error_string(err) << "\n";
1059 return err;
1060 }
1061 /* record ID and return it */
1062 args[0] = state->next_handle_id;
1063 state->handle_map[state->next_handle_id++] = handle;
1064 return error::SUCCESS;
1065 }
1066 /* DEV_CLOSE */
1067 else if(cmd == HWSERVER_DEV_CLOSE)
1068 {
1069 uint32_t hid = args[0];
1070 debug() << "[net::srv::cmd] --> DEV_CLOSE(" << hid << ")\n";
1071 /* check ID is valid */
1072 auto it = state->handle_map.find(hid);
1073 if(it == state->handle_map.end())
1074 {
1075 debug() << "[net::srv::cmd] unknwon handle ID\n";
1076 debug() << "[net::srv::cmd] <-- DEV_CLOSE (error)\n";
1077 return error::ERROR;
1078 }
1079 /* release ID and handle */
1080 state->handle_map.erase(it);
1081 debug() << "[net::srv::cmd] <-- DEV_CLOSE\n";
1082 return error::SUCCESS;
1083 }
1084 /* HWSERVER_GET_DESC */
1085 else if(cmd == HWSERVER_GET_DESC)
1086 {
1087 uint32_t hid = args[0];
1088 uint32_t did = args[1];
1089 uint32_t len = args[2];
1090 debug() << "[net::srv::cmd] --> GET_DESC(" << hid << ",0x" << std::hex << did
1091 << "," << len << ")\n";
1092 /* check ID is valid */
1093 auto it = state->handle_map.find(hid);
1094 if(it == state->handle_map.end())
1095 {
1096 debug() << "[net::srv::cmd] unknown handle ID\n";
1097 debug() << "[net::srv::cmd] <-- GET_DESC (error)\n";
1098 return error::ERROR;
1099 }
1100 /* query desc */
1101 send_size = len;
1102 send_data = new uint8_t[send_size];
1103 error err = it->second->get_desc(did, send_data, send_size);
1104 if(err != error::SUCCESS)
1105 {
1106 delete[] send_data;
1107 debug() << "[net::srv::cmd] cannot get descriptor: " << error_string(err) << "\n";
1108 debug() << "[net::srv::cmd] <-- GET_DESC (error)\n";
1109 return err;
1110 }
1111 debug() << "[net::srv::cmd] <-- GET_DESC\n";
1112 return error::SUCCESS;
1113 }
1114 /* HWSERVER_GET_LOG */
1115 else if(cmd == HWSERVER_GET_LOG)
1116 {
1117 uint32_t hid = args[0];
1118 uint32_t len = args[1];
1119 debug() << "[net::srv::cmd] --> GET_LOG(" << hid << "," << len << ")\n";
1120 /* check ID is valid */
1121 auto it = state->handle_map.find(hid);
1122 if(it == state->handle_map.end())
1123 {
1124 debug() << "[net::srv::cmd] unknown handle ID\n";
1125 debug() << "[net::srv::cmd] <-- GET_DESC (error)\n";
1126 return error::ERROR;
1127 }
1128 /* query log */
1129 send_size = len;
1130 send_data = new uint8_t[send_size];
1131 error err = it->second->get_log(send_data, send_size);
1132 if(err != error::SUCCESS)
1133 {
1134 delete[] send_data;
1135 debug() << "[net::srv::cmd] cannot get log: " << error_string(err) << "\n";
1136 debug() << "[net::srv::cmd] <-- GET_LOG (error)\n";
1137 return err;
1138 }
1139 if(send_size == 0)
1140 delete[] send_data;
1141 debug() << "[net::srv::cmd] <-- GET_LOG\n";
1142 return error::SUCCESS;
1143 }
1144 /* HWSERVER_READ */
1145 else if(cmd == HWSERVER_READ)
1146 {
1147 uint32_t hid = args[0];
1148 uint32_t addr = args[1];
1149 uint32_t len = args[2];
1150 uint32_t flags = args[3];
1151 debug() << "[net::srv::cmd] --> READ(" << hid << ",0x" << std::hex << addr << ","
1152 << len << ",0x" << std::hex << flags << ")\n";
1153 /* check ID is valid */
1154 auto it = state->handle_map.find(hid);
1155 if(it == state->handle_map.end())
1156 {
1157 debug() << "[net::srv::cmd] unknown handle ID\n";
1158 debug() << "[net::srv::cmd] <-- READ (error)\n";
1159 return error::ERROR;
1160 }
1161 /* read */
1162 send_size = len;
1163 send_data = new uint8_t[send_size];
1164 error err = it->second->read(addr, send_data, send_size, !!(flags & HWSERVER_RW_ATOMIC));
1165 if(err != error::SUCCESS)
1166 {
1167 delete[] send_data;
1168 debug() << "[net::srv::cmd] cannot read: " << error_string(err) << "\n";
1169 debug() << "[net::srv::cmd] <-- READ (error)\n";
1170 return err;
1171 }
1172 debug() << "[net::srv::cmd] <-- READ\n";
1173 return error::SUCCESS;
1174 }
1175 /* HWSERVER_WRITE */
1176 else if(cmd == HWSERVER_WRITE)
1177 {
1178 uint32_t hid = args[0];
1179 uint32_t addr = args[1];
1180 uint32_t flags = args[2];
1181 debug() << "[net::srv::cmd] --> WRITE(" << hid << ",0x" << std::hex << addr << ","
1182 << recv_size << ",0x" << std::hex << flags << ")\n";
1183 /* check ID is valid */
1184 auto it = state->handle_map.find(hid);
1185 if(it == state->handle_map.end())
1186 {
1187 debug() << "[net::srv::cmd] unknown handle ID\n";
1188 debug() << "[net::srv::cmd] <-- WRITE (error)\n";
1189 return error::ERROR;
1190 }
1191 /* write */
1192 error err = it->second->write(addr, recv_data, recv_size, !!(flags & HWSERVER_RW_ATOMIC));
1193 if(err != error::SUCCESS)
1194 {
1195 delete[] send_data;
1196 debug() << "[net::srv::cmd] cannot write: " << error_string(err) << "\n";
1197 debug() << "[net::srv::cmd] <-- WRITE (error)\n";
1198 return err;
1199 }
1200 debug() << "[net::srv::cmd] <-- WRITE\n";
1201 return error::SUCCESS;
1202 }
1203 else
1204 {
1205 debug() << "[net::srv::cmd] <-> unknown cmd (0x" << std::hex << cmd << ")\n";
1206 return error::ERROR;
1207 }
1208}
1209
1210/*
1211 * Socket server
1212 */
1213socket_server::socket_server(std::shared_ptr<hwstub::context> contex, int socket_fd)
1214 :server(contex), m_socketfd(socket_fd)
1215{
1216 m_discovery_exit = false;
1217 set_timeout(std::chrono::milliseconds(1000));
1218 m_discovery_thread = std::thread(&socket_server::discovery_thread1, this);
1219}
1220
1221socket_server::~socket_server()
1222{
1223 /* first stop discovery thread to make sure no more clients are created */
1224 m_discovery_exit = true;
1225 m_discovery_thread.join();
1226 close(m_socketfd);
1227 /* ask server to do a clean stop */
1228 stop_server();
1229}
1230
1231std::shared_ptr<server> socket_server::create(std::shared_ptr<hwstub::context> ctx,
1232 int socket_fd)
1233{
1234 // NOTE: can't use make_shared() because of the protected ctor */
1235 return std::shared_ptr<socket_server>(new socket_server(ctx, socket_fd));
1236}
1237
1238void socket_server::set_timeout(std::chrono::milliseconds ms)
1239{
1240 m_timeout.tv_usec = 1000 * (ms.count() % 1000);
1241 m_timeout.tv_sec = ms.count() / 1000;
1242}
1243
1244int socket_server::from_srv_client(srv_client_t cli)
1245{
1246 return (int)(intptr_t)cli;
1247}
1248
1249socket_server::srv_client_t socket_server::to_srv_client(int fd)
1250{
1251 return (srv_client_t)(intptr_t)fd;
1252}
1253
1254void socket_server::discovery_thread1(socket_server *s)
1255{
1256 s->discovery_thread();
1257}
1258
1259void socket_server::terminate_client(srv_client_t client)
1260{
1261 debug() << "[net::srv::sock] terminate client: " << client << "\n";
1262 /* simply close connection */
1263 close(from_srv_client(client));
1264}
1265
1266error socket_server::send(srv_client_t client, void *buffer, size_t& sz)
1267{
1268 debug() << "[net::ctx::sock] send(" << client << ", " << sz << "): ";
1269 int ret = ::send(from_srv_client(client), buffer, sz, MSG_NOSIGNAL);
1270 if(ret >= 0)
1271 {
1272 debug() << "good(" << ret << ")\n";
1273 sz = (size_t)ret;
1274 return error::SUCCESS;
1275 }
1276 /* convert some errors */
1277 debug() << "fail(" << errno << "," << strerror(errno) << ")\n";
1278 switch(errno)
1279 {
1280#if EAGAIN != EWOULDBLOCK
1281 case EAGAIN:
1282#endif
1283 case EWOULDBLOCK: return error::TIMEOUT;
1284 case ECONNRESET: case EPIPE: return error::SERVER_DISCONNECTED;
1285 default: return error::NET_ERROR;
1286 }
1287}
1288
1289error socket_server::recv(srv_client_t client, void *buffer, size_t& sz)
1290{
1291 debug() << "[net::ctx::sock] recv(" << client << ", " << sz << "): ";
1292 int ret = ::recv(from_srv_client(client), buffer, sz, MSG_WAITALL);
1293 if(ret > 0)
1294 {
1295 debug() << "good(" << ret << ")\n";
1296 sz = (size_t)ret;
1297 return error::SUCCESS;
1298 }
1299 if(ret == 0)
1300 {
1301 debug() << "disconnected\n";
1302 return error::SERVER_DISCONNECTED;
1303 }
1304 debug() << "fail(" << errno << "," << strerror(errno) << ")\n";
1305 switch(errno)
1306 {
1307#if EAGAIN != EWOULDBLOCK
1308 case EAGAIN:
1309#endif
1310 case EWOULDBLOCK: return error::TIMEOUT;
1311 default: return error::NET_ERROR;
1312 }
1313}
1314
1315void socket_server::discovery_thread()
1316{
1317 debug() << "[net::srv:sock::discovery] start\n";
1318 /* begin listening to incoming connections */
1319 if(listen(m_socketfd, LISTEN_QUEUE_SIZE) < 0)
1320 {
1321 debug() << "[net::srv::sock::discovery] listen() failed: " << errno << "\n";
1322 return;
1323 }
1324
1325 /* handle connections */
1326 while(!m_discovery_exit)
1327 {
1328 /* since accept() is blocking, use select to ensure a timeout */
1329 struct timeval tmo = m_timeout; /* NOTE select() can overwrite timeout */
1330 fd_set set;
1331 FD_ZERO(&set);
1332 FD_SET(m_socketfd, &set);
1333 /* wait for some activity */
1334 int ret = select(m_socketfd + 1, &set, nullptr, nullptr, &tmo);
1335 if(ret < 0 || !FD_ISSET(m_socketfd, &set))
1336 continue;
1337 int clifd = accept(m_socketfd, nullptr, nullptr);
1338 if(clifd >= 0)
1339 {
1340 debug() << "[net::srv::sock::discovery] new client\n";
1341 /* set timeout for the client operations */
1342 setsockopt(clifd, SOL_SOCKET, SO_RCVTIMEO, (char *)&m_timeout, sizeof(m_timeout));
1343 setsockopt(clifd, SOL_SOCKET, SO_SNDTIMEO, (char *)&m_timeout, sizeof(m_timeout));
1344 client_arrived(to_srv_client(clifd));
1345 }
1346 }
1347 debug() << "[net::srv:sock::discovery] stop\n";
1348}
1349
1350} // namespace uri
1351} // 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 @@
1#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 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2015 by Amaury Pouly
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#include "hwstub_uri.hpp"
22#include "hwstub_usb.hpp"
23#include "hwstub_virtual.hpp"
24#include "hwstub_net.hpp"
25#include <cctype>
26
27namespace hwstub {
28namespace uri {
29
30void print_usage(FILE *f, bool client, bool server)
31{
32 if(client && server)
33 fprintf(f, "A context/server");
34 else if(client)
35 fprintf(f, "A context");
36 else
37 fprintf(f, "A server");
38 fprintf(f, " URI has the following format:\n");
39 fprintf(f, " scheme:[//domain[:port]][/]\n");
40 fprintf(f, "The scheme is mandatory and specifies the type of context to create.\n");
41 fprintf(f, "The following scheme are recognized:\n");
42 if(client)
43 fprintf(f, " usb USB context (using libusb)\n");
44 fprintf(f, " tcp TCP context\n");
45 fprintf(f, " unix Local unix domain context\n");
46 if(client)
47 fprintf(f, " virt A virtual context (testing and debugging mostly)\n");
48 fprintf(f, " default Default choice made by the library\n");
49 if(client)
50 {
51 fprintf(f, "When creating a USB context, the domain and port must be empty:\n");
52 fprintf(f, " usb:\n");
53 }
54 fprintf(f, "When creating a TCP context, the domain and port are the usual TCP parameters:\n");
55 fprintf(f, " tcp://localhost\n");
56 fprintf(f, " tcp://localhost:6666\n");
57 fprintf(f, "The port is optional, in which the default port %s is used.\n", hwstub::net::context::default_tcp_port().c_str());
58 fprintf(f, "When creating a unix domain context, the port must be empty and there are two type of unix domains.\n");
59 fprintf(f, "Normal domains are specified by filesystem paths. Abstract domains (Linux-only) can be arbitrary strings,\n");
60 fprintf(f, "in which case the domain must start with a '#':\n");
61 fprintf(f, " unix:///path/to/socket\n");
62 fprintf(f, " unix://#hwstub\n");
63 if(client)
64 {
65 fprintf(f, "When creating a virtual context, the domain must contain a specification of the devices.\n");
66 fprintf(f, "The device list is of the form type(param);type(param);... where the only supported type\n");
67 fprintf(f, "at the moment is 'dummy' with a single parameter which is the device name:\n");
68 fprintf(f, " virt://dummy(Device A);dummy(Device B);dummy(Super device C)\n");
69 }
70 if(server && client)
71 fprintf(f, "Note that usb and virt schemes are not supported for server URIs.\n");
72}
73
74uri::uri(const std::string& uri)
75 :m_uri(uri), m_valid(false)
76{
77 parse();
78}
79
80bool uri::validate_scheme()
81{
82 /* scheme must be nonempty */
83 if(m_scheme.empty())
84 {
85 m_error = "empty scheme";
86 return false;
87 }
88 /* and contain alphanumeric characters or '+', '-' or '.' */
89 for(auto c : m_scheme)
90 if(!isalnum(c) && c != '+' && c != '-' && c != '.')
91 {
92 m_error = std::string("invalid character '") + c + "' in scheme";
93 return false;
94 }
95 return true;
96}
97
98bool uri::validate_domain()
99{
100 return true;
101}
102
103bool uri::validate_port()
104{
105 return true;
106}
107
108void uri::parse()
109{
110 std::string str = m_uri;
111 /* try to find the first ':' */
112 size_t scheme_colon = str.find(':');
113 if(scheme_colon == std::string::npos)
114 {
115 m_error = "URI contains no scheme";
116 return;
117 }
118 /* found scheme */
119 m_scheme = str.substr(0, scheme_colon);
120 /* validate scheme */
121 if(!validate_scheme())
122 return;
123 /* remove scheme: */
124 str = str.substr(scheme_colon + 1);
125 /* check if it begins with // */
126 if(str.size() >= 2 && str[0] == '/' && str[1] == '/')
127 {
128 /* remove it */
129 str = str.substr(2);
130 /* find next '/' */
131 std::string path;
132 size_t next_slash = str.find('/');
133 /* if none, we can assume the path is empty, otherwise split path */
134 if(next_slash != std::string::npos)
135 {
136 path = str.substr(next_slash);
137 str = str.substr(0, next_slash);
138 }
139 /* find last ':' if any, indicating a port */
140 size_t port_colon = str.rfind(':');
141 if(port_colon != std::string::npos)
142 {
143 m_domain = str.substr(0, port_colon);
144 m_port = str.substr(port_colon + 1);
145 }
146 else
147 m_domain = str;
148 if(!validate_domain() || !validate_port())
149 return;
150 /* pursue with path */
151 str = path;
152 }
153 /* next is path */
154 m_path = str;
155 m_valid = true;
156}
157
158bool uri::valid() const
159{
160 return m_valid;
161}
162
163std::string uri::error() const
164{
165 return m_error;
166}
167
168std::string uri::full_uri() const
169{
170 return m_uri;
171}
172
173std::string uri::scheme() const
174{
175 return m_scheme;
176}
177
178std::string uri::domain() const
179{
180 return m_domain;
181}
182
183std::string uri::port() const
184{
185 return m_port;
186}
187
188std::string uri::path() const
189{
190 return m_path;
191}
192
193/** Context creator */
194
195std::shared_ptr<context> create_default_context(std::string *error)
196{
197 /* first try to create a unix context with abstract name 'hwstub' */
198 std::shared_ptr<context> ctx = hwstub::net::context::create_unix_abstract(
199 hwstub::net::context::default_unix_path(), error);
200 if(ctx)
201 return ctx;
202 /* otherwise try default tcp */
203 ctx = hwstub::net::context::create_tcp(hwstub::net::context::default_tcp_domain(),
204 hwstub::net::context::default_tcp_port(), error);
205 if(ctx)
206 return ctx;
207 /* otherwise default to usb */
208 return hwstub::usb::context::create(nullptr, false, error);
209}
210
211std::shared_ptr<context> create_context(const uri& uri, std::string *error)
212{
213 /* check URI is valid */
214 if(!uri.valid())
215 {
216 if(error)
217 *error = "invalid URI: " + uri.error();
218 return std::shared_ptr<context>();
219 }
220 /* handle different types of contexts */
221 if(uri.scheme() == "usb")
222 {
223 /* domain and port must be empty */
224 if(!uri.domain().empty() || !uri.port().empty())
225 {
226 if(error)
227 *error = "USB URI cannot contain a domain or a port";
228 return std::shared_ptr<context>();
229 }
230 /* in doubt, create a new libusb context and let the context destroy it */
231 libusb_context *ctx;
232 libusb_init(&ctx);
233 return hwstub::usb::context::create(ctx, true);
234 }
235 else if(uri.scheme() == "virt")
236 {
237 /* port must be empty */
238 if(!uri.port().empty())
239 {
240 if(error)
241 *error = "virt URI cannot contain a port";
242 return std::shared_ptr<context>();
243 }
244 return hwstub::virt::context::create_spec(uri.domain(), error);
245 }
246 else if(uri.scheme() == "tcp")
247 {
248 return hwstub::net::context::create_tcp(uri.domain(), uri.port(), error);
249 }
250 else if(uri.scheme() == "unix")
251 {
252 /* port must be empty */
253 if(!uri.port().empty())
254 {
255 if(error)
256 *error = "unix URI cannot contain a port";
257 return std::shared_ptr<context>();
258 }
259 if(!uri.domain().empty() && uri.domain()[0] == '#')
260 return hwstub::net::context::create_unix_abstract(uri.domain().substr(1), error);
261 else
262 return hwstub::net::context::create_unix(uri.domain(), error);
263 }
264 else if(uri.scheme() == "default")
265 {
266 /* domain and port must be empty */
267 if(!uri.domain().empty() || !uri.port().empty())
268 {
269 if(error)
270 *error = "Default URI cannot contain a domain or a port";
271 return std::shared_ptr<context>();
272 }
273 return create_default_context(error);
274 }
275 else
276 {
277 if(error)
278 *error = "unknown scheme '" + uri.scheme() + "'";
279 return std::shared_ptr<context>();
280 }
281}
282
283std::shared_ptr<net::server> create_server(std::shared_ptr<context> ctx,
284 const uri& uri, std::string *error)
285{
286 /* check URI is valid */
287 if(!uri.valid())
288 {
289 if(error)
290 *error = "invalid URI: " + uri.error();
291 return std::shared_ptr<net::server>();
292 }
293 /* handle different types of contexts */
294 if(uri.scheme() == "unix")
295 {
296 /* port must be empty */
297 if(!uri.port().empty())
298 {
299 if(error)
300 *error = "unix URI cannot contain a port";
301 return std::shared_ptr<net::server>();
302 }
303 if(!uri.domain().empty() && uri.domain()[0] == '#')
304 return hwstub::net::server::create_unix_abstract(ctx, uri.domain().substr(1), error);
305 else
306 return hwstub::net::server::create_unix(ctx, uri.domain(), error);
307 }
308 else if(uri.scheme() == "tcp")
309 {
310 return hwstub::net::server::create_tcp(ctx, uri.domain(), uri.port(), error);
311 }
312 else
313 {
314 if(error)
315 *error = "unknown scheme '" + uri.scheme() + "'";
316 return std::shared_ptr<net::server>();
317 }
318}
319
320uri default_uri()
321{
322 return uri("default:");
323}
324
325uri default_server_uri()
326{
327 return uri("unix://#" + hwstub::net::context::default_unix_path());
328}
329
330} // namespace uri
331} // namespace hwstub
332
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 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2015 by Amaury Pouly
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#include "hwstub.hpp"
22#include "hwstub_usb.hpp"
23#include <cstring> /* for memcpy */
24
25namespace hwstub {
26namespace usb {
27
28const uint8_t VR_GET_CPU_INFO = 0;
29const uint8_t VR_SET_DATA_ADDRESS = 1;
30const uint8_t VR_SET_DATA_LENGTH = 2;
31const uint8_t VR_FLUSH_CACHES = 3;
32const uint8_t VR_PROGRAM_START1 = 4;
33const uint8_t VR_PROGRAM_START2 = 5;
34
35/**
36 * Context
37 */
38
39context::context(libusb_context *ctx, bool cleanup_ctx)
40 :m_usb_ctx(ctx), m_cleanup_ctx(cleanup_ctx)
41{
42}
43
44context::~context()
45{
46 if(m_cleanup_ctx)
47 libusb_exit(m_usb_ctx);
48}
49
50std::shared_ptr<context> context::create(libusb_context *ctx, bool cleanup_ctx,
51 std::string *error)
52{
53 (void) error;
54 if(ctx == nullptr)
55 libusb_init(nullptr);
56 // NOTE: can't use make_shared() because of the protected ctor */
57 return std::shared_ptr<context>(new context(ctx, cleanup_ctx));
58}
59
60libusb_context *context::native_context()
61{
62 return m_usb_ctx;
63}
64
65libusb_device *context::from_ctx_dev(ctx_dev_t dev)
66{
67 return reinterpret_cast<libusb_device*>(dev);
68}
69
70hwstub::context::ctx_dev_t context::to_ctx_dev(libusb_device *dev)
71{
72 return static_cast<ctx_dev_t>(dev);
73}
74
75error context::fetch_device_list(std::vector<ctx_dev_t>& list, void*& ptr)
76{
77 libusb_device **usb_list;
78 ssize_t ret = libusb_get_device_list(m_usb_ctx, &usb_list);
79 if(ret < 0)
80 return error::ERROR;
81 ptr = (void *)usb_list;
82 list.clear();
83 for(int i = 0; i < ret; i++)
84 if(device::is_hwstub_dev(usb_list[i]))
85 list.push_back(to_ctx_dev(usb_list[i]));
86 return error::SUCCESS;
87}
88
89void context::destroy_device_list(void *ptr)
90{
91 /* remove all references */
92 libusb_free_device_list((libusb_device **)ptr, 1);
93}
94
95error context::create_device(ctx_dev_t dev, std::shared_ptr<hwstub::device>& hwdev)
96{
97 // NOTE: can't use make_shared() because of the protected ctor */
98 hwdev.reset(new device(shared_from_this(), from_ctx_dev(dev)));
99 return error::SUCCESS;
100}
101
102bool context::match_device(ctx_dev_t dev, std::shared_ptr<hwstub::device> hwdev)
103{
104 device *udev = dynamic_cast<device*>(hwdev.get());
105 return udev != nullptr && udev->native_device() == dev;
106}
107
108/**
109 * Device
110 */
111device::device(std::shared_ptr<hwstub::context> ctx, libusb_device *dev)
112 :hwstub::device(ctx), m_dev(dev)
113{
114 libusb_ref_device(dev);
115}
116
117device::~device()
118{
119 libusb_unref_device(m_dev);
120}
121
122libusb_device *device::native_device()
123{
124 return m_dev;
125}
126
127bool device::is_hwstub_dev(libusb_device *dev)
128{
129 struct libusb_device_descriptor dev_desc;
130 struct libusb_config_descriptor *config = nullptr;
131 int intf = 0;
132 if(libusb_get_device_descriptor(dev, &dev_desc) != 0)
133 goto Lend;
134 if(libusb_get_config_descriptor(dev, 0, &config) != 0)
135 goto Lend;
136 /* Try to find Rockbox hwstub interface or a JZ device */
137 if(rb_handle::find_intf(&dev_desc, config, intf) ||
138 jz_handle::is_boot_dev(&dev_desc, config))
139 {
140 libusb_free_config_descriptor(config);
141 return true;
142 }
143Lend:
144 if(config)
145 libusb_free_config_descriptor(config);
146 return false;
147}
148
149error device::open_dev(std::shared_ptr<hwstub::handle>& handle)
150{
151 int intf = -1;
152 /* open the device */
153 libusb_device_handle *h;
154 int err = libusb_open(m_dev, &h);
155 if(err != LIBUSB_SUCCESS)
156 return error::ERROR;
157 /* fetch some descriptors */
158 struct libusb_device_descriptor dev_desc;
159 struct libusb_config_descriptor *config = nullptr;
160 if(libusb_get_device_descriptor(m_dev, &dev_desc) != 0)
161 goto Lend;
162 if(libusb_get_config_descriptor(m_dev, 0, &config) != 0)
163 goto Lend;
164 /* Try to find Rockbox hwstub interface */
165 if(rb_handle::find_intf(&dev_desc, config, intf))
166 {
167 libusb_free_config_descriptor(config);
168 /* create the handle */
169 // NOTE: can't use make_shared() because of the protected ctor */
170 handle.reset(new rb_handle(shared_from_this(), h, intf));
171 }
172 /* Maybe this is a JZ device ? */
173 else if(jz_handle::is_boot_dev(&dev_desc, config))
174 {
175 libusb_free_config_descriptor(config);
176 /* create the handle */
177 // NOTE: can't use make_shared() because of the protected ctor */
178 handle.reset(new jz_handle(shared_from_this(), h));
179 }
180 else
181 {
182 libusb_free_config_descriptor(config);
183 return error::ERROR;
184 }
185 /* the class will perform some probing on creation: check that it actually worked */
186 if(handle->valid())
187 return error::SUCCESS;
188 /* abort */
189 handle.reset(); // will close the libusb handle
190 return error::ERROR;
191
192Lend:
193 if(config)
194 libusb_free_config_descriptor(config);
195 libusb_close(h);
196 return error::ERROR;
197}
198
199bool device::has_multiple_open() const
200{
201 /* libusb only allows one handle per device */
202 return false;
203}
204
205uint8_t device::get_bus_number()
206{
207 return libusb_get_bus_number(native_device());
208}
209
210uint8_t device::get_address()
211{
212 return libusb_get_device_address(native_device());
213}
214
215uint16_t device::get_vid()
216{
217 /* NOTE: doc says it's cached so it should always succeed */
218 struct libusb_device_descriptor dev_desc;
219 libusb_get_device_descriptor(native_device(), &dev_desc);
220 return dev_desc.idVendor;
221}
222
223uint16_t device::get_pid()
224{
225 /* NOTE: doc says it's cached so it should always succeed */
226 struct libusb_device_descriptor dev_desc;
227 libusb_get_device_descriptor(native_device(), &dev_desc);
228 return dev_desc.idProduct;
229}
230
231/**
232 * USB handle
233 */
234handle::handle(std::shared_ptr<hwstub::device> dev, libusb_device_handle *handle)
235 :hwstub::handle(dev), m_handle(handle)
236{
237 set_timeout(std::chrono::milliseconds(100));
238}
239
240handle::~handle()
241{
242 libusb_close(m_handle);
243}
244
245error handle::interpret_libusb_error(int err)
246{
247 if(err >= 0)
248 return error::SUCCESS;
249 if(err == LIBUSB_ERROR_NO_DEVICE)
250 return error::DISCONNECTED;
251 else
252 return error::USB_ERROR;
253}
254
255error handle::interpret_libusb_error(int err, size_t expected_val)
256{
257 if(err < 0)
258 return interpret_libusb_error(err);
259 if((size_t)err != expected_val)
260 return error::ERROR;
261 return error::SUCCESS;
262}
263
264error handle::interpret_libusb_size(int err, size_t& out_siz)
265{
266 if(err < 0)
267 return interpret_libusb_error(err);
268 out_siz = (size_t)err;
269 return error::SUCCESS;
270}
271
272void handle::set_timeout(std::chrono::milliseconds ms)
273{
274 m_timeout = ms.count();
275}
276
277/**
278 * Rockbox Handle
279 */
280
281rb_handle::rb_handle(std::shared_ptr<hwstub::device> dev,
282 libusb_device_handle *handle, int intf)
283 :hwstub::usb::handle(dev, handle), m_intf(intf), m_transac_id(0), m_buf_size(1)
284{
285 m_probe_status = error::SUCCESS;
286 /* claim interface */
287 if(libusb_claim_interface(m_handle, m_intf) != 0)
288 m_probe_status = error::PROBE_FAILURE;
289 /* check version */
290 if(m_probe_status == error::SUCCESS)
291 {
292 struct hwstub_version_desc_t ver_desc;
293 m_probe_status = get_version_desc(ver_desc);
294 if(m_probe_status == error::SUCCESS)
295 {
296 if(ver_desc.bMajor != HWSTUB_VERSION_MAJOR ||
297 ver_desc.bMinor < HWSTUB_VERSION_MINOR)
298 m_probe_status = error::PROBE_FAILURE;
299 }
300 }
301 /* get buffer size */
302 if(m_probe_status == error::SUCCESS)
303 {
304 struct hwstub_layout_desc_t layout_desc;
305 m_probe_status = get_layout_desc(layout_desc);
306 if(m_probe_status == error::SUCCESS)
307 m_buf_size = layout_desc.dBufferSize;
308 }
309}
310
311rb_handle::~rb_handle()
312{
313}
314
315size_t rb_handle::get_buffer_size()
316{
317 return m_buf_size;
318}
319
320error rb_handle::status() const
321{
322 error err = handle::status();
323 if(err == error::SUCCESS)
324 err = m_probe_status;
325 return err;
326}
327
328error rb_handle::get_dev_desc(uint16_t desc, void *buf, size_t& buf_sz)
329{
330 return interpret_libusb_size(libusb_control_transfer(m_handle,
331 LIBUSB_REQUEST_TYPE_STANDARD | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_IN,
332 LIBUSB_REQUEST_GET_DESCRIPTOR, desc << 8, m_intf, (unsigned char *)buf, buf_sz, m_timeout),
333 buf_sz);
334}
335
336error rb_handle::get_dev_log(void *buf, size_t& buf_sz)
337{
338 return interpret_libusb_size(libusb_control_transfer(m_handle,
339 LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_IN,
340 HWSTUB_GET_LOG, 0, m_intf, (unsigned char *)buf, buf_sz, m_timeout), buf_sz);
341}
342
343error rb_handle::exec_dev(uint32_t addr, uint16_t flags)
344{
345 struct hwstub_exec_req_t exec;
346 exec.dAddress = addr;
347 exec.bmFlags = flags;
348 return interpret_libusb_error(libusb_control_transfer(m_handle,
349 LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_OUT,
350 HWSTUB_EXEC, 0, m_intf, (unsigned char *)&exec, sizeof(exec), m_timeout), sizeof(exec));
351}
352
353error rb_handle::read_dev(uint32_t addr, void *buf, size_t& sz, bool atomic)
354{
355 struct hwstub_read_req_t read;
356 read.dAddress = addr;
357 error err = interpret_libusb_error(libusb_control_transfer(m_handle,
358 LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_OUT,
359 HWSTUB_READ, m_transac_id, m_intf, (unsigned char *)&read, sizeof(read), m_timeout),
360 sizeof(read));
361 if(err != error::SUCCESS)
362 return err;
363 return interpret_libusb_size(libusb_control_transfer(m_handle,
364 LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_IN,
365 atomic ? HWSTUB_READ2_ATOMIC : HWSTUB_READ2, m_transac_id++, m_intf,
366 (unsigned char *)buf, sz, m_timeout), sz);
367}
368
369error rb_handle::write_dev(uint32_t addr, const void *buf, size_t& sz, bool atomic)
370{
371 size_t hdr_sz = sizeof(struct hwstub_write_req_t);
372 uint8_t *tmp_buf = new uint8_t[sz + hdr_sz];
373 struct hwstub_write_req_t *req = reinterpret_cast<struct hwstub_write_req_t *>(tmp_buf);
374 req->dAddress = addr;
375 memcpy(tmp_buf + hdr_sz, buf, sz);
376 error ret = interpret_libusb_error(libusb_control_transfer(m_handle,
377 LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_OUT,
378 atomic ? HWSTUB_WRITE_ATOMIC : HWSTUB_WRITE, m_transac_id++, m_intf,
379 (unsigned char *)req, sz + hdr_sz, m_timeout), sz + hdr_sz);
380 delete[] tmp_buf;
381 return ret;
382}
383
384bool rb_handle::find_intf(struct libusb_device_descriptor *dev,
385 struct libusb_config_descriptor *config, int& intf_idx)
386{
387 (void) dev;
388 /* search hwstub interface */
389 for(unsigned i = 0; i < config->bNumInterfaces; i++)
390 {
391 /* hwstub interface has only one setting */
392 if(config->interface[i].num_altsetting != 1)
393 continue;
394 const struct libusb_interface_descriptor *intf = &config->interface[i].altsetting[0];
395 /* check class/subclass/protocol */
396 if(intf->bInterfaceClass == HWSTUB_CLASS &&
397 intf->bInterfaceSubClass == HWSTUB_SUBCLASS &&
398 intf->bInterfaceProtocol == HWSTUB_PROTOCOL)
399 {
400 /* found it ! */
401 intf_idx = i;
402 return true;
403 }
404 }
405 return false;
406}
407
408/**
409 * JZ Handle
410 */
411
412namespace
413{
414 uint16_t jz_bcd(char *bcd)
415 {
416 uint16_t v = 0;
417 for(int i = 0; i < 4; i++)
418 v = (bcd[i] - '0') | v << 4;
419 return v;
420 }
421}
422
423jz_handle::jz_handle(std::shared_ptr<hwstub::device> dev,
424 libusb_device_handle *handle)
425 :hwstub::usb::handle(dev, handle)
426{
427 m_probe_status = probe();
428}
429
430jz_handle::~jz_handle()
431{
432}
433
434error jz_handle::probe()
435{
436 char cpuinfo[8];
437 /* Get CPU info and devise descriptor */
438 error err = jz_cpuinfo(cpuinfo);
439 if(err != error::SUCCESS)
440 return err;
441 struct libusb_device_descriptor dev_desc;
442 err = interpret_libusb_error(libusb_get_device_descriptor(
443 libusb_get_device(m_handle), &dev_desc), 0);
444 if(err != error::SUCCESS)
445 return err;
446 /** parse CPU info */
447 /* if cpuinfo if of the form JZxxxxVy then extract xxxx */
448 if(cpuinfo[0] == 'J' && cpuinfo[1] == 'Z' && cpuinfo[6] == 'V')
449 m_desc_jz.wChipID = jz_bcd(cpuinfo + 2);
450 /* if cpuinfo if of the form Bootxxxx then extract xxxx */
451 else if(strncmp(cpuinfo, "Boot", 4) == 4)
452 m_desc_jz.wChipID = jz_bcd(cpuinfo + 4);
453 /* else use usb id */
454 else
455 m_desc_jz.wChipID = dev_desc.idProduct;
456 m_desc_jz.bRevision = 0;
457
458 /** Retrieve product string */
459 memset(m_desc_target.bName, 0, sizeof(m_desc_target.bName));
460 err = interpret_libusb_error(libusb_get_string_descriptor_ascii(m_handle,
461 dev_desc.iProduct, (unsigned char *)m_desc_target.bName, sizeof(m_desc_target.bName)));
462 if(err != error::SUCCESS)
463 return err;
464 /** The JZ4760 and JZ4760B cannot be distinguished by the above information,
465 * for this the best way I have found is to check the SRAM size: 48KiB vs 16KiB.
466 * This requires to enable AHB1 and SRAM clock and read/write to SRAM, but
467 * this code will leaves registers and ram is the same state as before.
468 * In case of failure, simply assume JZ4760. */
469 if(m_desc_jz.wChipID == 0x4760)
470 probe_jz4760b();
471
472 /** Fill descriptors */
473 m_desc_version.bLength = sizeof(m_desc_version);
474 m_desc_version.bDescriptorType = HWSTUB_DT_VERSION;
475 m_desc_version.bMajor = HWSTUB_VERSION_MAJOR;
476 m_desc_version.bMinor = HWSTUB_VERSION_MINOR;
477 m_desc_version.bRevision = 0;
478
479 m_desc_layout.bLength = sizeof(m_desc_layout);
480 m_desc_layout.bDescriptorType = HWSTUB_DT_LAYOUT;
481 m_desc_layout.dCodeStart = 0xbfc00000; /* ROM */
482 m_desc_layout.dCodeSize = 0x2000; /* 8kB per datasheet */
483 m_desc_layout.dStackStart = 0; /* As far as I can tell, the ROM uses no stack */
484 m_desc_layout.dStackSize = 0;
485 m_desc_layout.dBufferStart = 0x080000000;
486 m_desc_layout.dBufferSize = 0x4000;
487
488 m_desc_target.bLength = sizeof(m_desc_target);
489 m_desc_target.bDescriptorType = HWSTUB_DT_TARGET;
490 m_desc_target.dID = HWSTUB_TARGET_JZ;
491
492 m_desc_jz.bLength = sizeof(m_desc_jz);
493 m_desc_jz.bDescriptorType = HWSTUB_DT_JZ;
494
495 /* claim interface */
496 if(libusb_claim_interface(m_handle, 0) != 0)
497 m_probe_status = error::PROBE_FAILURE;
498
499 return m_probe_status;
500}
501
502error jz_handle::read_reg32(uint32_t addr, uint32_t& value)
503{
504 size_t sz = sizeof(value);
505 error err = read_dev(addr, &value, sz, true);
506 if(err == error::SUCCESS && sz != sizeof(value))
507 err = error::ERROR;
508 return err;
509}
510
511error jz_handle::write_reg32(uint32_t addr, uint32_t value)
512{
513 size_t sz = sizeof(value);
514 error err = write_dev(addr, &value, sz, true);
515 if(err == error::SUCCESS && sz != sizeof(value))
516 err = error::ERROR;
517 return err;
518}
519
520error jz_handle::probe_jz4760b()
521{
522 /* first read CPM_CLKGR1 */
523 const uint32_t cpm_clkgr1_addr = 0xb0000028;
524 uint32_t cpm_clkgr1;
525 error err = read_reg32(cpm_clkgr1_addr, cpm_clkgr1);
526 if(err != error::SUCCESS)
527 return err;
528 /* Bit 7 controls AHB1 clock and bit 5 the SRAM. Note that SRAM is on AHB1.
529 * Only ungate if gated */
530 uint32_t cpm_clkgr1_mask = 1 << 7 | 1 << 5;
531 if(cpm_clkgr1 & cpm_clkgr1_mask)
532 {
533 /* ungate both clocks */
534 err = write_reg32(cpm_clkgr1_addr, cpm_clkgr1 & ~cpm_clkgr1_mask);
535 if(err != error::SUCCESS)
536 return err;
537 }
538 /* read first word of SRAM and then at end (supposedly) */
539 uint32_t sram_addr = 0xb32d0000;
540 uint32_t sram_end_addr = sram_addr + 16 * 1024; /* SRAM is 16KiB on JZ4760B */
541 uint32_t sram_start, sram_end;
542 err = read_reg32(sram_addr, sram_start);
543 if(err != error::SUCCESS)
544 goto Lrestore;
545 err = read_reg32(sram_end_addr, sram_end);
546 if(err != error::SUCCESS)
547 goto Lrestore;
548 /* if start and end are different, clearly the size is not 16KiB and this is
549 * JZ4760 and we have nothing to do */
550 if(sram_start != sram_end)
551 goto Lrestore;
552 /* now reverse all bits of the first word */
553 sram_start ^= 0xffffffff;
554 err = write_reg32(sram_addr, sram_start);
555 if(err != error::SUCCESS)
556 goto Lrestore;
557 /* and read again at end */
558 err = read_reg32(sram_end_addr, sram_end);
559 if(err != error::SUCCESS)
560 goto Lrestore;
561 /* if they are still equal, we identified JZ4760B */
562 if(sram_start == sram_end)
563 m_desc_jz.bRevision = 'B';
564 /* restore SRAM value */
565 sram_start ^= 0xffffffff;
566 err = write_reg32(sram_addr, sram_start);
567 if(err != error::SUCCESS)
568 goto Lrestore;
569
570Lrestore:
571 /* restore gates if needed */
572 if(cpm_clkgr1 & cpm_clkgr1_mask)
573 return write_reg32(cpm_clkgr1_addr, cpm_clkgr1);
574 else
575 return error::SUCCESS;
576}
577
578size_t jz_handle::get_buffer_size()
579{
580 return m_desc_layout.dBufferSize;
581}
582
583error jz_handle::status() const
584{
585 error err = handle::status();
586 if(err == error::SUCCESS)
587 err = m_probe_status;
588 return err;
589}
590
591error jz_handle::get_dev_desc(uint16_t desc, void *buf, size_t& buf_sz)
592{
593 void *p = nullptr;
594 switch(desc)
595 {
596 case HWSTUB_DT_VERSION: p = &m_desc_version; break;
597 case HWSTUB_DT_LAYOUT: p = &m_desc_layout; break;
598 case HWSTUB_DT_TARGET: p = &m_desc_target; break;
599 case HWSTUB_DT_JZ: p = &m_desc_jz; break;
600 default: break;
601 }
602 if(p == nullptr)
603 return error::ERROR;
604 /* size is in the bLength field of the descriptor */
605 size_t desc_sz = *(uint8_t *)p;
606 buf_sz = std::min(buf_sz, desc_sz);
607 memcpy(buf, p, buf_sz);
608 return error::SUCCESS;
609}
610
611error jz_handle::get_dev_log(void *buf, size_t& buf_sz)
612{
613 (void) buf;
614 buf_sz = 0;
615 return error::SUCCESS;
616}
617
618error jz_handle::exec_dev(uint32_t addr, uint16_t flags)
619{
620 (void) flags;
621 /* FIXME the ROM always do call so the stub can always return, this behaviour
622 * cannot be changed */
623 /* NOTE assume that exec at 0x80000000 is a first stage load with START1,
624 * otherwise flush cache and use START2 */
625 if(addr == 0x80000000)
626 return jz_start1(addr);
627 error ret = jz_flush_caches();
628 if(ret == error::SUCCESS)
629 return jz_start2(addr);
630 else
631 return ret;
632}
633
634error jz_handle::read_dev(uint32_t addr, void *buf, size_t& sz, bool atomic)
635{
636 (void) atomic;
637 /* NOTE disassembly shows that the ROM will do atomic read on aligned words */
638 error ret = jz_set_addr(addr);
639 if(ret == error::SUCCESS)
640 ret = jz_set_length(sz);
641 if(ret == error::SUCCESS)
642 ret = jz_upload(buf, sz);
643 return ret;
644}
645
646error jz_handle::write_dev(uint32_t addr, const void *buf, size_t& sz, bool atomic)
647{
648 (void) atomic;
649 /* NOTE disassembly shows that the ROM will do atomic read on aligned words */
650 /* IMPORTANT BUG Despite what the manual suggest, one must absolutely NOT send
651 * a VR_SET_DATA_LENGTH request for a write, otherwise it will have completely
652 * random effects */
653 error ret = jz_set_addr(addr);
654 if(ret == error::SUCCESS)
655 ret = jz_download(buf, sz);
656 return ret;
657}
658
659bool jz_handle::is_boot_dev(struct libusb_device_descriptor *dev,
660 struct libusb_config_descriptor *config)
661{
662 (void) config;
663 /* don't bother checking the config descriptor and use the device ID only */
664 return dev->idVendor == 0x601a && dev->idProduct >= 0x4740 && dev->idProduct <= 0x4780;
665}
666
667error jz_handle::jz_cpuinfo(char cpuinfo[8])
668{
669 return interpret_libusb_error(libusb_control_transfer(m_handle,
670 LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
671 VR_GET_CPU_INFO, 0, 0, (unsigned char *)cpuinfo, 8, m_timeout), 8);
672}
673
674error jz_handle::jz_set_addr(uint32_t addr)
675{
676 return interpret_libusb_error(libusb_control_transfer(m_handle,
677 LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
678 VR_SET_DATA_ADDRESS, addr >> 16, addr & 0xffff, NULL, 0, m_timeout), 0);
679}
680
681error jz_handle::jz_set_length(uint32_t size)
682{
683 return interpret_libusb_error(libusb_control_transfer(m_handle,
684 LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
685 VR_SET_DATA_LENGTH, size >> 16, size & 0xffff, NULL, 0, m_timeout), 0);
686}
687
688error jz_handle::jz_upload(void *data, size_t& length)
689{
690 int xfer = 0;
691 error err = interpret_libusb_error(libusb_bulk_transfer(m_handle,
692 LIBUSB_ENDPOINT_IN | 1, (unsigned char *)data, length, &xfer, m_timeout));
693 length = xfer;
694 return err;
695}
696
697error jz_handle::jz_download(const void *data, size_t& length)
698{
699 int xfer = 0;
700 error err = interpret_libusb_error(libusb_bulk_transfer(m_handle,
701 LIBUSB_ENDPOINT_OUT | 1, (unsigned char *)data, length, &xfer, m_timeout));
702 length = xfer;
703 return err;
704}
705
706error jz_handle::jz_start1(uint32_t addr)
707{
708 return interpret_libusb_error(libusb_control_transfer(m_handle,
709 LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
710 VR_PROGRAM_START1, addr >> 16, addr & 0xffff, NULL, 0, m_timeout), 0);
711}
712
713error jz_handle::jz_flush_caches()
714{
715 return interpret_libusb_error(libusb_control_transfer(m_handle,
716 LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
717 VR_FLUSH_CACHES, 0, 0, NULL, 0, m_timeout), 0);
718}
719
720error jz_handle::jz_start2(uint32_t addr)
721{
722 return interpret_libusb_error(libusb_control_transfer(m_handle,
723 LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
724 VR_PROGRAM_START2, addr >> 16, addr & 0xffff, NULL, 0, m_timeout), 0);
725}
726
727} // namespace usb
728} // 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 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2016 by Amaury Pouly
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#include "hwstub_virtual.hpp"
22#include <algorithm>
23#include <cstring>
24
25namespace hwstub {
26namespace virt {
27
28/**
29 * Context
30 */
31context::context()
32{
33}
34
35context::~context()
36{
37}
38
39std::shared_ptr<context> context::create()
40{
41 // NOTE: can't use make_shared() because of the protected ctor */
42 return std::shared_ptr<context>(new context());
43}
44
45std::shared_ptr<context> context::create_spec(const std::string& spec, std::string *error)
46{
47 std::shared_ptr<context> ctx = create();
48 /* parse spec */
49 std::string str = spec;
50 while(!str.empty())
51 {
52 /* find next separator, if any */
53 std::string dev_spec;
54 size_t dev_sep = str.find(';');
55 if(dev_sep == std::string::npos)
56 {
57 dev_spec = str;
58 str.clear();
59 }
60 else
61 {
62 dev_spec = str.substr(0, dev_sep);
63 str = str.substr(dev_sep + 1);
64 }
65 /* handle dev spec: find ( and )*/
66 size_t lparen = dev_spec.find('(');
67 if(lparen == std::string::npos)
68 {
69 if(error)
70 *error = "invalid device spec '" + dev_spec + "': missing (";
71 return std::shared_ptr<context>();
72 }
73 if(dev_spec.back() != ')')
74 {
75 if(error)
76 *error = "invalid device spec '" + dev_spec + "': missing )";
77 return std::shared_ptr<context>();
78 }
79 std::string args = dev_spec.substr(lparen + 1, dev_spec.size() - lparen - 2);
80 std::string type = dev_spec.substr(0, lparen);
81 if(type == "dummy")
82 {
83 ctx->connect(std::make_shared<dummy_hardware>(args));
84 }
85 else
86 {
87 if(error)
88 *error = "invalid device spec '" + dev_spec + "': unknown device type";
89 return std::shared_ptr<context>();
90 }
91 }
92 return ctx;
93}
94
95bool context::connect(std::shared_ptr<hardware> hw)
96{
97 std::unique_lock<std::recursive_mutex> lock(m_mutex);
98 if(std::find(m_hwlist.begin(), m_hwlist.end(), hw) != m_hwlist.end())
99 return false;
100 m_hwlist.push_back(hw);
101 return true;
102}
103
104bool context::disconnect(std::shared_ptr<hardware> hw)
105{
106 std::unique_lock<std::recursive_mutex> lock(m_mutex);
107 auto it = std::find(m_hwlist.begin(), m_hwlist.end(), hw);
108 if(it == m_hwlist.end())
109 return false;
110 m_hwlist.erase(it);
111 return true;
112}
113
114std::shared_ptr<hardware> context::from_ctx_dev(ctx_dev_t dev)
115{
116 return ((hardware *)dev)->shared_from_this();
117}
118
119hwstub::context::ctx_dev_t context::to_ctx_dev(std::shared_ptr<hardware>& dev)
120{
121 return (ctx_dev_t)dev.get();
122}
123
124error context::fetch_device_list(std::vector<ctx_dev_t>& list, void*& ptr)
125{
126 (void) ptr;
127 list.resize(m_hwlist.size());
128 for(size_t i = 0; i < m_hwlist.size(); i++)
129 list[i] = to_ctx_dev(m_hwlist[i]);
130 return error::SUCCESS;
131}
132
133void context::destroy_device_list(void *ptr)
134{
135 (void) ptr;
136}
137
138error context::create_device(ctx_dev_t dev, std::shared_ptr<hwstub::device>& hwdev)
139{
140 // NOTE: can't use make_shared() because of the protected ctor */
141 hwdev.reset(new device(shared_from_this(), from_ctx_dev(dev)));
142 return error::SUCCESS;
143}
144
145bool context::match_device(ctx_dev_t dev, std::shared_ptr<hwstub::device> hwdev)
146{
147 device *udev = dynamic_cast<device*>(hwdev.get());
148 return udev != nullptr && udev->native_device().get() == from_ctx_dev(dev).get();
149}
150
151/**
152 * Device
153 */
154device::device(std::shared_ptr<hwstub::context> ctx, std::shared_ptr<hardware> dev)
155 :hwstub::device(ctx), m_hwdev(dev)
156{
157}
158
159device::~device()
160{
161}
162
163std::shared_ptr<hardware> device::native_device()
164{
165 return m_hwdev.lock();
166}
167
168error device::open_dev(std::shared_ptr<hwstub::handle>& h)
169{
170 // NOTE: can't use make_shared() because of the protected ctor */
171 h.reset(new handle(shared_from_this()));
172 return error::SUCCESS;
173}
174
175bool device::has_multiple_open() const
176{
177 return true;
178}
179
180/**
181 * Handle
182 */
183handle::handle(std::shared_ptr<hwstub::device> dev)
184 :hwstub::handle(dev)
185{
186 m_hwdev = dynamic_cast<device*>(dev.get())->native_device();
187}
188
189handle::~handle()
190{
191}
192
193
194error handle::read_dev(uint32_t addr, void *buf, size_t& sz, bool atomic)
195{
196 auto p = m_hwdev.lock();
197 return p ? p->read_dev(addr, buf, sz, atomic) : error::DISCONNECTED;
198}
199
200error handle::write_dev(uint32_t addr, const void *buf, size_t& sz, bool atomic)
201{
202 auto p = m_hwdev.lock();
203 return p ? p->write_dev(addr, buf, sz, atomic) : error::DISCONNECTED;
204}
205
206error handle::get_dev_desc(uint16_t desc, void *buf, size_t& buf_sz)
207{
208 auto p = m_hwdev.lock();
209 return p ? p->get_dev_desc(desc, buf, buf_sz) : error::DISCONNECTED;
210}
211
212error handle::get_dev_log(void *buf, size_t& buf_sz)
213{
214 auto p = m_hwdev.lock();
215 return p ? p->get_dev_log(buf, buf_sz) : error::DISCONNECTED;
216}
217
218error handle::exec_dev(uint32_t addr, uint16_t flags)
219{
220 auto p = m_hwdev.lock();
221 return p ? p->exec_dev(addr, flags) : error::DISCONNECTED;
222}
223
224error handle::status() const
225{
226 return hwstub::handle::status();
227}
228
229size_t handle::get_buffer_size()
230{
231 auto p = m_hwdev.lock();
232 return p ? p->get_buffer_size() : 1;
233}
234
235/**
236 * Hardware
237 */
238hardware::hardware()
239{
240}
241
242hardware::~hardware()
243{
244}
245
246/**
247 * Dummy hardware
248 */
249dummy_hardware::dummy_hardware(const std::string& name)
250{
251 m_desc_version.bLength = sizeof(m_desc_version);
252 m_desc_version.bDescriptorType = HWSTUB_DT_VERSION;
253 m_desc_version.bMajor = HWSTUB_VERSION_MAJOR;
254 m_desc_version.bMinor = HWSTUB_VERSION_MINOR;
255 m_desc_version.bRevision = 0;
256
257 m_desc_layout.bLength = sizeof(m_desc_layout);
258 m_desc_layout.bDescriptorType = HWSTUB_DT_LAYOUT;
259 m_desc_layout.dCodeStart = 0;
260 m_desc_layout.dCodeSize = 0;
261 m_desc_layout.dStackStart = 0;
262 m_desc_layout.dStackSize = 0;
263 m_desc_layout.dBufferStart = 0;
264 m_desc_layout.dBufferSize = 1;
265
266 m_desc_target.bLength = sizeof(m_desc_target);
267 m_desc_target.bDescriptorType = HWSTUB_DT_TARGET;
268 m_desc_target.dID = HWSTUB_TARGET_UNK;
269 strncpy(m_desc_target.bName, name.c_str(), sizeof(m_desc_target.bName));
270 m_desc_target.bName[sizeof(m_desc_target.bName) - 1] = 0;
271}
272
273dummy_hardware::~dummy_hardware()
274{
275}
276
277error dummy_hardware::read_dev(uint32_t addr, void *buf, size_t& sz, bool atomic)
278{
279 (void) addr;
280 (void) buf;
281 (void) sz;
282 (void) atomic;
283 return error::DUMMY;
284}
285
286error dummy_hardware::write_dev(uint32_t addr, const void *buf, size_t& sz, bool atomic)
287{
288 (void) addr;
289 (void) buf;
290 (void) sz;
291 (void) atomic;
292 return error::DUMMY;
293}
294
295error dummy_hardware::get_dev_desc(uint16_t desc, void *buf, size_t& buf_sz)
296{
297 void *p = nullptr;
298 switch(desc)
299 {
300 case HWSTUB_DT_VERSION: p = &m_desc_version; break;
301 case HWSTUB_DT_LAYOUT: p = &m_desc_layout; break;
302 case HWSTUB_DT_TARGET: p = &m_desc_target; break;
303 default: break;
304 }
305 if(p == nullptr)
306 return error::ERROR;
307 /* size is in the bLength field of the descriptor */
308 size_t desc_sz = *(uint8_t *)p;
309 buf_sz = std::min(buf_sz, desc_sz);
310 memcpy(buf, p, buf_sz);
311 return error::SUCCESS;
312}
313
314error dummy_hardware::get_dev_log(void *buf, size_t& buf_sz)
315{
316 (void) buf;
317 (void) buf_sz;
318 return error::DUMMY;
319}
320
321error dummy_hardware::exec_dev(uint32_t addr, uint16_t flags)
322{
323 (void) addr;
324 (void) flags;
325 return error::DUMMY;
326}
327
328size_t dummy_hardware::get_buffer_size()
329{
330 return 1;
331}
332
333} // namespace virt
334} // namespace net
335
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 @@
1INCLUDES+=-I$(ROOT_DIR) 1INCLUDES+=-I$(ROOT_DIR) -I$(ROOT_DIR)/../include/
2LINKER_FILE=hwstub.lds 2LINKER_FILE=hwstub.lds
3TMP_LDS=$(BUILD_DIR)/link.lds 3TMP_LDS=$(BUILD_DIR)/link.lds
4TMP_MAP=$(BUILD_DIR)/hwstub.map 4TMP_MAP=$(BUILD_DIR)/hwstub.map
@@ -45,7 +45,7 @@ $(TMP_LDS): $(LINKER_FILE)
45$(EXEC_ELF): $(OBJ) $(TMP_LDS) 45$(EXEC_ELF): $(OBJ) $(TMP_LDS)
46 $(call PRINTS,LD $(@F)) 46 $(call PRINTS,LD $(@F))
47 $(SILENT)$(LD) $(LDFLAGS) -o $@ $(OBJ_EXCEPT_CRT0) 47 $(SILENT)$(LD) $(LDFLAGS) -o $@ $(OBJ_EXCEPT_CRT0)
48 48
49$(EXEC_BIN): $(EXEC_ELF) 49$(EXEC_BIN): $(EXEC_ELF)
50 $(call PRINTS,OC $(@F)) 50 $(call PRINTS,OC $(@F))
51 $(SILENT)$(OC) -O binary $< $@ 51 $(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 @@
1#include "../hwstub_protocol.h" 1#include "hwstub_protocol.h"
2 2
3#define HWSTUB_VERSION_REV 0 3#define HWSTUB_VERSION_REV 0
4 4
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 @@
1CC=gcc 1CC=gcc
2CXX=g++ 2CXX=g++
3LD=g++ 3LD=g++
4HWSTUB_INCLUDE_DIR=../include
4HWSTUB_LIB_DIR=../lib 5HWSTUB_LIB_DIR=../lib
5REGTOOLS_INCLUDE_DIR=../../regtools/include 6REGTOOLS_INCLUDE_DIR=../../regtools/include
6REGTOOLS_LIB_DIR=../../regtools/lib 7REGTOOLS_LIB_DIR=../../regtools/lib
7CFLAGS=-Wall -O2 `pkg-config --cflags libusb-1.0` -std=c99 -g -I$(HWSTUB_LIB_DIR) -I$(REGTOOLS_INCLUDE_DIR) `pkg-config --cflags lua5.2` 8INCLUDES=-I$(HWSTUB_INCLUDE_DIR) -I$(REGTOOLS_INCLUDE_DIR) `pkg-config --cflags lua5.2` `pkg-config --cflags libusb-1.0`
8CXXFLAGS=-Wall -O2 `pkg-config --cflags libusb-1.0` -g -I$(HWSTUB_LIB_DIR) -I$(REGTOOLS_INCLUDE_DIR) `pkg-config --cflags lua5.2` 9CFLAGS=-Wall -O2 -std=c99 -g $(INCLUDES) -D_XOPEN_SOURCE=600
9LDFLAGS=`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` 10CXXFLAGS=-Wall -O2 -std=c++11 -g $(INCLUDES)
10EXEC=hwstub_shell hwstub_load 11LDFLAGS=`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
12EXEC=hwstub_shell hwstub_load hwstub_server hwstub_test
11SRC=$(wildcard *.c) 13SRC=$(wildcard *.c)
12SRCXX=$(wildcard *.cpp) 14SRCXX=$(wildcard *.cpp)
13OBJ=$(SRC:.c=.o) $(SRCXX:.cpp=.o) 15OBJ=$(SRC:.c=.o) $(SRCXX:.cpp=.o)
@@ -33,6 +35,12 @@ hwstub_shell: hwstub_shell.o prompt.o $(LIBS)
33hwstub_load: hwstub_load.o $(LIBS) 35hwstub_load: hwstub_load.o $(LIBS)
34 $(LD) -o $@ $^ $(LDFLAGS) 36 $(LD) -o $@ $^ $(LDFLAGS)
35 37
38hwstub_server: hwstub_server.o $(LIBS)
39 $(LD) -o $@ $^ $(LDFLAGS)
40
41hwstub_test: hwstub_test.o $(LIBS)
42 $(LD) -o $@ $^ $(LDFLAGS)
43
36clean: 44clean:
37 rm -rf $(OBJ) $(LIB) $(EXEC) 45 rm -rf $(OBJ) $(LIB) $(EXEC)
38 46
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 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2016 by Amaury Pouly
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#include <cstdio>
22#include <thread>
23#include <chrono>
24#include <cstring>
25#include <iostream>
26#include "hwstub.hpp"
27#include "hwstub_usb.hpp"
28#include "hwstub_uri.hpp"
29#include "hwstub_net.hpp"
30#include <signal.h>
31#include <getopt.h>
32
33/* capture CTRL+C */
34volatile sig_atomic_t g_exit_loop = 0;
35
36void do_signal(int sig)
37{
38 g_exit_loop = 1;
39}
40
41std::shared_ptr<hwstub::context> g_ctx;
42std::shared_ptr<hwstub::net::server> g_srv;
43
44int usage()
45{
46 printf("usage: hwstub_server [options]\n");
47 printf(" --help/-h Display this help\n");
48 printf(" --verbose/-v Verbose output\n");
49 printf(" --context/-c <uri> Context URI (see below)\n");
50 printf(" --server/-s <uri> Server URI (see below)\n");
51 printf("\n");
52 hwstub::uri::print_usage(stdout, true, true);
53 return 1;
54}
55
56int main(int argc, char **argv)
57{
58 hwstub::uri::uri ctx_uri = hwstub::uri::default_uri();
59 hwstub::uri::uri srv_uri = hwstub::uri::default_server_uri();
60 bool verbose = false;
61
62 while(1)
63 {
64 static struct option long_options[] =
65 {
66 {"help", no_argument, 0, 'h'},
67 {"verbose", no_argument, 0, 'v'},
68 {"context", required_argument, 0, 'c'},
69 {"server", required_argument, 0, 's'},
70 {0, 0, 0, 0}
71 };
72
73 int c = getopt_long(argc, argv, "hvc:s:", long_options, NULL);
74 if(c == -1)
75 break;
76 switch(c)
77 {
78 case -1:
79 break;
80 case 'v':
81 verbose = true;
82 break;
83 case 'h':
84 return usage();
85 case 'c':
86 ctx_uri = hwstub::uri::uri(optarg);
87 break;
88 case 's':
89 srv_uri = hwstub::uri::uri(optarg);
90 break;
91 default:
92 abort();
93 }
94 }
95
96 if(optind != argc)
97 return usage();
98
99 /* intercept CTRL+C */
100 signal(SIGINT, do_signal);
101
102 std::string error;
103 g_ctx = hwstub::uri::create_context(ctx_uri, &error);
104 if(!g_ctx)
105 {
106 printf("Cannot create context: %s\n", error.c_str());
107 return 1;
108 }
109 g_ctx->start_polling();
110
111 g_srv = hwstub::uri::create_server(g_ctx, srv_uri, &error);
112 if(!g_srv)
113 {
114 printf("Cannot create server: %s\n", error.c_str());
115 return 1;
116 }
117 if(verbose)
118 g_srv->set_debug(std::cout);
119
120 while(!g_exit_loop)
121 std::this_thread::sleep_for(std::chrono::seconds(1));
122 printf("Shutting down...\n");
123 g_srv.reset(); /* will cleanup */
124 g_ctx.reset(); /* will cleanup */
125
126 return 0;
127}
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 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2015 by Amaury Pouly
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#include <cstdio>
22#include <thread>
23#include <chrono>
24#include <cstring>
25#include <iostream>
26#include "hwstub.hpp"
27#include "hwstub_usb.hpp"
28#include "hwstub_uri.hpp"
29#include <signal.h>
30#include <getopt.h>
31
32/* capture CTRL+C */
33volatile sig_atomic_t g_exit_loop = 0;
34
35void do_signal(int sig)
36{
37 g_exit_loop = 1;
38}
39
40std::shared_ptr<hwstub::context> g_ctx;
41
42void print_error(hwstub::error err, bool nl = true)
43{
44 switch(err)
45 {
46 case hwstub::error::SUCCESS: printf("success"); break;
47 case hwstub::error::ERROR: printf("error"); break;
48 default: printf("unknown(%d)", (int)err); break;
49 }
50 if(nl)
51 printf("\n");
52}
53
54const char *target_string(uint32_t id)
55{
56 switch(id)
57 {
58 case HWSTUB_TARGET_UNK: return "unknown";
59 case HWSTUB_TARGET_STMP: return "stmp";
60 case HWSTUB_TARGET_RK27: return "rk27";
61 case HWSTUB_TARGET_PP: return "pp";
62 case HWSTUB_TARGET_ATJ: return "atj";
63 case HWSTUB_TARGET_JZ: return "jz";
64 default: return "unknown";
65 }
66}
67
68void print_dev_details(std::shared_ptr<hwstub::device> dev)
69{
70 std::shared_ptr<hwstub::handle> h;
71 hwstub::error err = dev->open(h);
72 if(err != hwstub::error::SUCCESS)
73 {
74 printf(" [cannot open dev: %s]\n", error_string(err).c_str());
75 return;
76 }
77 /* version */
78 struct hwstub_version_desc_t ver_desc;
79 err = h->get_version_desc(ver_desc);
80 if(err != hwstub::error::SUCCESS)
81 {
82 printf(" [cannot get version descriptor: %s]\n", error_string(err).c_str());
83 return;
84 }
85 printf(" [version %d.%d.%d]\n", ver_desc.bMajor, ver_desc.bMinor, ver_desc.bRevision);
86 /* target */
87 struct hwstub_target_desc_t target_desc;
88 err = h->get_target_desc(target_desc);
89 if(err != hwstub::error::SUCCESS)
90 {
91 printf(" [cannot get target descriptor: %s]\n", error_string(err).c_str());
92 return;
93 }
94 std::string name(target_desc.bName, sizeof(target_desc.bName));
95 printf(" [target %s: %s]\n", target_string(target_desc.dID), name.c_str());
96 /* layout */
97 struct hwstub_layout_desc_t layout_desc;
98 err = h->get_layout_desc(layout_desc);
99 if(err != hwstub::error::SUCCESS)
100 {
101 printf(" [cannot get layout descriptor: %s]\n", error_string(err).c_str());
102 return;
103 }
104 printf(" [code layout %#x bytes @ %#x]\n", layout_desc.dCodeSize, layout_desc.dCodeStart);
105 printf(" [stack layout %#x bytes @ %#x]\n", layout_desc.dStackSize, layout_desc.dStackStart);
106 printf(" [buffer layout %#x bytes @ %#x]\n", layout_desc.dBufferSize, layout_desc.dBufferStart);
107}
108
109void print_device(std::shared_ptr<hwstub::device> dev, bool arrived = true)
110{
111 hwstub::usb::device *udev = dynamic_cast< hwstub::usb::device* >(dev.get());
112 if(arrived)
113 printf("--> ");
114 else
115 printf("<-- ");
116 if(udev)
117 {
118 libusb_device *uudev = udev->native_device();
119 struct libusb_device_descriptor dev_desc;
120 libusb_get_device_descriptor(uudev, &dev_desc);
121 printf("USB device @ %d.%u: ID %04x:%04x\n", udev->get_bus_number(),
122 udev->get_address(), dev_desc.idVendor, dev_desc.idProduct);
123 }
124 else
125 printf("Unknown device\n");
126 if(arrived)
127 print_dev_details(dev);
128}
129
130void dev_changed(std::shared_ptr<hwstub::context> ctx, bool arrived, std::shared_ptr<hwstub::device> dev)
131{
132 print_device(dev, arrived);
133}
134
135void print_list()
136{
137 std::vector<std::shared_ptr<hwstub::device>> list;
138 hwstub::error ret = g_ctx->get_device_list(list);
139 if(ret != hwstub::error::SUCCESS)
140 {
141 printf("Cannot get device list: %s\n", error_string(ret).c_str());
142 return;
143 }
144 for(auto d : list)
145 print_device(d);
146}
147
148int usage()
149{
150 printf("usage: hwstub_test [options]\n");
151 printf(" --help/-h Display this help\n");
152 printf(" --verbose/-v Verbose output\n");
153 printf(" --context/-c <uri> Context URI (see below)\n");
154 printf("\n");
155 hwstub::uri::print_usage(stdout, true, false);
156 return 1;
157}
158
159int main(int argc, char **argv)
160{
161 hwstub::uri::uri uri = hwstub::uri::default_uri();
162 bool verbose = false;
163
164 while(1)
165 {
166 static struct option long_options[] =
167 {
168 {"help", no_argument, 0, 'h'},
169 {"verbose", no_argument, 0, 'v'},
170 {"context", required_argument, 0, 'c'},
171 {0, 0, 0, 0}
172 };
173
174 int c = getopt_long(argc, argv, "hvc:", long_options, NULL);
175 if(c == -1)
176 break;
177 switch(c)
178 {
179 case -1:
180 break;
181 case 'v':
182 verbose = true;
183 break;
184 case 'h':
185 return usage();
186 case 'c':
187 uri = hwstub::uri::uri(optarg);
188 break;
189 default:
190 abort();
191 }
192 }
193
194 if(optind != argc)
195 return usage();
196
197 /* intercept CTRL+C */
198 signal(SIGINT, do_signal);
199
200 std::string error;
201 g_ctx = hwstub::uri::create_context(uri, &error);
202 if(!g_ctx)
203 {
204 printf("Cannot create context: %s\n", error.c_str());
205 return 1;
206 }
207 if(verbose)
208 g_ctx->set_debug(std::cout);
209 print_list();
210 g_ctx->register_callback(dev_changed);
211 g_ctx->start_polling();
212 while(!g_exit_loop)
213 std::this_thread::sleep_for(std::chrono::seconds(1));
214 printf("Shutting down...\n");
215 g_ctx.reset(); /* will cleanup */
216
217 return 0;
218}
219