diff options
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.hpp | 351 | ||||
-rw-r--r-- | utils/hwstub/include/hwstub_net.hpp | 334 | ||||
-rw-r--r-- | utils/hwstub/include/hwstub_protocol.h (renamed from utils/hwstub/hwstub_protocol.h) | 155 | ||||
-rw-r--r-- | utils/hwstub/include/hwstub_uri.hpp | 131 | ||||
-rw-r--r-- | utils/hwstub/include/hwstub_usb.hpp | 194 | ||||
-rw-r--r-- | utils/hwstub/include/hwstub_virtual.hpp | 159 | ||||
-rw-r--r-- | utils/hwstub/lib/Makefile | 13 | ||||
-rw-r--r-- | utils/hwstub/lib/hwstub.cpp | 627 | ||||
-rw-r--r-- | utils/hwstub/lib/hwstub_net.cpp | 1351 | ||||
-rw-r--r-- | utils/hwstub/lib/hwstub_protocol.h | 1 | ||||
-rw-r--r-- | utils/hwstub/lib/hwstub_uri.cpp | 332 | ||||
-rw-r--r-- | utils/hwstub/lib/hwstub_usb.cpp | 728 | ||||
-rw-r--r-- | utils/hwstub/lib/hwstub_virtual.cpp | 335 | ||||
-rw-r--r-- | utils/hwstub/stub/hwstub.make | 4 | ||||
-rw-r--r-- | utils/hwstub/stub/protocol.h | 2 | ||||
-rw-r--r-- | utils/hwstub/tools/Makefile | 16 | ||||
-rw-r--r-- | utils/hwstub/tools/hwstub_server.cpp | 127 | ||||
-rw-r--r-- | utils/hwstub/tools/hwstub_test.cpp | 219 |
19 files changed, 5051 insertions, 28 deletions
diff --git a/utils/hwstub/lib/hwstub.h b/utils/hwstub/include/hwstub.h index 4d12de8eda..4d12de8eda 100644 --- a/utils/hwstub/lib/hwstub.h +++ b/utils/hwstub/include/hwstub.h | |||
diff --git a/utils/hwstub/include/hwstub.hpp b/utils/hwstub/include/hwstub.hpp new file mode 100644 index 0000000000..deac976240 --- /dev/null +++ b/utils/hwstub/include/hwstub.hpp | |||
@@ -0,0 +1,351 @@ | |||
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 | |||
37 | namespace hwstub { | ||
38 | |||
39 | class context; | ||
40 | class device; | ||
41 | class handle; | ||
42 | class context_poller; | ||
43 | |||
44 | /** C++ equivalent of /dev/null for streams */ | ||
45 | extern std::ostream cnull; | ||
46 | extern std::wostream wcnull; | ||
47 | |||
48 | /** Errors */ | ||
49 | enum 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 */ | ||
68 | std::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 | */ | ||
85 | class context : public std::enable_shared_from_this<context> | ||
86 | { | ||
87 | protected: | ||
88 | context(); | ||
89 | public: | ||
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 | |||
121 | protected: | ||
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() */ | ||
166 | class context_poller | ||
167 | { | ||
168 | public: | ||
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 | |||
178 | protected: | ||
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. */ | ||
197 | class device : public std::enable_shared_from_this<device> | ||
198 | { | ||
199 | protected: | ||
200 | device(std::shared_ptr<context> ctx); | ||
201 | public: | ||
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 | |||
212 | protected: | ||
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 | */ | ||
243 | class handle : public std::enable_shared_from_this<handle> | ||
244 | { | ||
245 | protected: | ||
246 | /** A handle will always hold a reference to the device */ | ||
247 | handle(std::shared_ptr<device> dev); | ||
248 | public: | ||
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 | |||
289 | protected: | ||
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 */ | ||
313 | class dummy_device : public device | ||
314 | { | ||
315 | friend class context; /* for ctor */ | ||
316 | protected: | ||
317 | dummy_device(std::shared_ptr<context> ctx); | ||
318 | public: | ||
319 | virtual ~dummy_device(); | ||
320 | |||
321 | protected: | ||
322 | virtual error open_dev(std::shared_ptr<handle>& handle); | ||
323 | virtual bool has_multiple_open() const; | ||
324 | }; | ||
325 | |||
326 | /** Dummy handle */ | ||
327 | class dummy_handle : public handle | ||
328 | { | ||
329 | friend class dummy_device; | ||
330 | protected: | ||
331 | dummy_handle(std::shared_ptr<device> dev); | ||
332 | public: | ||
333 | virtual ~dummy_handle(); | ||
334 | |||
335 | protected: | ||
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 | |||
30 | namespace hwstub { | ||
31 | namespace 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 */ | ||
39 | class context : public hwstub::context | ||
40 | { | ||
41 | friend class device; | ||
42 | friend class handle; | ||
43 | protected: | ||
44 | context(); | ||
45 | public: | ||
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 | |||
69 | protected: | ||
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. */ | ||
132 | class socket_context : public context | ||
133 | { | ||
134 | friend class context; | ||
135 | protected: | ||
136 | socket_context(int socket_fd); | ||
137 | public: | ||
138 | virtual ~socket_context(); | ||
139 | /** set operation timeout */ | ||
140 | void set_timeout(std::chrono::milliseconds ms); | ||
141 | |||
142 | protected: | ||
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 */ | ||
153 | class device : public hwstub::device | ||
154 | { | ||
155 | friend class context; /* for ctor */ | ||
156 | protected: | ||
157 | device(std::shared_ptr<hwstub::context> ctx, uint32_t devid); | ||
158 | public: | ||
159 | virtual ~device(); | ||
160 | |||
161 | protected: | ||
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. */ | ||
173 | class handle : public hwstub::handle | ||
174 | { | ||
175 | friend class device; | ||
176 | protected: | ||
177 | handle(std::shared_ptr<hwstub::device> dev, uint32_t hid); | ||
178 | public: | ||
179 | virtual ~handle(); | ||
180 | |||
181 | protected: | ||
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 */ | ||
196 | class server | ||
197 | { | ||
198 | protected: | ||
199 | server(std::shared_ptr<hwstub::context> contex); | ||
200 | public: | ||
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(); | ||
227 | protected: | ||
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 | */ | ||
299 | class socket_server : public server | ||
300 | { | ||
301 | protected: | ||
302 | socket_server(std::shared_ptr<hwstub::context> contex, int socket_fd); | ||
303 | public: | ||
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 | |||
311 | protected: | ||
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 | ||
64 | struct hwstub_version_desc_t | 75 | struct 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 | ||
119 | struct 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 | ||
114 | struct hwstub_target_desc_t | 135 | struct 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 | ||
123 | struct 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 | |||
150 | struct 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 | |||
27 | namespace hwstub { | ||
28 | namespace 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 */ | ||
75 | class uri | ||
76 | { | ||
77 | public: | ||
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 | |||
94 | protected: | ||
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 */ | ||
112 | std::shared_ptr<context> create_context(const uri& uri, std::string *error = nullptr); | ||
113 | /** Return a safe default for a URI */ | ||
114 | uri default_uri(); | ||
115 | /** Special case function for the default function */ | ||
116 | std::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 */ | ||
120 | std::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 */ | ||
123 | uri 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. */ | ||
126 | void 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 | |||
27 | namespace hwstub { | ||
28 | namespace usb { | ||
29 | |||
30 | /** USB context | ||
31 | * | ||
32 | * Context based on libusb. */ | ||
33 | class context : public hwstub::context | ||
34 | { | ||
35 | protected: | ||
36 | context(libusb_context *ctx, bool cleanup_ctx); | ||
37 | public: | ||
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 | |||
47 | protected: | ||
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. */ | ||
63 | class device : public hwstub::device | ||
64 | { | ||
65 | friend class context; /* for ctor */ | ||
66 | protected: | ||
67 | device(std::shared_ptr<hwstub::context> ctx, libusb_device *dev); | ||
68 | public: | ||
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 | |||
81 | protected: | ||
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. */ | ||
94 | class handle : public hwstub::handle | ||
95 | { | ||
96 | protected: | ||
97 | handle(std::shared_ptr<hwstub::device> dev, libusb_device_handle *handle); | ||
98 | public: | ||
99 | virtual ~handle(); | ||
100 | /** set operation timeout */ | ||
101 | void set_timeout(std::chrono::milliseconds ms); | ||
102 | |||
103 | protected: | ||
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. */ | ||
121 | class rb_handle : public handle | ||
122 | { | ||
123 | friend class device; /* for find_intf() */ | ||
124 | protected: | ||
125 | rb_handle(std::shared_ptr<hwstub::device> dev, libusb_device_handle *handle, int intf); | ||
126 | public: | ||
127 | virtual ~rb_handle(); | ||
128 | |||
129 | protected: | ||
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 */ | ||
151 | class jz_handle : public handle | ||
152 | { | ||
153 | friend class device; /* for is_boot_dev() */ | ||
154 | protected: | ||
155 | jz_handle(std::shared_ptr<hwstub::device> dev, libusb_device_handle *handle); | ||
156 | public: | ||
157 | virtual ~jz_handle(); | ||
158 | |||
159 | protected: | ||
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 | |||
27 | namespace hwstub { | ||
28 | namespace virt { | ||
29 | |||
30 | class 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 */ | ||
36 | class context : public hwstub::context | ||
37 | { | ||
38 | protected: | ||
39 | context(); | ||
40 | public: | ||
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 | |||
58 | protected: | ||
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 */ | ||
76 | class hardware : public std::enable_shared_from_this<hardware> | ||
77 | { | ||
78 | public: | ||
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 */ | ||
94 | class dummy_hardware : public hardware | ||
95 | { | ||
96 | public: | ||
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 | |||
107 | protected: | ||
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. */ | ||
116 | class device : public hwstub::device | ||
117 | { | ||
118 | friend class context; /* for ctor */ | ||
119 | protected: | ||
120 | device(std::shared_ptr<hwstub::context> ctx, std::shared_ptr<hardware> dev); | ||
121 | public: | ||
122 | virtual ~device(); | ||
123 | /** Get native device (possibly null) */ | ||
124 | std::shared_ptr<hardware> native_device(); | ||
125 | |||
126 | protected: | ||
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. */ | ||
136 | class handle : public hwstub::handle | ||
137 | { | ||
138 | friend class device; /* for ctor */ | ||
139 | protected: | ||
140 | handle(std::shared_ptr<hwstub::device> dev); | ||
141 | public: | ||
142 | virtual ~handle(); | ||
143 | |||
144 | protected: | ||
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 @@ | |||
1 | CC=gcc | ||
2 | AR=ar | 1 | AR=ar |
3 | CFLAGS=-W -Wall -O2 `pkg-config --cflags libusb-1.0` -std=c99 -g -fPIC | 2 | INCLUDE=../include |
4 | LDFLAGS=`pkg-config --libs libusb-1.0` -fPIC | 3 | CFLAGS=-W -Wall -O2 `pkg-config --cflags libusb-1.0` -std=c99 -g -fPIC -D_XOPEN_SOURCE=700 -I$(INCLUDE) |
4 | CXXFLAGS=-W -Wall -O2 `pkg-config --cflags libusb-1.0` -std=c++11 -g -fPIC -D_XOPEN_SOURCE=700 -I$(INCLUDE) | ||
5 | LDFLAGS=`pkg-config --libs libusb-1.0` -fPIC -lpthread | ||
5 | LIB=libhwstub.a | 6 | LIB=libhwstub.a |
6 | SRC=$(wildcard *.c) | 7 | SRC=$(wildcard *.c) |
7 | OBJ=$(SRC:.c=.o) | 8 | SRCXX=$(wildcard *.cpp) |
9 | OBJ=$(SRCXX:.cpp=.oxx) $(SRCXX:.cpp=.o) | ||
8 | 10 | ||
9 | all: $(LIB) | 11 | all: $(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 | |||
25 | namespace hwstub { | ||
26 | |||
27 | std::ostream cnull(0); | ||
28 | std::wostream wcnull(0); | ||
29 | |||
30 | std::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 | |||
56 | context::context() | ||
57 | :m_next_cb_ref(0) | ||
58 | { | ||
59 | clear_debug(); | ||
60 | } | ||
61 | |||
62 | context::~context() | ||
63 | { | ||
64 | } | ||
65 | |||
66 | void context::set_debug(std::ostream& os) | ||
67 | { | ||
68 | m_debug = &os; | ||
69 | } | ||
70 | |||
71 | std::ostream& context::debug() | ||
72 | { | ||
73 | return *m_debug; | ||
74 | } | ||
75 | |||
76 | error 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 | |||
88 | error 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 | |||
95 | void 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 | |||
101 | void 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 | |||
122 | error 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 | |||
168 | context::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 | |||
178 | void 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 | |||
191 | void 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 | |||
202 | void 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 | |||
212 | context_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 | |||
218 | context_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 | |||
228 | void 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 | |||
236 | void 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 | |||
244 | void 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 | |||
252 | void 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 | |||
275 | void context_poller::thread(context_poller *poller) | ||
276 | { | ||
277 | poller->poll(); | ||
278 | } | ||
279 | |||
280 | /** | ||
281 | * Device | ||
282 | */ | ||
283 | device::device(std::shared_ptr<context> ctx) | ||
284 | :m_ctx(ctx), m_connected(true) | ||
285 | { | ||
286 | } | ||
287 | |||
288 | device::~device() | ||
289 | { | ||
290 | } | ||
291 | |||
292 | error 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 | |||
318 | void device::disconnect() | ||
319 | { | ||
320 | std::unique_lock<std::recursive_mutex> lock(m_mutex); | ||
321 | m_connected = false; | ||
322 | } | ||
323 | |||
324 | bool device::connected() | ||
325 | { | ||
326 | return m_connected; | ||
327 | } | ||
328 | |||
329 | std::shared_ptr<context> device::get_context() | ||
330 | { | ||
331 | return m_ctx.lock(); | ||
332 | } | ||
333 | |||
334 | /** | ||
335 | * Handle | ||
336 | */ | ||
337 | |||
338 | handle::handle(std::shared_ptr<device > dev) | ||
339 | :m_dev(dev) | ||
340 | { | ||
341 | } | ||
342 | |||
343 | handle::~handle() | ||
344 | { | ||
345 | } | ||
346 | |||
347 | std::shared_ptr<device> handle::get_device() | ||
348 | { | ||
349 | return m_dev; | ||
350 | } | ||
351 | |||
352 | error 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 | |||
367 | error 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 | |||
382 | error 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 | |||
397 | error 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 | |||
427 | error 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 | |||
457 | error 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 | |||
468 | namespace | ||
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 | |||
485 | error handle::get_version_desc(hwstub_version_desc_t& desc) | ||
486 | { | ||
487 | return helper_get_desc(this, HWSTUB_DT_VERSION, desc); | ||
488 | } | ||
489 | |||
490 | error handle::get_layout_desc(hwstub_layout_desc_t& desc) | ||
491 | { | ||
492 | return helper_get_desc(this, HWSTUB_DT_LAYOUT, desc); | ||
493 | } | ||
494 | |||
495 | error handle::get_stmp_desc(hwstub_stmp_desc_t& desc) | ||
496 | { | ||
497 | return helper_get_desc(this, HWSTUB_DT_STMP, desc); | ||
498 | } | ||
499 | |||
500 | error handle::get_pp_desc(hwstub_pp_desc_t& desc) | ||
501 | { | ||
502 | return helper_get_desc(this, HWSTUB_DT_PP, desc); | ||
503 | } | ||
504 | |||
505 | error handle::get_jz_desc(hwstub_jz_desc_t& desc) | ||
506 | { | ||
507 | return helper_get_desc(this, HWSTUB_DT_JZ, desc); | ||
508 | } | ||
509 | |||
510 | error 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 */ | ||
516 | dummy_device::dummy_device(std::shared_ptr<context> ctx) | ||
517 | :device(ctx) | ||
518 | { | ||
519 | } | ||
520 | |||
521 | dummy_device::~dummy_device() | ||
522 | { | ||
523 | } | ||
524 | |||
525 | error 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 | |||
531 | bool dummy_device::has_multiple_open() const | ||
532 | { | ||
533 | return true; | ||
534 | } | ||
535 | |||
536 | /** Dummy handle */ | ||
537 | dummy_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 | |||
561 | dummy_handle::~dummy_handle() | ||
562 | { | ||
563 | } | ||
564 | |||
565 | error 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 | |||
574 | error 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 | |||
583 | error 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 | |||
602 | error 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 | |||
609 | error dummy_handle::exec_dev(uint32_t addr, uint16_t flags) | ||
610 | { | ||
611 | (void) addr; | ||
612 | (void) flags; | ||
613 | return error::DUMMY; | ||
614 | } | ||
615 | |||
616 | error dummy_handle::status() const | ||
617 | { | ||
618 | error err = handle::status(); | ||
619 | return err == error::SUCCESS ? error::DUMMY : err; | ||
620 | } | ||
621 | |||
622 | size_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 | |||
30 | namespace hwstub { | ||
31 | namespace net { | ||
32 | |||
33 | /** | ||
34 | * Context | ||
35 | */ | ||
36 | context::context() | ||
37 | :m_state(state::HELLO), m_error(error::SUCCESS) | ||
38 | { | ||
39 | } | ||
40 | |||
41 | context::~context() | ||
42 | { | ||
43 | } | ||
44 | |||
45 | std::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 | |||
51 | std::string context::default_unix_path() | ||
52 | { | ||
53 | return "hwstub"; | ||
54 | } | ||
55 | |||
56 | std::string context::default_tcp_domain() | ||
57 | { | ||
58 | return "localhost"; | ||
59 | } | ||
60 | |||
61 | std::string context::default_tcp_port() | ||
62 | { | ||
63 | return "6666"; | ||
64 | } | ||
65 | |||
66 | namespace | ||
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 | |||
173 | std::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 | |||
183 | std::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 | |||
192 | std::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 | |||
202 | uint32_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 | |||
207 | hwstub::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 | |||
212 | error 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 | |||
246 | void context::destroy_device_list(void *ptr) | ||
247 | { | ||
248 | (void)ptr; | ||
249 | } | ||
250 | |||
251 | error 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 | |||
258 | bool 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 | |||
264 | uint32_t context::to_net_order(uint32_t u) | ||
265 | { | ||
266 | return htonl(u); | ||
267 | } | ||
268 | |||
269 | uint32_t context::from_net_order(uint32_t u) | ||
270 | { | ||
271 | return ntohl(u); | ||
272 | } | ||
273 | |||
274 | error 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 | |||
420 | void 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 | |||
451 | void 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 | */ | ||
482 | socket_context::socket_context(int socket_fd) | ||
483 | :m_socketfd(socket_fd) | ||
484 | { | ||
485 | set_timeout(std::chrono::milliseconds(1000)); | ||
486 | } | ||
487 | |||
488 | socket_context::~socket_context() | ||
489 | { | ||
490 | stop_context(); | ||
491 | close(m_socketfd); | ||
492 | } | ||
493 | |||
494 | void 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 | |||
504 | error 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 | |||
527 | error 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 | */ | ||
556 | device::device(std::shared_ptr<hwstub::context> ctx, uint32_t devid) | ||
557 | :hwstub::device(ctx), m_device_id(devid) | ||
558 | { | ||
559 | } | ||
560 | |||
561 | device::~device() | ||
562 | { | ||
563 | } | ||
564 | |||
565 | uint32_t device::device_id() | ||
566 | { | ||
567 | return m_device_id; | ||
568 | } | ||
569 | |||
570 | error 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 | |||
593 | bool device::has_multiple_open() const | ||
594 | { | ||
595 | return false; | ||
596 | } | ||
597 | |||
598 | /** | ||
599 | * Handle | ||
600 | */ | ||
601 | handle::handle(std::shared_ptr<hwstub::device> dev, uint32_t hid) | ||
602 | :hwstub::handle(dev), m_handle_id(hid) | ||
603 | { | ||
604 | } | ||
605 | |||
606 | handle::~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 | |||
624 | error 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 | |||
648 | error 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 | |||
671 | error 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 | |||
694 | error 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 | |||
715 | error handle::exec_dev(uint32_t addr, uint16_t flags) | ||
716 | { | ||
717 | (void) addr; | ||
718 | (void) flags; | ||
719 | return error::DUMMY; | ||
720 | } | ||
721 | |||
722 | error handle::status() const | ||
723 | { | ||
724 | return hwstub::handle::status(); | ||
725 | } | ||
726 | |||
727 | size_t handle::get_buffer_size() | ||
728 | { | ||
729 | return 2048; | ||
730 | } | ||
731 | |||
732 | /** | ||
733 | * Server | ||
734 | */ | ||
735 | server::server(std::shared_ptr<hwstub::context> ctx) | ||
736 | :m_context(ctx) | ||
737 | { | ||
738 | clear_debug(); | ||
739 | } | ||
740 | |||
741 | server::~server() | ||
742 | { | ||
743 | } | ||
744 | |||
745 | void 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 | |||
756 | std::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 | |||
766 | std::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 | |||
777 | std::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 | |||
783 | std::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 | |||
793 | void server::set_debug(std::ostream& os) | ||
794 | { | ||
795 | m_debug = &os; | ||
796 | } | ||
797 | |||
798 | std::ostream& server::debug() | ||
799 | { | ||
800 | return *m_debug; | ||
801 | } | ||
802 | |||
803 | server::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 | |||
809 | void server::client_thread2(server *s, client_state *cs) | ||
810 | { | ||
811 | s->client_thread(cs); | ||
812 | } | ||
813 | |||
814 | uint32_t server::to_net_order(uint32_t u) | ||
815 | { | ||
816 | return htonl(u); | ||
817 | } | ||
818 | |||
819 | uint32_t server::from_net_order(uint32_t u) | ||
820 | { | ||
821 | return ntohl(u); | ||
822 | } | ||
823 | |||
824 | void 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 | |||
922 | void 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 | |||
936 | void 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 | |||
956 | error 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 | */ | ||
1213 | socket_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 | |||
1221 | socket_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 | |||
1231 | std::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 | |||
1238 | void 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 | |||
1244 | int socket_server::from_srv_client(srv_client_t cli) | ||
1245 | { | ||
1246 | return (int)(intptr_t)cli; | ||
1247 | } | ||
1248 | |||
1249 | socket_server::srv_client_t socket_server::to_srv_client(int fd) | ||
1250 | { | ||
1251 | return (srv_client_t)(intptr_t)fd; | ||
1252 | } | ||
1253 | |||
1254 | void socket_server::discovery_thread1(socket_server *s) | ||
1255 | { | ||
1256 | s->discovery_thread(); | ||
1257 | } | ||
1258 | |||
1259 | void 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 | |||
1266 | error 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 | |||
1289 | error 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 | |||
1315 | void 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 | |||
27 | namespace hwstub { | ||
28 | namespace uri { | ||
29 | |||
30 | void 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 | |||
74 | uri::uri(const std::string& uri) | ||
75 | :m_uri(uri), m_valid(false) | ||
76 | { | ||
77 | parse(); | ||
78 | } | ||
79 | |||
80 | bool 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 | |||
98 | bool uri::validate_domain() | ||
99 | { | ||
100 | return true; | ||
101 | } | ||
102 | |||
103 | bool uri::validate_port() | ||
104 | { | ||
105 | return true; | ||
106 | } | ||
107 | |||
108 | void 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 | |||
158 | bool uri::valid() const | ||
159 | { | ||
160 | return m_valid; | ||
161 | } | ||
162 | |||
163 | std::string uri::error() const | ||
164 | { | ||
165 | return m_error; | ||
166 | } | ||
167 | |||
168 | std::string uri::full_uri() const | ||
169 | { | ||
170 | return m_uri; | ||
171 | } | ||
172 | |||
173 | std::string uri::scheme() const | ||
174 | { | ||
175 | return m_scheme; | ||
176 | } | ||
177 | |||
178 | std::string uri::domain() const | ||
179 | { | ||
180 | return m_domain; | ||
181 | } | ||
182 | |||
183 | std::string uri::port() const | ||
184 | { | ||
185 | return m_port; | ||
186 | } | ||
187 | |||
188 | std::string uri::path() const | ||
189 | { | ||
190 | return m_path; | ||
191 | } | ||
192 | |||
193 | /** Context creator */ | ||
194 | |||
195 | std::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 | |||
211 | std::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 | |||
283 | std::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 | |||
320 | uri default_uri() | ||
321 | { | ||
322 | return uri("default:"); | ||
323 | } | ||
324 | |||
325 | uri 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 | |||
25 | namespace hwstub { | ||
26 | namespace usb { | ||
27 | |||
28 | const uint8_t VR_GET_CPU_INFO = 0; | ||
29 | const uint8_t VR_SET_DATA_ADDRESS = 1; | ||
30 | const uint8_t VR_SET_DATA_LENGTH = 2; | ||
31 | const uint8_t VR_FLUSH_CACHES = 3; | ||
32 | const uint8_t VR_PROGRAM_START1 = 4; | ||
33 | const uint8_t VR_PROGRAM_START2 = 5; | ||
34 | |||
35 | /** | ||
36 | * Context | ||
37 | */ | ||
38 | |||
39 | context::context(libusb_context *ctx, bool cleanup_ctx) | ||
40 | :m_usb_ctx(ctx), m_cleanup_ctx(cleanup_ctx) | ||
41 | { | ||
42 | } | ||
43 | |||
44 | context::~context() | ||
45 | { | ||
46 | if(m_cleanup_ctx) | ||
47 | libusb_exit(m_usb_ctx); | ||
48 | } | ||
49 | |||
50 | std::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 | |||
60 | libusb_context *context::native_context() | ||
61 | { | ||
62 | return m_usb_ctx; | ||
63 | } | ||
64 | |||
65 | libusb_device *context::from_ctx_dev(ctx_dev_t dev) | ||
66 | { | ||
67 | return reinterpret_cast<libusb_device*>(dev); | ||
68 | } | ||
69 | |||
70 | hwstub::context::ctx_dev_t context::to_ctx_dev(libusb_device *dev) | ||
71 | { | ||
72 | return static_cast<ctx_dev_t>(dev); | ||
73 | } | ||
74 | |||
75 | error 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 | |||
89 | void context::destroy_device_list(void *ptr) | ||
90 | { | ||
91 | /* remove all references */ | ||
92 | libusb_free_device_list((libusb_device **)ptr, 1); | ||
93 | } | ||
94 | |||
95 | error 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 | |||
102 | bool 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 | */ | ||
111 | device::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 | |||
117 | device::~device() | ||
118 | { | ||
119 | libusb_unref_device(m_dev); | ||
120 | } | ||
121 | |||
122 | libusb_device *device::native_device() | ||
123 | { | ||
124 | return m_dev; | ||
125 | } | ||
126 | |||
127 | bool 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 | } | ||
143 | Lend: | ||
144 | if(config) | ||
145 | libusb_free_config_descriptor(config); | ||
146 | return false; | ||
147 | } | ||
148 | |||
149 | error 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 | |||
192 | Lend: | ||
193 | if(config) | ||
194 | libusb_free_config_descriptor(config); | ||
195 | libusb_close(h); | ||
196 | return error::ERROR; | ||
197 | } | ||
198 | |||
199 | bool device::has_multiple_open() const | ||
200 | { | ||
201 | /* libusb only allows one handle per device */ | ||
202 | return false; | ||
203 | } | ||
204 | |||
205 | uint8_t device::get_bus_number() | ||
206 | { | ||
207 | return libusb_get_bus_number(native_device()); | ||
208 | } | ||
209 | |||
210 | uint8_t device::get_address() | ||
211 | { | ||
212 | return libusb_get_device_address(native_device()); | ||
213 | } | ||
214 | |||
215 | uint16_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 | |||
223 | uint16_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 | */ | ||
234 | handle::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 | |||
240 | handle::~handle() | ||
241 | { | ||
242 | libusb_close(m_handle); | ||
243 | } | ||
244 | |||
245 | error 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 | |||
255 | error 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 | |||
264 | error 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 | |||
272 | void handle::set_timeout(std::chrono::milliseconds ms) | ||
273 | { | ||
274 | m_timeout = ms.count(); | ||
275 | } | ||
276 | |||
277 | /** | ||
278 | * Rockbox Handle | ||
279 | */ | ||
280 | |||
281 | rb_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 | |||
311 | rb_handle::~rb_handle() | ||
312 | { | ||
313 | } | ||
314 | |||
315 | size_t rb_handle::get_buffer_size() | ||
316 | { | ||
317 | return m_buf_size; | ||
318 | } | ||
319 | |||
320 | error 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 | |||
328 | error 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 | |||
336 | error 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 | |||
343 | error 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 | |||
353 | error 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 | |||
369 | error 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 | |||
384 | bool 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 | |||
412 | namespace | ||
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 | |||
423 | jz_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 | |||
430 | jz_handle::~jz_handle() | ||
431 | { | ||
432 | } | ||
433 | |||
434 | error 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 | |||
502 | error 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 | |||
511 | error 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 | |||
520 | error 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 | |||
570 | Lrestore: | ||
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 | |||
578 | size_t jz_handle::get_buffer_size() | ||
579 | { | ||
580 | return m_desc_layout.dBufferSize; | ||
581 | } | ||
582 | |||
583 | error 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 | |||
591 | error 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 | |||
611 | error 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 | |||
618 | error 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 | |||
634 | error 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 | |||
646 | error 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 | |||
659 | bool 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 | |||
667 | error 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 | |||
674 | error 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 | |||
681 | error 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 | |||
688 | error 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 | |||
697 | error 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 | |||
706 | error 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 | |||
713 | error 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 | |||
720 | error 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 | |||
25 | namespace hwstub { | ||
26 | namespace virt { | ||
27 | |||
28 | /** | ||
29 | * Context | ||
30 | */ | ||
31 | context::context() | ||
32 | { | ||
33 | } | ||
34 | |||
35 | context::~context() | ||
36 | { | ||
37 | } | ||
38 | |||
39 | std::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 | |||
45 | std::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 | |||
95 | bool 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 | |||
104 | bool 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 | |||
114 | std::shared_ptr<hardware> context::from_ctx_dev(ctx_dev_t dev) | ||
115 | { | ||
116 | return ((hardware *)dev)->shared_from_this(); | ||
117 | } | ||
118 | |||
119 | hwstub::context::ctx_dev_t context::to_ctx_dev(std::shared_ptr<hardware>& dev) | ||
120 | { | ||
121 | return (ctx_dev_t)dev.get(); | ||
122 | } | ||
123 | |||
124 | error 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 | |||
133 | void context::destroy_device_list(void *ptr) | ||
134 | { | ||
135 | (void) ptr; | ||
136 | } | ||
137 | |||
138 | error 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 | |||
145 | bool 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 | */ | ||
154 | device::device(std::shared_ptr<hwstub::context> ctx, std::shared_ptr<hardware> dev) | ||
155 | :hwstub::device(ctx), m_hwdev(dev) | ||
156 | { | ||
157 | } | ||
158 | |||
159 | device::~device() | ||
160 | { | ||
161 | } | ||
162 | |||
163 | std::shared_ptr<hardware> device::native_device() | ||
164 | { | ||
165 | return m_hwdev.lock(); | ||
166 | } | ||
167 | |||
168 | error 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 | |||
175 | bool device::has_multiple_open() const | ||
176 | { | ||
177 | return true; | ||
178 | } | ||
179 | |||
180 | /** | ||
181 | * Handle | ||
182 | */ | ||
183 | handle::handle(std::shared_ptr<hwstub::device> dev) | ||
184 | :hwstub::handle(dev) | ||
185 | { | ||
186 | m_hwdev = dynamic_cast<device*>(dev.get())->native_device(); | ||
187 | } | ||
188 | |||
189 | handle::~handle() | ||
190 | { | ||
191 | } | ||
192 | |||
193 | |||
194 | error 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 | |||
200 | error 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 | |||
206 | error 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 | |||
212 | error 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 | |||
218 | error 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 | |||
224 | error handle::status() const | ||
225 | { | ||
226 | return hwstub::handle::status(); | ||
227 | } | ||
228 | |||
229 | size_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 | */ | ||
238 | hardware::hardware() | ||
239 | { | ||
240 | } | ||
241 | |||
242 | hardware::~hardware() | ||
243 | { | ||
244 | } | ||
245 | |||
246 | /** | ||
247 | * Dummy hardware | ||
248 | */ | ||
249 | dummy_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 | |||
273 | dummy_hardware::~dummy_hardware() | ||
274 | { | ||
275 | } | ||
276 | |||
277 | error 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 | |||
286 | error 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 | |||
295 | error 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 | |||
314 | error 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 | |||
321 | error dummy_hardware::exec_dev(uint32_t addr, uint16_t flags) | ||
322 | { | ||
323 | (void) addr; | ||
324 | (void) flags; | ||
325 | return error::DUMMY; | ||
326 | } | ||
327 | |||
328 | size_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 @@ | |||
1 | INCLUDES+=-I$(ROOT_DIR) | 1 | INCLUDES+=-I$(ROOT_DIR) -I$(ROOT_DIR)/../include/ |
2 | LINKER_FILE=hwstub.lds | 2 | LINKER_FILE=hwstub.lds |
3 | TMP_LDS=$(BUILD_DIR)/link.lds | 3 | TMP_LDS=$(BUILD_DIR)/link.lds |
4 | TMP_MAP=$(BUILD_DIR)/hwstub.map | 4 | TMP_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 @@ | |||
1 | CC=gcc | 1 | CC=gcc |
2 | CXX=g++ | 2 | CXX=g++ |
3 | LD=g++ | 3 | LD=g++ |
4 | HWSTUB_INCLUDE_DIR=../include | ||
4 | HWSTUB_LIB_DIR=../lib | 5 | HWSTUB_LIB_DIR=../lib |
5 | REGTOOLS_INCLUDE_DIR=../../regtools/include | 6 | REGTOOLS_INCLUDE_DIR=../../regtools/include |
6 | REGTOOLS_LIB_DIR=../../regtools/lib | 7 | REGTOOLS_LIB_DIR=../../regtools/lib |
7 | CFLAGS=-Wall -O2 `pkg-config --cflags libusb-1.0` -std=c99 -g -I$(HWSTUB_LIB_DIR) -I$(REGTOOLS_INCLUDE_DIR) `pkg-config --cflags lua5.2` | 8 | INCLUDES=-I$(HWSTUB_INCLUDE_DIR) -I$(REGTOOLS_INCLUDE_DIR) `pkg-config --cflags lua5.2` `pkg-config --cflags libusb-1.0` |
8 | CXXFLAGS=-Wall -O2 `pkg-config --cflags libusb-1.0` -g -I$(HWSTUB_LIB_DIR) -I$(REGTOOLS_INCLUDE_DIR) `pkg-config --cflags lua5.2` | 9 | CFLAGS=-Wall -O2 -std=c99 -g $(INCLUDES) -D_XOPEN_SOURCE=600 |
9 | LDFLAGS=`pkg-config --libs libusb-1.0` `pkg-config --libs lua5.2` -lreadline -L$(HWSTUB_LIB_DIR) -L$(REGTOOLS_LIB_DIR) -lsocdesc -lhwstub `xml2-config --libs` | 10 | CXXFLAGS=-Wall -O2 -std=c++11 -g $(INCLUDES) |
10 | EXEC=hwstub_shell hwstub_load | 11 | LDFLAGS=`pkg-config --libs libusb-1.0` `pkg-config --libs lua5.2` -lreadline -L$(HWSTUB_LIB_DIR) -L$(REGTOOLS_LIB_DIR) -lsocdesc -lhwstub `xml2-config --libs` -pthread |
12 | EXEC=hwstub_shell hwstub_load hwstub_server hwstub_test | ||
11 | SRC=$(wildcard *.c) | 13 | SRC=$(wildcard *.c) |
12 | SRCXX=$(wildcard *.cpp) | 14 | SRCXX=$(wildcard *.cpp) |
13 | OBJ=$(SRC:.c=.o) $(SRCXX:.cpp=.o) | 15 | OBJ=$(SRC:.c=.o) $(SRCXX:.cpp=.o) |
@@ -33,6 +35,12 @@ hwstub_shell: hwstub_shell.o prompt.o $(LIBS) | |||
33 | hwstub_load: hwstub_load.o $(LIBS) | 35 | hwstub_load: hwstub_load.o $(LIBS) |
34 | $(LD) -o $@ $^ $(LDFLAGS) | 36 | $(LD) -o $@ $^ $(LDFLAGS) |
35 | 37 | ||
38 | hwstub_server: hwstub_server.o $(LIBS) | ||
39 | $(LD) -o $@ $^ $(LDFLAGS) | ||
40 | |||
41 | hwstub_test: hwstub_test.o $(LIBS) | ||
42 | $(LD) -o $@ $^ $(LDFLAGS) | ||
43 | |||
36 | clean: | 44 | clean: |
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 */ | ||
34 | volatile sig_atomic_t g_exit_loop = 0; | ||
35 | |||
36 | void do_signal(int sig) | ||
37 | { | ||
38 | g_exit_loop = 1; | ||
39 | } | ||
40 | |||
41 | std::shared_ptr<hwstub::context> g_ctx; | ||
42 | std::shared_ptr<hwstub::net::server> g_srv; | ||
43 | |||
44 | int 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 | |||
56 | int 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 */ | ||
33 | volatile sig_atomic_t g_exit_loop = 0; | ||
34 | |||
35 | void do_signal(int sig) | ||
36 | { | ||
37 | g_exit_loop = 1; | ||
38 | } | ||
39 | |||
40 | std::shared_ptr<hwstub::context> g_ctx; | ||
41 | |||
42 | void 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 | |||
54 | const 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 | |||
68 | void 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 | |||
109 | void 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 | |||
130 | void dev_changed(std::shared_ptr<hwstub::context> ctx, bool arrived, std::shared_ptr<hwstub::device> dev) | ||
131 | { | ||
132 | print_device(dev, arrived); | ||
133 | } | ||
134 | |||
135 | void 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 | |||
148 | int 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 | |||
159 | int 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 | |||