diff options
Diffstat (limited to 'utils/hwstub/include/hwstub.hpp')
-rw-r--r-- | utils/hwstub/include/hwstub.hpp | 351 |
1 files changed, 351 insertions, 0 deletions
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__ */ | ||