diff options
Diffstat (limited to 'utils/hwstub/lib')
-rw-r--r-- | utils/hwstub/lib/Makefile | 13 | ||||
-rw-r--r-- | utils/hwstub/lib/hwstub.cpp | 627 | ||||
-rw-r--r-- | utils/hwstub/lib/hwstub.h | 70 | ||||
-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 |
8 files changed, 3382 insertions, 75 deletions
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.h b/utils/hwstub/lib/hwstub.h deleted file mode 100644 index 4d12de8eda..0000000000 --- a/utils/hwstub/lib/hwstub.h +++ /dev/null | |||
@@ -1,70 +0,0 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2012 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__ | ||
22 | #define __HWSTUB__ | ||
23 | |||
24 | #include <libusb.h> | ||
25 | #include "hwstub_protocol.h" | ||
26 | |||
27 | #ifdef __cplusplus | ||
28 | extern "C" { | ||
29 | #endif | ||
30 | |||
31 | /** | ||
32 | * | ||
33 | * Low-Level interface | ||
34 | * | ||
35 | */ | ||
36 | |||
37 | struct hwstub_device_t; | ||
38 | |||
39 | /* Returns hwstub interface, or -1 if none was found */ | ||
40 | int hwstub_probe(libusb_device *dev); | ||
41 | /* Helper function which returns a list of all hwstub devices found. The caller | ||
42 | * must unref all of them when done, possibly using libusb_free_device_list(). | ||
43 | * Return number of devices or <0 on error */ | ||
44 | ssize_t hwstub_get_device_list(libusb_context *ctx, libusb_device ***list); | ||
45 | /* Returns NULL on error */ | ||
46 | struct hwstub_device_t *hwstub_open(libusb_device_handle *handle); | ||
47 | /* Returns 0 on success. Does *NOT* close the usb handle */ | ||
48 | int hwstub_release(struct hwstub_device_t *dev); | ||
49 | |||
50 | /* Returns number of bytes filled */ | ||
51 | int hwstub_get_desc(struct hwstub_device_t *dev, uint16_t desc, void *info, size_t sz); | ||
52 | /* Returns number of bytes filled */ | ||
53 | int hwstub_get_log(struct hwstub_device_t *dev, void *buf, size_t sz); | ||
54 | /* Returns number of bytes written/read or <0 on error */ | ||
55 | int hwstub_read(struct hwstub_device_t *dev, uint32_t addr, void *buf, size_t sz); | ||
56 | int hwstub_read_atomic(struct hwstub_device_t *dev, uint32_t addr, void *buf, size_t sz); | ||
57 | int hwstub_write(struct hwstub_device_t *dev, uint32_t addr, const void *buf, size_t sz); | ||
58 | int hwstub_write_atomic(struct hwstub_device_t *dev, uint32_t addr, const void *buf, size_t sz); | ||
59 | int hwstub_rw_mem(struct hwstub_device_t *dev, int read, uint32_t addr, void *buf, size_t sz); | ||
60 | int hwstub_rw_mem_atomic(struct hwstub_device_t *dev, int read, uint32_t addr, void *buf, size_t sz); | ||
61 | /* Returns <0 on error */ | ||
62 | int hwstub_exec(struct hwstub_device_t *dev, uint32_t addr, uint16_t flags); | ||
63 | int hwstub_call(struct hwstub_device_t *dev, uint32_t addr); | ||
64 | int hwstub_jump(struct hwstub_device_t *dev, uint32_t addr); | ||
65 | |||
66 | #ifdef __cplusplus | ||
67 | } // extern "C" | ||
68 | #endif | ||
69 | |||
70 | #endif /* __HWSTUB__ */ \ No newline at end of file | ||
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 | |||