]> sigrok.org Git - libsigrok.git/commitdiff
Add the Lauterbach Trace32 logic analyzer data import module
authorSoeren Apel <redacted>
Sun, 20 Dec 2015 19:20:56 +0000 (20:20 +0100)
committerUwe Hermann <redacted>
Mon, 21 Dec 2015 15:32:49 +0000 (16:32 +0100)
Makefile.am
src/input/input.c
src/input/trace32_ad.c [new file with mode: 0644]

index 276ffb25cb8f44c6b006d4a56431f4f8ba642b51..882abb951e8be9125a930231c5106025638ec11d 100644 (file)
@@ -74,6 +74,7 @@ libsigrok_la_SOURCES += \
        src/input/chronovu_la8.c \
        src/input/csv.c \
        src/input/raw_analog.c \
+       src/input/trace32_ad.c \
        src/input/vcd.c \
        src/input/wav.c
 
index 7465e125cbab235c7685d1e13aab718ba944fd53..f2cc976935147b8396d31f0e086382a987d0efd2 100644 (file)
@@ -62,6 +62,7 @@
 extern SR_PRIV struct sr_input_module input_chronovu_la8;
 extern SR_PRIV struct sr_input_module input_csv;
 extern SR_PRIV struct sr_input_module input_binary;
+extern SR_PRIV struct sr_input_module input_trace32_ad;
 extern SR_PRIV struct sr_input_module input_vcd;
 extern SR_PRIV struct sr_input_module input_wav;
 extern SR_PRIV struct sr_input_module input_raw_analog;
