summaryrefslogtreecommitdiff
path: root/utils/hwstub/include/hwstub.hpp
diff options
context:
space:
mode:
Diffstat (limited to 'utils/hwstub/include/hwstub.hpp')
-rw-r--r--utils/hwstub/include/hwstub.hpp351
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
37namespace hwstub {
38
39class context;
40class device;
41class handle;
42class context_poller;
43
44/** C++ equivalent of /dev/null for streams */
45extern std::ostream cnull;
46extern std::wostream wcnull;
47
48/** Errors */
49enum class error
50{
51 SUCCESS, /** Success */
52 ERROR, /** Unspecified error */
53 DISCONNECTED, /** Device has been disconnected */
54 PROBE_FAILURE, /** Device did not pass probing */
55 NO_CONTEXT, /** The context has been destroyed */
56 USB_ERROR, /** Unspecified USB error */
57 DUMMY, /** Call on dummy device/handle */
58 NO_SERVER, /** The server could not be reached */
59 SERVER_DISCONNECTED, /** Context got disconnected from the server */
60 SERVER_MISMATCH, /** The server is not compatible with hwstub */
61 PROTOCOL_ERROR, /** Network protocol error */
62 NET_ERROR, /** Network error */
63 TIMEOUT, /** Operation timed out */
64 OVERFLW, /** Operation stopped to prevent buffer overflow */
65};
66
67/** Return a string explaining the error */
68std::string error_string(error err);
69
70/** NOTE Multithreading:
71 * Unless specified, all methods are thread-safe
72 */
73
74/** Context
75 *
76 * A context provides a list of available devices and may notify devices
77 * arrival and departure.
78 *
79 * A context provides a way to regularly poll for derive changes. There are two
80 * ways to manually force an update:
81 * - on call to get_device_list(), the list is already refetched
82 * - on call to update_list() to force list update
83 * Note that automatic polling is disabled by default.
84 */
85class context : public std::enable_shared_from_this<context>
86{
87protected:
88 context();
89public:
90 /** On destruction, the context will destroy all the devices. */
91 virtual ~context();
92 /** Get device list, clears the list in argument first. All devices in the list
93 * are still connected (or believe to be). This function will update the device
94 * list. */
95 error get_device_list(std::vector<std::shared_ptr<device>>& list);
96 /** Force the context to update its internal list of devices. */
97 error update_list();
98 /** Ask the context to automatically poll for device changes.
99 * Note that this might spawn a new thread to do so, in which case it will
100 * be destroyed/stop on deletetion or when stop_polling() is called. If
101 * polling is already enabled, this function will change the polling interval. */
102 void start_polling(std::chrono::milliseconds interval = std::chrono::milliseconds(250));
103 /** Stop polling. */
104 void stop_polling();
105 /** Register a notification callback with arguments (context,arrived,device)
106 * WARNING the callback may be called asynchronously ! */
107 typedef std::function<void(std::shared_ptr<context>, bool, std::shared_ptr<device>)> notification_callback_t;
108 typedef size_t callback_ref_t;
109 callback_ref_t register_callback(const notification_callback_t& fn);
110 void unregister_callback(callback_ref_t ref);
111 /** Return a dummy device that does nothing. A dummy device might be useful
112 * in cases where one still wants a valid pointer to no device. This dummy
113 * device does not appear in the list, it can be opened and will fail all requests. */
114 error get_dummy_device(std::shared_ptr<device>& dev);
115 /** Set/clear debug output for this context */
116 void set_debug(std::ostream& os);
117 inline void clear_debug() { set_debug(cnull); }
118 /** Get debug output for this context */
119 std::ostream& debug();
120
121protected:
122 /** Notify the context about a device. If arrived is true, the device is
123 * added to the list and a reference will be added to it. If arrived is false,
124 * the device is marked as disconnected(), removed from the list and a
125 * reference will be removed from it. Adding a device that matches an
126 * existing one will do nothing. */
127 void change_device(bool arrived, std::shared_ptr<device> dev);
128 /** Do device notification */
129 void notify_device(bool arrived, std::shared_ptr<device> dev);
130 /** Opaque device type */
131 typedef void* ctx_dev_t;
132 /** Fetch the device list. Each item in the list is an opaque pointer. The function
133 * can also provide a pointer that will be used to free the list resources
134 * if necessary. Return <0 on error. */
135 virtual error fetch_device_list(std::vector<ctx_dev_t>& list, void*& ptr) = 0;
136 /** Destroy the resources created to get the list. */
137 virtual void destroy_device_list(void *ptr) = 0;
138 /** Create a new hwstub device from the opaque pointer. Return <0 on error.
139 * This function needs not add a reference to the newly created device. */
140 virtual error create_device(ctx_dev_t dev, std::shared_ptr<device>& hwdev) = 0;
141 /** Return true if the opaque pointer corresponds to the device. Only called
142 * from map_device(). */
143 virtual bool match_device(ctx_dev_t dev, std::shared_ptr<device> hwdev) = 0;
144 /** Check if a device matches another one in the list */
145 bool contains_dev(const std::vector<device*>& list, ctx_dev_t dev);
146
147 struct callback_t
148 {
149 notification_callback_t callback;
150 callback_ref_t ref;
151 };
152
153 std::shared_ptr<context_poller> m_poller; /* poller object */
154 std::recursive_mutex m_mutex; /* list mutex */
155 std::vector<std::shared_ptr<device>> m_devlist; /* list of devices */
156 std::vector<callback_t> m_callbacks; /* list of callbacks */
157 callback_ref_t m_next_cb_ref; /* next callback reference */
158 std::ostream *m_debug; /* debug stream */
159};
160
161/** Context Poller
162 *
163 * This class provides a way to regularly poll a context for device changes.
164 * NOTE this class is not meant to be used directly since context already
165 * provides access to it via start_polling() and stop_polling() */
166class context_poller
167{
168public:
169 context_poller(std::weak_ptr<context> ctx, std::chrono::milliseconds interval = std::chrono::milliseconds(250));
170 ~context_poller();
171 /** Set polling interval (in milliseconds) (works even if polling already enabled) */
172 void set_interval(std::chrono::milliseconds interval);
173 /** Start polling */
174 void start();
175 /** Stop polling. After return, no function will be made. */
176 void stop();
177
178protected:
179 static void thread(context_poller *poller);
180 void poll();
181
182 std::weak_ptr<context> m_ctx; /* context */
183 bool m_running; /* are we running ? */
184 bool m_exit; /* exit flag for the thread */
185 std::thread m_thread; /* polling thread */
186 std::mutex m_mutex; /* mutex lock */
187 std::condition_variable m_cond; /* signalling condition */
188 std::chrono::milliseconds m_interval; /* Interval */
189};
190
191/** Device
192 *
193 * A device belongs to a context.
194 * Note that a device only keeps a weak pointer to the context, so it is possible
195 * for the context to be destroyed during the life of the device, in which case
196 * all operations on it will fail. */
197class device : public std::enable_shared_from_this<device>
198{
199protected:
200 device(std::shared_ptr<context> ctx);
201public:
202 virtual ~device();
203 /** Open a handle to the device. Several handles may be opened concurrently. */
204 error open(std::shared_ptr<handle>& handle);
205 /** Disconnect the device. This will notify the context that the device is gone. */
206 void disconnect();
207 /** Returns true if the device is still connected. */
208 bool connected();
209 /** Get context (might be empty) */
210 std::shared_ptr<context> get_context();
211
212protected:
213 /** Some subsystems allow for hardware to be open several times and so do not.
214 * For example, libusb only allows one handle per device. To workaround this issue,
215 * open() will do some magic to allow for several open() even when the hardware
216 * supports only one. If the device does not support multiple
217 * handles (as reported by has_multiple_open()), open() will only call open_dev()
218 * the first time the device is opened and will redirect other open() calls to
219 * this handle using proxy handles. If the device supports multiple handles,
220 * open() will simply call open_dev() each time.
221 * The open_dev() does not need to care about this magic and only needs to
222 * open the device and returns the handle to it.
223 * NOTE this function is always called with the mutex locked already. */
224 virtual error open_dev(std::shared_ptr<handle>& handle) = 0;
225 /** Return true if device can be opened multiple times. In this case, each
226 * call to open() will generate a call to do_open(). Otherwise, proxy handles
227 * will be created for each open() and do_open() will only be called the first
228 * time. */
229 virtual bool has_multiple_open() const = 0;
230
231 std::weak_ptr<context> m_ctx; /* pointer to context */
232 std::recursive_mutex m_mutex; /* device state mutex: ref count, connection status */
233 bool m_connected; /* false once device is disconnected */
234 std::weak_ptr<handle> m_handle; /* weak pointer to the opened handle (if !has_multiple_open()) */
235};
236
237/** Handle
238 *
239 * A handle is tied to a device and provides access to the stub operation.
240 * The handle is reference counted and is destroyed
241 * when its reference count decreased to zero.
242 */
243class handle : public std::enable_shared_from_this<handle>
244{
245protected:
246 /** A handle will always hold a reference to the device */
247 handle(std::shared_ptr<device> dev);
248public:
249 /** When destroyed, the handle will release its reference to the device */
250 virtual ~handle();
251 /** Return associated device */
252 std::shared_ptr<device> get_device();
253 /** Fetch a descriptor, buf_sz is the size of the buffer and is updated to
254 * reflect the number of bytes written to the buffer. */
255 error get_desc(uint16_t desc, void *buf, size_t& buf_sz);
256 /** Fetch part of the log, buf_sz is the size of the buffer and is updated to
257 * reflect the number of bytes written to the buffer. */
258 error get_log(void *buf, size_t& buf_sz);
259 /** Ask the stub to execute some code.
260 * NOTE: this may kill the stub */
261 error exec(uint32_t addr, uint16_t flags);
262 /** Read/write some device memory. sz is the size of the buffer and is updated to
263 * reflect the number of bytes written to the buffer.
264 * NOTE: the stub may or may not recover from bad read/write, so this may kill it.
265 * NOTE: the default implemtentation of read() and write() will split transfers
266 * according to the buffer size and call read_dev() and write_dev() */
267 error read(uint32_t addr, void *buf, size_t& sz, bool atomic);
268 error write(uint32_t addr, const void *buf, size_t& sz, bool atomic);
269 /** Get device buffer size: any read() or write() greater than this size
270 * will be split into several transaction to avoid overflowing device
271 * buffer. */
272 virtual size_t get_buffer_size() = 0;
273 /** Check a handle status. A successful handle does not guarantee successful
274 * operations. An invalid handle will typically report probing failure (the
275 * device did not pass probing) or disconnection.
276 * The default implemtentation will test if context still exists and connection status. */
277 virtual error status() const;
278 /** Shorthand for status() == HWSTUB_SUCCESS */
279 inline bool valid() const { return status() == error::SUCCESS; }
280
281 /** Helper functions */
282 error get_version_desc(hwstub_version_desc_t& desc);
283 error get_layout_desc(hwstub_layout_desc_t& desc);
284 error get_stmp_desc(hwstub_stmp_desc_t& desc);
285 error get_pp_desc(hwstub_pp_desc_t& desc);
286 error get_jz_desc(hwstub_jz_desc_t& desc);
287 error get_target_desc(hwstub_target_desc_t& desc);
288
289protected:
290 /** The get_desc(), get_log(), exec(), read() and write() function
291 * take care of details so that each implementation can safely assume that
292 * the hwstub context exists and will not be destroyed during the execution
293 * of the function. It will also return early if the device has been disconnected.
294 *
295 * NOTE on read() and write():
296 * Since devices have a limited buffer, big transfers must be split into
297 * smaller ones. The high-level read() and write() functions perform this
298 * splitting in a generic way, based on get_buffer_size(), and calling read_dev()
299 * and write_dev() which do the actual operation.
300 * These function can safely assume that sz <= get_buffer_size(). */
301 virtual error read_dev(uint32_t addr, void *buf, size_t& sz, bool atomic) = 0;
302 virtual error write_dev(uint32_t addr, const void *buf, size_t& sz, bool atomic) = 0;
303 virtual error get_dev_desc(uint16_t desc, void *buf, size_t& buf_sz) = 0;
304 virtual error get_dev_log(void *buf, size_t& buf_sz) = 0;
305 virtual error exec_dev(uint32_t addr, uint16_t flags) = 0;
306
307 std::shared_ptr<device> m_dev; /* pointer to device */
308 std::atomic<int> m_refcnt; /* reference count */
309 std::recursive_mutex m_mutex; /* operation mutex to serialise operations */
310};
311
312/** Dummy device */
313class dummy_device : public device
314{
315 friend class context; /* for ctor */
316protected:
317 dummy_device(std::shared_ptr<context> ctx);
318public:
319 virtual ~dummy_device();
320
321protected:
322 virtual error open_dev(std::shared_ptr<handle>& handle);
323 virtual bool has_multiple_open() const;
324};
325
326/** Dummy handle */
327class dummy_handle : public handle
328{
329 friend class dummy_device;
330protected:
331 dummy_handle(std::shared_ptr<device> dev);
332public:
333 virtual ~dummy_handle();
334
335protected:
336 virtual error read_dev(uint32_t addr, void *buf, size_t& sz, bool atomic);
337 virtual error write_dev(uint32_t addr, const void *buf, size_t& sz, bool atomic);
338 virtual error get_dev_desc(uint16_t desc, void *buf, size_t& buf_sz);
339 virtual error get_dev_log(void *buf, size_t& buf_sz);
340 virtual error exec_dev(uint32_t addr, uint16_t flags);
341 virtual error status() const;
342 virtual size_t get_buffer_size();
343
344 struct hwstub_version_desc_t m_desc_version;
345 struct hwstub_layout_desc_t m_desc_layout;
346 struct hwstub_target_desc_t m_desc_target;
347};
348
349} // namespace hwstub
350
351#endif /* __HWSTUB_HPP__ */