]> sigrok.org Git - libsigrok.git/blob - src/hardware/asix-omega-rtm-cli/api.c
asix-omega-rtm-cli: add support for "probe names" scan option
[libsigrok.git] / src / hardware / asix-omega-rtm-cli / api.c
1 /*
2  * This file is part of the libsigrok project.
3  *
4  * Copyright (C) 2021 Gerhard Sittig <gerhard.sittig@gmx.net>
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 /*
21  * This sigrok driver implementation uses the vendor's CLI application
22  * for the ASIX OMEGA to operate the device in real time mode. The
23  * external process handles the device detection, USB communication
24  * (FTDI FIFO), FPGA netlist download, and device control. The process'
25  * stdout provides a continuous RLE compressed stream of 16bit samples
26  * taken at 200MHz.
27  *
28  * Known limitations: The samplerate is fixed. Hardware triggers are not
29  * available in this mode. The start of the acquisition takes a few
30  * seconds, but the device's native protocol is unknown and its firmware
31  * is unavailable, so that a native sigrok driver is in some distant
32  * future. Users need to initiate the acquisition in sigrok early so
33  * that the device is capturing when the event of interest happens.
34  *
35  * The vendor application's executable either must be named omegartmcli
36  * and must be found in PATH, or the OMEGARTMCLI environment variable
37  * must contain its location. A scan option could be used when a
38  * suitable SR_CONF key gets identified which communicates executable
39  * locations.
40  *
41  * When multiple devices are connected, then a conn=sn=... specification
42  * can select one of the devices. The serial number should contain six
43  * or eight hex digits (this follows the vendor's approach for the CLI
44  * application).
45  */
46
47 /*
48  * Implementor's notes. Examples of program output which gets parsed by
49  * this sigrok driver.
50  *
51  *   $ ./omegartmcli.exe -version
52  *   omegartmcli.exe Omega Real-Time Mode
53  *   Version 2016-12-14
54  *   Copyright (c) 1991-2016 ASIX s.r.o.
55  *   Email: support@asix.net
56  *
57  *   $ ./omegartmcli.exe -bin [-serial SERNO] <NULL>
58  *   (five command line words including the terminator)
59  *
60  * The RTM CLI application terminates when its stdin closes, or when
61  * CTRL-C is pressed. The former is more portable across platforms. The
62  * stderr output should get ignored, it's rather noisy here under wine,
63  * communicates non-fatal diagnostics, and may communicate "progress"
64  * which we don't care about.
65  *
66  * Ideally the external process could get started earlier, and gets
67  * re-used across several sigrok acquisition activities. Unfortunately
68  * the driver's open/close actions lack a sigrok session, and cannot
69  * register the receive callback (or needs to duplicate common support
70  * code). When such an approach gets implemented, the external process'
71  * output must get drained even outside of sigrok acquisition phases,
72  * the cost of which is yet to get determined (depends on the input
73  * signals, may be expensive).
74  *
75  * The binary data format is used to reduce the amount of inter process
76  * communication. The format is rather simple: Three 16bit items (LE
77  * format) carry a timestamp (10ns resolution), and two 16bit samples
78  * (taken at 5ns intervals). The timestamp may translate to a repetition
79  * of the last sample a given number of times (RLE compression of idle
80  * phases where inputs don't change). The first timestamp after program
81  * startup is to get ignored. Chunks are sent after at most 32Ki 10ns
82  * ticks, to not overflow the 16bit counter. Which translates to a data
83  * volume of 6 bytes each 328us for idle inputs, higher for changing
84  * input signals.
85  *
86  * Is it useful to implement a set of samplerates in the sigrok driver,
87  * and downsample the data which is provided by the Asix application?
88  * This would not avoid the pressure of receiving the acquisition
89  * process' output, but may result in reduced cost on the sigrok side
90  * when users want to inspect slow signals, or export to "expensive"
91  * file formats.
92  *
93  * This driver implementation may benefit from software trigger support.
94  */
95
96 #include <config.h>
97
98 #include <stdlib.h>
99 #include <string.h>
100
101 #include "protocol.h"
102
103 static const char *channel_names[] = {
104         "1", "2", "3", "4", "5", "6", "7", "8",
105         "9", "10", "11", "12", "13", "14", "15", "16",
106 };
107
108 static const uint64_t samplerates[] = {
109         SR_MHZ(200),
110 };
111
112 static const uint32_t scanopts[] = {
113         SR_CONF_CONN, /* Accepts serial number specs. */
114 };
115
116 static const uint32_t drvopts[] = {
117         SR_CONF_LOGIC_ANALYZER,
118 };
119
120 static const uint32_t devopts[] = {
121         SR_CONF_LIMIT_MSEC | SR_CONF_GET | SR_CONF_SET,
122         SR_CONF_LIMIT_SAMPLES | SR_CONF_GET | SR_CONF_SET,
123         SR_CONF_CONN | SR_CONF_GET,
124         SR_CONF_SAMPLERATE | SR_CONF_GET | SR_CONF_LIST,
125 };
126
127 static GSList *scan(struct sr_dev_driver *di, GSList *options)
128 {
129         const char *conn, *probe_names, *serno, *exe;
130         GSList *devices, *l;
131         struct sr_config *src;
132         size_t argc, chmax, chidx;
133         gchar **argv, *output, *vers_text, *eol;
134         GSpawnFlags flags;
135         GError *error;
136         gboolean ok;
137         char serno_buff[10];
138         struct sr_dev_inst *sdi;
139         struct dev_context *devc;
140
141         /* Extract optional serial number from conn= spec. */
142         conn = NULL;
143         probe_names = NULL;
144         (void)sr_serial_extract_options(options, &conn, NULL);
145         if (!conn || !*conn)
146                 conn = NULL;
147         for (l = options; l; l = l->next) {
148                 src = l->data;
149                 switch (src->key) {
150                 case SR_CONF_PROBE_NAMES:
151                         probe_names = g_variant_get_string(src->data, NULL);
152                         break;
153                 }
154         }
155         serno = NULL;
156         if (conn) {
157                 if (!g_str_has_prefix(conn, "sn=")) {
158                         sr_err("conn= must specify a serial number.");
159                         return NULL;
160                 }
161                 serno = conn + strlen("sn=");
162                 if (!*serno)
163                         serno = NULL;
164         }
165         if (serno)
166                 sr_dbg("User specified serial number: %s", serno);
167         if (serno && strlen(serno) == 4) {
168                 sr_dbg("Adding 03 prefix to user specified serial number");
169                 snprintf(serno_buff, sizeof(serno_buff), "03%s", serno);
170                 serno = serno_buff;
171         }
172         if (serno && strlen(serno) != 6 && strlen(serno) != 8) {
173                 sr_err("Serial number must be 03xxxx or A603xxxx");
174                 serno = NULL;
175         }
176
177         devices = NULL;
178
179         /*
180          * Check availability of the external executable. Notice that
181          * failure is non-fatal, the scan can take place even when users
182          * don't request and don't expect to use Asix Omega devices.
183          */
184         exe = getenv("OMEGARTMCLI");
185         if (!exe || !*exe)
186                 exe = "omegartmcli";
187         sr_dbg("Vendor application executable: %s", exe);
188         argv = g_malloc0(5 * sizeof(argv[0]));
189         argc = 0;
190         argv[argc++] = g_strdup(exe);
191         argv[argc++] = g_strdup("-version");
192         argv[argc++] = NULL;
193         flags = G_SPAWN_SEARCH_PATH | G_SPAWN_STDERR_TO_DEV_NULL;
194         output = NULL;
195         error = NULL;
196         ok = g_spawn_sync(NULL, argv, NULL, flags, NULL, NULL,
197                 &output, NULL, NULL, &error);
198         if (error && error->code != G_SPAWN_ERROR_NOENT)
199                 sr_err("Cannot execute RTM CLI process: %s", error->message);
200         if (error) {
201                 ok = FALSE;
202                 g_error_free(error);
203         }
204         if (!output || !*output)
205                 ok = FALSE;
206         if (!ok) {
207                 sr_dbg("External RTM CLI execution failed.");
208                 g_free(output);
209                 g_strfreev(argv);
210                 return NULL;
211         }
212
213         /*
214          * Get the executable's version from second stdout line. This
215          * only executes when the executable is found, failure to get
216          * the version information is considered fatal.
217          */
218         vers_text = strstr(output, "Version ");
219         if (!vers_text)
220                 ok = FALSE;
221         if (ok) {
222                 vers_text += strlen("Version ");
223                 eol = strchr(vers_text, '\n');
224                 if (eol)
225                         *eol = '\0';
226                 eol = strchr(vers_text, '\r');
227                 if (eol)
228                         *eol = '\0';
229                 if (!vers_text || !*vers_text)
230                         ok = FALSE;
231         }
232         if (!ok) {
233                 sr_err("Cannot get RTM CLI executable's version.");
234                 g_free(output);
235                 g_strfreev(argv);
236                 return NULL;
237         }
238         sr_info("RTM CLI executable version: %s", vers_text);
239
240         /*
241          * Create a device instance, add it to the result set. Create a
242          * device context. Change the -version command into the command
243          * for acquisition for later use in the driver's lifetime.
244          */
245         sdi = g_malloc0(sizeof(*sdi));
246         devices = g_slist_append(devices, sdi);
247         sdi->status = SR_ST_INITIALIZING;
248         sdi->vendor = g_strdup("ASIX");
249         sdi->model = g_strdup("OMEGA RTM CLI");
250         sdi->version = g_strdup(vers_text);
251         if (serno)
252                 sdi->serial_num = g_strdup(serno);
253         if (conn)
254                 sdi->connection_id = g_strdup(conn);
255         devc = g_malloc0(sizeof(*devc));
256         sdi->priv = devc;
257         devc->channel_names = sr_parse_probe_names(probe_names,
258                 channel_names, ARRAY_SIZE(channel_names),
259                 ARRAY_SIZE(channel_names), &chmax);
260         for (chidx = 0; chidx < chmax; chidx++) {
261                 sr_channel_new(sdi, chidx, SR_CHANNEL_LOGIC,
262                         TRUE, devc->channel_names[chidx]);
263         }
264
265         sr_sw_limits_init(&devc->limits);
266         argc = 1;
267         g_free(argv[argc]);
268         argv[argc++] = g_strdup("-bin");
269         if (serno) {
270                 argv[argc++] = g_strdup("-serial");
271                 argv[argc++] = g_strdup(serno);
272         }
273         argv[argc++] = NULL;
274         devc->child.argv = argv;
275         devc->child.flags = flags | G_SPAWN_CLOEXEC_PIPES;
276         devc->child.fd_stdin_write = -1;
277         devc->child.fd_stdout_read = -1;
278
279         return std_scan_complete(di, devices);
280 }
281
282 static int config_get(uint32_t key, GVariant **data,
283         const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
284 {
285         struct dev_context *devc;
286
287         (void)cg;
288
289         if (!sdi)
290                 return SR_ERR_ARG;
291         devc = sdi->priv;
292
293         switch (key) {
294         case SR_CONF_CONN:
295                 if (!sdi->connection_id)
296                         return SR_ERR_NA;
297                 *data = g_variant_new_string(sdi->connection_id);
298                 break;
299         case SR_CONF_SAMPLERATE:
300                 *data = g_variant_new_uint64(samplerates[0]);
301                 break;
302         case SR_CONF_LIMIT_MSEC:
303         case SR_CONF_LIMIT_SAMPLES:
304                 return sr_sw_limits_config_get(&devc->limits, key, data);
305         default:
306                 return SR_ERR_NA;
307         }
308
309         return SR_OK;
310 }
311
312 static int config_set(uint32_t key, GVariant *data,
313         const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
314 {
315         struct dev_context *devc;
316
317         (void)cg;
318
319         if (!sdi)
320                 return SR_ERR_ARG;
321         devc = sdi->priv;
322
323         switch (key) {
324         case SR_CONF_LIMIT_MSEC:
325         case SR_CONF_LIMIT_SAMPLES:
326                 return sr_sw_limits_config_set(&devc->limits, key, data);
327         default:
328                 return SR_ERR_NA;
329         }
330
331         return SR_OK;
332 }
333
334 static int config_list(uint32_t key, GVariant **data,
335         const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
336 {
337
338         switch (key) {
339         case SR_CONF_SCAN_OPTIONS:
340         case SR_CONF_DEVICE_OPTIONS:
341                 if (cg)
342                         return SR_ERR_NA;
343                 return STD_CONFIG_LIST(key, data, sdi, cg,
344                         scanopts, drvopts, devopts);
345         case SR_CONF_SAMPLERATE:
346                 *data = std_gvar_samplerates(ARRAY_AND_SIZE(samplerates));
347                 break;
348         default:
349                 return SR_ERR_NA;
350         }
351
352         return SR_OK;
353 }
354
355 static int dev_acquisition_start(const struct sr_dev_inst *sdi)
356 {
357         struct dev_context *devc;
358         int ret;
359         int fd, events;
360         uint64_t remain_count;
361
362         devc = sdi->priv;
363
364         /* Start the external acquisition process. */
365         ret = omega_rtm_cli_open(sdi);
366         if (ret != SR_OK)
367                 return ret;
368         fd = devc->child.fd_stdout_read;
369         events = G_IO_IN | G_IO_ERR;
370
371         /*
372          * Start supervising acquisition limits. Arrange for a stricter
373          * "samples count" check than supported by the common approach.
374          */
375         sr_sw_limits_acquisition_start(&devc->limits);
376         ret = sr_sw_limits_get_remain(&devc->limits,
377                 &remain_count, NULL, NULL, NULL);
378         if (ret != SR_OK)
379                 return ret;
380         if (remain_count) {
381                 devc->samples.remain_count = remain_count;
382                 devc->samples.check_count = TRUE;
383         }
384
385         /* Send the session feed header. */
386         ret = std_session_send_df_header(sdi);
387         if (ret != SR_OK)
388                 return ret;
389
390         /* Start processing the external process' output. */
391         ret = sr_session_source_add(sdi->session, fd, events, 10,
392                 omega_rtm_cli_receive_data, (void *)sdi); /* Un-const. */
393         if (ret != SR_OK)
394                 return ret;
395
396         return SR_OK;
397 }
398
399 static int dev_acquisition_stop(struct sr_dev_inst *sdi)
400 {
401         struct dev_context *devc;
402         int ret;
403         int fd;
404
405         devc = sdi->priv;
406
407         /*
408          * Implementor's note: Do run all stop activities even if
409          * some of them may fail. Emit diagnostics messages as errors
410          * are seen, but don't return early.
411          */
412
413         /* Stop processing the external process' output. */
414         fd = devc->child.fd_stdout_read;
415         if (fd >= 0) {
416                 ret = sr_session_source_remove(sdi->session, fd);
417                 if (ret != SR_OK) {
418                         sr_err("Cannot stop reading acquisition data");
419                 }
420         }
421
422         ret = std_session_send_df_end(sdi);
423         (void)ret;
424
425         ret = omega_rtm_cli_close(sdi);
426         if (ret != SR_OK) {
427                 sr_err("Could not terminate acquisition process");
428         }
429         (void)ret;
430
431         return SR_OK;
432 }
433
434 static struct sr_dev_driver asix_omega_rtm_cli_driver_info = {
435         .name = "asix-omega-rtm-cli",
436         .longname = "ASIX OMEGA RTM CLI",
437         .api_version = 1,
438         .init = std_init,
439         .cleanup = std_cleanup,
440         .scan = scan,
441         .dev_list = std_dev_list,
442         .dev_clear = std_dev_clear,
443         .config_get = config_get,
444         .config_set = config_set,
445         .config_list = config_list,
446         .dev_open = std_dummy_dev_open,
447         .dev_close = std_dummy_dev_close,
448         .dev_acquisition_start = dev_acquisition_start,
449         .dev_acquisition_stop = dev_acquisition_stop,
450         .context = NULL,
451 };
452 SR_REGISTER_DEV_DRIVER(asix_omega_rtm_cli_driver_info);