summaryrefslogtreecommitdiffstats
path: root/utils/hwstub/include/hwstub.hpp
blob: 90b29ebf9da22d6e1e36561dd1e4b34ef138d655 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Copyright (C) 2015 by Amaury Pouly
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/
#ifndef __HWSTUB_HPP__
#define __HWSTUB_HPP__

#include "hwstub_protocol.h"
#include <string>
#include <mutex>
#include <vector>
#include <cstdint>
#include <atomic>
#include <memory>
#include <chrono>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <ostream>
#include <functional>

namespace hwstub {

class context;
class device;
class handle;
class context_poller;

/** C++ equivalent of /dev/null for streams */
extern std::ostream cnull;
extern std::wostream wcnull;

/** Errors */
enum class error
{
    SUCCESS, /** Success */
    ERROR, /** Unspecified error */
    DISCONNECTED, /** Device has been disconnected */
    PROBE_FAILURE, /** Device did not pass probing */
    NO_CONTEXT, /** The context has been destroyed */
    USB_ERROR, /** Unspecified USB error */
    DUMMY, /** Call on dummy device/handle */
    NO_SERVER, /** The server could not be reached */
    SERVER_DISCONNECTED, /** Context got disconnected from the server */
    SERVER_MISMATCH, /** The server is not compatible with hwstub */
    PROTOCOL_ERROR, /** Network protocol error */
    NET_ERROR, /** Network error */
    TIMEOUT, /** Operation timed out */
    OVERFLW, /** Operation stopped to prevent buffer overflow */
    UNIMPLEMENTED, /** Operation has not been implemented */
    UNSUPPORTED, /** Operation is not supported by device/protocol */
};

/** Return a string explaining the error */
std::string error_string(error err);

/** NOTE Multithreading:
 * Unless specified, all methods are thread-safe
 */

/** Context
 *
 * A context provides a list of available devices and may notify devices
 * arrival and departure.
 *
 * A context provides a way to regularly poll for derive changes. There are two
 * ways to manually force an update:
 * - on call to get_device_list(), the list is always refetched
 * - on call to update_list() to force list update
 * Note that automatic polling is disabled by default.
 */
class context : public std::enable_shared_from_this<context>
{
protected:
    context();
public:
    /** On destruction, the context will destroy all the devices. */
    virtual ~context();
    /** Get device list, clears the list in argument first. All devices in the list
     * are still connected (or believe to be). This function will update the device
     * list. */
    error get_device_list(std::vector<std::shared_ptr<device>>& list);
    /** Force the context to update its internal list of devices. */
    error update_list();
    /** Ask the context to automatically poll for device changes.
     * Note that this might spawn a new thread to do so, in which case it will
     * be destroyed/stop on deletetion or when stop_polling() is called. If
     * polling is already enabled, this function will change the polling interval. */
    void start_polling(std::chrono::milliseconds interval = std::chrono::milliseconds(250));
    /** Stop polling. */
    void stop_polling();
    /** Register a notification callback with arguments (context,arrived,device)
     * WARNING the callback may be called asynchronously ! */
    typedef std::function<void(std::shared_ptr<context>, bool, std::shared_ptr<device>)> notification_callback_t;
    typedef size_t callback_ref_t;
    callback_ref_t register_callback(const notification_callback_t& fn);
    void unregister_callback(callback_ref_t ref);
    /** Return a dummy device that does nothing. A dummy device might be useful
     * in cases where one still wants a valid pointer to no device. This dummy
     * device does not appear in the list, it can be opened and will fail all requests. */
    error get_dummy_device(std::shared_ptr<device>& dev);
    /** Set/clear debug output for this context */
    void set_debug(std::ostream& os);
    inline void clear_debug() { set_debug(cnull); }
    /** Get debug output for this context */
    std::ostream& debug();

protected:
    /** Notify the context about a device. If arrived is true, the device is
     * added to the list and a reference will be added to it. If arrived is false,
     * the device is marked as disconnected(), removed from the list and a
     * reference will be removed from it. Adding a device that matches an
     * existing one will do nothing. */
    void change_device(bool arrived, std::shared_ptr<device> dev);
    /** Do device notification */
    void notify_device(bool arrived, std::shared_ptr<device> dev);
    /** Opaque device type */
    typedef void* ctx_dev_t;
    /** Fetch the device list. Each item in the list is an opaque pointer. The function
     * can also provide a pointer that will be used to free the list resources
     * if necessary. Return <0 on error. */
    virtual error fetch_device_list(std::vector<ctx_dev_t>& list, void*& ptr) = 0;
    /** Destroy the resources created to get the list. */
    virtual void destroy_device_list(void *ptr) = 0;
    /** Create a new hwstub device from the opaque pointer. Return <0 on error.
     * This function needs not add a reference to the newly created device. */
    virtual error create_device(ctx_dev_t dev, std::shared_ptr<device>& hwdev) = 0;
    /** Return true if the opaque pointer corresponds to the device. Only called
     * from map_device(). */
    virtual bool match_device(ctx_dev_t dev, std::shared_ptr<device> hwdev) = 0;
    /** Check if a device matches another one in the list */
    bool contains_dev(const std::vector<device*>& list, ctx_dev_t dev);

