]>
Commit | Line | Data |
---|---|---|
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 | ||
48 | #define MAX_CHUNK_SIZE 4096 | |
49 | #define OUTBUF_FLUSH_SIZE 10240 | |
50 | #define MAX_POD_COUNT 12 | |
51 | #define HEADER_SIZE 80 | |
52 | ||
53 | #define TIMESTAMP_RESOLUTION ((double)0.000000000078125) /* 0.078125 ns */ | |
54 | ||
55 | /* | |
56 | * The resolution equals a sampling freq of 12.8 GHz. That's a bit high | |
57 | * for inter-record sample generation, so we scale it down to 200 MHz | |
58 | * for now. That way, the scaling factor becomes 32. | |
59 | */ | |
60 | #define DEFAULT_SAMPLERATE 200 | |
61 | ||
62 | enum { | |
63 | AD_FORMAT_BINHDR = 1, /* Binary header, binary data, textual setup info */ | |
64 | AD_FORMAT_TXTHDR /* Textual header, binary data */ | |
65 | }; | |
66 | ||
67 | enum { | |
68 | AD_DEVICE_PI = 1, /* Data recorded by LA-7940 PowerIntegrator or */ | |
69 | /* LA-394x PowerIntegrator II. */ | |
70 | AD_DEVICE_IPROBE /* Data recorded by LA-769x PowerTrace II IProbe. */ | |
71 | /* Missing file format info for LA-793x ICD PowerProbe */ | |
72 | /* Missing file format info for LA-4530 uTrace analog probe */ | |
73 | }; | |
74 | ||
75 | enum { | |
76 | AD_MODE_250MHZ = 0, | |
77 | AD_MODE_500MHZ = 1 | |
78 | }; | |
79 | ||
80 | enum { | |
81 | AD_COMPR_NONE = 0, /* File created with /NOCOMPRESS */ | |
82 | AD_COMPR_QCOMP = 6 /* File created with /COMPRESS or /QUICKCOMPRESS */ | |
83 | }; | |
84 | ||
85 | struct context { | |
86 | gboolean meta_sent; | |
87 | gboolean header_read, records_read, trigger_sent; | |
88 | char format, device, record_mode, compression; | |
89 | char pod_status[MAX_POD_COUNT]; | |
90 | struct sr_channel *channels[MAX_POD_COUNT][17]; /* 16 + CLK */ | |
91 | uint64_t trigger_timestamp; | |
92 | uint32_t record_size, record_count, cur_record; | |
93 | int32_t last_record; | |
94 | uint64_t samplerate; | |
95 | double timestamp_scale; | |
96 | GString *out_buf; | |
97 | }; | |
98 | ||
99 | static int process_header(GString *buf, struct context *inc); | |
100 | static void create_channels(struct sr_input *in); | |
101 | ||
102 | static char get_pod_name_from_id(int id) | |
103 | { | |
104 | switch (id) { | |
105 | case 0: return 'A'; | |
106 | case 1: return 'B'; | |
107 | case 2: return 'C'; | |
108 | case 3: return 'D'; | |
109 | case 4: return 'E'; | |
110 | case 5: return 'F'; | |
111 | case 6: return 'J'; | |
112 | case 7: return 'K'; | |
113 | case 8: return 'L'; | |
114 | case 9: return 'M'; | |
115 | case 10: return 'N'; | |
116 | case 11: return 'O'; | |
117 | default: | |
118 | sr_err("get_pod_name_from_id() called with invalid ID %d!", id); | |
119 | } | |
120 | return 'X'; | |
121 | } | |
122 | ||
123 | static int init(struct sr_input *in, GHashTable *options) | |
124 | { | |
125 | struct context *inc; | |
126 | int pod; | |
127 | char id[17]; | |
128 | ||
129 | in->sdi = g_malloc0(sizeof(struct sr_dev_inst)); | |
130 | in->priv = g_malloc0(sizeof(struct context)); | |
131 | ||
132 | inc = in->priv; | |
133 | ||
134 | /* Calculate the desired timestamp scaling factor. */ | |
135 | inc->samplerate = 1000000 * | |
136 | g_variant_get_uint32(g_hash_table_lookup(options, "samplerate")); | |
137 | ||
138 | inc->timestamp_scale = ((1 / TIMESTAMP_RESOLUTION) / (double)inc->samplerate); | |
139 | ||
140 | /* Enable the pods the user chose to see. */ | |
141 | for (pod = 0; pod < MAX_POD_COUNT; pod++) { | |
142 | g_snprintf(id, sizeof(id), "pod%c", get_pod_name_from_id(pod)); | |
143 | if (g_variant_get_boolean(g_hash_table_lookup(options, id))) | |
144 | inc->pod_status[pod] = 1; | |
145 | } | |
146 | ||
147 | create_channels(in); | |
148 | if (g_slist_length(in->sdi->channels) == 0) { | |
149 | sr_err("No pods were selected and thus no channels created, aborting."); | |
150 | g_free(in->priv); | |
151 | g_free(in->sdi); | |
152 | return SR_ERR; | |
153 | } | |
154 | ||
155 | inc->out_buf = g_string_sized_new(OUTBUF_FLUSH_SIZE); | |
156 | ||
157 | return SR_OK; | |
158 | } | |
159 | ||
160 | static int format_match(GHashTable *metadata) | |
161 | { | |
162 | GString *buf; | |
163 | ||
164 | buf = g_hash_table_lookup(metadata, GINT_TO_POINTER(SR_INPUT_META_HEADER)); | |
165 | ||
166 | return process_header(buf, NULL); | |
167 | } | |
168 | ||
169 | static int process_header(GString *buf, struct context *inc) | |
170 | { | |
171 | char *format_name, *format_name_sig; | |
172 | int i, record_size, device_id; | |
173 | ||
174 | /* | |
175 | * 00-31 (0x00-1F) file format name | |
176 | * 32-39 (0x20-27) trigger timestamp u64 | |
177 | * 40-47 (0x28-2F) unused | |
178 | * 48 (0x30) compression | |
179 | * 49-53 (0x31-35) ?? | |
180 | * 50 (0x32) 0x00 (PI), 0x01 (iprobe) | |
181 | * 54 (0x36) 0x08 (PI 250/500), 0x0A (iprobe 250) | |
182 | * 55 (0x37) 0x00 (250), 0x01 (500) | |
183 | * 56 (0x38) record size u8 | |
184 | * 57-59 (0x39-3B) const 0x00 | |
185 | * 60-63 (0x3C-3F) number of records u32 | |
186 | * 64-67 (0x40-43) id of last record s32 | |
187 | * 68-77 (0x44-4D) ?? | |
188 | * 71 (0x47) const 0x80=128 | |
189 | * 72 (0x48) const 0x01 | |
190 | * 78-79 (0x4E-4F) ?? | |
191 | */ | |
192 | ||
193 | /* Note: inc is off-limits until we check whether it's a valid pointer. */ | |
194 | ||
195 | format_name = g_strndup(buf->str, 32); | |
196 | ||
197 | /* File format name ends on 0x20/0x1A, let's remove both. */ | |
198 | for (i = 1; i < 31; i++) { | |
199 | if (format_name[i] == 0x1A) { | |
200 | format_name[i - 1] = 0; | |
201 | format_name[i] = 0; | |
202 | } | |
203 | } | |
204 | g_strchomp(format_name); /* This is for additional padding spaces. */ | |
205 | ||
206 | format_name_sig = g_strndup(format_name, 5); | |
207 | ||
208 | /* Desired file formats either start with digit+space or "trace32". */ | |
209 | if (g_strcmp0(format_name_sig, "trace32")) { | |
210 | if (inc) | |
211 | inc->format = AD_FORMAT_BINHDR; | |
212 | } else if (g_ascii_isdigit(format_name[0]) && (format_name[1] == 0x20)) { | |
213 | if (inc) | |
214 | inc->format = AD_FORMAT_TXTHDR; | |
215 | g_free(format_name_sig); | |
216 | g_free(format_name); | |
217 | sr_err("This format isn't implemented yet, aborting."); | |
218 | return SR_ERR; | |
219 | } else { | |
220 | g_free(format_name_sig); | |
221 | g_free(format_name); | |
222 | sr_err("Don't know this file format, aborting."); | |
223 | return SR_ERR; | |
224 | } | |
225 | ||
226 | sr_dbg("File says it's \"%s\"", format_name); | |
227 | ||
228 | record_size = R8(buf->str + 56); | |
229 | device_id = 0; | |
230 | ||
231 | if (g_strcmp0(format_name, "trace32 power integrator data") == 0) { | |
232 | if (record_size == 28 || record_size == 45) | |
233 | device_id = AD_DEVICE_PI; | |
234 | } else if (g_strcmp0(format_name, "trace32 iprobe data") == 0) { | |
235 | if (record_size == 11) | |
236 | device_id = AD_DEVICE_IPROBE; | |
237 | } | |
238 | ||
239 | if (!device_id) { | |
240 | g_free(format_name_sig); | |
241 | g_free(format_name); | |
242 | sr_err("Don't know how to handle this file with record size %d.", | |
243 | record_size); | |
244 | return SR_ERR; | |
245 | } | |
246 | ||
247 | g_free(format_name_sig); | |
248 | g_free(format_name); | |
249 | ||
250 | /* Stop processing the header if we just want to identify the file. */ | |
251 | if (!inc) | |
252 | return SR_OK; | |
253 | ||
254 | inc->device = device_id; | |
255 | inc->trigger_timestamp = RL64(buf->str + 32); | |
256 | inc->compression = R8(buf->str + 48); /* Maps to the enum. */ | |
257 | inc->record_mode = R8(buf->str + 55); /* Maps to the enum. */ | |
258 | inc->record_size = record_size; | |
259 | inc->record_count = RL32(buf->str + 60); | |
260 | inc->last_record = RL32S(buf->str + 64); | |
261 | ||
262 | sr_dbg("Trigger occured at %lf s.", | |
263 | inc->trigger_timestamp * TIMESTAMP_RESOLUTION); | |
264 | sr_dbg("File contains %d records: first one is %d, last one is %d.", | |
265 | inc->record_count, (inc->last_record - inc->record_count + 1), | |
266 | inc->last_record); | |
267 | ||
268 | /* Check if we can work with this compression. */ | |
269 | if (inc->compression != AD_COMPR_NONE) { | |
270 | sr_err("File uses unsupported compression (0x%02X), can't continue.", | |
271 | inc->compression); | |
272 | return SR_ERR; | |
273 | } | |
274 | ||
275 | inc->header_read = TRUE; | |
276 | ||
277 | return SR_OK; | |
278 | } | |
279 | ||
280 | static void create_channels(struct sr_input *in) | |
281 | { | |
282 | struct context *inc; | |
283 | int pod, channel, chan_id; | |
284 | char name[8]; | |
285 | ||
286 | inc = in->priv; | |
287 | chan_id = 0; | |
288 | ||
289 | for (pod = 0; pod < MAX_POD_COUNT; pod++) { | |
290 | if (!inc->pod_status[pod]) | |
291 | continue; | |
292 | ||
293 | for (channel = 0; channel < 16; channel++) { | |
294 | snprintf(name, 8, "%c%d", get_pod_name_from_id(pod), channel); | |
295 | inc->channels[pod][channel] = | |
296 | sr_channel_new(in->sdi, chan_id, SR_CHANNEL_LOGIC, TRUE, name); | |
297 | chan_id++; | |
298 | } | |
299 | ||
300 | snprintf(name, 8, "CLK%c", get_pod_name_from_id(pod)); | |
301 | inc->channels[pod][16] = | |
302 | sr_channel_new(in->sdi, chan_id, SR_CHANNEL_LOGIC, TRUE, name); | |
303 | chan_id++; | |
304 | } | |
305 | } | |
306 | ||
307 | static void send_metadata(struct sr_input *in) | |
308 | { | |
309 | struct sr_datafeed_packet packet; | |
310 | struct sr_datafeed_meta meta; | |
311 | struct sr_config *src; | |
312 | struct context *inc; | |
313 | ||
314 | inc = in->priv; | |
315 | ||
316 | packet.type = SR_DF_META; | |
317 | packet.payload = &meta; | |
318 | src = sr_config_new(SR_CONF_SAMPLERATE, g_variant_new_uint64(inc->samplerate)); | |
319 | meta.config = g_slist_append(NULL, src); | |
320 | sr_session_send(in->sdi, &packet); | |
321 | g_slist_free(meta.config); | |
322 | sr_config_free(src); | |
323 | ||
324 | inc->meta_sent = TRUE; | |
325 | } | |
326 | ||
327 | static void flush_output_buffer(struct sr_input *in) | |
328 | { | |
329 | struct context *inc; | |
330 | struct sr_datafeed_packet packet; | |
331 | struct sr_datafeed_logic logic; | |
332 | ||
333 | inc = in->priv; | |
334 | ||
335 | if (inc->out_buf->len) { | |
336 | packet.type = SR_DF_LOGIC; | |
337 | packet.payload = &logic; | |
338 | logic.unitsize = (g_slist_length(in->sdi->channels) + 7) / 8; | |
339 | logic.data = inc->out_buf->str; | |
340 | logic.length = inc->out_buf->len; | |
341 | sr_session_send(in->sdi, &packet); | |
342 | ||
343 | g_string_truncate(inc->out_buf, 0); | |
344 | } | |
345 | } | |
346 | ||
347 | static void process_record_pi(struct sr_input *in, gsize start) | |
348 | { | |
349 | struct sr_datafeed_packet packet; | |
350 | struct context *inc; | |
351 | uint64_t timestamp, next_timestamp; | |
352 | uint32_t pod_data; | |
353 | char single_payload[12 * 3]; | |
354 | GString *buf; | |
355 | int i, pod_count, clk_offset, packet_count, pod; | |
356 | int payload_bit, payload_len, value; | |
357 | ||
358 | inc = in->priv; | |
359 | buf = in->buf; | |
360 | ||
361 | /* | |
362 | * 00-07 timestamp | |
363 | * 08-09 A15..0 | |
364 | * 10-11 B15..0 | |
365 | * 12-13 C15..0 | |
366 | * 14-15 D15..0 | |
367 | * 16-17 E15..0 | |
368 | * 18-19 F15..0 | |
369 | * 20-23 ?? | |
370 | * 24-25 J15..0 Not present in 500MHz mode | |
371 | * 26-27 K15..0 Not present in 500MHz mode | |
372 | * 28-29 L15..0 Not present in 500MHz mode | |
373 | * 30-31 M15..0 Not present in 500MHz mode | |
374 | * 32-33 N15..0 Not present in 500MHz mode | |
375 | * 34-35 O15..0 Not present in 500MHz mode | |
376 | * 36-39 ?? Not present in 500MHz mode | |
377 | * 40/24 CLKF..A (32=CLKF, .., 1=CLKA) | |
378 | * 41 CLKO..J (32=CLKO, .., 1=CLKJ) Not present in 500MHz mode | |
379 | * 42/25 ?? | |
380 | * 43/26 ?? | |
381 | * 44/27 ?? | |
382 | */ | |
383 | ||
384 | timestamp = RL64(buf->str + start); | |
385 | ||
386 | if (inc->record_mode == AD_MODE_500MHZ) { | |
387 | pod_count = 6; | |
388 | clk_offset = 24; | |
389 | } else { | |
390 | pod_count = 12; | |
391 | clk_offset = 40; | |
392 | } | |
393 | ||
394 | payload_bit = 0; | |
395 | payload_len = 0; | |
396 | single_payload[0] = 0; | |
397 | ||
398 | for (pod = 0; pod < pod_count; pod++) { | |
399 | if (!inc->pod_status[pod]) | |
400 | continue; | |
401 | ||
402 | switch (pod) { | |
403 | case 0: /* A */ | |
404 | pod_data = RL16(buf->str + start + 8); | |
405 | pod_data |= (RL16(buf->str + start + clk_offset) & 1) << 16; | |
406 | break; | |
407 | case 1: /* B */ | |
408 | pod_data = RL16(buf->str + start + 10); | |
409 | pod_data |= (RL16(buf->str + start + clk_offset) & 2) << 15; | |
410 | break; | |
411 | case 2: /* C */ | |
412 | pod_data = RL16(buf->str + start + 12); | |
413 | pod_data |= (RL16(buf->str + start + clk_offset) & 4) << 14; | |
414 | break; | |
415 | case 3: /* D */ | |
416 | pod_data = RL16(buf->str + start + 14); | |
417 | pod_data |= (RL16(buf->str + start + clk_offset) & 8) << 13; | |
418 | break; | |
419 | case 4: /* E */ | |
420 | pod_data = RL16(buf->str + start + 16); | |
421 | pod_data |= (RL16(buf->str + start + clk_offset) & 16) << 12; | |
422 | break; | |
423 | case 5: /* F */ | |
424 | pod_data = RL16(buf->str + start + 18); | |
425 | pod_data |= (RL16(buf->str + start + clk_offset) & 32) << 11; | |
426 | break; | |
427 | case 6: /* J */ | |
428 | pod_data = RL16(buf->str + start + 24); | |
429 | pod_data |= (RL16(buf->str + start + 41) & 1) << 16; | |
430 | break; | |
431 | case 7: /* K */ | |
432 | pod_data = RL16(buf->str + start + 26); | |
433 | pod_data |= (RL16(buf->str + start + 41) & 2) << 15; | |
434 | break; | |
435 | case 8: /* L */ | |
436 | pod_data = RL16(buf->str + start + 28); | |
437 | pod_data |= (RL16(buf->str + start + 41) & 4) << 14; | |
438 | break; | |
439 | case 9: /* M */ | |
440 | pod_data = RL16(buf->str + start + 30); | |
441 | pod_data |= (RL16(buf->str + start + 41) & 8) << 13; | |
442 | break; | |
443 | case 10: /* N */ | |
444 | pod_data = RL16(buf->str + start + 32); | |
445 | pod_data |= (RL16(buf->str + start + 41) & 16) << 12; | |
446 | break; | |
447 | case 11: /* O */ | |
448 | pod_data = RL16(buf->str + start + 34); | |
449 | pod_data |= (RL16(buf->str + start + 41) & 32) << 11; | |
450 | break; | |
451 | default: | |
452 | sr_err("Don't know how to obtain data for pod %d.", pod); | |
453 | } | |
454 | ||
455 | for (i = 0; i < 17; i++) { | |
456 | value = (pod_data >> i) & 1; | |
457 | single_payload[payload_len] |= value << payload_bit; | |
458 | ||
459 | payload_bit++; | |
460 | if (payload_bit > 7) { | |
461 | payload_bit = 0; | |
462 | payload_len++; | |
463 | single_payload[payload_len] = 0; | |
464 | } | |
465 | } | |
466 | } | |
467 | ||
468 | /* Make sure that payload_len accounts for any incomplete bytes used. */ | |
469 | if (payload_bit) | |
470 | payload_len++; | |
471 | ||
472 | i = (g_slist_length(in->sdi->channels) + 7) / 8; | |
473 | if (payload_len != i) { | |
474 | sr_err("Payload unit size is %d but should be %d!", payload_len, i); | |
475 | return; | |
476 | } | |
477 | ||
478 | if (timestamp == inc->trigger_timestamp && !inc->trigger_sent) { | |
479 | sr_dbg("Trigger @%lf s, record #%d.", | |
480 | timestamp * TIMESTAMP_RESOLUTION, inc->cur_record); | |
481 | ||
482 | packet.type = SR_DF_TRIGGER; | |
483 | packet.payload = NULL; | |
484 | sr_session_send(in->sdi, &packet); | |
485 | inc->trigger_sent = TRUE; | |
486 | } | |
487 | ||
488 | /* Is this the last record in the file? */ | |
489 | if (inc->cur_record == inc->record_count - 1) { | |
490 | /* It is, so send the last sample data only once. */ | |
491 | g_string_append_len(inc->out_buf, single_payload, payload_len); | |
492 | } else { | |
493 | /* It's not, so fill the time gap by sending lots of data. */ | |
494 | next_timestamp = RL64(buf->str + start + inc->record_size); | |
495 | packet_count = (int)(next_timestamp - timestamp) / inc->timestamp_scale; | |
496 | ||
497 | /* Make sure we send at least one data set. */ | |
498 | if (packet_count == 0) | |
499 | packet_count = 1; | |
500 | ||
501 | for (i = 0; i < packet_count; i++) | |
502 | g_string_append_len(inc->out_buf, single_payload, payload_len); | |
503 | } | |
504 | ||
505 | if (inc->out_buf->len >= OUTBUF_FLUSH_SIZE) | |
506 | flush_output_buffer(in); | |
507 | } | |
508 | ||
509 | static void process_record_iprobe(struct sr_input *in, gsize start) | |
510 | { | |
511 | struct sr_datafeed_packet packet; | |
512 | struct context *inc; | |
513 | uint64_t timestamp, next_timestamp; | |
514 | char single_payload[3]; | |
515 | int i, payload_len, packet_count; | |
516 | ||
517 | inc = in->priv; | |
518 | ||
519 | /* | |
520 | * 00-07 timestamp | |
521 | * 08-09 IP15..0 | |
522 | * 10 CLK | |
523 | */ | |
524 | ||
525 | timestamp = RL64(in->buf->str + start); | |
526 | single_payload[0] = R8(in->buf->str + start + 8); | |
527 | single_payload[1] = R8(in->buf->str + start + 9); | |
528 | single_payload[2] = R8(in->buf->str + start + 10) & 1; | |
529 | payload_len = 3; | |
530 | ||
531 | if (timestamp == inc->trigger_timestamp && !inc->trigger_sent) { | |
532 | sr_dbg("Trigger @%lf s, record #%d.", | |
533 | timestamp * TIMESTAMP_RESOLUTION, inc->cur_record); | |
534 | ||
535 | packet.type = SR_DF_TRIGGER; | |
536 | packet.payload = NULL; | |
537 | sr_session_send(in->sdi, &packet); | |
538 | inc->trigger_sent = TRUE; | |
539 | } | |
540 | ||
541 | /* Is this the last record in the file? */ | |
542 | if (inc->cur_record == inc->record_count - 1) { | |
543 | /* It is, so send the last sample data only once. */ | |
544 | g_string_append_len(inc->out_buf, single_payload, payload_len); | |
545 | } else { | |
546 | /* It's not, so fill the time gap by sending lots of data. */ | |
547 | next_timestamp = RL64(in->buf->str + start + inc->record_size); | |
548 | packet_count = (int)(next_timestamp - timestamp) / inc->timestamp_scale; | |
549 | ||
550 | /* Make sure we send at least one data set. */ | |
551 | if (packet_count == 0) | |
552 | packet_count = 1; | |
553 | ||
554 | for (i = 0; i < packet_count; i++) | |
555 | g_string_append_len(inc->out_buf, single_payload, payload_len); | |
556 | } | |
557 | ||
558 | if (inc->out_buf->len >= OUTBUF_FLUSH_SIZE) | |
559 | flush_output_buffer(in); | |
560 | } | |
561 | ||
562 | static void process_practice_token(struct sr_input *in, char *cmd_token) | |
563 | { | |
564 | struct context *inc; | |
565 | char **tokens; | |
566 | char chan_suffix[2], chan_name[33]; | |
567 | char *s1, *s2; | |
568 | int pod, ch; | |
569 | struct sr_channel *channel; | |
570 | ||
571 | inc = in->priv; | |
572 | ||
573 | /* | |
574 | * Commands of interest (I may also be IPROBE): | |
575 | * | |
576 | * I.TWIDTH | |
577 | * I.TPREDELAY | |
578 | * I.TDELAY | |
579 | * I.TYSNC.SELECT I.A0 HIGH | |
580 | * NAME.SET <port.chan> <name> <+/-> ... | |
581 | */ | |
582 | ||
583 | if (!cmd_token) | |
584 | return; | |
585 | ||
586 | if (cmd_token[0] == 0) | |
587 | return; | |
588 | ||
589 | tokens = g_strsplit(cmd_token, " ", 0); | |
590 | ||
591 | if (!tokens) | |
592 | return; | |
593 | ||
594 | if (g_strcmp0(tokens[0], "NAME.SET") == 0) { | |
595 | /* Let the user know when the channel has been inverted. */ | |
596 | /* This *should* be token #3 but there's an additonal space, making it #4. */ | |
597 | chan_suffix[0] = 0; | |
598 | chan_suffix[1] = 0; | |
599 | if (tokens[4]) { | |
600 | if (tokens[4][0] == '-') | |
601 | chan_suffix[0] = '-'; /* This is the way PowerView shows it. */ | |
602 | } | |
603 | ||
604 | /* | |
605 | * Command is using structure "NAME.SET I.A00 I.XYZ" or | |
606 | * "NAME.SET IP.00 IP.XYZ", depending on the device used. | |
607 | * Let's get strings with the I./IP. from both tokens removed. | |
608 | */ | |
609 | s1 = g_strstr_len(tokens[1], -1, ".") + 1; | |
610 | s2 = g_strstr_len(tokens[2], -1, ".") + 1; | |
611 | ||
612 | if (g_strcmp0(s1, "CLK") == 0) { | |
613 | /* CLK for iprobe */ | |
614 | pod = 0; | |
615 | ch = 16; | |
616 | } else if ((strlen(s1) == 4) && g_ascii_isupper(s1[3])) { | |
617 | /* CLKA/B/J/K for PowerIntegrator */ | |
618 | pod = s1[3] - (char)'A'; | |
619 | ch = 16; | |
620 | } else if (g_ascii_isupper(s1[0])) { | |
621 | /* A00 for PowerIntegrator */ | |
622 | pod = s1[0] - (char)'A'; | |
623 | ch = atoi(s1 + 1); | |
624 | } else { | |
625 | /* 00 for iprobe */ | |
626 | pod = 0; | |
627 | ch = atoi(s1); | |
628 | } | |
629 | ||
630 | channel = inc->channels[pod][ch]; | |
631 | g_snprintf(chan_name, sizeof(chan_name), "%s%s", s2, chan_suffix); | |
632 | ||
633 | sr_dbg("Changing channel name for %s to %s.", s1, chan_name); | |
634 | sr_dev_channel_name_set(channel, chan_name); | |
635 | } | |
636 | ||
637 | g_strfreev(tokens); | |
638 | } | |
639 | ||
640 | static void process_practice(struct sr_input *in) | |
641 | { | |
642 | char delimiter[3]; | |
643 | char **tokens, *token; | |
644 | int i; | |
645 | ||
646 | /* Gather all input data until we see the end marker. */ | |
647 | if (in->buf->str[in->buf->len - 1] != 0x29) | |
648 | return; | |
649 | ||
650 | delimiter[0] = 0x0A; | |
651 | delimiter[1] = ' '; | |
652 | delimiter[2] = 0; | |
653 | ||
654 | tokens = g_strsplit(in->buf->str, delimiter, 0); | |
655 | ||
656 | /* Special case: first token contains the start marker, too. Skip it. */ | |
657 | token = tokens[0]; | |
658 | for (i = 0; token[i]; i++) { | |
659 | if (token[i] == ' ') | |
660 | process_practice_token(in, token + i + 1); | |
661 | } | |
662 | ||
663 | for (i = 1; tokens[i]; i++) | |
664 | process_practice_token(in, tokens[i]); | |
665 | ||
666 | g_strfreev(tokens); | |
667 | ||
668 | g_string_erase(in->buf, 0, in->buf->len); | |
669 | } | |
670 | ||
671 | static int process_buffer(struct sr_input *in) | |
672 | { | |
673 | struct context *inc; | |
674 | int i, chunk_size, res; | |
675 | ||
676 | inc = in->priv; | |
677 | ||
678 | if (!inc->header_read) { | |
679 | res = process_header(in->buf, inc); | |
680 | g_string_erase(in->buf, 0, HEADER_SIZE); | |
681 | if (res != SR_OK) | |
682 | return res; | |
683 | } | |
684 | ||
685 | if (!inc->meta_sent) { | |
686 | std_session_send_df_header(in->sdi); | |
687 | send_metadata(in); | |
688 | } | |
689 | ||
690 | if (!inc->records_read) { | |
691 | /* Cut off at a multiple of the record size. */ | |
692 | chunk_size = ((in->buf->len) / inc->record_size) * inc->record_size; | |
693 | ||
694 | /* There needs to be at least one more record process_record() can peek into. */ | |
695 | chunk_size -= inc->record_size; | |
696 | ||
697 | for (i = 0; (i < chunk_size) && (!inc->records_read); i += inc->record_size) { | |
698 | switch (inc->device) { | |
699 | case AD_DEVICE_PI: | |
700 | process_record_pi(in, i); | |
701 | break; | |
702 | case AD_DEVICE_IPROBE: | |
703 | process_record_iprobe(in, i); | |
704 | break; | |
705 | default: | |
706 | sr_err("Trying to process records for unknown device!"); | |
707 | return SR_ERR; | |
708 | } | |
709 | ||
710 | inc->cur_record++; | |
711 | if (inc->cur_record == inc->record_count) | |
712 | inc->records_read = TRUE; | |
713 | } | |
714 | ||
715 | g_string_erase(in->buf, 0, i); | |
716 | } | |
717 | ||
718 | if (inc->records_read) { | |
719 | /* Read practice commands that configure the setup. */ | |
720 | process_practice(in); | |
721 | } | |
722 | ||
723 | return SR_OK; | |
724 | } | |
725 | ||
726 | static int receive(struct sr_input *in, GString *buf) | |
727 | { | |
728 | g_string_append_len(in->buf, buf->str, buf->len); | |
729 | ||
730 | if (!in->sdi_ready) { | |
731 | /* sdi is ready, notify frontend. */ | |
732 | in->sdi_ready = TRUE; | |
733 | return SR_OK; | |
734 | } | |
735 | ||
736 | return process_buffer(in); | |
737 | } | |
738 | ||
739 | static int end(struct sr_input *in) | |
740 | { | |
741 | struct context *inc; | |
742 | int ret; | |
743 | ||
744 | inc = in->priv; | |
745 | ||
746 | if (in->sdi_ready) | |
747 | ret = process_buffer(in); | |
748 | else | |
749 | ret = SR_OK; | |
750 | ||
751 | flush_output_buffer(in); | |
752 | ||
753 | if (inc->meta_sent) | |
754 | std_session_send_df_end(in->sdi); | |
755 | ||
756 | return ret; | |
757 | } | |
758 | ||
759 | static int reset(struct sr_input *in) | |
760 | { | |
761 | struct context *inc = in->priv; | |
762 | ||
763 | inc->meta_sent = FALSE; | |
764 | inc->header_read = FALSE; | |
765 | inc->records_read = FALSE; | |
766 | inc->trigger_sent = FALSE; | |
767 | inc->cur_record = 0; | |
768 | ||
769 | g_string_truncate(in->buf, 0); | |
770 | ||
771 | return SR_OK; | |
772 | } | |
773 | ||
774 | static struct sr_option options[] = { | |
775 | { "podA", "Import pod A / iprobe", | |
776 | "Create channels and data for pod A / iprobe", NULL, NULL }, | |
777 | ||
778 | { "podB", "Import pod B", "Create channels and data for pod B", NULL, NULL }, | |
779 | { "podC", "Import pod C", "Create channels and data for pod C", NULL, NULL }, | |
780 | { "podD", "Import pod D", "Create channels and data for pod D", NULL, NULL }, | |
781 | { "podE", "Import pod E", "Create channels and data for pod E", NULL, NULL }, | |
782 | { "podF", "Import pod F", "Create channels and data for pod F", NULL, NULL }, | |
783 | { "podJ", "Import pod J", "Create channels and data for pod J", NULL, NULL }, | |
784 | { "podK", "Import pod K", "Create channels and data for pod K", NULL, NULL }, | |
785 | { "podL", "Import pod L", "Create channels and data for pod L", NULL, NULL }, | |
786 | { "podM", "Import pod M", "Create channels and data for pod M", NULL, NULL }, | |
787 | { "podN", "Import pod N", "Create channels and data for pod N", NULL, NULL }, | |
788 | { "podO", "Import pod O", "Create channels and data for pod O", NULL, NULL }, | |
789 | ||
790 | { "samplerate", "Reduced sample rate in MHz", "Reduced sample rate in MHz", NULL, NULL }, | |
791 | ||
792 | ALL_ZERO | |
793 | }; | |
794 | ||
795 | static const struct sr_option *get_options(void) | |
796 | { | |
797 | if (!options[0].def) { | |
798 | options[0].def = g_variant_ref_sink(g_variant_new_boolean(TRUE)); | |
799 | options[1].def = g_variant_ref_sink(g_variant_new_boolean(FALSE)); | |
800 | options[2].def = g_variant_ref_sink(g_variant_new_boolean(FALSE)); | |
801 | options[3].def = g_variant_ref_sink(g_variant_new_boolean(FALSE)); | |
802 | options[4].def = g_variant_ref_sink(g_variant_new_boolean(FALSE)); | |
803 | options[5].def = g_variant_ref_sink(g_variant_new_boolean(FALSE)); | |
804 | options[6].def = g_variant_ref_sink(g_variant_new_boolean(FALSE)); | |
805 | options[7].def = g_variant_ref_sink(g_variant_new_boolean(FALSE)); | |
806 | options[8].def = g_variant_ref_sink(g_variant_new_boolean(FALSE)); | |
807 | options[9].def = g_variant_ref_sink(g_variant_new_boolean(FALSE)); | |
808 | options[10].def = g_variant_ref_sink(g_variant_new_boolean(FALSE)); | |
809 | options[11].def = g_variant_ref_sink(g_variant_new_boolean(FALSE)); | |
810 | options[12].def = g_variant_ref_sink(g_variant_new_uint32(DEFAULT_SAMPLERATE)); | |
811 | } | |
812 | ||
813 | return options; | |
814 | } | |
815 | ||
816 | SR_PRIV struct sr_input_module input_trace32_ad = { | |
817 | .id = "trace32_ad", | |
818 | .name = "Trace32_ad", | |
819 | .desc = "Lauterbach Trace32 logic analyzer data", | |
820 | .exts = (const char*[]){"ad", NULL}, | |
821 | .options = get_options, | |
822 | .metadata = { SR_INPUT_META_HEADER | SR_INPUT_META_REQUIRED }, | |
823 | .format_match = format_match, | |
824 | .init = init, | |
825 | .receive = receive, | |
826 | .end = end, | |
827 | .reset = reset, | |
828 | }; |