2 * This file is part of the libsigrok project.
4 * Copyright (C) 2015 Soeren Apel <soeren@apelpie.net>
5 * Copyright (C) 2015 Bert Vermeulen <bert@biot.com>
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.
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.
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/>.
23 * This input module reads .ad files created using the
24 * following practice commands:
26 * I.SAVE <file> /NoCompress
27 * IPROBE.SAVE <file> /NoCompress
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.
38 #include <sys/types.h>
43 #include <libsigrok/libsigrok.h>
44 #include "libsigrok-internal.h"
46 #define LOG_PREFIX "input/trace32_ad"
48 #define CHUNK_SIZE (4 * 1024 * 1024)
49 #define MAX_POD_COUNT 12
53 #define TRACE32 "trace32"
55 #define TIMESTAMP_RESOLUTION ((double)0.000000000078125) /* 0.078125 ns */
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.
62 #define DEFAULT_SAMPLERATE 200
66 AD_FORMAT_BINHDR1, /* Binary header, binary data, textual setup info, v1 */
67 AD_FORMAT_BINHDR2, /* Binary header, binary data, textual setup info, v2 */
68 AD_FORMAT_TXTHDR, /* Textual header, binary data */
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 */
85 AD_COMPR_NONE = 0, /* File created with /NOCOMPRESS */
86 AD_COMPR_QCOMP = 6, /* File created with /COMPRESS or /QUICKCOMPRESS */
91 gboolean header_read, records_read, trigger_sent;
92 enum ad_format format;
93 enum ad_device device;
94 enum ad_mode record_mode;
95 enum ad_compr compression;
96 char pod_status[MAX_POD_COUNT];
97 struct sr_channel *channels[MAX_POD_COUNT][17]; /* 16 + CLK */
98 uint64_t trigger_timestamp;
99 uint32_t header_size, record_size, record_count, cur_record;
102 double timestamp_scale;
106 static int process_header(GString *buf, struct context *inc);
107 static void create_channels(struct sr_input *in);
109 /* Transform non-printable chars to '\xNN' presentation. */
110 static char *printable_name(const char *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])) {
123 snprintf(p, 5, "\\x%02x", name[i]);
124 p += strlen("\\x00");
132 static char get_pod_name_from_id(int id)
148 sr_err("get_pod_name_from_id() called with invalid ID %d!", id);
153 static int init(struct sr_input *in, GHashTable *options)
159 in->sdi = g_malloc0(sizeof(struct sr_dev_inst));
160 in->priv = g_malloc0(sizeof(struct context));
164 /* Calculate the desired timestamp scaling factor. */
165 inc->samplerate = 1000000 *
166 g_variant_get_uint32(g_hash_table_lookup(options, "samplerate"));
168 inc->timestamp_scale = ((1 / TIMESTAMP_RESOLUTION) / (double)inc->samplerate);
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;
178 if (g_slist_length(in->sdi->channels) == 0) {
179 sr_err("No pods were selected and thus no channels created, aborting.");
185 inc->out_buf = g_string_sized_new(CHUNK_SIZE);
190 static int format_match(GHashTable *metadata, unsigned int *confidence)
195 buf = g_hash_table_lookup(metadata, GINT_TO_POINTER(SR_INPUT_META_HEADER));
196 rc = process_header(buf, NULL);
205 static int process_header(GString *buf, struct context *inc)
207 char *format_name, *format_name_sig;
211 enum ad_device device_id;
212 enum ad_format format;
215 * First-level file header:
216 * 0x00-1F file format name
217 * 0x20 u64 trigger timestamp
219 * 0x30 u8 compression
221 * 0x32 u8 0x00 (PI), 0x01 (iprobe)
222 * 0x36 u8 device id: 0x08 (PI 250/500), 0x0A (iprobe 250)
226 * Second-level file header, version 1:
227 * 0x37 u8 capture speed: 0x00 (250), 0x01 (500)
228 * 0x38 u8 record size
230 * 0x3C u32 number of records
231 * 0x40 s32 id of last record
233 * 0x47 u8 const 0x80=128
239 * Second-level file header, version 2:
243 * 0x48 u8 record size
246 * 0x58 u64 number of records
252 * 0x88 u64 ?? (timestamp of some kind?)
255 * 0x9F u8 capture speed: 0x00 (250), 0x01 (500)
259 * 0xB8-CF version string? (e.g. '93173--96069', same for all tested .ad files)
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.
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.
276 format_name = g_strndup(buf->str, 32);
277 p = strchr(format_name, CTRLZ);
280 g_strchomp(format_name);
283 * File format names either start with the "trace32" literal,
284 * or with a digit and SPACE.
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);
290 format = AD_FORMAT_UNKNOWN;
292 /* Literal "trace32" leader, binary header follows. */
293 format = AD_FORMAT_BINHDR1;
294 } else if (g_ascii_isdigit(format_name[0]) && (format_name[1] == SPACE)) {
295 /* Digit and SPACE leader, currently unsupported text header. */
296 format = AD_FORMAT_TXTHDR;
299 sr_err("This format isn't implemented yet, aborting.");
302 /* Unknown kind of format name. Unsupported. */
305 sr_err("Don't know this file format, aborting.");
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;
315 p = printable_name(format_name);
317 sr_dbg("File says it's \"%s\" -> format type %u.", p, format);
320 record_size = (format == AD_FORMAT_BINHDR1) ?
321 R8(buf->str + 0x38) : R8(buf->str + 0x48);
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;
335 sr_err("Cannot handle file with record size %zu.",
342 /* Stop processing the header if we just want to identify the file. */
346 inc->format = format;
347 inc->device = device_id;
348 inc->trigger_timestamp = RL64(buf->str + 0x20);
349 inc->compression = R8(buf->str + 0x30); /* Maps to the enum. */
350 inc->header_size = (format == AD_FORMAT_BINHDR1) ? 0x50 : 0xCA;
351 inc->record_size = record_size;
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);
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;
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),
369 /* Check if we can work with this compression. */
370 if (inc->compression) {
371 sr_err("File uses unsupported compression (0x%02X), can't continue.",
376 inc->header_read = TRUE;
381 static void create_channels(struct sr_input *in)
384 int pod, channel, chan_id;
390 for (pod = 0; pod < MAX_POD_COUNT; pod++) {
391 if (!inc->pod_status[pod])
394 for (channel = 0; channel < 16; channel++) {
395 snprintf(name, sizeof(name), "%c%d", get_pod_name_from_id(pod), channel);
396 inc->channels[pod][channel] =
397 sr_channel_new(in->sdi, chan_id, SR_CHANNEL_LOGIC, TRUE, name);
401 snprintf(name, sizeof(name), "CLK%c", get_pod_name_from_id(pod));
402 inc->channels[pod][16] =
403 sr_channel_new(in->sdi, chan_id, SR_CHANNEL_LOGIC, TRUE, name);
408 static void send_metadata(struct sr_input *in)
413 (void)sr_session_send_meta(in->sdi, SR_CONF_SAMPLERATE,
414 g_variant_new_uint64(inc->samplerate));
415 inc->meta_sent = TRUE;
418 static void flush_output_buffer(struct sr_input *in)
421 struct sr_datafeed_packet packet;
422 struct sr_datafeed_logic logic;
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);
434 g_string_truncate(inc->out_buf, 0);
438 static void process_record_pi(struct sr_input *in, gsize start)
441 uint64_t timestamp, next_timestamp;
443 char single_payload[12 * 3];
445 int i, pod_count, clk_offset, packet_count, pod;
446 int payload_bit, payload_len, value;
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
474 timestamp = RL64(buf->str + start);
476 if (inc->record_mode == AD_MODE_500MHZ) {
486 single_payload[0] = 0;
488 for (pod = 0; pod < pod_count; pod++) {
489 if (!inc->pod_status[pod])
494 pod_data = RL16(buf->str + start + 0x08);
495 pod_data |= (RL16(buf->str + start + clk_offset) & 1) << 16;
498 pod_data = RL16(buf->str + start + 0x0A);
499 pod_data |= (RL16(buf->str + start + clk_offset) & 2) << 15;
502 pod_data = RL16(buf->str + start + 0x0C);
503 pod_data |= (RL16(buf->str + start + clk_offset) & 4) << 14;
506 pod_data = RL16(buf->str + start + 0x0E);
507 pod_data |= (RL16(buf->str + start + clk_offset) & 8) << 13;
510 pod_data = RL16(buf->str + start + 0x10);
511 pod_data |= (RL16(buf->str + start + clk_offset) & 16) << 12;
514 pod_data = RL16(buf->str + start + 0x12);
515 pod_data |= (RL16(buf->str + start + clk_offset) & 32) << 11;
518 pod_data = RL16(buf->str + start + 0x18);
519 pod_data |= (RL16(buf->str + start + 0x29) & 1) << 16;
522 pod_data = RL16(buf->str + start + 0x1A);
523 pod_data |= (RL16(buf->str + start + 0x29) & 2) << 15;
526 pod_data = RL16(buf->str + start + 0x1C);
527 pod_data |= (RL16(buf->str + start + 0x29) & 4) << 14;
530 pod_data = RL16(buf->str + start + 0x1E);
531 pod_data |= (RL16(buf->str + start + 0x29) & 8) << 13;
534 pod_data = RL16(buf->str + start + 0x20);
535 pod_data |= (RL16(buf->str + start + 0x29) & 16) << 12;
538 pod_data = RL16(buf->str + start + 0x22);
539 pod_data |= (RL16(buf->str + start + 0x29) & 32) << 11;
543 sr_err("Don't know how to obtain data for pod %d.", pod);
546 for (i = 0; i < 17; i++) {
547 value = (pod_data >> i) & 1;
548 single_payload[payload_len] |= value << payload_bit;
551 if (payload_bit > 7) {
554 single_payload[payload_len] = 0;
559 /* Make sure that payload_len accounts for any incomplete bytes used. */
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);
569 if (timestamp == inc->trigger_timestamp && !inc->trigger_sent) {
570 sr_dbg("Trigger @%lf s, record #%d.",
571 timestamp * TIMESTAMP_RESOLUTION, inc->cur_record);
572 std_session_send_df_trigger(in->sdi);
573 inc->trigger_sent = TRUE;
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);
581 /* It's not, so fill the time gap by sending lots of data. */
582 next_timestamp = RL64(buf->str + start + inc->record_size);
583 packet_count = (int)(next_timestamp - timestamp) / inc->timestamp_scale;
585 /* Make sure we send at least one data set. */
586 if (packet_count == 0)
589 for (i = 0; i < packet_count; i++)
590 g_string_append_len(inc->out_buf, single_payload, payload_len);
593 if (inc->out_buf->len >= CHUNK_SIZE)
594 flush_output_buffer(in);
597 static void process_record_iprobe(struct sr_input *in, gsize start)
600 uint64_t timestamp, next_timestamp;
601 char single_payload[3];
602 int i, payload_len, packet_count;
612 timestamp = RL64(in->buf->str + start);
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;
618 if (timestamp == inc->trigger_timestamp && !inc->trigger_sent) {
619 sr_dbg("Trigger @%lf s, record #%d.",
620 timestamp * TIMESTAMP_RESOLUTION, inc->cur_record);
621 std_session_send_df_trigger(in->sdi);
622 inc->trigger_sent = TRUE;
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);
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);
632 packet_count = (int)(next_timestamp - timestamp) / inc->timestamp_scale;
634 /* Make sure we send at least one data set. */
635 if (packet_count == 0)
638 for (i = 0; i < packet_count; i++)
639 g_string_append_len(inc->out_buf, single_payload, payload_len);
642 if (inc->out_buf->len >= CHUNK_SIZE)
643 flush_output_buffer(in);
646 static void process_practice_token(struct sr_input *in, char *cmd_token)
650 char chan_suffix[2], chan_name[33];
653 struct sr_channel *channel;
658 * Commands of interest (I may also be IPROBE):
663 * I.TYSNC.SELECT I.A0 HIGH
664 * NAME.SET <port.chan> <name> <+/-> ...
670 if (cmd_token[0] == 0)
673 tokens = g_strsplit(cmd_token, " ", 0);
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. */
684 if (tokens[4][0] == '-')
685 chan_suffix[0] = '-'; /* This is the way PowerView shows it. */
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.
693 s1 = g_strstr_len(tokens[1], -1, ".") + 1;
694 s2 = g_strstr_len(tokens[2], -1, ".") + 1;
696 if (g_strcmp0(s1, "CLK") == 0) {
700 } else if ((strlen(s1) == 4) && g_ascii_isupper(s1[3])) {
701 /* CLKA/B/J/K for PowerIntegrator */
702 pod = s1[3] - (char)'A';
704 } else if (g_ascii_isupper(s1[0])) {
705 /* A00 for PowerIntegrator */
706 pod = s1[0] - (char)'A';
714 channel = inc->channels[pod][ch];
715 g_snprintf(chan_name, sizeof(chan_name), "%s%s", s2, chan_suffix);
717 sr_dbg("Changing channel name for %s to %s.", s1, chan_name);
718 sr_dev_channel_name_set(channel, chan_name);
724 static void process_practice(struct sr_input *in)
727 char **tokens, *token;
730 /* Gather all input data until we see the end marker. */
731 if (in->buf->str[in->buf->len - 1] != 0x29)
738 tokens = g_strsplit(in->buf->str, delimiter, 0);
740 /* Special case: first token contains the start marker, too. Skip it. */
742 for (i = 0; token[i]; i++) {
744 process_practice_token(in, token + i + 1);
747 for (i = 1; tokens[i]; i++)
748 process_practice_token(in, tokens[i]);
752 g_string_erase(in->buf, 0, in->buf->len);
755 static int process_buffer(struct sr_input *in)
758 int i, chunk_size, res;
762 if (!inc->header_read) {
763 res = process_header(in->buf, inc);
764 g_string_erase(in->buf, 0, inc->header_size);
769 if (!inc->meta_sent) {
770 std_session_send_df_header(in->sdi);
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;
778 /* There needs to be at least one more record process_record() can peek into. */
779 chunk_size -= inc->record_size;
781 for (i = 0; (i < chunk_size) && (!inc->records_read); i += inc->record_size) {
782 switch (inc->device) {
784 process_record_pi(in, i);
786 case AD_DEVICE_IPROBE:
787 process_record_iprobe(in, i);
790 sr_err("Trying to process records for unknown device!");
795 if (inc->cur_record == inc->record_count)
796 inc->records_read = TRUE;
799 g_string_erase(in->buf, 0, i);
802 if (inc->records_read) {
803 /* Read practice commands that configure the setup. */
804 process_practice(in);
810 static int receive(struct sr_input *in, GString *buf)
812 g_string_append_len(in->buf, buf->str, buf->len);
814 if (!in->sdi_ready) {
815 /* sdi is ready, notify frontend. */
816 in->sdi_ready = TRUE;
820 return process_buffer(in);
823 static int end(struct sr_input *in)
831 ret = process_buffer(in);
835 flush_output_buffer(in);
838 std_session_send_df_end(in->sdi);
843 static int reset(struct sr_input *in)
845 struct context *inc = in->priv;
847 inc->meta_sent = FALSE;
848 inc->header_read = FALSE;
849 inc->records_read = FALSE;
850 inc->trigger_sent = FALSE;
853 g_string_truncate(in->buf, 0);
858 static struct sr_option options[] = {
859 { "podA", "Import pod A / iprobe",
860 "Create channels and data for pod A / iprobe", NULL, NULL },
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 },
874 { "samplerate", "Reduced sample rate (MHz)", "Reduce the original sample rate of 12.8 GHz to the specified sample rate in MHz", NULL, NULL },
879 static const struct sr_option *get_options(void)
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));
894 options[12].def = g_variant_ref_sink(g_variant_new_uint32(DEFAULT_SAMPLERATE));
900 SR_PRIV struct sr_input_module input_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,