    struct callback_t
    {
        notification_callback_t callback;
        callback_ref_t ref;
    };

    std::shared_ptr<context_poller> m_poller; /* poller object */
    std::recursive_mutex m_mutex; /* list mutex */
    std::vector<std::shared_ptr<device>> m_devlist; /* list of devices */
    std::vector<callback_t> m_callbacks; /* list of callbacks */
    callback_ref_t m_next_cb_ref; /* next callback reference */
    std::ostream *m_debug; /* debug stream */
};

/** Context Poller
 *
 * This class provides a way to regularly poll a context for device changes.
 * NOTE this class is not meant to be used directly since context already
 * provides access to it via start_polling() and stop_polling() */
class context_poller
{
public:
    context_poller(std::weak_ptr<context> ctx, std::chrono::milliseconds interval = std::chrono::milliseconds(250));
    ~context_poller();
    /** Set polling interval (in milliseconds) (works even if polling already enabled) */
    void set_interval(std::chrono::milliseconds interval);
    /** Start polling */
    void start();
    /** Stop polling. After return, no function will be made. */
    void stop();

protected:
    static void thread(context_poller *poller);
    void poll();

    std::weak_ptr<context> m_ctx; /* context */
    bool m_running; /* are we running ? */
    bool m_exit; /* exit flag for the thread */
    std::thread m_thread; /* polling thread */
    std::mutex m_mutex; /* mutex lock */
    std::condition_variable m_cond; /* signalling condition */
    std::chrono::milliseconds m_interval; /* Interval */
};

/** Device
 *
 * A device belongs to a context.
 * Note that a device only keeps a weak pointer to the context, so it is possible
 * for the context to be destroyed during the life of the device, in which case
 * all operations on it will fail. */
class device : public std::enable_shared_from_this<device>
{
protected:
    device(std::shared_ptr<context> ctx);
public:
    virtual ~device();
    /** Open a handle to the device. Several handles may be opened concurrently. */
    error open(std::shared_ptr<handle>& handle);
    /** Disconnect the device. This will notify the context that the device is gone. */
    void disconnect();
    /** Returns true if the device is still connected. */
    bool connected();
    /** Get context (might be empty) */
    std::shared_ptr<context> get_context();

protected:
    /** Some subsystems allow for hardware to be open several times and some do not.
     * For example, libusb only allows one handle per device. To workaround this issue,
     * open() will do some magic to allow for several open() even when the hardware
     * supports only one. If the device does not support multiple
     * handles (as reported by has_multiple_open()), open() will only call open_dev()
     * the first time the device is opened and will redirect other open() calls to
     * this handle using proxy handles. If the device supports multiple handles,
     * open() will simply call open_dev() each time.
     * The open_dev() does not need to care about this magic and only needs to
     * open the device and returns the handle to it.
     * NOTE this function is always called with the mutex locked already. */
    virtual error open_dev(std::shared_ptr<handle>& handle) = 0;
    /** Return true if device can be opened multiple times. In this case, each
     * call to open() will generate a call to do_open(). Otherwise, proxy handles
     * will be created for each open() and do_open() will only be called the first
     * time. */
    virtual bool has_multiple_open() const = 0;

    std::weak_ptr<context> m_ctx; /* pointer to context */
    std::recursive_mutex m_mutex; /* device state mutex: ref count, connection status */
    bool m_connected; /* false once device is disconnected */
    std::weak_ptr<handle> m_handle; /* weak pointer to the opened handle (if !has_multiple_open()) */
};

/** Handle
 *
 * A handle is tied to a device and provides access to the stub operation.
 * The handle is reference counted and is destroyed
 * when its reference count decreased to zero.
 */
class handle : public std::enable_shared_from_this<handle>
{
protected:
    /** A handle will always hold a reference to the device */
    handle(std::shared_ptr<device> dev);
public:
    /** When destroyed, the handle will release its reference to the device */
    virtual ~handle();
    /** Return associated device */
    std::shared_ptr<device> get_device();
    /** Fetch a descriptor, buf_sz is the size of the buffer and is updated to
     * reflect the number of bytes written to the buffer. */
    error get_desc(uint16_t desc, void *buf, size_t& buf_sz);
    /** Fetch part of the log, buf_sz is the size of the buffer and is updated to
     * reflect the number of bytes written to the buffer. */
    error get_log(void *buf, size_t& buf_sz);
    /** Ask the stub to execute some code.
     * NOTE: this may kill the stub */
    error exec(uint32_t addr, uint16_t flags);
    /** Read/write some device memory. sz is the size of the buffer and is updated to
     * reflect the number of bytes written to the buffer.
     * NOTE: the stub may or may not recover from bad read/write, so this may kill it.
     * NOTE: the default implemtentation of read() and write() will split transfers
     *       according to the buffer size and call read_dev() and write_dev() */
    error read(uint32_t addr, void *buf, size_t& sz, bool atomic);
    error write(uint32_t addr, const void *buf, size_t& sz, bool atomic);
    /** Execute a general cop operation: if out_data is not null, data is appended to header,
     * if in_data is not null, a read operation follows to retrieve some data.
     * The in_data parameters is updated to reflect the number of transfered bytes */
    error cop_op(uint8_t op, uint8_t args[HWSTUB_COP_ARGS], const void *out_data,
        size_t out_size, void *in_data, size_t *in_size);
    /** Execute a coprocessor read operation */
    error read32_cop(uint8_t args[HWSTUB_COP_ARGS], uint32_t& value);
    /** Execute a coprocessor write operation */
    error write32_cop(uint8_t args[HWSTUB_COP_ARGS], uint32_t value);
    /** Get device buffer size: any read() or write() greater than this size
     * will be split into several transaction to avoid overflowing device
     * buffer. */
    virtual size_t get_buffer_size() = 0;
    /** Check a handle status. A successful handle does not guarantee successful
     * operations. An invalid handle will typically report probing failure (the
     * device did not pass probing) or disconnection.
     * The default implemtentation will test if context still exists and connection status. */
    virtual error status() const;
    /** Shorthand for status() == HWSTUB_SUCCESS */
    inline bool valid() const { return status() == error::SUCCESS; }

