--- /dev/null
+#include <stdbool.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <getopt.h>
+#include <string.h>
+
+// Digilent SDK
+#include "dpcdecl.h"
+#include "dstm.h"
+#include "dmgr.h"
+
+#include "ec.h"
+
+
+// -----------------------------------------------------------------------------
+
+
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+
+
+typedef struct
+{
+ uint32_t file_size;
+ uint32_t block_size;
+ uint16_t audio_format;
+ uint16_t num_channels;
+ uint32_t sample_rate;
+ uint32_t bytes_per_sec;
+ uint16_t bytes_per_block;
+ uint16_t bits_per_sample;
+ uint32_t data_block_size;
+} wav_t;
+
+
+// -----------------------------------------------------------------------------
+
+
+bool file_expect(FILE *f, const char *data, size_t len)
+{
+ assert(f != NULL);
+ assert(data != NULL);
+
+ size_t read_len;
+ uint8_t value[len];
+
+ EC_NE(read_len = fread(value, sizeof(value[0]), len, f), len);
+
+ return !memcmp(data, value, len);
+
+EC_CLEANUP_BEGIN
+ return false;
+EC_CLEANUP_END
+}
+
+
+bool file_read_u32(FILE *f, uint32_t *value_out)
+{
+ assert(f != NULL);
+ assert(value_out != NULL);
+
+ size_t read_len;
+ uint8_t bytes[4];
+
+ EC_NE(read_len = fread(bytes, sizeof(bytes[0]), 4, f), 4);
+ *value_out = (uint32_t)bytes[0] << 0 |
+ (uint32_t)bytes[1] << 8 |
+ (uint32_t)bytes[2] << 16 |
+ (uint32_t)bytes[3] << 24;
+ return true;
+
+EC_CLEANUP_BEGIN
+ return false;
+EC_CLEANUP_END
+}
+
+
+bool file_read_u16(FILE *f, uint16_t *value_out)
+{
+ assert(f != NULL);
+ assert(value_out != NULL);
+
+ size_t read_len;
+ uint8_t bytes[2];
+
+ EC_NE(read_len = fread(bytes, sizeof(bytes[0]), 2, f), 2);
+ *value_out = (uint32_t)bytes[0] << 0 |
+ (uint32_t)bytes[1] << 8;
+ return true;
+
+EC_CLEANUP_BEGIN
+ return false;
+EC_CLEANUP_END
+}
+
+
+bool file_emit_str(FILE *f, const char *data, size_t len)
+{
+ assert(f != NULL);
+ assert(data != NULL);
+
+ EC_NE(fwrite(data, 1, len, f), len);
+
+ return true;
+
+EC_CLEANUP_BEGIN
+ return false;
+EC_CLEANUP_END
+}
+
+
+bool file_emit_u32(FILE *f, uint32_t data)
+{
+ assert(f != NULL);
+
+ uint8_t bytes[4];
+
+ bytes[0] = (data >> 0) & 0xff;
+ bytes[1] = (data >> 8) & 0xff;
+ bytes[2] = (data >> 16) & 0xff;
+ bytes[3] = (data >> 24) & 0xff;
+
+ EC_NE(fwrite(bytes, 1, 4, f), 4);
+
+ return true;
+
+EC_CLEANUP_BEGIN
+ return false;
+EC_CLEANUP_END
+}
+
+
+bool file_emit_u16(FILE *f, uint16_t data)
+{
+ assert(f != NULL);
+
+ uint8_t bytes[2];
+
+ bytes[0] = (data >> 0) & 0xff;
+ bytes[1] = (data >> 8) & 0xff;
+
+ EC_NE(fwrite(bytes, 1, 2, f), 2);
+
+ return true;
+
+EC_CLEANUP_BEGIN
+ return false;
+EC_CLEANUP_END
+}
+
+
+bool file_read_wav_header(FILE *f, wav_t *info)
+{
+ EC_FALSE(file_expect(f, "RIFF", 4));
+ EC_FALSE(file_read_u32(f, &info->file_size));
+ EC_FALSE(file_expect(f, "WAVE", 4));
+ EC_FALSE(file_expect(f, "fmt ", 4));
+ EC_FALSE(file_read_u32(f, &info->block_size));
+ EC_FALSE(file_read_u16(f, &info->audio_format));
+ EC_FALSE(file_read_u16(f, &info->num_channels));
+ EC_FALSE(file_read_u32(f, &info->sample_rate));
+ EC_FALSE(file_read_u32(f, &info->bytes_per_sec));
+ EC_FALSE(file_read_u16(f, &info->bytes_per_block));
+ EC_FALSE(file_read_u16(f, &info->bits_per_sample));
+ EC_FALSE(file_expect(f, "data", 4));
+ EC_FALSE(file_read_u32(f, &info->data_block_size));
+
+ printf("File size: %" PRIi32 "\n", info->file_size);
+ printf("Block size: %" PRIi32 "\n", info->block_size);
+ printf("Audio format: %" PRIi16 "\n", info->audio_format);
+ printf("Num channels: %" PRIi16 "\n", info->num_channels);
+ printf("Sample rate: %" PRIi32 "\n", info->sample_rate);
+ printf("Bytes per sec: %" PRIi32 "\n", info->bytes_per_sec);
+ printf("Bytes per block: %" PRIi16 "\n", info->bytes_per_block);
+ printf("Bits per sample: %" PRIi16 "\n", info->bits_per_sample);
+ printf("Data block size: %" PRIi32 "\n", info->data_block_size);
+
+ // Check for supported settings
+ EC_NE(info->block_size, 16);
+ EC_NE(info->audio_format, 1);
+ EC_NE(info->num_channels, 2);
+ EC_NE(info->bytes_per_block, 4);
+ EC_NE(info->bits_per_sample, 16);
+
+ return true;
+
+EC_CLEANUP_BEGIN
+ return false;
+EC_CLEANUP_END
+}
+
+
+bool file_emit_wav_header(FILE *f, wav_t *info)
+{
+ EC_FALSE(file_emit_str(f, "RIFF", 4));
+ EC_FALSE(file_emit_u32(f, info->file_size));
+ EC_FALSE(file_emit_str(f, "WAVE", 4));
+ EC_FALSE(file_emit_str(f, "fmt ", 4));
+ EC_FALSE(file_emit_u32(f, info->block_size));
+ EC_FALSE(file_emit_u16(f, info->audio_format));
+ EC_FALSE(file_emit_u16(f, info->num_channels));
+ EC_FALSE(file_emit_u32(f, info->sample_rate));
+ EC_FALSE(file_emit_u32(f, info->bytes_per_sec));
+ EC_FALSE(file_emit_u16(f, info->bytes_per_block));
+ EC_FALSE(file_emit_u16(f, info->bits_per_sample));
+ EC_FALSE(file_emit_str(f, "data", 4));
+ EC_FALSE(file_emit_u32(f, info->data_block_size));
+
+ return true;
+
+EC_CLEANUP_BEGIN
+ return false;
+EC_CLEANUP_END
+}
+
+
+int32_t parse_length_arg(const char *optarg)
+{
+ int32_t value;
+ char *endptr;
+ value = strtol(optarg, &endptr, 0);
+ switch (*endptr)
+ {
+ case 'k': case 'K': value *= 1024; break;
+ case 'm': case 'M': value *= 1024*1024; break;
+ default: return -2;
+ }
+
+ return value;
+}
+
+
+void usage(void)
+{
+ fprintf(stderr,
+ "STM test program for Digilent Nexys2\n"
+ "\n"
+ "Usage: stm [options]\n"
+ "\nConnection options:\n"
+ " -p, --part <name> Specify device (default: \"Nexys2\")\n"
+ " -P, --port <portnum> STM port number (default: 0)\n"
+ "\nOperation options:\n"
+ " -f, --format <format> Input/output file format (default: \"bin\")\n"
+ " \"bin\" - raw binary bytes\n"
+ " \"wav\" - WAV audio file\n"
+ " -b, --blocksize <bytes> Make transfers in blocks of <bytes>\n"
+ " (default: \"2M\")\n"
+ " -n, --nopad Don't pad data to a multiple of 512B\n"
+ " -i, --infile <filename> Input filename\n"
+ " -l, --inlen <bytes> Use length of <bytes> from the input\n"
+ " (default: use all input bytes)\n"
+ " -o, --outfile <filename> Output filename\n"
+ " -x, --outlen <bytes> Expect <bytes> length uploaded\n"
+ " (default: same length as input)\n"
+ "\nOutput options:\n"
+ " -v, --verbose Verbose output; -v -v for more\n"
+ " -q, --quell Quell progress output; -q -q for less\n"
+ " -?, --help Display this message\n"
+ "\n"
+ );
+}
+
+
+int main(int argc, char **argv)
+{
+ // Arguments with defaults
+ char *part = "Nexys2";
+ int32_t port = 0;
+ char *format = "bin";
+ int32_t block_len = 1024 * 1024; // Empirically, this is where diminishing returns begin as block size is made larger
+ bool nopad = false;
+ char *in_name = NULL;
+ int32_t in_len = -1;
+ char *out_name = NULL;
+ int32_t out_len = -1;
+ int verbose = 0;
+ int quell = 0;
+
+ // Options
+ int opt;
+ int option_idx = 0;
+ struct option longopts[] = {
+ // Output options
+ {"help", no_argument, NULL, '?'},
+ {"quell", no_argument, NULL, 'q'},
+ {"verbose", no_argument, NULL, 'v'},
+
+ // Operation options
+ {"format", required_argument, NULL, 'f'},
+ {"blocksize", required_argument, NULL, 'b'},
+ {"nopad", no_argument, NULL, 'n'},
+ {"infile", required_argument, NULL, 'i'},
+ {"inlen", required_argument, NULL, 'l'},
+ {"outfile", required_argument, NULL, 'o'},
+ {"outlen", required_argument, NULL, 'x'},
+
+ // Connection options
+ {"part", required_argument, NULL, 'p'},
+ {"port", required_argument, NULL, 'P'},
+ };
+
+ // Port handle and properties
+ HIF hif = hifInvalid;
+ int32_t port_count;
+ char version[cchVersionMax];
+
+ // -------------------------------------------------------------
+ // Parse arguments
+
+ while ((opt = getopt_long(argc, argv, "?qvf:b:ni:l:o:x:p:P:", longopts, &option_idx)) != -1)
+ {
+ switch (opt)
+ {
+ case '?': usage(); exit(0); break;
+ case 'q': quell++; break;
+ case 'v': verbose++; break;
+
+ case 'f': format = optarg; break;
+ case 'b': block_len = parse_length_arg(optarg); break;
+ case 'n': nopad = true; break;
+ case 'i': in_name = optarg; break;
+ case 'l': in_len = parse_length_arg(optarg); break;
+ case 'o': out_name = optarg; break;
+ case 'x': out_len = parse_length_arg(optarg); break;
+
+ case 'p': part = optarg; break;
+ case 'P': port = strtol(optarg, NULL, 0); break;
+
+ default:
+ fprintf(stderr, "Invalid option -%c\n\n", opt);
+ usage();
+ exit(1);
+ break;
+ }
+ }
+
+ if (block_len <= 0)
+ {
+ fprintf(stderr, "Invalid block length\n");
+ usage();
+ exit(1);
+ }
+
+ if (in_len < -1)
+ {
+ fprintf(stderr, "Invalid input length\n");
+ usage();
+ exit(1);
+ }
+
+ if (out_len < -1)
+ {
+ fprintf(stderr, "Invalid output length\n");
+ usage();
+ exit(1);
+ }
+
+ // -------------------------------------------------------------
+ // Open files
+
+ FILE *in_file;
+ FILE *out_file;
+ uint8_t dl_block[block_len];
+ uint8_t ul_block[block_len];
+
+ EC_NULL(in_file = fopen(in_name, "r"));
+ EC_NULL(out_file = fopen(out_name, "w"));
+
+ if (!strcmp(format, "wav"))
+ {
+ // WAV format, parse headers, emit new header, read data length
+ wav_t info;
+
+ EC_FALSE(file_read_wav_header(in_file, &info));
+ EC_FALSE(file_emit_wav_header(out_file, &info));
+ in_len = info.data_block_size; // Make sure we don't download metadata
+ }
+ else if (!strcmp(format, "bin"))
+ {
+ // Binary format, nothing to do
+ }
+ else
+ {
+ fprintf(stderr, "Invalid output format\n");
+ usage();
+ exit(1);
+ }
+
+ // -------------------------------------------------------------
+ // Open session
+
+ EC_FALSE(DstmGetVersion(version));
+ printf("DSTM version: %s\n", version);
+
+ EC_FALSE(DmgrOpen(&hif, part));
+ printf("Opened device \"%s\"\n", part);
+
+ EC_FALSE(DstmGetPortCount(hif, &port_count));
+ printf("Port count: %" PRIi32 "\n", port_count);
+ for (int32_t i = 0; i < port_count; i++)
+ {
+ uint32_t port_properties = 0;
+
+ EC_FALSE(DstmGetPortProperties(hif, i, &port_properties));
+ printf("Port %" PRIi32 " properties: 0x%08" PRIx32 "\n", i, port_properties);
+ }
+
+ EC_FALSE(DstmEnableEx(hif, port));
+ printf("Opened STM port %" PRIi32 "\n", port);
+
+ // -------------------------------------------------------------
+ // Download input file to device and upload to output file
+
+ (void)nopad; // TODO
+ int32_t in_left = in_len;
+ int32_t out_left = out_len;
+ while (true)
+ {
+ // Compute number of download bytes for this block
+ int32_t request_len;
+ if (in_left < 0)
+ request_len = block_len;
+ else
+ request_len = MIN(block_len, in_left);
+
+ // Load the download block from the input file
+ int32_t dl_len = fread(dl_block, sizeof(dl_block[0]), request_len, in_file);
+
+ // Compute the number of upload bytes for this block
+ size_t ul_len;
+ if (out_left < 0)
+ ul_len = dl_len; // Upload size same as download size
+ else
+ ul_len = MIN(block_len, out_left); // Separate upload size
+
+ // Perform the transfer
+ if (dl_len == 0 && ul_len == 0) break;
+ EC_FALSE(DstmIO(hif, dl_block, dl_len, ul_block, ul_len, false));
+ if (in_left > 0) in_left -= dl_len;
+ if (out_left > 0) out_left -= ul_len;
+
+ // Write the upload portion to the output file
+ fwrite(ul_block, sizeof(ul_block[0]), ul_len, out_file);
+ }
+
+ // -------------------------------------------------------------
+ // Close session
+
+ if (hif != hifInvalid)
+ {
+ DstmDisable(hif);
+ DmgrClose(hif);
+ printf("Closed session\n");
+ }
+
+ return 0;
+
+EC_CLEANUP_BEGIN
+ if (hif != hifInvalid)
+ {
+ DstmDisable(hif);
+ DmgrClose(hif);
+ }
+
+ ec_print();
+ return 1;
+EC_CLEANUP_END
+}
+