]>
Commit | Line | Data |
---|---|---|
6d2897e3 SA |
1 | /* |
2 | * This file is part of the libsigrok project. | |
3 | * | |
4 | * Copyright (C) 2015 Soeren Apel <soeren@apelpie.net> | |
5 | * Copyright (C) 2015 Bert Vermeulen <bert@biot.com> | |
6 | * | |
7 | * This program is free software: you can redistribute it and/or modify | |
8 | * it under the terms of the GNU General Public License as published by | |
9 | * the Free Software Foundation, either version 3 of the License, or | |
10 | * (at your option) any later version. | |
11 | * | |
12 | * This program is distributed in the hope that it will be useful, | |
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 | * GNU General Public License for more details. | |
16 | * | |
17 | * You should have received a copy of the GNU General Public License | |
18 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
19 | */ | |
20 | ||
21 | /* | |
22 | * Usage notes: | |
23 | * This input module reads .ad files created using the | |
24 | * following practice commands: | |
25 | * | |
26 | * I.SAVE <file> /NoCompress | |
27 | * IPROBE.SAVE <file> /NoCompress | |
28 | * | |
29 | * It currently cannot make use of files that have been | |
30 | * saved using /QuickCompress, /Compress or /ZIP. | |
31 | * As a workaround you may load the file in PowerView | |
32 | * using I.LOAD / IPROBE.LOAD and re-save using /NoCompress. | |
33 | */ | |
34 | ||
35 | #include <config.h> | |
36 | #include <stdlib.h> | |
37 | #include <string.h> | |
38 | #include <sys/types.h> | |
39 | #include <sys/stat.h> | |
40 | #include <fcntl.h> | |
41 | #include <unistd.h> | |
42 | #include <sys/time.h> | |
43 | #include <libsigrok/libsigrok.h> | |
44 | #include "libsigrok-internal.h" | |
45 | ||
46 | #define LOG_PREFIX "input/trace32_ad" | |
47 | ||
9a4fd01a | 48 | #define CHUNK_SIZE (4 * 1024 * 1024) |
6d2897e3 | 49 | #define MAX_POD_COUNT 12 |
6d2897e3 | 50 | |
80430d4d GS |
51 | #define SPACE ' ' |
52 | #define CTRLZ '\x1a' | |
53 | #define TRACE32 "trace32" | |
54 | ||
d9251a2c | 55 | #define TIMESTAMP_RESOLUTION ((double)0.000000000078125) /* 0.078125 ns */ |
6d2897e3 SA |
56 | |
57 | /* | |
58 | * The resolution equals a sampling freq of 12.8 GHz. That's a bit high | |
59 | * for inter-record sample generation, so we scale it down to 200 MHz | |
60 | * for now. That way, the scaling factor becomes 32. | |
61 | */ | |
6266deb8 | 62 | #define DEFAULT_SAMPLERATE 200 |
6d2897e3 | 63 | |
83df004b GS |
64 | enum ad_format { |
65 | AD_FORMAT_UNKNOWN, | |
7ed4ae63 SA |
66 | AD_FORMAT_BINHDR1, /* Binary header, binary data, textual setup info, v1 */ |
67 | AD_FORMAT_BINHDR2, /* Binary header, binary data, textual setup info, v2 */ | |
83df004b | 68 | AD_FORMAT_TXTHDR, /* Textual header, binary data */ |
6d2897e3 SA |
69 | }; |
70 | ||
83df004b | 71 | enum ad_device { |
6d2897e3 SA |
72 | AD_DEVICE_PI = 1, /* Data recorded by LA-7940 PowerIntegrator or */ |
73 | /* LA-394x PowerIntegrator II. */ | |
74 | AD_DEVICE_IPROBE /* Data recorded by LA-769x PowerTrace II IProbe. */ | |
75 | /* Missing file format info for LA-793x ICD PowerProbe */ | |
76 | /* Missing file format info for LA-4530 uTrace analog probe */ | |
77 | }; | |
78 | ||
83df004b | 79 | enum ad_mode { |
6d2897e3 SA |
80 | AD_MODE_250MHZ = 0, |
81 | AD_MODE_500MHZ = 1 | |
82 | }; | |
83 | ||
83df004b | 84 | enum ad_compr { |
6d2897e3 | 85 | AD_COMPR_NONE = 0, /* File created with /NOCOMPRESS */ |
d9251a2c | 86 | AD_COMPR_QCOMP = 6, /* File created with /COMPRESS or /QUICKCOMPRESS */ |
6d2897e3 SA |
87 | }; |
88 | ||
89 | struct context { | |
90 | gboolean meta_sent; | |
a5be5d5b | 91 | gboolean header_read, records_read, trigger_sent; |
83df004b GS |
92 | enum ad_format format; |
93 | enum ad_device device; | |
94 | enum ad_mode record_mode; | |
95 | enum ad_compr compression; | |
6d2897e3 SA |
96 | char pod_status[MAX_POD_COUNT]; |
97 | struct sr_channel *channels[MAX_POD_COUNT][17]; /* 16 + CLK */ | |
98 | uint64_t trigger_timestamp; | |
7ed4ae63 | 99 | uint32_t header_size, record_size, record_count, cur_record; |
6d2897e3 | 100 | int32_t last_record; |
6266deb8 SA |
101 | uint64_t samplerate; |
102 | double timestamp_scale; | |
6d2897e3 SA |
103 | GString *out_buf; |
104 | }; | |
105 | ||
106 | static int process_header(GString *buf, struct context *inc); | |
107 | static void create_channels(struct sr_input *in); | |
108 | ||
8c4bff1d GS |
109 | /* Transform non-printable chars to '\xNN' presentation. */ |
110 | static char *printable_name(const char *name) | |
111 | { | |
112 | size_t l, i; | |
113 | char *s, *p; | |
114 | ||
115 | if (!name) | |
116 | return NULL; | |
117 | l = strlen(name); | |
118 | s = g_malloc0(l * strlen("\\x00") + 1); | |
119 | for (p = s, i = 0; i < l; i++) { | |
120 | if (g_ascii_isprint(name[i])) { | |
121 | *p++ = name[i]; | |
122 | } else { | |
67765e46 | 123 | snprintf(p, 5, "\\x%02x", name[i]); |
8c4bff1d GS |
124 | p += strlen("\\x00"); |
125 | } | |
126 | } | |
127 | *p = '\0'; | |
128 | ||
129 | return s; | |
130 | } | |
131 | ||
6d2897e3 SA |
132 | static char get_pod_name_from_id(int id) |
133 | { | |
134 | switch (id) { | |
135 | case 0: return 'A'; | |
136 | case 1: return 'B'; | |
137 | case 2: return 'C'; | |
138 | case 3: return 'D'; | |
139 | case 4: return 'E'; | |
140 | case 5: return 'F'; | |
141 | case 6: return 'J'; | |
142 | case 7: return 'K'; | |
143 | case 8: return 'L'; | |
144 | case 9: return 'M'; | |
145 | case 10: return 'N'; | |
146 | case 11: return 'O'; | |
147 | default: | |
148 | sr_err("get_pod_name_from_id() called with invalid ID %d!", id); | |
149 | } | |
150 | return 'X'; | |
151 | } | |
152 | ||
153 | static int init(struct sr_input *in, GHashTable *options) | |
154 | { | |
155 | struct context *inc; | |
156 | int pod; | |
157 | char id[17]; | |
158 | ||
159 | in->sdi = g_malloc0(sizeof(struct sr_dev_inst)); | |
160 | in->priv = g_malloc0(sizeof(struct context)); | |
161 | ||
162 | inc = in->priv; | |
163 | ||
6266deb8 SA |
164 | /* Calculate the desired timestamp scaling factor. */ |
165 | inc->samplerate = 1000000 * | |
166 | g_variant_get_uint32(g_hash_table_lookup(options, "samplerate")); | |
167 | ||
168 | inc->timestamp_scale = ((1 / TIMESTAMP_RESOLUTION) / (double)inc->samplerate); | |
169 | ||
6d2897e3 SA |
170 | /* Enable the pods the user chose to see. */ |
171 | for (pod = 0; pod < MAX_POD_COUNT; pod++) { | |
172 | g_snprintf(id, sizeof(id), "pod%c", get_pod_name_from_id(pod)); | |
173 | if (g_variant_get_boolean(g_hash_table_lookup(options, id))) | |
174 | inc->pod_status[pod] = 1; | |
175 | } | |
176 | ||
177 | create_channels(in); | |
178 | if (g_slist_length(in->sdi->channels) == 0) { | |
179 | sr_err("No pods were selected and thus no channels created, aborting."); | |
180 | g_free(in->priv); | |
181 | g_free(in->sdi); | |
182 | return SR_ERR; | |
183 | } | |
184 | ||
8bc2fa6d | 185 | inc->out_buf = g_string_sized_new(CHUNK_SIZE); |
6d2897e3 SA |
186 | |
187 | return SR_OK; | |
188 | } | |
189 | ||
54ee427d | 190 | static int format_match(GHashTable *metadata, unsigned int *confidence) |
6d2897e3 SA |
191 | { |
192 | GString *buf; | |
54ee427d | 193 | int rc; |
6d2897e3 SA |
194 | |
195 | buf = g_hash_table_lookup(metadata, GINT_TO_POINTER(SR_INPUT_META_HEADER)); | |
54ee427d | 196 | rc = process_header(buf, NULL); |
6d2897e3 | 197 | |
54ee427d GS |
198 | if (rc != SR_OK) |
199 | return rc; | |
200 | *confidence = 10; | |
201 | ||
202 | return SR_OK; | |
6d2897e3 SA |
203 | } |
204 | ||
205 | static int process_header(GString *buf, struct context *inc) | |
206 | { | |
207 | char *format_name, *format_name_sig; | |
8c4bff1d | 208 | char *p; |
80430d4d | 209 | int has_trace32; |
83df004b GS |
210 | size_t record_size; |
211 | enum ad_device device_id; | |
212 | enum ad_format format; | |
6d2897e3 SA |
213 | |
214 | /* | |
7ed4ae63 | 215 | * First-level file header: |
628dc330 SA |
216 | * 0x00-1F file format name |
217 | * 0x20 u64 trigger timestamp | |
218 | * 0x28-2F unused | |
219 | * 0x30 u8 compression | |
220 | * 0x31-35 ?? | |
221 | * 0x32 u8 0x00 (PI), 0x01 (iprobe) | |
7ed4ae63 SA |
222 | * 0x36 u8 device id: 0x08 (PI 250/500), 0x0A (iprobe 250) |
223 | */ | |
224 | ||
225 | /* | |
226 | * Second-level file header, version 1: | |
227 | * 0x37 u8 capture speed: 0x00 (250), 0x01 (500) | |
628dc330 SA |
228 | * 0x38 u8 record size |
229 | * 0x39-3B const 0x00 | |
230 | * 0x3C u32 number of records | |
231 | * 0x40 s32 id of last record | |
232 | * 0x44-4D ?? | |
233 | * 0x47 u8 const 0x80=128 | |
234 | * 0x48 u8 const 0x01 | |
235 | * 0x4E-4F ?? | |
6d2897e3 SA |
236 | */ |
237 | ||
7ed4ae63 SA |
238 | /* |
239 | * Second-level file header, version 2: | |
240 | * 0x37 u8 ?? | |
241 | * 0x38 u64 ?? | |
242 | * 0x40 u64 ?? | |
243 | * 0x48 u8 record size | |
244 | * 0x49-4F ?? | |
245 | * 0x50 u64 ?? | |
246 | * 0x58 u64 number of records | |
247 | * 0x60 u64 ?? | |
248 | * 0x68 u64 ?? | |
249 | * 0x70 u64 ?? | |
250 | * 0x78 u64 ?? | |
251 | * 0x80 u64 ?? | |
252 | * 0x88 u64 ?? (timestamp of some kind?) | |
253 | * 0x90 u64 ?? | |
254 | * 0x98-9E ?? | |
255 | * 0x9F u8 capture speed: 0x00 (250), 0x01 (500) | |
256 | * 0xA0 u64 ?? | |
257 | * 0xA8 u64 ?? | |
258 | * 0xB0 u64 ?? | |
259 | * 0xB8-CF version string? (e.g. '93173--96069', same for all tested .ad files) | |
260 | * 0xC8 u16 ?? | |
261 | */ | |
262 | ||
8c4bff1d GS |
263 | /* |
264 | * Note: The routine is called from different contexts. Either | |
265 | * to auto-detect the file format (format_match(), 'inc' is NULL), | |
266 | * or to process the data during acquisition (receive(), 'inc' | |
267 | * is a valid pointer). This header parse routine shall gracefully | |
268 | * deal with unexpected or incorrect input data. | |
269 | */ | |
6d2897e3 | 270 | |
80430d4d GS |
271 | /* |
272 | * Get up to the first 32 bytes of the file content. File format | |
273 | * names end on SPACE or CTRL-Z (or NUL). Trim trailing SPACE | |
274 | * before further processing. | |
275 | */ | |
6d2897e3 | 276 | format_name = g_strndup(buf->str, 32); |
80430d4d GS |
277 | p = strchr(format_name, CTRLZ); |
278 | if (p) | |
279 | *p = '\0'; | |
280 | g_strchomp(format_name); | |
6d2897e3 | 281 | |
80430d4d GS |
282 | /* |
283 | * File format names either start with the "trace32" literal, | |
284 | * or with a digit and SPACE. | |
285 | */ | |
286 | format_name_sig = g_strndup(format_name, strlen(TRACE32)); | |
287 | has_trace32 = g_strcmp0(format_name_sig, TRACE32) == 0; | |
288 | g_free(format_name_sig); | |
6d2897e3 | 289 | |
83df004b | 290 | format = AD_FORMAT_UNKNOWN; |
80430d4d GS |
291 | if (has_trace32) { |
292 | /* Literal "trace32" leader, binary header follows. */ | |
7ed4ae63 | 293 | format = AD_FORMAT_BINHDR1; |
80430d4d GS |
294 | } else if (g_ascii_isdigit(format_name[0]) && (format_name[1] == SPACE)) { |
295 | /* Digit and SPACE leader, currently unsupported text header. */ | |
83df004b | 296 | format = AD_FORMAT_TXTHDR; |
6d2897e3 | 297 | g_free(format_name); |
cdb134eb GS |
298 | if (inc) |
299 | sr_err("This format isn't implemented yet, aborting."); | |
6d2897e3 SA |
300 | return SR_ERR; |
301 | } else { | |
80430d4d | 302 | /* Unknown kind of format name. Unsupported. */ |
6d2897e3 | 303 | g_free(format_name); |
cdb134eb GS |
304 | if (inc) |
305 | sr_err("Don't know this file format, aborting."); | |
6d2897e3 SA |
306 | return SR_ERR; |
307 | } | |
83df004b GS |
308 | if (!format) |
309 | return SR_ERR; | |
6d2897e3 | 310 | |
7ed4ae63 SA |
311 | /* If the device id is 0x00, we have a v2 format file. */ |
312 | if (R8(buf->str + 0x36) == 0x00) | |
313 | format = AD_FORMAT_BINHDR2; | |
314 | ||
8c4bff1d | 315 | p = printable_name(format_name); |
83df004b GS |
316 | if (inc) |
317 | sr_dbg("File says it's \"%s\" -> format type %u.", p, format); | |
8c4bff1d | 318 | g_free(p); |
6d2897e3 | 319 | |
7ed4ae63 SA |
320 | record_size = (format == AD_FORMAT_BINHDR1) ? |
321 | R8(buf->str + 0x38) : R8(buf->str + 0x48); | |
6d2897e3 SA |
322 | device_id = 0; |
323 | ||
324 | if (g_strcmp0(format_name, "trace32 power integrator data") == 0) { | |
325 | if (record_size == 28 || record_size == 45) | |
326 | device_id = AD_DEVICE_PI; | |
327 | } else if (g_strcmp0(format_name, "trace32 iprobe data") == 0) { | |
328 | if (record_size == 11) | |
329 | device_id = AD_DEVICE_IPROBE; | |
330 | } | |
331 | ||
332 | if (!device_id) { | |
6d2897e3 | 333 | g_free(format_name); |
83df004b GS |
334 | if (inc) |
335 | sr_err("Cannot handle file with record size %zu.", | |
336 | record_size); | |
6d2897e3 SA |
337 | return SR_ERR; |
338 | } | |
339 | ||
6d2897e3 SA |
340 | g_free(format_name); |
341 | ||
342 | /* Stop processing the header if we just want to identify the file. */ | |
343 | if (!inc) | |
344 | return SR_OK; | |
345 | ||
83df004b | 346 | inc->format = format; |
6d2897e3 | 347 | inc->device = device_id; |
628dc330 SA |
348 | inc->trigger_timestamp = RL64(buf->str + 0x20); |
349 | inc->compression = R8(buf->str + 0x30); /* Maps to the enum. */ | |
7ed4ae63 | 350 | inc->header_size = (format == AD_FORMAT_BINHDR1) ? 0x50 : 0xCA; |
6d2897e3 | 351 | inc->record_size = record_size; |
7ed4ae63 SA |
352 | |
353 | if (format == AD_FORMAT_BINHDR1) { | |
354 | inc->record_mode = R8(buf->str + 0x37); /* Maps to the enum. */ | |
355 | inc->record_count = RL32(buf->str + 0x3C); | |
356 | inc->last_record = RL32S(buf->str + 0x40); | |
357 | } else { | |
358 | inc->record_mode = R8(buf->str + 0x9F); /* Maps to the enum. */ | |
359 | inc->record_count = RL32(buf->str + 0x58); | |
360 | inc->last_record = inc->record_count; | |
361 | } | |
6d2897e3 SA |
362 | |
363 | sr_dbg("Trigger occured at %lf s.", | |
364 | inc->trigger_timestamp * TIMESTAMP_RESOLUTION); | |
365 | sr_dbg("File contains %d records: first one is %d, last one is %d.", | |
366 | inc->record_count, (inc->last_record - inc->record_count + 1), | |
367 | inc->last_record); | |
368 | ||
369 | /* Check if we can work with this compression. */ | |
83df004b | 370 | if (inc->compression) { |
6d2897e3 SA |
371 | sr_err("File uses unsupported compression (0x%02X), can't continue.", |
372 | inc->compression); | |
373 | return SR_ERR; | |
374 | } | |
375 | ||
376 | inc->header_read = TRUE; | |
377 | ||
378 | return SR_OK; | |
379 | } | |
380 | ||
381 | static void create_channels(struct sr_input *in) | |
382 | { | |
383 | struct context *inc; | |
384 | int pod, channel, chan_id; | |
385 | char name[8]; | |
386 | ||
387 | inc = in->priv; | |
388 | chan_id = 0; | |
389 | ||
390 | for (pod = 0; pod < MAX_POD_COUNT; pod++) { | |
391 | if (!inc->pod_status[pod]) | |
392 | continue; | |
393 | ||
394 | for (channel = 0; channel < 16; channel++) { | |
00ed77f2 | 395 | snprintf(name, sizeof(name), "%c%d", get_pod_name_from_id(pod), channel); |
6d2897e3 SA |
396 | inc->channels[pod][channel] = |
397 | sr_channel_new(in->sdi, chan_id, SR_CHANNEL_LOGIC, TRUE, name); | |
398 | chan_id++; | |
399 | } | |
400 | ||
00ed77f2 | 401 | snprintf(name, sizeof(name), "CLK%c", get_pod_name_from_id(pod)); |
6d2897e3 SA |
402 | inc->channels[pod][16] = |
403 | sr_channel_new(in->sdi, chan_id, SR_CHANNEL_LOGIC, TRUE, name); | |
404 | chan_id++; | |
405 | } | |
406 | } | |
407 | ||
408 | static void send_metadata(struct sr_input *in) | |
409 | { | |
6d2897e3 SA |
410 | struct context *inc; |
411 | ||
6266deb8 | 412 | inc = in->priv; |
f8a8d4bb GS |
413 | (void)sr_session_send_meta(in->sdi, SR_CONF_SAMPLERATE, |
414 | g_variant_new_uint64(inc->samplerate)); | |
6d2897e3 SA |
415 | inc->meta_sent = TRUE; |
416 | } | |
417 | ||
418 | static void flush_output_buffer(struct sr_input *in) | |
419 | { | |
420 | struct context *inc; | |
421 | struct sr_datafeed_packet packet; | |
422 | struct sr_datafeed_logic logic; | |
423 | ||
424 | inc = in->priv; | |
425 | ||
426 | if (inc->out_buf->len) { | |
427 | packet.type = SR_DF_LOGIC; | |
428 | packet.payload = &logic; | |
429 | logic.unitsize = (g_slist_length(in->sdi->channels) + 7) / 8; | |
430 | logic.data = inc->out_buf->str; | |
431 | logic.length = inc->out_buf->len; | |
432 | sr_session_send(in->sdi, &packet); | |
433 | ||
434 | g_string_truncate(inc->out_buf, 0); | |
435 | } | |
436 | } | |
437 | ||
438 | static void process_record_pi(struct sr_input *in, gsize start) | |
439 | { | |
6d2897e3 SA |
440 | struct context *inc; |
441 | uint64_t timestamp, next_timestamp; | |
442 | uint32_t pod_data; | |
443 | char single_payload[12 * 3]; | |
444 | GString *buf; | |
445 | int i, pod_count, clk_offset, packet_count, pod; | |
446 | int payload_bit, payload_len, value; | |
447 | ||
448 | inc = in->priv; | |
449 | buf = in->buf; | |
450 | ||
451 | /* | |
628dc330 SA |
452 | * 0x00 u8 timestamp |
453 | * 0x08 u16 A15..0 | |
454 | * 0x0A u16 B15..0 | |
455 | * 0x0C u16 C15..0 | |
456 | * 0x0E u16 D15..0 | |
457 | * 0x10 u16 E15..0 | |
458 | * 0x12 u16 F15..0 | |
459 | * 0x14 u32 ?? | |
460 | * 0x18 u16 J15..0 Not present in 500MHz mode | |
461 | * 0x1A u16 K15..0 Not present in 500MHz mode | |
462 | * 0x1C u16 L15..0 Not present in 500MHz mode | |
463 | * 0x1E u16 M15..0 Not present in 500MHz mode | |
464 | * 0x20 u16 N15..0 Not present in 500MHz mode | |
465 | * 0x22 u16 O15..0 Not present in 500MHz mode | |
466 | * 0x24 u32 ?? Not present in 500MHz mode | |
467 | * 0x28/18 u8 CLKF..A (32=CLKF, .., 1=CLKA) | |
468 | * 0x29/1A u8 CLKO..J (32=CLKO, .., 1=CLKJ) Not present in 500MHz mode | |
469 | * 0x2A/19 u8 ?? | |
470 | * 0x2B/1A u8 ?? | |
471 | * 0x2C/1B u8 ?? | |
6d2897e3 SA |
472 | */ |
473 | ||
474 | timestamp = RL64(buf->str + start); | |
475 | ||
476 | if (inc->record_mode == AD_MODE_500MHZ) { | |
477 | pod_count = 6; | |
628dc330 | 478 | clk_offset = 0x18; |
6d2897e3 SA |
479 | } else { |
480 | pod_count = 12; | |
628dc330 | 481 | clk_offset = 0x28; |
6d2897e3 SA |
482 | } |
483 | ||
484 | payload_bit = 0; | |
485 | payload_len = 0; | |
486 | single_payload[0] = 0; | |
487 | ||
488 | for (pod = 0; pod < pod_count; pod++) { | |
489 | if (!inc->pod_status[pod]) | |
490 | continue; | |
491 | ||
492 | switch (pod) { | |
493 | case 0: /* A */ | |
628dc330 | 494 | pod_data = RL16(buf->str + start + 0x08); |
6d2897e3 SA |
495 | pod_data |= (RL16(buf->str + start + clk_offset) & 1) << 16; |
496 | break; | |
497 | case 1: /* B */ | |
628dc330 | 498 | pod_data = RL16(buf->str + start + 0x0A); |
6d2897e3 SA |
499 | pod_data |= (RL16(buf->str + start + clk_offset) & 2) << 15; |
500 | break; | |
501 | case 2: /* C */ | |
628dc330 | 502 | pod_data = RL16(buf->str + start + 0x0C); |
6d2897e3 SA |
503 | pod_data |= (RL16(buf->str + start + clk_offset) & 4) << 14; |
504 | break; | |
505 | case 3: /* D */ | |
628dc330 | 506 | pod_data = RL16(buf->str + start + 0x0E); |
6d2897e3 SA |
507 | pod_data |= (RL16(buf->str + start + clk_offset) & 8) << 13; |
508 | break; | |
509 | case 4: /* E */ | |
628dc330 | 510 | pod_data = RL16(buf->str + start + 0x10); |
6d2897e3 SA |
511 | pod_data |= (RL16(buf->str + start + clk_offset) & 16) << 12; |
512 | break; | |
513 | case 5: /* F */ | |
628dc330 | 514 | pod_data = RL16(buf->str + start + 0x12); |
6d2897e3 SA |
515 | pod_data |= (RL16(buf->str + start + clk_offset) & 32) << 11; |
516 | break; | |
517 | case 6: /* J */ | |
628dc330 SA |
518 | pod_data = RL16(buf->str + start + 0x18); |
519 | pod_data |= (RL16(buf->str + start + 0x29) & 1) << 16; | |
6d2897e3 SA |
520 | break; |
521 | case 7: /* K */ | |
628dc330 SA |
522 | pod_data = RL16(buf->str + start + 0x1A); |
523 | pod_data |= (RL16(buf->str + start + 0x29) & 2) << 15; | |
6d2897e3 SA |
524 | break; |
525 | case 8: /* L */ | |
628dc330 SA |
526 | pod_data = RL16(buf->str + start + 0x1C); |
527 | pod_data |= (RL16(buf->str + start + 0x29) & 4) << 14; | |
6d2897e3 SA |
528 | break; |
529 | case 9: /* M */ | |
628dc330 SA |
530 | pod_data = RL16(buf->str + start + 0x1E); |
531 | pod_data |= (RL16(buf->str + start + 0x29) & 8) << 13; | |
6d2897e3 SA |
532 | break; |
533 | case 10: /* N */ | |
628dc330 SA |
534 | pod_data = RL16(buf->str + start + 0x20); |
535 | pod_data |= (RL16(buf->str + start + 0x29) & 16) << 12; | |
6d2897e3 SA |
536 | break; |
537 | case 11: /* O */ | |
628dc330 SA |
538 | pod_data = RL16(buf->str + start + 0x22); |
539 | pod_data |= (RL16(buf->str + start + 0x29) & 32) << 11; | |
6d2897e3 SA |
540 | break; |
541 | default: | |
35037b1d | 542 | pod_data = 0; |
6d2897e3 SA |
543 | sr_err("Don't know how to obtain data for pod %d.", pod); |
544 | } | |
545 | ||
546 | for (i = 0; i < 17; i++) { | |
547 | value = (pod_data >> i) & 1; | |
548 | single_payload[payload_len] |= value << payload_bit; | |
549 | ||
550 | payload_bit++; | |
551 | if (payload_bit > 7) { | |
552 | payload_bit = 0; | |
553 | payload_len++; | |
554 | single_payload[payload_len] = 0; | |
555 | } | |
556 | } | |
557 | } | |
558 | ||
559 | /* Make sure that payload_len accounts for any incomplete bytes used. */ | |
560 | if (payload_bit) | |
561 | payload_len++; | |
562 | ||
563 | i = (g_slist_length(in->sdi->channels) + 7) / 8; | |
564 | if (payload_len != i) { | |
565 | sr_err("Payload unit size is %d but should be %d!", payload_len, i); | |
566 | return; | |
567 | } | |
568 | ||
a5be5d5b | 569 | if (timestamp == inc->trigger_timestamp && !inc->trigger_sent) { |
6d2897e3 SA |
570 | sr_dbg("Trigger @%lf s, record #%d.", |
571 | timestamp * TIMESTAMP_RESOLUTION, inc->cur_record); | |
0fa71943 | 572 | std_session_send_df_trigger(in->sdi); |
a5be5d5b | 573 | inc->trigger_sent = TRUE; |
6d2897e3 SA |
574 | } |
575 | ||
576 | /* Is this the last record in the file? */ | |
577 | if (inc->cur_record == inc->record_count - 1) { | |
578 | /* It is, so send the last sample data only once. */ | |
579 | g_string_append_len(inc->out_buf, single_payload, payload_len); | |
580 | } else { | |
581 | /* It's not, so fill the time gap by sending lots of data. */ | |
582 | next_timestamp = RL64(buf->str + start + inc->record_size); | |
6266deb8 | 583 | packet_count = (int)(next_timestamp - timestamp) / inc->timestamp_scale; |
6d2897e3 SA |
584 | |
585 | /* Make sure we send at least one data set. */ | |
586 | if (packet_count == 0) | |
587 | packet_count = 1; | |
588 | ||
589 | for (i = 0; i < packet_count; i++) | |
590 | g_string_append_len(inc->out_buf, single_payload, payload_len); | |
591 | } | |
592 | ||
8bc2fa6d | 593 | if (inc->out_buf->len >= CHUNK_SIZE) |
6d2897e3 SA |
594 | flush_output_buffer(in); |
595 | } | |
596 | ||
597 | static void process_record_iprobe(struct sr_input *in, gsize start) | |
598 | { | |
6d2897e3 SA |
599 | struct context *inc; |
600 | uint64_t timestamp, next_timestamp; | |
601 | char single_payload[3]; | |
602 | int i, payload_len, packet_count; | |
603 | ||
604 | inc = in->priv; | |
605 | ||
606 | /* | |
628dc330 SA |
607 | * 0x00 u64 timestamp |
608 | * 0x08 u16 IP15..0 | |
609 | * 0x0A u8 CLK | |
6d2897e3 SA |
610 | */ |
611 | ||
612 | timestamp = RL64(in->buf->str + start); | |
628dc330 SA |
613 | single_payload[0] = R8(in->buf->str + start + 0x08); |
614 | single_payload[1] = R8(in->buf->str + start + 0x09); | |
615 | single_payload[2] = R8(in->buf->str + start + 0x0A) & 1; | |
6d2897e3 SA |
616 | payload_len = 3; |
617 | ||
a5be5d5b | 618 | if (timestamp == inc->trigger_timestamp && !inc->trigger_sent) { |
6d2897e3 SA |
619 | sr_dbg("Trigger @%lf s, record #%d.", |
620 | timestamp * TIMESTAMP_RESOLUTION, inc->cur_record); | |
0fa71943 | 621 | std_session_send_df_trigger(in->sdi); |
a5be5d5b | 622 | inc->trigger_sent = TRUE; |
6d2897e3 SA |
623 | } |
624 | ||
625 | /* Is this the last record in the file? */ | |
626 | if (inc->cur_record == inc->record_count - 1) { | |
627 | /* It is, so send the last sample data only once. */ | |
628 | g_string_append_len(inc->out_buf, single_payload, payload_len); | |
629 | } else { | |
630 | /* It's not, so fill the time gap by sending lots of data. */ | |
631 | next_timestamp = RL64(in->buf->str + start + inc->record_size); | |
6266deb8 | 632 | packet_count = (int)(next_timestamp - timestamp) / inc->timestamp_scale; |
6d2897e3 SA |
633 | |
634 | /* Make sure we send at least one data set. */ | |
635 | if (packet_count == 0) | |
636 | packet_count = 1; | |
637 | ||
638 | for (i = 0; i < packet_count; i++) | |
639 | g_string_append_len(inc->out_buf, single_payload, payload_len); | |
640 | } | |
641 | ||
8bc2fa6d | 642 | if (inc->out_buf->len >= CHUNK_SIZE) |
6d2897e3 SA |
643 | flush_output_buffer(in); |
644 | } | |
645 | ||
646 | static void process_practice_token(struct sr_input *in, char *cmd_token) | |
647 | { | |
648 | struct context *inc; | |
649 | char **tokens; | |
650 | char chan_suffix[2], chan_name[33]; | |
651 | char *s1, *s2; | |
652 | int pod, ch; | |
653 | struct sr_channel *channel; | |
654 | ||
655 | inc = in->priv; | |
656 | ||
657 | /* | |
658 | * Commands of interest (I may also be IPROBE): | |
659 | * | |
660 | * I.TWIDTH | |
661 | * I.TPREDELAY | |
662 | * I.TDELAY | |
663 | * I.TYSNC.SELECT I.A0 HIGH | |
664 | * NAME.SET <port.chan> <name> <+/-> ... | |
665 | */ | |
666 | ||
667 | if (!cmd_token) | |
668 | return; | |
669 | ||
670 | if (cmd_token[0] == 0) | |
671 | return; | |
672 | ||
673 | tokens = g_strsplit(cmd_token, " ", 0); | |
674 | ||
675 | if (!tokens) | |
676 | return; | |
677 | ||
678 | if (g_strcmp0(tokens[0], "NAME.SET") == 0) { | |
679 | /* Let the user know when the channel has been inverted. */ | |
680 | /* This *should* be token #3 but there's an additonal space, making it #4. */ | |
681 | chan_suffix[0] = 0; | |
682 | chan_suffix[1] = 0; | |
683 | if (tokens[4]) { | |
684 | if (tokens[4][0] == '-') | |
685 | chan_suffix[0] = '-'; /* This is the way PowerView shows it. */ | |
686 | } | |
687 | ||
688 | /* | |
689 | * Command is using structure "NAME.SET I.A00 I.XYZ" or | |
690 | * "NAME.SET IP.00 IP.XYZ", depending on the device used. | |
691 | * Let's get strings with the I./IP. from both tokens removed. | |
692 | */ | |
693 | s1 = g_strstr_len(tokens[1], -1, ".") + 1; | |
694 | s2 = g_strstr_len(tokens[2], -1, ".") + 1; | |
695 | ||
696 | if (g_strcmp0(s1, "CLK") == 0) { | |
697 | /* CLK for iprobe */ | |
698 | pod = 0; | |
699 | ch = 16; | |
700 | } else if ((strlen(s1) == 4) && g_ascii_isupper(s1[3])) { | |
701 | /* CLKA/B/J/K for PowerIntegrator */ | |
702 | pod = s1[3] - (char)'A'; | |
703 | ch = 16; | |
704 | } else if (g_ascii_isupper(s1[0])) { | |
705 | /* A00 for PowerIntegrator */ | |
706 | pod = s1[0] - (char)'A'; | |
707 | ch = atoi(s1 + 1); | |
708 | } else { | |
709 | /* 00 for iprobe */ | |
710 | pod = 0; | |
711 | ch = atoi(s1); | |
712 | } | |
713 | ||
714 | channel = inc->channels[pod][ch]; | |
715 | g_snprintf(chan_name, sizeof(chan_name), "%s%s", s2, chan_suffix); | |
716 | ||
717 | sr_dbg("Changing channel name for %s to %s.", s1, chan_name); | |
718 | sr_dev_channel_name_set(channel, chan_name); | |
719 | } | |
720 | ||
721 | g_strfreev(tokens); | |
722 | } | |
723 | ||
724 | static void process_practice(struct sr_input *in) | |
725 | { | |
726 | char delimiter[3]; | |
727 | char **tokens, *token; | |
728 | int i; | |
729 | ||
730 | /* Gather all input data until we see the end marker. */ | |
731 | if (in->buf->str[in->buf->len - 1] != 0x29) | |
732 | return; | |
733 | ||
734 | delimiter[0] = 0x0A; | |
735 | delimiter[1] = ' '; | |
736 | delimiter[2] = 0; | |
737 | ||
738 | tokens = g_strsplit(in->buf->str, delimiter, 0); | |
739 | ||
740 | /* Special case: first token contains the start marker, too. Skip it. */ | |
741 | token = tokens[0]; | |
742 | for (i = 0; token[i]; i++) { | |
743 | if (token[i] == ' ') | |
744 | process_practice_token(in, token + i + 1); | |
745 | } | |
746 | ||
747 | for (i = 1; tokens[i]; i++) | |
748 | process_practice_token(in, tokens[i]); | |
749 | ||
750 | g_strfreev(tokens); | |
751 | ||
752 | g_string_erase(in->buf, 0, in->buf->len); | |
753 | } | |
754 | ||
755 | static int process_buffer(struct sr_input *in) | |
756 | { | |
757 | struct context *inc; | |
758 | int i, chunk_size, res; | |
759 | ||
760 | inc = in->priv; | |
761 | ||
762 | if (!inc->header_read) { | |
763 | res = process_header(in->buf, inc); | |
7ed4ae63 | 764 | g_string_erase(in->buf, 0, inc->header_size); |
6d2897e3 SA |
765 | if (res != SR_OK) |
766 | return res; | |
767 | } | |
768 | ||
769 | if (!inc->meta_sent) { | |
bee2b016 | 770 | std_session_send_df_header(in->sdi); |
6d2897e3 SA |
771 | send_metadata(in); |
772 | } | |
773 | ||
774 | if (!inc->records_read) { | |
775 | /* Cut off at a multiple of the record size. */ | |
776 | chunk_size = ((in->buf->len) / inc->record_size) * inc->record_size; | |
777 | ||
778 | /* There needs to be at least one more record process_record() can peek into. */ | |
779 | chunk_size -= inc->record_size; | |
780 | ||
781 | for (i = 0; (i < chunk_size) && (!inc->records_read); i += inc->record_size) { | |
782 | switch (inc->device) { | |
783 | case AD_DEVICE_PI: | |
784 | process_record_pi(in, i); | |
785 | break; | |
786 | case AD_DEVICE_IPROBE: | |
787 | process_record_iprobe(in, i); | |
788 | break; | |
789 | default: | |
790 | sr_err("Trying to process records for unknown device!"); | |
791 | return SR_ERR; | |
792 | } | |
793 | ||
794 | inc->cur_record++; | |
795 | if (inc->cur_record == inc->record_count) | |
796 | inc->records_read = TRUE; | |
797 | } | |
798 | ||
799 | g_string_erase(in->buf, 0, i); | |
800 | } | |
801 | ||
802 | if (inc->records_read) { | |
803 | /* Read practice commands that configure the setup. */ | |
804 | process_practice(in); | |
805 | } | |
806 | ||
807 | return SR_OK; | |
808 | } | |
809 | ||
810 | static int receive(struct sr_input *in, GString *buf) | |
811 | { | |
812 | g_string_append_len(in->buf, buf->str, buf->len); | |
813 | ||
814 | if (!in->sdi_ready) { | |
815 | /* sdi is ready, notify frontend. */ | |
816 | in->sdi_ready = TRUE; | |
817 | return SR_OK; | |
818 | } | |
819 | ||
820 | return process_buffer(in); | |
821 | } | |
822 | ||
823 | static int end(struct sr_input *in) | |
824 | { | |
825 | struct context *inc; | |
6d2897e3 SA |
826 | int ret; |
827 | ||
828 | inc = in->priv; | |
829 | ||
830 | if (in->sdi_ready) | |
831 | ret = process_buffer(in); | |
832 | else | |
833 | ret = SR_OK; | |
834 | ||
835 | flush_output_buffer(in); | |
836 | ||
3be42bc2 | 837 | if (inc->meta_sent) |
bee2b016 | 838 | std_session_send_df_end(in->sdi); |
6d2897e3 SA |
839 | |
840 | return ret; | |
841 | } | |
842 | ||
87616181 SA |
843 | static int reset(struct sr_input *in) |
844 | { | |
845 | struct context *inc = in->priv; | |
846 | ||
847 | inc->meta_sent = FALSE; | |
848 | inc->header_read = FALSE; | |
849 | inc->records_read = FALSE; | |
850 | inc->trigger_sent = FALSE; | |
851 | inc->cur_record = 0; | |
852 | ||
853 | g_string_truncate(in->buf, 0); | |
854 | ||
855 | return SR_OK; | |
856 | } | |
857 | ||
6d2897e3 SA |
858 | static struct sr_option options[] = { |
859 | { "podA", "Import pod A / iprobe", | |
860 | "Create channels and data for pod A / iprobe", NULL, NULL }, | |
861 | ||
862 | { "podB", "Import pod B", "Create channels and data for pod B", NULL, NULL }, | |
863 | { "podC", "Import pod C", "Create channels and data for pod C", NULL, NULL }, | |
864 | { "podD", "Import pod D", "Create channels and data for pod D", NULL, NULL }, | |
865 | { "podE", "Import pod E", "Create channels and data for pod E", NULL, NULL }, | |
866 | { "podF", "Import pod F", "Create channels and data for pod F", NULL, NULL }, | |
867 | { "podJ", "Import pod J", "Create channels and data for pod J", NULL, NULL }, | |
868 | { "podK", "Import pod K", "Create channels and data for pod K", NULL, NULL }, | |
869 | { "podL", "Import pod L", "Create channels and data for pod L", NULL, NULL }, | |
870 | { "podM", "Import pod M", "Create channels and data for pod M", NULL, NULL }, | |
871 | { "podN", "Import pod N", "Create channels and data for pod N", NULL, NULL }, | |
872 | { "podO", "Import pod O", "Create channels and data for pod O", NULL, NULL }, | |
6266deb8 | 873 | |
867293a1 | 874 | { "samplerate", "Reduced sample rate (MHz)", "Reduce the original sample rate of 12.8 GHz to the specified sample rate in MHz", NULL, NULL }, |
6266deb8 | 875 | |
6d2897e3 SA |
876 | ALL_ZERO |
877 | }; | |
878 | ||
879 | static const struct sr_option *get_options(void) | |
880 | { | |
881 | if (!options[0].def) { | |
882 | options[0].def = g_variant_ref_sink(g_variant_new_boolean(TRUE)); | |
883 | options[1].def = g_variant_ref_sink(g_variant_new_boolean(FALSE)); | |
884 | options[2].def = g_variant_ref_sink(g_variant_new_boolean(FALSE)); | |
885 | options[3].def = g_variant_ref_sink(g_variant_new_boolean(FALSE)); | |
886 | options[4].def = g_variant_ref_sink(g_variant_new_boolean(FALSE)); | |
887 | options[5].def = g_variant_ref_sink(g_variant_new_boolean(FALSE)); | |
888 | options[6].def = g_variant_ref_sink(g_variant_new_boolean(FALSE)); | |
889 | options[7].def = g_variant_ref_sink(g_variant_new_boolean(FALSE)); | |
890 | options[8].def = g_variant_ref_sink(g_variant_new_boolean(FALSE)); | |
891 | options[9].def = g_variant_ref_sink(g_variant_new_boolean(FALSE)); | |
892 | options[10].def = g_variant_ref_sink(g_variant_new_boolean(FALSE)); | |
893 | options[11].def = g_variant_ref_sink(g_variant_new_boolean(FALSE)); | |
6266deb8 | 894 | options[12].def = g_variant_ref_sink(g_variant_new_uint32(DEFAULT_SAMPLERATE)); |
6d2897e3 SA |
895 | } |
896 | ||
897 | return options; | |
898 | } | |
899 | ||
900 | SR_PRIV struct sr_input_module input_trace32_ad = { | |
901 | .id = "trace32_ad", | |
902 | .name = "Trace32_ad", | |
903 | .desc = "Lauterbach Trace32 logic analyzer data", | |
904 | .exts = (const char*[]){"ad", NULL}, | |
905 | .options = get_options, | |
906 | .metadata = { SR_INPUT_META_HEADER | SR_INPUT_META_REQUIRED }, | |
907 | .format_match = format_match, | |
908 | .init = init, | |
909 | .receive = receive, | |
910 | .end = end, | |
87616181 | 911 | .reset = reset, |
6d2897e3 | 912 | }; |