    /** Helper functions */
    error get_version_desc(hwstub_version_desc_t& desc);
    error get_layout_desc(hwstub_layout_desc_t& desc);
    error get_stmp_desc(hwstub_stmp_desc_t& desc);
    error get_pp_desc(hwstub_pp_desc_t& desc);
    error get_jz_desc(hwstub_jz_desc_t& desc);
    error get_target_desc(hwstub_target_desc_t& desc);

protected:
    /** The get_desc(), get_log(), exec(), read() and write() function
     * take care of details so that each implementation can safely assume that
     * the hwstub context exists and will not be destroyed during the execution
     * of the function. It will also return early if the device has been disconnected.
     * 
     * NOTE on read() and write():
     * Since devices have a limited buffer, big transfers must be split into
     * smaller ones. The high-level read() and write() functions perform this
     * splitting in a generic way, based on get_buffer_size(), and calling read_dev()
     * and write_dev() which do the actual operation.
     * These function can safely assume that sz <= get_buffer_size(). */
    virtual error read_dev(uint32_t addr, void *buf, size_t& sz, bool atomic) = 0;
    virtual error write_dev(uint32_t addr, const void *buf, size_t& sz, bool atomic) = 0;
    virtual error get_dev_desc(uint16_t desc, void *buf, size_t& buf_sz) = 0;
    virtual error get_dev_log(void *buf, size_t& buf_sz) = 0;
    virtual error exec_dev(uint32_t addr, uint16_t flags) = 0;
    /* cop operation: if out_data is not null, data is appended to header, if
     * in_data is not null, a READ2 operation follows to retrieve some data
     * The in_data parameters is updated to reflect the number of transfered bytes*/
    virtual error cop_dev(uint8_t op, uint8_t args[HWSTUB_COP_ARGS], const void *out_data,
        size_t out_size, void *in_data, size_t *in_size) = 0;

    std::shared_ptr<device> m_dev; /* pointer to device */
    std::atomic<int> m_refcnt; /* reference count */
    std::recursive_mutex m_mutex; /* operation mutex to serialise operations */
};

/** Dummy device */
class dummy_device : public device
{
    friend class context; /* for ctor */
protected:
    dummy_device(std::shared_ptr<context> ctx);
public:
    virtual ~dummy_device();

protected:
    virtual error open_dev(std::shared_ptr<handle>& handle);
    virtual bool has_multiple_open() const;
};

/** Dummy handle */
class dummy_handle : public handle
{
    friend class dummy_device;
protected:
    dummy_handle(std::shared_ptr<device> dev);
public:
    virtual ~dummy_handle();

protected:
    virtual error read_dev(uint32_t addr, void *buf, size_t& sz, bool atomic);
    virtual error write_dev(uint32_t addr, const void *buf, size_t& sz, bool atomic);
    virtual error get_dev_desc(uint16_t desc, void *buf, size_t& buf_sz);
    virtual error get_dev_log(void *buf, size_t& buf_sz);
    virtual error exec_dev(uint32_t addr, uint16_t flags);
    virtual error cop_dev(uint8_t op, uint8_t args[HWSTUB_COP_ARGS], const void *out_data,
        size_t out_size, void *in_data, size_t *in_size);
    virtual error status() const;
    virtual size_t get_buffer_size();

    struct hwstub_version_desc_t m_desc_version;
    struct hwstub_layout_desc_t m_desc_layout;
    struct hwstub_target_desc_t m_desc_target;
};

} // namespace hwstub

#endif /* __HWSTUB_HPP__ */