@@ -71,6 +72,7 @@ static const struct sr_input_module *input_module_list[] = {
        &input_binary,
        &input_chronovu_la8,
        &input_csv,
+       &input_trace32_ad,
        &input_vcd,
        &input_wav,
        &input_raw_analog,
diff --git a/src/input/trace32_ad.c b/src/input/trace32_ad.c
new file mode 100644 (file)
index 0000000..06fc521
--- /dev/null
@@ -0,0 +1,801 @@
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2015 Soeren Apel <soeren@apelpie.net>
+ * Copyright (C) 2015 Bert Vermeulen <bert@biot.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Usage notes:
+ * This input module reads .ad files created using the
+ * following practice commands:
+ *
+ * I.SAVE <file> /NoCompress
+ * IPROBE.SAVE <file> /NoCompress
+ *
+ * It currently cannot make use of files that have been
+ * saved using /QuickCompress, /Compress or /ZIP.
+ * As a workaround you may load the file in PowerView
+ * using I.LOAD / IPROBE.LOAD and re-save using /NoCompress.
+ */
+
+#include <config.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <libsigrok/libsigrok.h>
+#include "libsigrok-internal.h"
+
+#define LOG_PREFIX "input/trace32_ad"
+
+#define MAX_CHUNK_SIZE    4096
+#define OUTBUF_FLUSH_SIZE 10240
+#define MAX_POD_COUNT     12
+#define HEADER_SIZE       80
+
+#define TIMESTAMP_RESOLUTION  ((double)0.000000000078125) /* 0.078125 ns */
+
+/*
+ * The resolution equals a sampling freq of 12.8 GHz. That's a bit high
+ * for inter-record sample generation, so we scale it down to 200 MHz
+ * for now. That way, the scaling factor becomes 32.
+ */
+#define SAMPLING_FREQ 200000000
+#define TIMESTAMP_SCALE ((1 / TIMESTAMP_RESOLUTION) / SAMPLING_FREQ)
+
+enum {
+       AD_FORMAT_BINHDR = 1, /* Binary header, binary data, textual setup info */
+       AD_FORMAT_TXTHDR      /* Textual header, binary data */
+};
+
+enum {
+       AD_DEVICE_PI = 1, /* Data recorded by LA-7940 PowerIntegrator or */
+                          /* LA-394x PowerIntegrator II. */
+       AD_DEVICE_IPROBE  /* Data recorded by LA-769x PowerTrace II IProbe. */
+       /* Missing file format info for LA-793x ICD PowerProbe */
+       /* Missing file format info for LA-4530 uTrace analog probe */
+};
+
+enum {
+       AD_MODE_250MHZ = 0,
+       AD_MODE_500MHZ = 1
+};
+
+enum {
+       AD_COMPR_NONE  = 0, /* File created with /NOCOMPRESS */
+       AD_COMPR_QCOMP = 6  /* File created with /COMPRESS or /QUICKCOMPRESS */
+};
+
+struct context {
+       gboolean meta_sent;
+       gboolean header_read, records_read;
+       char format, device, record_mode, compression;
+       char pod_status[MAX_POD_COUNT];
+       struct sr_channel *channels[MAX_POD_COUNT][17]; /* 16 + CLK */
+       uint64_t trigger_timestamp;
+       uint32_t record_size, record_count, cur_record;
+       int32_t last_record;
+       GString *out_buf;
+};
+
+static int process_header(GString *buf, struct context *inc);
+static void create_channels(struct sr_input *in);
+
+static char get_pod_name_from_id(int id)
+{
+       switch (id) {
+       case 0:  return 'A';
+       case 1:  return 'B';
+       case 2:  return 'C';
+       case 3:  return 'D';
+       case 4:  return 'E';
+       case 5:  return 'F';
+       case 6:  return 'J';
+       case 7:  return 'K';
+       case 8:  return 'L';
+       case 9:  return 'M';
+       case 10: return 'N';
+       case 11: return 'O';
+       default:
+               sr_err("get_pod_name_from_id() called with invalid ID %d!", id);
+       }
+       return 'X';
+}
+
+static int init(struct sr_input *in, GHashTable *options)
+{
+       struct context *inc;
+       int pod;
+       char id[17];
+
+       in->sdi = g_malloc0(sizeof(struct sr_dev_inst));
+       in->priv = g_malloc0(sizeof(struct context));
+
+       inc = in->priv;
+
+       /* Enable the pods the user chose to see. */
+       for (pod = 0; pod < MAX_POD_COUNT; pod++) {
+               g_snprintf(id, sizeof(id), "pod%c", get_pod_name_from_id(pod));
+               if (g_variant_get_boolean(g_hash_table_lookup(options, id)))
+                       inc->pod_status[pod] = 1;
+       }
+
+       create_channels(in);
+       if (g_slist_length(in->sdi->channels) == 0) {
+               sr_err("No pods were selected and thus no channels created, aborting.");
+               g_free(in->priv);
+               g_free(in->sdi);
+               return SR_ERR;
+       }
+
+       inc->out_buf = g_string_sized_new(OUTBUF_FLUSH_SIZE);
+
+       return SR_OK;
+}
+
+static int format_match(GHashTable *metadata)
+{
+       GString *buf;
+
+       buf = g_hash_table_lookup(metadata, GINT_TO_POINTER(SR_INPUT_META_HEADER));
+
+       return process_header(buf, NULL);
+}
+
+static int process_header(GString *buf, struct context *inc)
+{
+       char *format_name, *format_name_sig;
+       int i, record_size, device_id;
+
+       /*
+        * 00-31 (0x00-1F) file format name
+        * 32-39 (0x20-27) trigger timestamp       u64
+        * 40-47 (0x28-2F) unused
+        * 48    (0x30)    compression
+        * 49-53 (0x31-35) ??
+        *       50 (0x32) 0x00 (PI), 0x01 (iprobe)
+        * 54    (0x36)    0x08 (PI 250/500), 0x0A (iprobe 250)
+        * 55    (0x37)    0x00 (250), 0x01 (500)
+        * 56    (0x38)    record size             u8
+        * 57-59 (0x39-3B) const 0x00
+        * 60-63 (0x3C-3F) number of records       u32
+        * 64-67 (0x40-43) id of last record       s32
+        * 68-77 (0x44-4D) ??
+        *       71 (0x47) const 0x80=128
+        *       72 (0x48) const 0x01
+        * 78-79 (0x4E-4F) ??
+        */
+
+       /* Note: inc is off-limits until we check whether it's a valid pointer. */
+
+       format_name = g_strndup(buf->str, 32);
+
+       /* File format name ends on 0x20/0x1A, let's remove both. */
+       for (i = 1; i < 31; i++) {
+               if (format_name[i] == 0x1A) {
+                       format_name[i - 1] = 0;
+                       format_name[i] = 0;
+               }
+       }
+       g_strchomp(format_name); /* This is for additional padding spaces. */
+
+       format_name_sig = g_strndup(format_name, 5);
+
+       /* Desired file formats either start with digit+space or "trace32". */
+       if (g_strcmp0(format_name_sig, "trace32")) {
+               if (inc)
+                       inc->format = AD_FORMAT_BINHDR;
+       } else if (g_ascii_isdigit(format_name[0]) && (format_name[1] == 0x20)) {
+               if (inc)
+                       inc->format = AD_FORMAT_TXTHDR;
+               g_free(format_name_sig);
+               g_free(format_name);
+               sr_err("This format isn't implemented yet, aborting.");
+               return SR_ERR;
+       } else {
+               g_free(format_name_sig);
+               g_free(format_name);
+               sr_err("Don't know this file format, aborting.");
+               return SR_ERR;
+       }
+
+       sr_dbg("File says it's \"%s\"", format_name);
+
+       record_size = R8(buf->str + 56);
+       device_id = 0;
+
+       if (g_strcmp0(format_name, "trace32 power integrator data") == 0) {
+               if (record_size == 28 || record_size == 45)
+                       device_id = AD_DEVICE_PI;
+       } else if (g_strcmp0(format_name, "trace32 iprobe data") == 0) {
+               if (record_size == 11)
+                       device_id = AD_DEVICE_IPROBE;
+       }
+
+       if (!device_id) {
+               g_free(format_name_sig);
+               g_free(format_name);
+               sr_err("Don't know how to handle this file with record size %d.",
+                       record_size);
+               return SR_ERR;
+       }
+
+       g_free(format_name_sig);
+       g_free(format_name);
+
+       /* Stop processing the header if we just want to identify the file. */
+       if (!inc)
+               return SR_OK;
+
+       inc->device       = device_id;
+       inc->trigger_timestamp = RL64(buf->str + 32);
+       inc->compression  = R8(buf->str + 48);        /* Maps to the enum. */
+       inc->record_mode  = R8(buf->str + 55);        /* Maps to the enum. */
+       inc->record_size  = record_size;
+       inc->record_count = RL32(buf->str + 60);
+       inc->last_record  = RL32S(buf->str + 64);
+
+       sr_dbg("Trigger occured at %lf s.",
+               inc->trigger_timestamp * TIMESTAMP_RESOLUTION);
+       sr_dbg("File contains %d records: first one is %d, last one is %d.",
+               inc->record_count, (inc->last_record - inc->record_count + 1),
+               inc->last_record);
+
+       /* Check if we can work with this compression. */
+       if (inc->compression != AD_COMPR_NONE) {
+               sr_err("File uses unsupported compression (0x%02X), can't continue.",
+                       inc->compression);
+               return SR_ERR;
+       }
+
+       inc->header_read = TRUE;
+
+       return SR_OK;
+}
+
+static void create_channels(struct sr_input *in)
+{
+       struct context *inc;
+       int pod, channel, chan_id;
+       char name[8];
+
+       inc = in->priv;
+       chan_id = 0;
+
+       for (pod = 0; pod < MAX_POD_COUNT; pod++) {
+               if (!inc->pod_status[pod])
+                       continue;
+
+               for (channel = 0; channel < 16; channel++) {
+                       snprintf(name, 8, "%c%d", get_pod_name_from_id(pod), channel);
+                       inc->channels[pod][channel] =
+                               sr_channel_new(in->sdi, chan_id, SR_CHANNEL_LOGIC, TRUE, name);
+                       chan_id++;
+               }
+
+               snprintf(name, 8, "CLK%c", get_pod_name_from_id(pod));
+               inc->channels[pod][16] =
+                       sr_channel_new(in->sdi, chan_id, SR_CHANNEL_LOGIC, TRUE, name);
+               chan_id++;
+       }
+}
+
+static void send_metadata(struct sr_input *in)
+{
+       struct sr_datafeed_packet packet;
+       struct sr_datafeed_meta meta;
+       struct sr_config *src;
+       struct context *inc;
+
+       packet.type = SR_DF_META;
+       packet.payload = &meta;
+       src = sr_config_new(SR_CONF_SAMPLERATE, g_variant_new_uint64(SAMPLING_FREQ));
+       meta.config = g_slist_append(NULL, src);
+       sr_session_send(in->sdi, &packet);
+       g_slist_free(meta.config);
+       sr_config_free(src);
+
+       inc = in->priv;
+       inc->meta_sent = TRUE;
+}
+
+static void flush_output_buffer(struct sr_input *in)
+{
+       struct context *inc;
+       struct sr_datafeed_packet packet;
+       struct sr_datafeed_logic logic;
+
+       inc = in->priv;
+
+       if (inc->out_buf->len) {
+               packet.type = SR_DF_LOGIC;
+               packet.payload = &logic;
+               logic.unitsize = (g_slist_length(in->sdi->channels) + 7) / 8;
+               logic.data = inc->out_buf->str;
+               logic.length = inc->out_buf->len;
+               sr_session_send(in->sdi, &packet);
+
+               g_string_truncate(inc->out_buf, 0);
+       }
+}
+
+static void process_record_pi(struct sr_input *in, gsize start)
+{
+       struct sr_datafeed_packet packet;
+       struct context *inc;
+       uint64_t timestamp, next_timestamp;
+       uint32_t pod_data;
+       char single_payload[12 * 3];
+       GString *buf;
+       int i, pod_count, clk_offset, packet_count, pod;
+       int payload_bit, payload_len, value;
+
+       inc = in->priv;
+       buf = in->buf;
+
+       /*
+        * 00-07 timestamp
+        * 08-09 A15..0
+        * 10-11 B15..0
+        * 12-13 C15..0
+        * 14-15 D15..0
+        * 16-17 E15..0
+        * 18-19 F15..0
+        * 20-23 ??
+        * 24-25 J15..0                         Not present in 500MHz mode
+        * 26-27 K15..0                         Not present in 500MHz mode
+        * 28-29 L15..0                         Not present in 500MHz mode
+        * 30-31 M15..0                         Not present in 500MHz mode
+        * 32-33 N15..0                         Not present in 500MHz mode
+        * 34-35 O15..0                         Not present in 500MHz mode
+        * 36-39 ??                             Not present in 500MHz mode
+        * 40/24 CLKF..A (32=CLKF, .., 1=CLKA)
+        * 41    CLKO..J (32=CLKO, .., 1=CLKJ)  Not present in 500MHz mode
+        * 42/25    ??
+        * 43/26    ??
+        * 44/27    ??
+        */
+
+       timestamp = RL64(buf->str + start);
+
+       if (inc->record_mode == AD_MODE_500MHZ) {
+               pod_count = 6;
+               clk_offset = 24;
+       } else {
+               pod_count = 12;
+               clk_offset = 40;
+       }
+
+       payload_bit = 0;
+       payload_len = 0;
+       single_payload[0] = 0;
+
+       for (pod = 0; pod < pod_count; pod++) {
+               if (!inc->pod_status[pod])
+                       continue;
+
+               switch (pod) {
+               case 0: /* A */
+                       pod_data = RL16(buf->str + start + 8);
+                       pod_data |= (RL16(buf->str + start + clk_offset) & 1) << 16;
+                       break;
+               case 1: /* B */
+                       pod_data = RL16(buf->str + start + 10);
+                       pod_data |= (RL16(buf->str + start + clk_offset) & 2) << 15;
+                       break;
+               case 2: /* C */
+                       pod_data = RL16(buf->str + start + 12);
+                       pod_data |= (RL16(buf->str + start + clk_offset) & 4) << 14;
+                       break;
+               case 3: /* D */
+                       pod_data = RL16(buf->str + start + 14);
+                       pod_data |= (RL16(buf->str + start + clk_offset) & 8) << 13;
+                       break;
+               case 4: /* E */
+                       pod_data = RL16(buf->str + start + 16);
+                       pod_data |= (RL16(buf->str + start + clk_offset) & 16) << 12;
+                       break;
+               case 5: /* F */
+                       pod_data = RL16(buf->str + start + 18);
+                       pod_data |= (RL16(buf->str + start + clk_offset) & 32) << 11;
+                       break;
+               case 6: /* J */
+                       pod_data = RL16(buf->str + start + 24);
+                       pod_data |= (RL16(buf->str + start + 41) & 1) << 16;
+                       break;
+               case 7: /* K */
+                       pod_data = RL16(buf->str + start + 26);
+                       pod_data |= (RL16(buf->str + start + 41) & 2) << 15;
+                       break;
+               case 8: /* L */
+                       pod_data = RL16(buf->str + start + 28);
+                       pod_data |= (RL16(buf->str + start + 41) & 4) << 14;
+                       break;
+               case 9: /* M */
+                       pod_data = RL16(buf->str + start + 30);
+                       pod_data |= (RL16(buf->str + start + 41) & 8) << 13;
+                       break;
+               case 10: /* N */
+                       pod_data = RL16(buf->str + start + 32);
+                       pod_data |= (RL16(buf->str + start + 41) & 16) << 12;
+                       break;
+               case 11: /* O */
+                       pod_data = RL16(buf->str + start + 34);
+                       pod_data |= (RL16(buf->str + start + 41) & 32) << 11;
+                       break;
+               default:
+                       sr_err("Don't know how to obtain data for pod %d.", pod);
+               }
+
+               for (i = 0; i < 17; i++) {
+                       value = (pod_data >> i) & 1;
+                       single_payload[payload_len] |= value << payload_bit;
+
+                       payload_bit++;
+                       if (payload_bit > 7) {
+                               payload_bit = 0;
+                               payload_len++;
+                               single_payload[payload_len] = 0;
+                       }
+               }
+       }
+
+       /* Make sure that payload_len accounts for any incomplete bytes used. */
+       if (payload_bit)
+               payload_len++;
+
+       i = (g_slist_length(in->sdi->channels) + 7) / 8;
+       if (payload_len != i) {
+               sr_err("Payload unit size is %d but should be %d!", payload_len, i);
+               return;
+       }
+
+       if (timestamp == inc->trigger_timestamp) {
+               sr_dbg("Trigger @%lf s, record #%d.",
+                       timestamp * TIMESTAMP_RESOLUTION, inc->cur_record);
+
+               packet.type = SR_DF_TRIGGER;
+               packet.payload = NULL;
+               sr_session_send(in->sdi, &packet);
+       }
+
+       /* Is this the last record in the file? */
+       if (inc->cur_record == inc->record_count - 1) {
+               /* It is, so send the last sample data only once. */
+               g_string_append_len(inc->out_buf, single_payload, payload_len);
+       } else {
+               /* It's not, so fill the time gap by sending lots of data. */
+               next_timestamp = RL64(buf->str + start + inc->record_size);
+               packet_count = (int)(next_timestamp - timestamp) / TIMESTAMP_SCALE;
+
+               /* Make sure we send at least one data set. */
+               if (packet_count == 0)
+                       packet_count = 1;
+
+               for (i = 0; i < packet_count; i++)
+                       g_string_append_len(inc->out_buf, single_payload, payload_len);
+       }
+
+       if (inc->out_buf->len >= OUTBUF_FLUSH_SIZE)
+               flush_output_buffer(in);
+}
+
+static void process_record_iprobe(struct sr_input *in, gsize start)
+{
+       struct sr_datafeed_packet packet;
+       struct context *inc;
+       uint64_t timestamp, next_timestamp;
+       char single_payload[3];
+       int i, payload_len, packet_count;
+
+       inc = in->priv;
+
+       /*
+        * 00-07 timestamp
+        * 08-09 IP15..0
+        * 10    CLK
+        */
+
+       timestamp = RL64(in->buf->str + start);
+       single_payload[0] = R8(in->buf->str + start + 8);
+       single_payload[1] = R8(in->buf->str + start + 9);
+       single_payload[2] = R8(in->buf->str + start + 10) & 1;
+       payload_len = 3;
+
+       if (timestamp == inc->trigger_timestamp) {
+               sr_dbg("Trigger @%lf s, record #%d.",
+                       timestamp * TIMESTAMP_RESOLUTION, inc->cur_record);
+
+               packet.type = SR_DF_TRIGGER;
+               packet.payload = NULL;
+               sr_session_send(in->sdi, &packet);
+       }
+
+       /* Is this the last record in the file? */
+       if (inc->cur_record == inc->record_count - 1) {
+               /* It is, so send the last sample data only once. */
+               g_string_append_len(inc->out_buf, single_payload, payload_len);
+       } else {
+               /* It's not, so fill the time gap by sending lots of data. */
+               next_timestamp = RL64(in->buf->str + start + inc->record_size);
+               packet_count = (int)(next_timestamp - timestamp) / TIMESTAMP_SCALE;
+
+               /* Make sure we send at least one data set. */
+               if (packet_count == 0)
+                       packet_count = 1;
+
+               for (i = 0; i < packet_count; i++)
+                       g_string_append_len(inc->out_buf, single_payload, payload_len);
+       }
+
+       if (inc->out_buf->len >= OUTBUF_FLUSH_SIZE)
+               flush_output_buffer(in);
+}
+
+static void process_practice_token(struct sr_input *in, char *cmd_token)
+{
+       struct context *inc;
+       char **tokens;
+       char chan_suffix[2], chan_name[33];
+       char *s1, *s2;
+       int pod, ch;
+       struct sr_channel *channel;
+
+       inc = in->priv;
+
+       /*
+        * Commands of interest (I may also be IPROBE):
+        *
+        * I.TWIDTH
+        * I.TPREDELAY
+        * I.TDELAY
+        * I.TYSNC.SELECT I.A0 HIGH
+        * NAME.SET <port.chan> <name> <+/-> ...
+        */
+
+       if (!cmd_token)
+               return;
+
+       if (cmd_token[0] == 0)
+               return;
+
+       tokens = g_strsplit(cmd_token, " ", 0);
+
+       if (!tokens)
+               return;
+
+       if (g_strcmp0(tokens[0], "NAME.SET") == 0) {
+               /* Let the user know when the channel has been inverted. */
+               /* This *should* be token #3 but there's an additonal space, making it #4. */
+               chan_suffix[0] = 0;
+               chan_suffix[1] = 0;
+               if (tokens[4]) {
+                       if (tokens[4][0] == '-')
+                               chan_suffix[0] = '-'; /* This is the way PowerView shows it. */
+               }
+
+               /*
+                * Command is using structure "NAME.SET I.A00 I.XYZ" or
+                * "NAME.SET IP.00 IP.XYZ", depending on the device used.
+                * Let's get strings with the I./IP. from both tokens removed.
+                */
+               s1 = g_strstr_len(tokens[1], -1, ".") + 1;
+               s2 = g_strstr_len(tokens[2], -1, ".") + 1;
+
+               if (g_strcmp0(s1, "CLK") == 0) {
+                       /* CLK for iprobe */
+                       pod = 0;
+                       ch = 16;
+               } else if ((strlen(s1) == 4) && g_ascii_isupper(s1[3])) {
+                       /* CLKA/B/J/K for PowerIntegrator */
+                       pod = s1[3] - (char)'A';
+                       ch = 16;
+               } else if (g_ascii_isupper(s1[0])) {
+                       /* A00 for PowerIntegrator */
+                       pod = s1[0] - (char)'A';
+                       ch = atoi(s1 + 1);
+               } else {
+                       /* 00 for iprobe */
+                       pod = 0;
+                       ch = atoi(s1);
+               }
+
+               channel = inc->channels[pod][ch];
+               g_snprintf(chan_name, sizeof(chan_name), "%s%s", s2, chan_suffix);
+
+               sr_dbg("Changing channel name for %s to %s.", s1, chan_name);
+               sr_dev_channel_name_set(channel, chan_name);
+       }
+
+       g_strfreev(tokens);
+}
+
+static void process_practice(struct sr_input *in)
+{
+       char delimiter[3];
+       char **tokens, *token;
+       int i;
+
+       /* Gather all input data until we see the end marker. */
+       if (in->buf->str[in->buf->len - 1] != 0x29)
+               return;
+
+       delimiter[0] = 0x0A;
+       delimiter[1] = ' ';
+       delimiter[2] = 0;
+
+       tokens = g_strsplit(in->buf->str, delimiter, 0);
+
+       /* Special case: first token contains the start marker, too. Skip it. */
+       token = tokens[0];
+       for (i = 0; token[i]; i++) {
+               if (token[i] == ' ')
+                       process_practice_token(in, token + i + 1);
+       }
+
+       for (i = 1; tokens[i]; i++)
+               process_practice_token(in, tokens[i]);
+
+       g_strfreev(tokens);
+
+       g_string_erase(in->buf, 0, in->buf->len);
+}
+
+static int process_buffer(struct sr_input *in)
+{
+       struct context *inc;
+       int i, chunk_size, res;
+
+       inc = in->priv;
+
+       if (!inc->header_read) {
+               res = process_header(in->buf, inc);
+               g_string_erase(in->buf, 0, HEADER_SIZE);
+               if (res != SR_OK)
+                       return res;
+       }
+
+       if (!inc->meta_sent) {
+               std_session_send_df_header(in->sdi, LOG_PREFIX);
+               send_metadata(in);
+       }
+
+       if (!inc->records_read) {
+               /* Cut off at a multiple of the record size. */
+               chunk_size = ((in->buf->len) / inc->record_size) * inc->record_size;
+
+               /* There needs to be at least one more record process_record() can peek into. */
+               chunk_size -= inc->record_size;
+
+               for (i = 0; (i < chunk_size) && (!inc->records_read); i += inc->record_size) {
+                       switch (inc->device) {
+                       case AD_DEVICE_PI:
+                               process_record_pi(in, i);
+                               break;
+                       case AD_DEVICE_IPROBE:
+                               process_record_iprobe(in, i);
+                               break;
+                       default:
+                               sr_err("Trying to process records for unknown device!");
+                               return SR_ERR;
+                       }
+
+                       inc->cur_record++;
+                       if (inc->cur_record == inc->record_count)
+                               inc->records_read = TRUE;
+               }
+
+               g_string_erase(in->buf, 0, i);
+       }
+
+       if (inc->records_read) {
+               /* Read practice commands that configure the setup. */
+               process_practice(in);
+       }
+
+       return SR_OK;
+}
+
+static int receive(struct sr_input *in, GString *buf)
+{
+       g_string_append_len(in->buf, buf->str, buf->len);
+
+       if (!in->sdi_ready) {
+               /* sdi is ready, notify frontend. */
+               in->sdi_ready = TRUE;
+               return SR_OK;
+       }
+
+       return process_buffer(in);
+}
+
+static int end(struct sr_input *in)
+{
+       struct context *inc;
+       struct sr_datafeed_packet packet;
+       int ret;
+
+       inc = in->priv;
+
+       if (in->sdi_ready)
+               ret = process_buffer(in);
+       else
+               ret = SR_OK;
+
+       flush_output_buffer(in);
+
+       if (inc->meta_sent) {
+               packet.type = SR_DF_END;
+               sr_session_send(in->sdi, &packet);
+       }
+
+       return ret;
+}
+
+static struct sr_option options[] = {
+       { "podA", "Import pod A / iprobe",
+               "Create channels and data for pod A / iprobe", NULL, NULL },
+
+       { "podB", "Import pod B", "Create channels and data for pod B", NULL, NULL },
+       { "podC", "Import pod C", "Create channels and data for pod C", NULL, NULL },
+       { "podD", "Import pod D", "Create channels and data for pod D", NULL, NULL },
+       { "podE", "Import pod E", "Create channels and data for pod E", NULL, NULL },
+       { "podF", "Import pod F", "Create channels and data for pod F", NULL, NULL },
+       { "podJ", "Import pod J", "Create channels and data for pod J", NULL, NULL },
+       { "podK", "Import pod K", "Create channels and data for pod K", NULL, NULL },
+       { "podL", "Import pod L", "Create channels and data for pod L", NULL, NULL },
+       { "podM", "Import pod M", "Create channels and data for pod M", NULL, NULL },
+       { "podN", "Import pod N", "Create channels and data for pod N", NULL, NULL },
+       { "podO", "Import pod O", "Create channels and data for pod O", NULL, NULL },
+       ALL_ZERO
+};
+
+static const struct sr_option *get_options(void)
+{
+       if (!options[0].def) {
+               options[0].def = g_variant_ref_sink(g_variant_new_boolean(TRUE));
+               options[1].def = g_variant_ref_sink(g_variant_new_boolean(FALSE));
+               options[2].def = g_variant_ref_sink(g_variant_new_boolean(FALSE));
+               options[3].def = g_variant_ref_sink(g_variant_new_boolean(FALSE));
+               options[4].def = g_variant_ref_sink(g_variant_new_boolean(FALSE));
+               options[5].def = g_variant_ref_sink(g_variant_new_boolean(FALSE));
+               options[6].def = g_variant_ref_sink(g_variant_new_boolean(FALSE));
+               options[7].def = g_variant_ref_sink(g_variant_new_boolean(FALSE));
+               options[8].def = g_variant_ref_sink(g_variant_new_boolean(FALSE));
+               options[9].def = g_variant_ref_sink(g_variant_new_boolean(FALSE));
+               options[10].def = g_variant_ref_sink(g_variant_new_boolean(FALSE));
+               options[11].def = g_variant_ref_sink(g_variant_new_boolean(FALSE));
+       }
+
+       return options;
+}
+
+SR_PRIV struct sr_input_module input_trace32_ad = {
+       .id = "trace32_ad",
+       .name = "Trace32_ad",
+       .desc = "Lauterbach Trace32 logic analyzer data",
+       .exts = (const char*[]){"ad", NULL},
+       .options = get_options,
+       .metadata = { SR_INPUT_META_HEADER | SR_INPUT_META_REQUIRED },
+       .format_match = format_match,
+       .init = init,
+       .receive = receive,
+       .end = end,
+};