asix-omega-rtm-cli: Implement RTM for ASIX OMEGA via external CLI process
[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, *serno, *exe;
130         GSList *devices;
131         size_t argc, chidx;
132         gchar **argv, *output, *vers_text, *eol;
133         GSpawnFlags flags;
134         GError *error;
135         gboolean ok;
136         char serno_buff[10];
137         struct sr_dev_inst *sdi;
138         struct dev_context *devc;
139
140         /* Extract optional serial number from conn= spec. */
141         conn = NULL;
142         (void)sr_serial_extract_options(options, &conn, NULL);
143         if (!conn || !*conn)
144                 conn = NULL;
145         serno = NULL;
146         if (conn) {
147                 if (!g_str_has_prefix(conn, "sn=")) {
148                         sr_err("conn= must specify a serial number.");
149                         return NULL;
150                 }
151                 serno = conn + strlen("sn=");
152                 if (!*serno)
153                         serno = NULL;
154         }
155         if (serno)
156                 sr_dbg("User specified serial number: %s", serno);
157         if (serno && strlen(serno) == 4) {
158                 sr_dbg("Adding 03 prefix to user specified serial number");
159                 snprintf(serno_buff, sizeof(serno_buff), "03%s", serno);
160                 serno = serno_buff;
161         }
162         if (serno && strlen(serno) != 6 && strlen(serno) != 8) {
163                 sr_err("Serial number must be 03xxxx or A603xxxx");
164                 serno = NULL;
165         }
166
167         devices = NULL;
168
169         /*
170          * Check availability of the external executable. Notice that
171          * failure is non-fatal, the scan can take place even when users
172          * don't request and don't expect to use Asix Omega devices.
173          */
174         exe = getenv("OMEGARTMCLI");
175         if (!exe || !*exe)
176                 exe = "omegartmcli";
177         sr_dbg("Vendor application executable: %s", exe);
178         argv = g_malloc0(5 * sizeof(argv[0]));
179         argc = 0;
180         argv[argc++] = g_strdup(exe);
181         argv[argc++] = g_strdup("-version");
182         argv[argc++] = NULL;
183         flags = G_SPAWN_SEARCH_PATH | G_SPAWN_STDERR_TO_DEV_NULL;
184         output = NULL;
185         error = NULL;
186         ok = g_spawn_sync(NULL, argv, NULL, flags, NULL, NULL,
187                 &output, NULL, NULL, &error);
188         if (error && error->code != G_SPAWN_ERROR_NOENT)
189                 sr_err("Cannot execute RTM CLI process: %s", error->message);
190         if (error) {
191                 ok = FALSE;
192                 g_error_free(error);
193         }
194         if (!output || !*output)
195                 ok = FALSE;
196         if (!ok) {
197                 sr_dbg("External RTM CLI execution failed.");
198                 g_free(output);
199                 g_strfreev(argv);
200                 return NULL;
201         }
202
203         /*
204          * Get the executable's version from second stdout line. This
205          * only executes when the executable is found, failure to get
206          * the version information is considered fatal.
207          */
208         vers_text = strstr(output, "Version ");
209         if (!vers_text)
210                 ok = FALSE;
211         if (ok) {
212                 vers_text += strlen("Version ");
213                 eol = strchr(vers_text, '\n');
214                 if (eol)
215                         *eol = '\0';
216                 eol = strchr(vers_text, '\r');
217                 if (eol)
218                         *eol = '\0';
219                 if (!vers_text || !*vers_text)
220                         ok = FALSE;
221         }
222         if (!ok) {
223                 sr_err("Cannot get RTM CLI executable's version.");
224                 g_free(output);
225                 g_strfreev(argv);
226                 return NULL;
227         }
228         sr_info("RTM CLI executable version: %s", vers_text);
229
230         /*
231          * Create a device instance, add it to the result set. Create a
232          * device context. Change the -version command into the command
233          * for acquisition for later use in the driver's lifetime.
234          */
235         sdi = g_malloc0(sizeof(*sdi));
236         devices = g_slist_append(devices, sdi);
237         sdi->status = SR_ST_INITIALIZING;
238         sdi->vendor = g_strdup("ASIX");
239         sdi->model = g_strdup("OMEGA RTM CLI");
240         sdi->version = g_strdup(vers_text);
241         if (serno)
242                 sdi->serial_num = g_strdup(serno);
243         if (conn)
244                 sdi->connection_id = g_strdup(conn);
245         for (chidx = 0; chidx < ARRAY_SIZE(channel_names); chidx++) {
246                 sr_channel_new(sdi, chidx, SR_CHANNEL_LOGIC,
247                         TRUE, channel_names[chidx]);
248         }
249
250         devc = g_malloc0(sizeof(*devc));
251         sdi->priv = devc;
252         sr_sw_limits_init(&devc->limits);
253         argc = 1;
254         g_free(argv[argc]);
255         argv[argc++] = g_strdup("-bin");
256         if (serno) {
257                 argv[argc++] = g_strdup("-serial");
258                 argv[argc++] = g_strdup(serno);
259         }
260         argv[argc++] = NULL;
261         devc->child.argv = argv;
262         devc->child.flags = flags | G_SPAWN_CLOEXEC_PIPES;
263         devc->child.fd_stdin_write = -1;
264         devc->child.fd_stdout_read = -1;
265
266         return std_scan_complete(di, devices);
267 }
268
269 static int config_get(uint32_t key, GVariant **data,
270         const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
271 {
272         struct dev_context *devc;
273
274         (void)cg;
275
276         if (!sdi)
277                 return SR_ERR_ARG;
278         devc = sdi->priv;
279
280         switch (key) {
281         case SR_CONF_CONN:
282                 if (!sdi->connection_id)
283                         return SR_ERR_NA;
284                 *data = g_variant_new_string(sdi->connection_id);
285                 break;
286         case SR_CONF_SAMPLERATE:
287                 *data = g_variant_new_uint64(samplerates[0]);
288                 break;
289         case SR_CONF_LIMIT_MSEC:
290         case SR_CONF_LIMIT_SAMPLES:
291                 return sr_sw_limits_config_get(&devc->limits, key, data);
292         default:
293                 return SR_ERR_NA;
294         }
295
296         return SR_OK;
297 }
298
299 static int config_set(uint32_t key, GVariant *data,
300         const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
301 {
302         struct dev_context *devc;
303
304         (void)cg;
305
306         if (!sdi)
307                 return SR_ERR_ARG;
308         devc = sdi->priv;
309
310         switch (key) {
311         case SR_CONF_LIMIT_MSEC:
312         case SR_CONF_LIMIT_SAMPLES:
313                 return sr_sw_limits_config_set(&devc->limits, key, data);
314         default:
315                 return SR_ERR_NA;
316         }
317
318         return SR_OK;
319 }
320
321 static int config_list(uint32_t key, GVariant **data,
322         const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
323 {
324
325         switch (key) {
326         case SR_CONF_SCAN_OPTIONS:
327         case SR_CONF_DEVICE_OPTIONS:
328                 if (cg)
329                         return SR_ERR_NA;
330                 return STD_CONFIG_LIST(key, data, sdi, cg,
331                         scanopts, drvopts, devopts);
332         case SR_CONF_SAMPLERATE:
333                 *data = std_gvar_samplerates(ARRAY_AND_SIZE(samplerates));
334                 break;
335         default:
336                 return SR_ERR_NA;
337         }
338
339         return SR_OK;
340 }
341
342 static int dev_acquisition_start(const struct sr_dev_inst *sdi)
343 {
344         struct dev_context *devc;
345         int ret;
346         int fd, events;
347         uint64_t remain_count;
348
349         devc = sdi->priv;
350
351         /* Start the external acquisition process. */
352         ret = omega_rtm_cli_open(sdi);
353         if (ret != SR_OK)
354                 return ret;
355         fd = devc->child.fd_stdout_read;
356         events = G_IO_IN | G_IO_ERR;
357
358         /*
359          * Start supervising acquisition limits. Arrange for a stricter
360          * "samples count" check than supported by the common approach.
361          */
362         sr_sw_limits_acquisition_start(&devc->limits);
363         ret = sr_sw_limits_get_remain(&devc->limits,
364                 &remain_count, NULL, NULL, NULL);
365         if (ret != SR_OK)
366                 return ret;
367         if (remain_count) {
368                 devc->samples.remain_count = remain_count;
369                 devc->samples.check_count = TRUE;
370         }
371
372         /* Send the session feed header. */
373         ret = std_session_send_df_header(sdi);
374         if (ret != SR_OK)
375                 return ret;
376
377         /* Start processing the external process' output. */
378         ret = sr_session_source_add(sdi->session, fd, events, 10,
379                 omega_rtm_cli_receive_data, (void *)sdi); /* Un-const. */
380         if (ret != SR_OK)
381                 return ret;
382
383         return SR_OK;
384 }
385
386 static int dev_acquisition_stop(struct sr_dev_inst *sdi)
387 {
388         struct dev_context *devc;
389         int ret;
390         int fd;
391
392         devc = sdi->priv;
393
394         /*
395          * Implementor's note: Do run all stop activities even if
396          * some of them may fail. Emit diagnostics messages as errors
397          * are seen, but don't return early.
398          */
399
400         /* Stop processing the external process' output. */
401         fd = devc->child.fd_stdout_read;
402         if (fd >= 0) {
403                 ret = sr_session_source_remove(sdi->session, fd);
404                 if (ret != SR_OK) {
405                         sr_err("Cannot stop reading acquisition data");
406                 }
407         }
408
409         ret = std_session_send_df_end(sdi);
410         (void)ret;
411
412         ret = omega_rtm_cli_close(sdi);
413         if (ret != SR_OK) {
414                 sr_err("Could not terminate acquisition process");
415         }
416         (void)ret;
417
418         return SR_OK;
419 }
420
421 static struct sr_dev_driver asix_omega_rtm_cli_driver_info = {
422         .name = "asix-omega-rtm-cli",
423         .longname = "ASIX OMEGA RTM CLI",
424         .api_version = 1,
425         .init = std_init,
426         .cleanup = std_cleanup,
427         .scan = scan,
428         .dev_list = std_dev_list,
429         .dev_clear = std_dev_clear,
430         .config_get = config_get,
431         .config_set = config_set,
432         .config_list = config_list,
433         .dev_open = std_dummy_dev_open,
434         .dev_close = std_dummy_dev_close,
435         .dev_acquisition_start = dev_acquisition_start,
436         .dev_acquisition_stop = dev_acquisition_stop,
437         .context = NULL,
438 };
439 SR_REGISTER_DEV_DRIVER(asix_omega_rtm_cli_driver_info);