summaryrefslogtreecommitdiff
path: root/utils
diff options
context:
space:
mode:
Diffstat (limited to 'utils')
-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