]>
Commit | Line | Data |
---|---|---|
d891892d GS |
1 | /* |
2 | * This file is part of the libsigrok project. | |
3 | * | |
4 | * Copyright (C) 2020 Gerhard Sittig <gerhard.sittig@gmx.net> | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License as published by | |
8 | * the Free Software Foundation; either version 2 of the License, or | |
9 | * (at your option) any later version. | |
10 | * | |
11 | * This program is distributed in the hope that it will be useful, | |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | * GNU General Public License for more details. | |
15 | * | |
16 | * You should have received a copy of the GNU General Public License | |
17 | * along with this program; if not, see <http://www.gnu.org/licenses/>. | |
18 | */ | |
19 | ||
20 | /* | |
21 | * See the vendor's FAQ on file format details for exported files and | |
22 | * different software versions: | |
23 | * | |
24 | * https://support.saleae.com/faq/technical-faq/binary-data-export-format | |
25 | * https://support.saleae.com/faq/technical-faq/data-export-format-analog-binary | |
26 | * https://support.saleae.com/faq/technical-faq/binary-export-format-logic-2 | |
27 | * | |
28 | * All data is in little endian representation, floating point values | |
29 | * in IEEE754 format. Recent versions add header information, while | |
30 | * previous versions tend to "raw" formats. This input module is about | |
31 | * digital and analog data in their "binary presentation". CSV and VCD | |
32 | * exports are handled by other input modules. | |
33 | * | |
34 | * Saleae Logic applications typically export one file per channel. The | |
35 | * sigrok input modules exclusively handle an individual file, existing | |
36 | * applications may not be prepared to handle a set of files, or handle | |
37 | * "special" file types like directories. Some of them will even actively | |
38 | * reject such input specs. Merging multiple exported channels into either | |
39 | * another input file or a sigrok session is supposed to be done outside | |
40 | * of this input module. Support for ZIP archives is currently missing. | |
41 | * | |
42 | * TODO | |
43 | * - Need to create a channel group in addition to channels? | |
44 | * - Check file re-load and channel references. See bug #1241. | |
45 | * - Fixup 'digits' use for analog data. The current implementation made | |
46 | * an educated guess, assuming some 12bit resolution and logic levels | |
47 | * which roughly results in the single digit mV range. | |
48 | * - Add support for "local I/O" in the input module when the common | |
49 | * support code becomes available. The .sal save files of the Logic | |
50 | * application appears to be a ZIP archive with *.bin files in it | |
51 | * plus some meta.json dictionary. This will also introduce a new | |
52 | * JSON reader dependency. | |
53 | * - When ZIP support gets added and .sal files become accepted, this | |
54 | * import module needs to merge the content of several per-channel | |
55 | * files, which may be of different types (mixed signal), and/or may | |
56 | * even differ in their samplerate (which becomes complex, similar to | |
57 | * VCD or CSV input). Given the .sal archive's layout this format may | |
58 | * even only become attractive when common sigrok infrastructure has | |
59 | * support for per-channel compression and rate(?). | |
60 | */ | |
61 | ||
62 | #include <config.h> | |
63 | #include <glib.h> | |
64 | #include <stdint.h> | |
65 | #include <stdlib.h> | |
66 | #include <string.h> | |
67 | #include <libsigrok/libsigrok.h> | |
68 | #include "libsigrok-internal.h" | |
69 | ||
70 | #define LOG_PREFIX "input/saleae" | |
71 | ||
72 | /* | |
73 | * Saleae Logic "save files" (ZIP archives with .sal file extension) | |
74 | * could get detected, but are not yet supported. Usability would be | |
75 | * rather limited when the current development support gets enabled. | |
76 | * This compile time switch is strictly for internal developer use. | |
77 | */ | |
78 | #define SALEAE_WITH_SAL_SUPPORT 0 | |
79 | ||
80 | #define CHUNK_SIZE (4 * 1024 * 1024) | |
81 | ||
82 | #define LOGIC2_MAGIC "<SALEAE>" | |
83 | #define LOGIC2_VERSION 0 | |
84 | #define LOGIC2_TYPE_DIGITAL 0 | |
85 | #define LOGIC2_TYPE_ANALOG 1 | |
86 | ||
87 | /* Simple header check approach. Assume minimum file size for all formats. */ | |
88 | #define LOGIC2_MIN_SIZE 0x30 | |
89 | ||
90 | enum logic_format { | |
91 | FMT_UNKNOWN, | |
92 | FMT_AUTO_DETECT, | |
93 | FMT_LOGIC1_DIGITAL, | |
94 | FMT_LOGIC1_ANALOG, | |
95 | FMT_LOGIC2_DIGITAL, | |
96 | FMT_LOGIC2_ANALOG, | |
97 | FMT_LOGIC2_ARCHIVE, | |
98 | }; | |
99 | ||
100 | enum input_stage { | |
101 | STAGE_ALL_WAIT_HEADER, | |
102 | STAGE_ALL_DETECT_TYPE, | |
103 | STAGE_ALL_READ_HEADER, | |
104 | STAGE_L1D_EVERY_VALUE, | |
105 | STAGE_L1D_CHANGE_INIT, | |
106 | STAGE_L1D_CHANGE_VALUE, | |
107 | STAGE_L1A_NEW_CHANNEL, | |
108 | STAGE_L1A_SAMPLE, | |
109 | STAGE_L2D_CHANGE_VALUE, | |
110 | STAGE_L2A_FIRST_VALUE, | |
111 | STAGE_L2A_EVERY_VALUE, | |
112 | }; | |
113 | ||
114 | struct context { | |
115 | struct context_options { | |
116 | enum logic_format format; | |
117 | gboolean when_changed; | |
118 | size_t word_size; | |
119 | size_t channel_count; | |
120 | uint64_t sample_rate; | |
121 | } options; | |
122 | struct { | |
123 | gboolean got_header; | |
124 | gboolean header_sent; | |
125 | gboolean rate_sent; | |
126 | GSList *prev_channels; | |
127 | } module_state; | |
128 | struct { | |
129 | enum logic_format format; | |
130 | gboolean when_changed; | |
131 | size_t word_size; | |
132 | size_t channel_count; | |
133 | uint64_t sample_rate; | |
134 | enum input_stage stage; | |
135 | struct { | |
136 | uint64_t samples_per_channel; | |
137 | uint64_t current_channel_idx; | |
138 | uint64_t current_per_channel; | |
139 | } l1a; | |
140 | struct { | |
141 | uint32_t init_state; | |
142 | double begin_time; | |
143 | double end_time; | |
144 | uint64_t transition_count; | |
145 | double sample_period; | |
146 | double min_time_step; | |
147 | } l2d; | |
148 | struct { | |
149 | double begin_time; | |
150 | uint64_t sample_rate; | |
151 | uint64_t down_sample; | |
152 | uint64_t sample_count; | |
153 | } l2a; | |
154 | } logic_state; | |
155 | struct { | |
156 | GSList *channels; | |
157 | gboolean is_analog; | |
158 | size_t unit_size; | |
159 | size_t samples_per_chunk; | |
160 | size_t samples_in_buffer; | |
161 | uint8_t *buffer_digital; | |
162 | float *buffer_analog; | |
163 | uint8_t *write_pos; | |
164 | struct { | |
165 | uint64_t stamp; | |
166 | double time; | |
167 | uint32_t digital; | |
168 | float analog; | |
169 | } last; | |
170 | } feed; | |
171 | }; | |
172 | ||
173 | static const char *format_texts[] = { | |
174 | [FMT_UNKNOWN] = "unknown", | |
175 | [FMT_AUTO_DETECT] = "auto-detect", | |
176 | [FMT_LOGIC1_DIGITAL] = "logic1-digital", | |
177 | [FMT_LOGIC1_ANALOG] = "logic1-analog", | |
178 | [FMT_LOGIC2_DIGITAL] = "logic2-digital", | |
179 | [FMT_LOGIC2_ANALOG] = "logic2-analog", | |
180 | #if SALEAE_WITH_SAL_SUPPORT | |
181 | [FMT_LOGIC2_ARCHIVE] = "logic2-archive", | |
182 | #endif | |
183 | }; | |
184 | ||
185 | static const char *get_format_text(enum logic_format fmt) | |
186 | { | |
187 | const char *text; | |
188 | ||
189 | if (fmt >= ARRAY_SIZE(format_texts)) | |
190 | return NULL; | |
191 | text = format_texts[fmt]; | |
192 | if (!text || !*text) | |
193 | return NULL; | |
194 | return text; | |
195 | } | |
196 | ||
197 | static int create_channels(struct sr_input *in) | |
198 | { | |
199 | struct context *inc; | |
200 | int type; | |
201 | size_t count, idx; | |
c3270084 | 202 | char name[24]; |
d891892d GS |
203 | struct sr_channel *ch; |
204 | ||
205 | inc = in->priv; | |
206 | ||
207 | if (in->sdi->channels) | |
208 | return SR_OK; | |
209 | ||
210 | count = inc->logic_state.channel_count; | |
211 | switch (inc->logic_state.format) { | |
212 | case FMT_LOGIC1_DIGITAL: | |
213 | case FMT_LOGIC2_DIGITAL: | |
214 | type = SR_CHANNEL_LOGIC; | |
215 | break; | |
216 | case FMT_LOGIC1_ANALOG: | |
217 | case FMT_LOGIC2_ANALOG: | |
218 | type = SR_CHANNEL_ANALOG; | |
219 | break; | |
220 | default: | |
221 | return SR_ERR_NA; | |
222 | } | |
223 | ||
224 | /* TODO Need to create a channel group? */ | |
225 | for (idx = 0; idx < count; idx++) { | |
226 | snprintf(name, sizeof(name), "%zu", idx); | |
227 | ch = sr_channel_new(in->sdi, idx, type, TRUE, name); | |
228 | if (!ch) | |
229 | return SR_ERR_MALLOC; | |
230 | } | |
231 | ||
232 | return SR_OK; | |
233 | } | |
234 | ||
235 | static int alloc_feed_buffer(struct sr_input *in) | |
236 | { | |
237 | struct context *inc; | |
238 | size_t alloc_size; | |
239 | ||
240 | inc = in->priv; | |
241 | ||
242 | inc->feed.is_analog = FALSE; | |
243 | alloc_size = CHUNK_SIZE; | |
244 | switch (inc->logic_state.format) { | |
245 | case FMT_LOGIC1_DIGITAL: | |
246 | case FMT_LOGIC2_DIGITAL: | |
247 | inc->feed.unit_size = sizeof(inc->feed.last.digital); | |
248 | alloc_size /= inc->feed.unit_size; | |
249 | inc->feed.samples_per_chunk = alloc_size; | |
250 | alloc_size *= inc->feed.unit_size; | |
251 | inc->feed.buffer_digital = g_try_malloc(alloc_size); | |
252 | if (!inc->feed.buffer_digital) | |
253 | return SR_ERR_MALLOC; | |
254 | inc->feed.write_pos = inc->feed.buffer_digital; | |
255 | break; | |
256 | case FMT_LOGIC1_ANALOG: | |
257 | case FMT_LOGIC2_ANALOG: | |
258 | inc->feed.is_analog = TRUE; | |
259 | alloc_size /= sizeof(inc->feed.last.analog); | |
260 | inc->feed.samples_per_chunk = alloc_size; | |
261 | alloc_size *= sizeof(inc->feed.last.analog); | |
262 | inc->feed.buffer_analog = g_try_malloc(alloc_size); | |
263 | if (!inc->feed.buffer_analog) | |
264 | return SR_ERR_MALLOC; | |
265 | inc->feed.write_pos = (void *)inc->feed.buffer_analog; | |
266 | break; | |
267 | default: | |
268 | return SR_ERR_NA; | |
269 | } | |
270 | inc->feed.samples_in_buffer = 0; | |
271 | ||
272 | return SR_OK; | |
273 | } | |
274 | ||
275 | static int relse_feed_buffer(struct sr_input *in) | |
276 | { | |
277 | struct context *inc; | |
278 | ||
279 | inc = in->priv; | |
280 | ||
281 | inc->feed.is_analog = FALSE; | |
282 | inc->feed.unit_size = 0; | |
283 | inc->feed.samples_per_chunk = 0; | |
284 | inc->feed.samples_in_buffer = 0; | |
285 | g_free(inc->feed.buffer_digital); | |
286 | inc->feed.buffer_digital = NULL; | |
287 | g_free(inc->feed.buffer_analog); | |
288 | inc->feed.buffer_analog = NULL; | |
289 | inc->feed.write_pos = NULL; | |
290 | ||
291 | return SR_OK; | |
292 | } | |
293 | ||
294 | static int setup_feed_buffer_channel(struct sr_input *in, size_t ch_idx) | |
295 | { | |
296 | struct context *inc; | |
297 | struct sr_channel *ch; | |
298 | ||
299 | inc = in->priv; | |
300 | ||
301 | g_slist_free(inc->feed.channels); | |
302 | inc->feed.channels = NULL; | |
303 | if (ch_idx >= inc->logic_state.channel_count) | |
304 | return SR_OK; | |
305 | ||
306 | ch = g_slist_nth_data(in->sdi->channels, ch_idx); | |
307 | if (!ch) | |
308 | return SR_ERR_ARG; | |
309 | inc->feed.channels = g_slist_append(NULL, ch); | |
310 | return SR_OK; | |
311 | } | |
312 | ||
313 | static int flush_feed_buffer(struct sr_input *in) | |
314 | { | |
315 | struct context *inc; | |
316 | struct sr_datafeed_packet packet; | |
317 | struct sr_datafeed_logic logic; | |
318 | struct sr_datafeed_analog analog; | |
319 | struct sr_analog_encoding encoding; | |
320 | struct sr_analog_meaning meaning; | |
321 | struct sr_analog_spec spec; | |
322 | int rc; | |
323 | ||
324 | inc = in->priv; | |
325 | ||
326 | if (!inc->feed.samples_in_buffer) | |
327 | return SR_OK; | |
328 | ||
329 | /* Automatically send a datafeed header before meta and samples. */ | |
330 | if (!inc->module_state.header_sent) { | |
331 | rc = std_session_send_df_header(in->sdi); | |
332 | if (rc) | |
333 | return rc; | |
334 | inc->module_state.header_sent = TRUE; | |
335 | } | |
336 | ||
337 | /* Automatically send the samplerate (when available). */ | |
338 | if (inc->logic_state.sample_rate && !inc->module_state.rate_sent) { | |
339 | rc = sr_session_send_meta(in->sdi, SR_CONF_SAMPLERATE, | |
340 | g_variant_new_uint64(inc->logic_state.sample_rate)); | |
341 | inc->module_state.rate_sent = TRUE; | |
342 | } | |
343 | ||
344 | /* | |
345 | * Create a packet with either logic or analog payload. Rewind | |
346 | * the caller's write position. | |
347 | */ | |
348 | memset(&packet, 0, sizeof(packet)); | |
349 | if (inc->feed.is_analog) { | |
350 | /* TODO: Use proper 'digits' value for this input module. */ | |
351 | sr_analog_init(&analog, &encoding, &meaning, &spec, 3); | |
352 | analog.data = inc->feed.buffer_analog; | |
353 | analog.num_samples = inc->feed.samples_in_buffer; | |
354 | analog.meaning->channels = inc->feed.channels; | |
355 | analog.meaning->mq = SR_MQ_VOLTAGE; | |
356 | analog.meaning->mqflags |= SR_MQFLAG_DC; | |
357 | analog.meaning->unit = SR_UNIT_VOLT; | |
358 | packet.type = SR_DF_ANALOG; | |
359 | packet.payload = &analog; | |
360 | inc->feed.write_pos = (void *)inc->feed.buffer_analog; | |
361 | } else { | |
362 | memset(&logic, 0, sizeof(logic)); | |
363 | logic.length = inc->feed.samples_in_buffer; | |
364 | logic.length *= inc->feed.unit_size; | |
365 | logic.unitsize = inc->feed.unit_size; | |
366 | logic.data = inc->feed.buffer_digital; | |
367 | packet.type = SR_DF_LOGIC; | |
368 | packet.payload = &logic; | |
369 | inc->feed.write_pos = inc->feed.buffer_digital; | |
370 | } | |
371 | inc->feed.samples_in_buffer = 0; | |
372 | ||
373 | /* Send the packet to the session feed. */ | |
374 | return sr_session_send(in->sdi, &packet); | |
375 | } | |
376 | ||
377 | static int addto_feed_buffer_logic(struct sr_input *in, | |
378 | uint64_t data, size_t count) | |
379 | { | |
380 | struct context *inc; | |
381 | ||
382 | inc = in->priv; | |
383 | ||
384 | if (inc->feed.is_analog) | |
385 | return SR_ERR_ARG; | |
386 | ||
387 | while (count--) { | |
388 | if (inc->feed.unit_size == sizeof(uint64_t)) | |
389 | write_u64le_inc(&inc->feed.write_pos, data); | |
390 | else if (inc->feed.unit_size == sizeof(uint32_t)) | |
391 | write_u32le_inc(&inc->feed.write_pos, data); | |
392 | else if (inc->feed.unit_size == sizeof(uint16_t)) | |
393 | write_u16le_inc(&inc->feed.write_pos, data); | |
394 | else if (inc->feed.unit_size == sizeof(uint8_t)) | |
395 | write_u8_inc(&inc->feed.write_pos, data); | |
396 | else | |
397 | return SR_ERR_BUG; | |
398 | inc->feed.samples_in_buffer++; | |
399 | if (inc->feed.samples_in_buffer == inc->feed.samples_per_chunk) | |
400 | flush_feed_buffer(in); | |
401 | } | |
402 | ||
403 | return SR_OK; | |
404 | } | |
405 | ||
406 | static int addto_feed_buffer_analog(struct sr_input *in, | |
407 | float data, size_t count) | |
408 | { | |
409 | struct context *inc; | |
410 | ||
411 | inc = in->priv; | |
412 | ||
413 | if (!inc->feed.is_analog) | |
414 | return SR_ERR_ARG; | |
415 | ||
416 | while (count--) { | |
417 | if (sizeof(inc->feed.buffer_analog[0]) == sizeof(float)) | |
418 | write_fltle_inc(&inc->feed.write_pos, data); | |
419 | else if (sizeof(inc->feed.buffer_analog[0]) == sizeof(double)) | |
420 | write_dblle_inc(&inc->feed.write_pos, data); | |
421 | else | |
422 | return SR_ERR_BUG; | |
423 | inc->feed.samples_in_buffer++; | |
424 | if (inc->feed.samples_in_buffer == inc->feed.samples_per_chunk) | |
425 | flush_feed_buffer(in); | |
426 | } | |
427 | ||
428 | return SR_OK; | |
429 | } | |
430 | ||
431 | static enum logic_format check_format(const uint8_t *data, size_t dlen) | |
432 | { | |
433 | const char *s; | |
434 | uint32_t v, t; | |
435 | ||
436 | /* TODO | |
437 | * Can we check ZIP content here in useful ways? Probably only | |
438 | * when the input module got extended to optionally handle local | |
439 | * file I/O, and passes some archive handle to this routine. | |
440 | */ | |
441 | ||
442 | /* Check for the magic literal. */ | |
443 | s = (void *)data; | |
444 | if (dlen < strlen(LOGIC2_MAGIC)) | |
445 | return FMT_UNKNOWN; | |
446 | if (strncmp(s, LOGIC2_MAGIC, strlen(LOGIC2_MAGIC)) != 0) | |
447 | return FMT_UNKNOWN; | |
448 | data += strlen(LOGIC2_MAGIC); | |
449 | dlen -= strlen(LOGIC2_MAGIC); | |
450 | ||
451 | /* Get the version and type fields. */ | |
452 | if (dlen < 2 * sizeof(uint32_t)) | |
453 | return FMT_UNKNOWN; | |
454 | v = read_u32le_inc(&data); | |
455 | t = read_u32le_inc(&data); | |
456 | if (v != LOGIC2_VERSION) | |
457 | return FMT_UNKNOWN; | |
458 | switch (t) { | |
459 | case LOGIC2_TYPE_DIGITAL: | |
460 | return FMT_LOGIC2_DIGITAL; | |
461 | case LOGIC2_TYPE_ANALOG: | |
462 | return FMT_LOGIC2_ANALOG; | |
463 | default: | |
464 | return FMT_UNKNOWN; | |
465 | } | |
466 | ||
467 | return FMT_UNKNOWN; | |
468 | } | |
469 | ||
470 | /* Check for availability of required header data. */ | |
471 | static gboolean have_header(struct context *inc, GString *buf) | |
472 | { | |
473 | ||
474 | /* | |
475 | * The amount of required data depends on the file format. Which | |
476 | * either was specified before, or is yet to get determined. The | |
477 | * input module ideally would apply a sequence of checks for the | |
478 | * currently available (partial) data, access a few first header | |
479 | * fields, before checking for a little more receive data, before | |
480 | * accessing more fields, until the input file's type was found, | |
481 | * and its header length is known, and can get checked. | |
482 | * | |
483 | * This simple implementation just assumes that any input file | |
484 | * has at least a given number of bytes, which should not be an | |
485 | * issue for typical use cases. Only extremely short yet valid | |
486 | * input files with just a few individual samples may fail this | |
487 | * check. It's assumed that these files are very rare, and may | |
488 | * be of types which are covered by other input modules (raw | |
489 | * binary). | |
490 | */ | |
491 | (void)inc; | |
492 | return buf->len >= LOGIC2_MIN_SIZE; | |
493 | } | |
494 | ||
495 | /* Process/inspect previously received input data. Get header parameters. */ | |
496 | static int parse_header(struct sr_input *in) | |
497 | { | |
498 | struct context *inc; | |
499 | const uint8_t *read_pos, *start_pos; | |
500 | size_t read_len, want_len; | |
501 | uint64_t samples_per_channel; | |
502 | size_t channel_count; | |
503 | double sample_period; | |
504 | uint64_t sample_rate; | |
505 | ||
506 | inc = in->priv; | |
507 | read_pos = (const uint8_t *)in->buf->str; | |
508 | read_len = in->buf->len; | |
509 | ||
510 | /* | |
511 | * Clear internal state. Normalize user specified option values | |
512 | * before amending them from the input file's header information. | |
513 | */ | |
514 | memset(&inc->logic_state, 0, sizeof(inc->logic_state)); | |
515 | inc->logic_state.format = inc->options.format; | |
516 | inc->logic_state.when_changed = inc->options.when_changed; | |
517 | inc->logic_state.word_size = inc->options.word_size; | |
518 | if (!inc->logic_state.word_size) { | |
519 | sr_err("Need a word size."); | |
520 | return SR_ERR_ARG; | |
521 | } | |
522 | inc->logic_state.word_size += 8 - 1; | |
523 | inc->logic_state.word_size /= 8; /* Sample width in bytes. */ | |
524 | if (inc->logic_state.word_size > sizeof(inc->feed.last.digital)) { | |
525 | sr_err("Excessive word size %zu.", inc->logic_state.word_size); | |
526 | return SR_ERR_ARG; | |
527 | } | |
528 | inc->logic_state.channel_count = inc->options.channel_count; | |
529 | inc->logic_state.sample_rate = inc->options.sample_rate; | |
530 | if (inc->logic_state.format == FMT_AUTO_DETECT) | |
531 | inc->logic_state.stage = STAGE_ALL_DETECT_TYPE; | |
532 | else | |
533 | inc->logic_state.stage = STAGE_ALL_READ_HEADER; | |
534 | ||
535 | /* | |
536 | * Optionally auto-detect the format if none was specified yet. | |
537 | * This only works for some of the supported formats. ZIP support | |
538 | * requires local I/O in the input module (won't work on memory | |
539 | * buffers). | |
540 | */ | |
541 | if (inc->logic_state.stage == STAGE_ALL_DETECT_TYPE) { | |
542 | inc->logic_state.format = check_format(read_pos, read_len); | |
543 | if (inc->logic_state.format == FMT_UNKNOWN) { | |
544 | sr_err("Unknown or unsupported file format."); | |
545 | return SR_ERR_DATA; | |
546 | } | |
547 | sr_info("Detected file format: '%s'.", | |
548 | get_format_text(inc->logic_state.format)); | |
549 | inc->logic_state.stage = STAGE_ALL_READ_HEADER; | |
550 | } | |
551 | ||
552 | /* | |
553 | * Read the header fields, depending on the specific file format. | |
554 | * Arrange for the subsequent inspection of sample data items. | |
555 | */ | |
556 | start_pos = read_pos; | |
557 | switch (inc->logic_state.format) { | |
558 | case FMT_LOGIC1_DIGITAL: | |
559 | channel_count = inc->logic_state.channel_count; | |
560 | if (!channel_count) { | |
561 | channel_count = inc->logic_state.word_size; | |
562 | channel_count *= 8; | |
563 | inc->logic_state.channel_count = channel_count; | |
564 | } | |
565 | /* EMPTY */ /* No header fields to read here. */ | |
566 | sr_dbg("L1D, empty header, changed %d.", | |
567 | inc->logic_state.when_changed ? 1 : 0); | |
568 | if (inc->logic_state.when_changed) | |
569 | inc->logic_state.stage = STAGE_L1D_CHANGE_INIT; | |
570 | else | |
571 | inc->logic_state.stage = STAGE_L1D_EVERY_VALUE; | |
572 | break; | |
573 | case FMT_LOGIC1_ANALOG: | |
574 | want_len = sizeof(uint64_t) + sizeof(uint32_t) + sizeof(double); | |
575 | if (read_len < want_len) | |
576 | return SR_ERR_DATA; | |
577 | samples_per_channel = read_u64le_inc(&read_pos); | |
578 | channel_count = read_u32le_inc(&read_pos); | |
579 | sample_period = read_dblle_inc(&read_pos); | |
580 | inc->logic_state.l1a.samples_per_channel = samples_per_channel; | |
581 | inc->logic_state.channel_count = channel_count; | |
582 | sample_rate = 0; | |
583 | if (sample_period) { | |
584 | sample_period = 1.0 / sample_period; | |
585 | sample_period += 0.5; | |
586 | sample_rate = (uint64_t)sample_period; | |
587 | inc->logic_state.sample_rate = sample_rate; | |
588 | } | |
589 | sr_dbg("L1A header, smpls %zu, chans %zu, per %lf, rate %zu.", | |
590 | (size_t)samples_per_channel, (size_t)channel_count, | |
591 | sample_period, (size_t)sample_rate); | |
592 | inc->logic_state.stage = STAGE_L1A_NEW_CHANNEL; | |
593 | inc->logic_state.l1a.current_channel_idx = 0; | |
594 | inc->logic_state.l1a.current_per_channel = 0; | |
595 | break; | |
596 | case FMT_LOGIC2_DIGITAL: | |
597 | inc->logic_state.channel_count = 1; | |
598 | want_len = sizeof(uint64_t); /* magic */ | |
599 | want_len += 2 * sizeof(uint32_t); /* version, type */ | |
600 | want_len += sizeof(uint32_t); /* initial state */ | |
601 | want_len += 2 * sizeof(double); /* begin time, end time */ | |
602 | want_len += sizeof(uint64_t); /* transition count */ | |
603 | if (read_len < want_len) | |
604 | return SR_ERR_DATA; | |
605 | if (check_format(read_pos, read_len) != FMT_LOGIC2_DIGITAL) | |
606 | return SR_ERR_DATA; | |
607 | (void)read_u64le_inc(&read_pos); | |
608 | (void)read_u32le_inc(&read_pos); | |
609 | (void)read_u32le_inc(&read_pos); | |
610 | inc->logic_state.l2d.init_state = read_u32le_inc(&read_pos); | |
611 | inc->logic_state.l2d.begin_time = read_dblle_inc(&read_pos); | |
612 | inc->logic_state.l2d.end_time = read_dblle_inc(&read_pos); | |
613 | inc->logic_state.l2d.transition_count = read_u64le_inc(&read_pos); | |
614 | sr_dbg("L2D header, init %u, begin %lf, end %lf, transitions %" PRIu64 ".", | |
615 | (unsigned)inc->logic_state.l2d.init_state, | |
616 | inc->logic_state.l2d.begin_time, | |
617 | inc->logic_state.l2d.end_time, | |
618 | inc->logic_state.l2d.transition_count); | |
619 | if (!inc->logic_state.sample_rate) { | |
620 | sr_err("Need a samplerate."); | |
621 | return SR_ERR_ARG; | |
622 | } | |
623 | inc->feed.last.time = inc->logic_state.l2d.begin_time; | |
624 | inc->feed.last.digital = inc->logic_state.l2d.init_state ? 1 : 0; | |
625 | inc->logic_state.l2d.sample_period = inc->logic_state.sample_rate; | |
626 | inc->logic_state.l2d.sample_period = 1.0 / inc->logic_state.l2d.sample_period; | |
627 | inc->logic_state.l2d.min_time_step = inc->logic_state.l2d.end_time; | |
628 | inc->logic_state.l2d.min_time_step -= inc->logic_state.l2d.begin_time; | |
629 | inc->logic_state.stage = STAGE_L2D_CHANGE_VALUE; | |
630 | break; | |
631 | case FMT_LOGIC2_ANALOG: | |
632 | inc->logic_state.channel_count = 1; | |
633 | want_len = sizeof(uint64_t); /* magic */ | |
634 | want_len += 2 * sizeof(uint32_t); /* version, type */ | |
635 | want_len += sizeof(double); /* begin time */ | |
636 | want_len += 2 * sizeof(uint64_t); /* sample rate, down sample */ | |
637 | want_len += sizeof(uint64_t); /* sample count */ | |
638 | if (read_len < want_len) | |
639 | return SR_ERR_DATA; | |
640 | if (check_format(read_pos, read_len) != FMT_LOGIC2_ANALOG) | |
641 | return SR_ERR_DATA; | |
642 | (void)read_u64le_inc(&read_pos); | |
643 | (void)read_u32le_inc(&read_pos); | |
644 | (void)read_u32le_inc(&read_pos); | |
645 | inc->logic_state.l2a.begin_time = read_dblle_inc(&read_pos); | |
646 | inc->logic_state.l2a.sample_rate = read_u64le_inc(&read_pos); | |
647 | inc->logic_state.l2a.down_sample = read_u64le_inc(&read_pos); | |
648 | inc->logic_state.l2a.sample_count = read_u64le_inc(&read_pos); | |
649 | if (!inc->logic_state.sample_rate) | |
650 | inc->logic_state.sample_rate = inc->logic_state.l2a.sample_rate; | |
651 | sr_dbg("L2A header, begin %lf, rate %" PRIu64 ", down %" PRIu64 ", samples %" PRIu64 ".", | |
652 | inc->logic_state.l2a.begin_time, | |
653 | inc->logic_state.l2a.sample_rate, | |
654 | inc->logic_state.l2a.down_sample, | |
655 | inc->logic_state.l2a.sample_count); | |
656 | inc->feed.last.time = inc->logic_state.l2a.begin_time; | |
657 | inc->logic_state.stage = STAGE_L2A_FIRST_VALUE; | |
658 | break; | |
659 | case FMT_LOGIC2_ARCHIVE: | |
660 | sr_err("Support for .sal archives not implemented yet."); | |
661 | return SR_ERR_NA; | |
662 | default: | |
663 | sr_err("Unknown or unsupported file format."); | |
664 | return SR_ERR_NA; | |
665 | } | |
666 | ||
667 | /* Remove the consumed header fields from the receive buffer. */ | |
668 | read_len = read_pos - start_pos; | |
669 | g_string_erase(in->buf, 0, read_len); | |
670 | ||
671 | return SR_OK; | |
672 | } | |
673 | ||
674 | /* Check availablity of the next sample data item. */ | |
675 | static gboolean have_next_item(struct sr_input *in, | |
676 | const uint8_t *buff, size_t blen, | |
677 | const uint8_t **curr, const uint8_t **next) | |
678 | { | |
679 | struct context *inc; | |
680 | size_t want_len; | |
681 | const uint8_t *pos; | |
682 | ||
683 | inc = in->priv; | |
684 | if (curr) | |
685 | *curr = NULL; | |
686 | if (next) | |
687 | *next = NULL; | |
688 | ||
689 | /* | |
690 | * The amount of required data depends on the file format and | |
691 | * the current state. Wait for the availabilty of the desired | |
692 | * data before processing it (to simplify data inspection | |
693 | * code paths). | |
694 | */ | |
695 | switch (inc->logic_state.stage) { | |
696 | case STAGE_L1D_EVERY_VALUE: | |
697 | want_len = inc->logic_state.word_size; | |
698 | break; | |
699 | case STAGE_L1D_CHANGE_INIT: | |
700 | case STAGE_L1D_CHANGE_VALUE: | |
701 | want_len = sizeof(uint64_t); | |
702 | want_len += inc->logic_state.word_size; | |
703 | break; | |
704 | case STAGE_L1A_NEW_CHANNEL: | |
705 | want_len = 0; | |
706 | break; | |
707 | case STAGE_L1A_SAMPLE: | |
708 | want_len = sizeof(float); | |
709 | break; | |
710 | case STAGE_L2D_CHANGE_VALUE: | |
711 | want_len = sizeof(double); | |
712 | break; | |
713 | case STAGE_L2A_FIRST_VALUE: | |
714 | case STAGE_L2A_EVERY_VALUE: | |
715 | want_len = sizeof(float); | |
716 | break; | |
717 | default: | |
718 | return FALSE; | |
719 | } | |
720 | if (blen < want_len) | |
721 | return FALSE; | |
722 | ||
723 | /* Provide references to the next item, and the position after it. */ | |
724 | pos = buff; | |
725 | if (curr) | |
726 | *curr = pos; | |
727 | pos += want_len; | |
728 | if (next) | |
729 | *next = pos; | |
730 | return TRUE; | |
731 | } | |
732 | ||
733 | /* Process the next sample data item after it became available. */ | |
734 | static int parse_next_item(struct sr_input *in, | |
735 | const uint8_t *curr, size_t len) | |
736 | { | |
737 | struct context *inc; | |
738 | uint64_t next_stamp, count; | |
739 | uint64_t digital; | |
740 | float analog; | |
741 | double next_time, diff_time; | |
742 | int rc; | |
743 | ||
744 | inc = in->priv; | |
745 | (void)len; | |
746 | ||
747 | /* | |
748 | * The specific item to get processed next depends on the file | |
749 | * format and current state. | |
750 | */ | |
751 | switch (inc->logic_state.stage) { | |
752 | case STAGE_L1D_CHANGE_INIT: | |
753 | case STAGE_L1D_CHANGE_VALUE: | |
754 | next_stamp = read_u64le_inc(&curr); | |
755 | if (inc->logic_state.stage == STAGE_L1D_CHANGE_INIT) { | |
756 | inc->feed.last.stamp = next_stamp; | |
757 | inc->logic_state.stage = STAGE_L1D_CHANGE_VALUE; | |
758 | } | |
759 | count = next_stamp - inc->feed.last.stamp; | |
760 | digital = inc->feed.last.digital; | |
761 | rc = addto_feed_buffer_logic(in, digital, count); | |
762 | if (rc) | |
763 | return rc; | |
764 | inc->feed.last.stamp = next_stamp - 1; | |
765 | /* FALLTHROUGH */ | |
766 | case STAGE_L1D_EVERY_VALUE: | |
767 | if (inc->logic_state.word_size == sizeof(uint8_t)) { | |
768 | digital = read_u8_inc(&curr); | |
769 | } else if (inc->logic_state.word_size == sizeof(uint16_t)) { | |
770 | digital = read_u16le_inc(&curr); | |
771 | } else if (inc->logic_state.word_size == sizeof(uint32_t)) { | |
772 | digital = read_u32le_inc(&curr); | |
773 | } else if (inc->logic_state.word_size == sizeof(uint64_t)) { | |
774 | digital = read_u64le_inc(&curr); | |
775 | } else { | |
776 | /* | |
777 | * In theory the sigrok input module could support | |
778 | * arbitrary word sizes, but the Saleae exporter | |
779 | * only provides the 8/16/32/64 choices anyway. | |
780 | */ | |
781 | sr_err("Unsupported word size %zu.", inc->logic_state.word_size); | |
782 | return SR_ERR_ARG; | |
783 | } | |
784 | rc = addto_feed_buffer_logic(in, digital, 1); | |
785 | if (rc) | |
786 | return rc; | |
787 | inc->feed.last.digital = digital; | |
788 | inc->feed.last.stamp++; | |
789 | return SR_OK; | |
790 | case STAGE_L1A_NEW_CHANNEL: | |
791 | /* Just select the channel. Don't consume any data. */ | |
792 | rc = setup_feed_buffer_channel(in, inc->logic_state.l1a.current_channel_idx); | |
793 | if (rc) | |
794 | return rc; | |
795 | inc->logic_state.l1a.current_channel_idx++; | |
796 | inc->logic_state.l1a.current_per_channel = 0; | |
797 | inc->logic_state.stage = STAGE_L1A_SAMPLE; | |
798 | return SR_OK; | |
799 | case STAGE_L1A_SAMPLE: | |
800 | analog = read_fltle_inc(&curr); | |
801 | rc = addto_feed_buffer_analog(in, analog, 1); | |
802 | if (rc) | |
803 | return rc; | |
804 | inc->logic_state.l1a.current_per_channel++; | |
805 | if (inc->logic_state.l1a.current_channel_idx == inc->logic_state.l1a.samples_per_channel) | |
806 | inc->logic_state.stage = STAGE_L1A_NEW_CHANNEL; | |
807 | return SR_OK; | |
808 | case STAGE_L2D_CHANGE_VALUE: | |
809 | next_time = read_dblle_inc(&curr); | |
810 | diff_time = next_time - inc->feed.last.time; | |
811 | if (inc->logic_state.l2d.min_time_step > diff_time) | |
812 | inc->logic_state.l2d.min_time_step = diff_time; | |
813 | diff_time /= inc->logic_state.l2d.sample_period; | |
814 | diff_time += 0.5; | |
815 | count = (uint64_t)diff_time; | |
52082147 GS |
816 | if (count) { |
817 | digital = inc->feed.last.digital; | |
818 | rc = addto_feed_buffer_logic(in, digital, count); | |
819 | if (rc) | |
820 | return rc; | |
821 | inc->feed.last.time = next_time; | |
822 | } | |
d891892d GS |
823 | inc->feed.last.digital = 1 - inc->feed.last.digital; |
824 | return SR_OK; | |
825 | case STAGE_L2A_FIRST_VALUE: | |
826 | case STAGE_L2A_EVERY_VALUE: | |
827 | analog = read_fltle_inc(&curr); | |
828 | if (inc->logic_state.stage == STAGE_L2A_FIRST_VALUE) { | |
829 | rc = setup_feed_buffer_channel(in, 0); | |
830 | if (rc) | |
831 | return rc; | |
832 | count = 1; | |
833 | } else { | |
834 | count = inc->logic_state.l2a.down_sample; | |
835 | } | |
836 | rc = addto_feed_buffer_analog(in, analog, 1); | |
837 | if (rc) | |
838 | return rc; | |
839 | return SR_OK; | |
840 | ||
841 | default: | |
842 | (void)analog; | |
843 | return SR_ERR_NA; | |
844 | } | |
845 | /* UNREACH */ | |
846 | } | |
847 | ||
848 | static int parse_samples(struct sr_input *in) | |
849 | { | |
850 | const uint8_t *buff, *start; | |
851 | size_t blen; | |
852 | ||
853 | const uint8_t *curr, *next; | |
854 | size_t len; | |
855 | int rc; | |
856 | ||
857 | start = (const uint8_t *)in->buf->str; | |
858 | buff = start; | |
859 | blen = in->buf->len; | |
860 | while (have_next_item(in, buff, blen, &curr, &next)) { | |
861 | len = next - curr; | |
862 | rc = parse_next_item(in, curr, len); | |
863 | if (rc) | |
864 | return rc; | |
865 | buff += len; | |
866 | blen -= len; | |
867 | } | |
868 | len = buff - start; | |
869 | g_string_erase(in->buf, 0, len); | |
870 | ||
871 | return SR_OK; | |
872 | } | |
873 | ||
874 | /* | |
875 | * Try to auto detect an input's file format. Mismatch is non-fatal. | |
876 | * Silent operation by design. Not all details need to be available. | |
877 | * Get the strongest possible match in a best-effort manner. | |
878 | * | |
879 | * TODO Extend the .sal check when local file I/O becomes available. | |
880 | * File extensions can lie, and need not be available. Check for a | |
881 | * ZIP archive and the meta.json member in it. | |
882 | */ | |
883 | static int format_match(GHashTable *metadata, unsigned int *confidence) | |
884 | { | |
885 | static const char *zip_ext = ".sal"; | |
886 | static const char *bin_ext = ".bin"; | |
887 | ||
9084c396 | 888 | gboolean matched; |
d891892d GS |
889 | const char *fn; |
890 | size_t fn_len, ext_len; | |
891 | const char *ext_pos; | |
892 | GString *buf; | |
893 | ||
9084c396 GS |
894 | matched = FALSE; |
895 | ||
d891892d GS |
896 | /* Weak match on the filename (when available). */ |
897 | fn = g_hash_table_lookup(metadata, GINT_TO_POINTER(SR_INPUT_META_FILENAME)); | |
898 | if (fn && *fn) { | |
899 | fn_len = strlen(fn); | |
900 | ext_len = strlen(zip_ext); | |
901 | ext_pos = &fn[fn_len - ext_len]; | |
902 | if (fn_len >= ext_len && g_ascii_strcasecmp(ext_pos, zip_ext) == 0) { | |
9084c396 | 903 | if (SALEAE_WITH_SAL_SUPPORT) { |
d891892d | 904 | *confidence = 10; |
9084c396 GS |
905 | matched = TRUE; |
906 | } | |
d891892d GS |
907 | } |
908 | ext_len = strlen(bin_ext); | |
909 | ext_pos = &fn[fn_len - ext_len]; | |
910 | if (fn_len >= ext_len && g_ascii_strcasecmp(ext_pos, bin_ext) == 0) { | |
911 | *confidence = 50; | |
9084c396 | 912 | matched = TRUE; |
d891892d GS |
913 | } |
914 | } | |
915 | ||
916 | /* Stronger match when magic literals are found in file content. */ | |
917 | buf = g_hash_table_lookup(metadata, GINT_TO_POINTER(SR_INPUT_META_HEADER)); | |
918 | if (!buf || !buf->len || !buf->str) | |
919 | return SR_ERR_ARG; | |
920 | switch (check_format((const uint8_t *)buf->str, buf->len)) { | |
921 | case FMT_LOGIC2_DIGITAL: | |
922 | case FMT_LOGIC2_ANALOG: | |
923 | *confidence = 1; | |
9084c396 | 924 | matched = TRUE; |
d891892d GS |
925 | break; |
926 | default: | |
927 | /* EMPTY */ | |
928 | break; | |
929 | } | |
930 | ||
9084c396 | 931 | return matched ? SR_OK : SR_ERR_DATA; |
d891892d GS |
932 | } |
933 | ||
934 | static int init(struct sr_input *in, GHashTable *options) | |
935 | { | |
936 | struct context *inc; | |
937 | const char *type, *fmt_text; | |
938 | enum logic_format format, fmt_idx; | |
939 | gboolean changed; | |
940 | size_t size, count; | |
941 | uint64_t rate; | |
942 | ||
943 | /* Allocate resources. */ | |
944 | in->sdi = g_malloc0(sizeof(*in->sdi)); | |
945 | inc = g_malloc0(sizeof(*inc)); | |
946 | in->priv = inc; | |
947 | ||
948 | /* Get caller provided specs, dump before check. */ | |
949 | type = g_variant_get_string(g_hash_table_lookup(options, "format"), NULL); | |
950 | changed = g_variant_get_boolean(g_hash_table_lookup(options, "changed")); | |
951 | size = g_variant_get_uint32(g_hash_table_lookup(options, "wordsize")); | |
952 | count = g_variant_get_uint32(g_hash_table_lookup(options, "logic_channels")); | |
953 | rate = g_variant_get_uint64(g_hash_table_lookup(options, "samplerate")); | |
954 | sr_dbg("Caller options: type '%s', changed %d, wordsize %zu, channels %zu, rate %" PRIu64 ".", | |
955 | type, changed ? 1 : 0, size, count, rate); | |
956 | ||
957 | /* Run a few simple checks. Normalization is done in .init(). */ | |
958 | format = FMT_UNKNOWN; | |
959 | for (fmt_idx = FMT_AUTO_DETECT; fmt_idx < ARRAY_SIZE(format_texts); fmt_idx++) { | |
960 | fmt_text = format_texts[fmt_idx]; | |
961 | if (!fmt_text || !*fmt_text) | |
962 | continue; | |
963 | if (g_ascii_strcasecmp(type, fmt_text) != 0) | |
964 | continue; | |
965 | format = fmt_idx; | |
966 | break; | |
967 | } | |
968 | if (format == FMT_UNKNOWN) { | |
969 | sr_err("Unknown file type name: '%s'.", type); | |
970 | return SR_ERR_ARG; | |
971 | } | |
972 | if (!size) { | |
973 | sr_err("Need a word size."); | |
974 | return SR_ERR_ARG; | |
975 | } | |
976 | ||
977 | /* | |
978 | * Keep input specs around. We never get back to .init() even | |
979 | * when input files are re-read later. | |
980 | */ | |
981 | inc->options.format = format; | |
982 | inc->options.when_changed = !!changed; | |
983 | inc->options.word_size = size; | |
984 | inc->options.channel_count = count; | |
985 | inc->options.sample_rate = rate; | |
986 | sr_dbg("Resulting options: type '%s', changed %d", | |
987 | get_format_text(format), changed ? 1 : 0); | |
988 | ||
989 | return SR_OK; | |
990 | } | |
991 | ||
992 | static int receive(struct sr_input *in, GString *buf) | |
993 | { | |
994 | struct context *inc; | |
995 | int rc; | |
996 | const char *text; | |
997 | ||
998 | inc = in->priv; | |
999 | ||
1000 | /* Accumulate another chunk of input data. */ | |
1001 | g_string_append_len(in->buf, buf->str, buf->len); | |
1002 | ||
1003 | /* | |
1004 | * Wait for the full header's availability, then process it in | |
1005 | * a single call, and set the "ready" flag. Make sure sample data | |
1006 | * and the header get processed in disjoint receive() calls, the | |
1007 | * backend requires those separate phases. | |
1008 | */ | |
1009 | if (!inc->module_state.got_header) { | |
1010 | if (!have_header(inc, in->buf)) | |
1011 | return SR_OK; | |
1012 | rc = parse_header(in); | |
1013 | if (rc) | |
1014 | return rc; | |
1015 | inc->module_state.got_header = TRUE; | |
1016 | text = get_format_text(inc->logic_state.format) ? : "<unknown>"; | |
1017 | sr_info("Using file format: '%s'.", text); | |
1018 | rc = create_channels(in); | |
1019 | if (rc) | |
1020 | return rc; | |
1021 | rc = alloc_feed_buffer(in); | |
1022 | if (rc) | |
1023 | return rc; | |
1024 | in->sdi_ready = TRUE; | |
1025 | return SR_OK; | |
1026 | } | |
1027 | ||
1028 | /* Process sample data, after the header got processed. */ | |
1029 | return parse_samples(in); | |
1030 | } | |
1031 | ||
1032 | static int end(struct sr_input *in) | |
1033 | { | |
1034 | struct context *inc; | |
1035 | int rc; | |
1036 | ||
1037 | /* Nothing to do here if we never started feeding the session. */ | |
1038 | if (!in->sdi_ready) | |
1039 | return SR_OK; | |
1040 | ||
1041 | /* | |
1042 | * Process input data which may not have been inspected before. | |
1043 | * Flush any potentially queued samples. | |
1044 | */ | |
1045 | rc = parse_samples(in); | |
1046 | if (rc) | |
1047 | return rc; | |
1048 | rc = flush_feed_buffer(in); | |
1049 | if (rc) | |
1050 | return rc; | |
1051 | ||
1052 | /* End the session feed if one was started. */ | |
1053 | inc = in->priv; | |
1054 | if (inc->module_state.header_sent) { | |
1055 | rc = std_session_send_df_end(in->sdi); | |
1056 | if (rc) | |
1057 | return rc; | |
1058 | inc->module_state.header_sent = FALSE; | |
1059 | } | |
1060 | ||
1061 | /* Input data shall be exhausted by now. Non-fatal condition. */ | |
1062 | if (in->buf->len) | |
1063 | sr_warn("Unprocessed remaining input: %zu bytes.", in->buf->len); | |
1064 | ||
1065 | return SR_OK; | |
1066 | } | |
1067 | ||
1068 | static void cleanup(struct sr_input *in) | |
1069 | { | |
1070 | struct context *inc; | |
1071 | struct context_options save_opts; | |
003ad0ab | 1072 | GSList *save_channels; |
d891892d GS |
1073 | |
1074 | if (!in) | |
1075 | return; | |
1076 | inc = in->priv; | |
1077 | if (!inc) | |
1078 | return; | |
1079 | ||
1080 | /* Keep references to previously created channels. */ | |
1081 | g_slist_free_full(inc->module_state.prev_channels, sr_channel_free_cb); | |
1082 | inc->module_state.prev_channels = in->sdi->channels; | |
1083 | in->sdi->channels = NULL; | |
1084 | ||
1085 | /* Release dynamically allocated resources. */ | |
1086 | relse_feed_buffer(in); | |
1087 | ||
1088 | /* Clear internal state, but keep what .init() has provided. */ | |
1089 | save_opts = inc->options; | |
003ad0ab | 1090 | save_channels = inc->module_state.prev_channels; |
d891892d GS |
1091 | memset(inc, 0, sizeof(*inc)); |
1092 | inc->options = save_opts; | |
003ad0ab | 1093 | inc->module_state.prev_channels = save_channels; |
d891892d GS |
1094 | } |
1095 | ||
1096 | static int reset(struct sr_input *in) | |
1097 | { | |
1098 | struct context *inc; | |
1099 | ||
1100 | inc = in->priv; | |
1101 | ||
1102 | /* | |
1103 | * The input module's .reset() routine clears the 'inc' context. | |
1104 | * But 'in' is kept which contains channel groups which reference | |
1105 | * channels. We cannot re-create the channels, since applications | |
1106 | * still reference them and expect us to keep them. The .cleanup() | |
1107 | * routine also keeps the user specified option values, the module | |
1108 | * will derive internal state again when the input gets re-read. | |
1109 | */ | |
1110 | cleanup(in); | |
1111 | in->sdi->channels = inc->module_state.prev_channels; | |
003ad0ab | 1112 | inc->module_state.prev_channels = NULL; |
d891892d GS |
1113 | |
1114 | inc->module_state.got_header = FALSE; | |
1115 | inc->module_state.header_sent = FALSE; | |
1116 | inc->module_state.rate_sent = FALSE; | |
1117 | g_string_truncate(in->buf, 0); | |
1118 | ||
1119 | return SR_OK; | |
1120 | } | |
1121 | ||
1122 | enum option_index { | |
1123 | OPT_FMT_TYPE, | |
1124 | OPT_CHANGE, | |
1125 | OPT_WORD_SIZE, | |
1126 | OPT_NUM_LOGIC, | |
1127 | OPT_SAMPLERATE, | |
1128 | OPT_MAX, | |
1129 | }; | |
1130 | ||
1131 | static struct sr_option options[] = { | |
1132 | [OPT_FMT_TYPE] = { | |
1133 | "format", "File format.", | |
1134 | "Type of input file format. Not all types can get auto-detected.", | |
1135 | NULL, NULL, | |
1136 | }, | |
1137 | [OPT_CHANGE] = { | |
1138 | "changed", "Save when changed.", | |
1139 | "Sample value was saved when changed (in contrast to: every sample).", | |
1140 | NULL, NULL, | |
1141 | }, | |
1142 | [OPT_WORD_SIZE] = { | |
1143 | "wordsize", "Word size.", | |
1144 | "The number of bits per set of samples for digital data.", | |
1145 | NULL, NULL, | |
1146 | }, | |
1147 | [OPT_NUM_LOGIC] = { | |
1148 | "logic_channels", "Channel count.", | |
1149 | "The number of digital channels. Word size is used when not specified.", | |
1150 | NULL, NULL, | |
1151 | }, | |
1152 | [OPT_SAMPLERATE] = { | |
1153 | "samplerate", "Samplerate.", | |
1154 | "The samplerate. Needed when the file content lacks this information.", | |
1155 | NULL, NULL, | |
1156 | }, | |
1157 | [OPT_MAX] = ALL_ZERO, | |
1158 | }; | |
1159 | ||
1160 | static const struct sr_option *get_options(void) | |
1161 | { | |
1162 | enum logic_format fmt_idx; | |
1163 | const char *fmt_text; | |
1164 | size_t word_size; | |
1165 | GSList *l; | |
1166 | ||
1167 | /* Been here before? Already assigned default values? */ | |
1168 | if (options[0].def) | |
1169 | return options; | |
1170 | ||
1171 | /* Assign default values, and list choices to select from. */ | |
1172 | fmt_text = format_texts[FMT_AUTO_DETECT]; | |
1173 | options[OPT_FMT_TYPE].def = g_variant_ref_sink(g_variant_new_string(fmt_text)); | |
1174 | l = NULL; | |
1175 | for (fmt_idx = FMT_AUTO_DETECT; fmt_idx < ARRAY_SIZE(format_texts); fmt_idx++) { | |
1176 | fmt_text = format_texts[fmt_idx]; | |
1177 | if (!fmt_text || !*fmt_text) | |
1178 | continue; | |
1179 | l = g_slist_append(l, g_variant_ref_sink(g_variant_new_string(fmt_text))); | |
1180 | } | |
1181 | options[OPT_FMT_TYPE].values = l; | |
1182 | options[OPT_CHANGE].def = g_variant_ref_sink(g_variant_new_boolean(FALSE)); | |
1183 | options[OPT_WORD_SIZE].def = g_variant_ref_sink(g_variant_new_uint32(8)); | |
1184 | l = NULL; | |
1185 | for (word_size = sizeof(uint8_t); word_size <= sizeof(uint64_t); word_size *= 2) | |
1186 | l = g_slist_append(l, g_variant_ref_sink(g_variant_new_uint32(8 * word_size))); | |
1187 | options[OPT_WORD_SIZE].values = l; | |
1188 | options[OPT_NUM_LOGIC].def = g_variant_ref_sink(g_variant_new_uint32(0)); | |
1189 | options[OPT_SAMPLERATE].def = g_variant_ref_sink(g_variant_new_uint64(0)); | |
1190 | ||
1191 | return options; | |
1192 | } | |
1193 | ||
1194 | SR_PRIV struct sr_input_module input_saleae = { | |
1195 | .id = "saleae", | |
1196 | .name = "Saleae", | |
1197 | #if SALEAE_WITH_SAL_SUPPORT | |
1198 | .desc = "Saleae Logic software export/save files", | |
1199 | .exts = (const char *[]){"bin", "sal", NULL}, | |
1200 | #else | |
1201 | .desc = "Saleae Logic software export files", | |
1202 | .exts = (const char *[]){"bin", NULL}, | |
1203 | #endif | |
1204 | .metadata = { | |
1205 | SR_INPUT_META_FILENAME, | |
1206 | SR_INPUT_META_HEADER | SR_INPUT_META_REQUIRED | |
1207 | }, | |
1208 | .options = get_options, | |
1209 | .format_match = format_match, | |
1210 | .init = init, | |
1211 | .receive = receive, | |
1212 | .end = end, | |
1213 | .cleanup = cleanup, | |
1214 | .reset = reset, | |
1215 | }; |