]> sigrok.org Git - libsigrok.git/blob - session.c
c605f1fefbed932188d8593877db92e2473534c4
[libsigrok.git] / session.c
1 /*
2  * This file is part of the sigrok project.
3  *
4  * Copyright (C) 2010 Bert Vermeulen <bert@biot.com>
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 #include "config.h"
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <unistd.h>
24 #include <string.h>
25 #include <glib.h>
26 #include "sigrok.h"
27 #include "sigrok-internal.h"
28
29 /* demo.c */
30 extern GIOChannel channels[2];
31
32 struct source {
33         int fd;
34         int events;
35         int timeout;
36         sr_receive_data_callback cb;
37         void *user_data;
38 };
39
40 /* There can only be one session at a time. */
41 /* 'session' is not static, it's used elsewhere (via 'extern'). */
42 struct sr_session *session;
43 static int num_sources = 0;
44
45 static struct source *sources = NULL;
46 static int source_timeout = -1;
47
48 /**
49  * Create a new session.
50  *
51  * TODO.
52  *
53  * TODO: Should return int?
54  * TODO: Should it use the file-global "session" variable or take an argument?
55  *       The same question applies to all the other session functions.
56  *
57  * @return A pointer to the newly allocated session, or NULL upon errors.
58  */
59 struct sr_session *sr_session_new(void)
60 {
61         if (!(session = calloc(1, sizeof(struct sr_session)))) {
62                 sr_err("session: %s: session malloc failed", __func__);
63                 return NULL; /* TODO: SR_ERR_MALLOC? */
64         }
65
66         return session;
67 }
68
69 /**
70  * Destroy the current session.
71  *
72  * This frees up all memory used by the session.
73  *
74  * TODO: Should return int?
75  */
76 void sr_session_destroy(void)
77 {
78         if (!session) {
79                 sr_warn("session: %s: session was NULL", __func__);
80                 return; /* TODO: SR_ERR_BUG? */
81         }
82
83         g_slist_free(session->devices);
84
85         /* TODO: Error checks needed? */
86
87         /* TODO: Loop over protocol decoders and free them. */
88
89         g_free(session);
90
91         session = NULL;
92 }
93
94 /**
95  * Remove all the devices from the current session. TODO?
96  *
97  * The session itself (i.e., the struct sr_session) is not free'd and still
98  * exists after this function returns.
99  *
100  * TODO: Return int?
101  */
102 void sr_session_device_clear(void)
103 {
104         if (!session) {
105                 sr_warn("session: %s: session was NULL", __func__);
106                 return; /* TODO: SR_ERR_BUG? */
107         }
108
109         g_slist_free(session->devices);
110         session->devices = NULL;
111 }
112
113 /**
114  * Add a device to the current session.
115  *
116  * @param device The device to add to the current session. Must not be NULL.
117  *               Also, device->plugin and device->plugin->opendev must not
118  *               be NULL.
119  *
120  * @return SR_OK upon success, SR_ERR_ARG upon invalid arguments.
121  */
122 int sr_session_device_add(struct sr_device *device)
123 {
124         int ret;
125
126         if (!device) {
127                 sr_err("session: %s: device was NULL", __func__);
128                 return SR_ERR_ARG;
129         }
130
131         if (!device->plugin) {
132                 sr_err("session: %s: device->plugin was NULL", __func__);
133                 return SR_ERR_ARG;
134         }
135
136         if (!device->plugin->opendev) {
137                 sr_err("session: %s: device->plugin->opendev was NULL",
138                        __func__);
139                 return SR_ERR_ARG;
140         }
141
142         if (!session) {
143                 sr_err("session: %s: session was NULL", __func__);
144                 return SR_ERR; /* TODO: SR_ERR_BUG? */
145         }
146
147         if ((ret = device->plugin->opendev(device->plugin_index)) != SR_OK) {
148                 sr_err("session: %s: opendev failed (%d)", __func__, ret);
149                 return ret;
150         }
151
152         session->devices = g_slist_append(session->devices, device);
153
154         return SR_OK;
155 }
156
157 /**
158  * Clear all datafeed callbacks in the current session.
159  *
160  * TODO: Should return int?
161  *
162  * TODO
163  */
164 void sr_session_datafeed_callback_clear(void)
165 {
166         if (!session) {
167                 sr_err("session: %s: session was NULL", __func__);
168                 return; /* TODO: SR_ERR_BUG? */
169         }
170
171         g_slist_free(session->datafeed_callbacks);
172         session->datafeed_callbacks = NULL;
173 }
174
175 /**
176  * Add a datafeed callback to the current session.
177  *
178  * TODO: Should return int?
179  *
180  * @param callback TODO
181  */
182 void sr_session_datafeed_callback_add(sr_datafeed_callback callback)
183 {
184         if (!session) {
185                 sr_err("session: %s: session was NULL", __func__);
186                 return; /* TODO: SR_ERR_BUG? */
187         }
188
189         /* TODO: Is 'callback' allowed to be NULL? */
190
191         session->datafeed_callbacks =
192             g_slist_append(session->datafeed_callbacks, callback);
193 }
194
195 /**
196  * TODO.
197  */
198 static void sr_session_run_poll(void)
199 {
200         GPollFD *fds, my_gpollfd;
201         int ret, i;
202
203         fds = NULL;
204         while (session->running) {
205                 if (fds)
206                         free(fds);
207
208                 /* Construct g_poll()'s array. */
209                 /* TODO: Check malloc return value. */
210                 fds = malloc(sizeof(GPollFD) * num_sources);
211                 for (i = 0; i < num_sources; i++) {
212 #ifdef _WIN32
213                         g_io_channel_win32_make_pollfd(&channels[0],
214                                         sources[i].events, &my_gpollfd);
215 #else
216                         my_gpollfd.fd = sources[i].fd;
217                         my_gpollfd.events = sources[i].events;
218                         fds[i] = my_gpollfd;
219 #endif
220                 }
221
222                 ret = g_poll(fds, num_sources, source_timeout);
223
224                 for (i = 0; i < num_sources; i++) {
225                         if (fds[i].revents > 0 || (ret == 0
226                                 && source_timeout == sources[i].timeout)) {
227                                 /*
228                                  * Invoke the source's callback on an event,
229                                  * or if the poll timeout out and this source
230                                  * asked for that timeout.
231                                  */
232                                 if (!sources[i].cb(fds[i].fd, fds[i].revents,
233                                                   sources[i].user_data))
234                                         sr_session_source_remove(sources[i].fd);
235                         }
236                 }
237         }
238         free(fds);
239 }
240
241 /**
242  * Start a session.
243  *
244  * There can only be one session at a time. TODO
245  *
246  * @return SR_OK upon success, SR_ERR upon errors.
247  */
248 int sr_session_start(void)
249 {
250         struct sr_device *device;
251         GSList *l;
252         int ret;
253
254         if (!session) {
255                 sr_err("session: %s: session was NULL; a session must be "
256                        "created first, before starting it.", __func__);
257                 return SR_ERR; /* TODO: SR_ERR_BUG? */
258         }
259
260         if (!session->devices) {
261                 /* TODO: Actually the case? */
262                 sr_err("session: %s: session->devices was NULL; a session "
263                        "cannot be started without devices.", __func__);
264                 return SR_ERR; /* TODO: SR_ERR_BUG? */
265         }
266
267         /* TODO: Check plugin_index validity? */
268
269         sr_info("session: starting");
270
271         for (l = session->devices; l; l = l->next) {
272                 device = l->data;
273                 /* TODO: Check for device != NULL. */
274                 if ((ret = device->plugin->start_acquisition(
275                                 device->plugin_index, device)) != SR_OK) {
276                         sr_err("session: %s: could not start an acquisition "
277                                "(%d)", __func__, ret);
278                         break;
279                 }
280         }
281
282         /* TODO: What if there are multiple devices? Which return code? */
283
284         return ret;
285 }
286
287 /**
288  * Run the session.
289  *
290  * TODO: Should return int.
291  * TODO: Various error checks etc.
292  *
293  * TODO.
294  */
295 void sr_session_run(void)
296 {
297         if (!session) {
298                 sr_err("session: %s: session was NULL; a session must be "
299                        "created first, before running it.", __func__);
300                 return; /* TODO: SR_ERR or SR_ERR_BUG? */
301         }
302
303         if (!session->devices) {
304                 /* TODO: Actually the case? */
305                 sr_err("session: %s: session->devices was NULL; a session "
306                        "cannot be run without devices.", __func__);
307                 return; /* TODO: SR_ERR or SR_ERR_BUG? */
308         }
309
310         sr_info("session: running");
311         session->running = TRUE;
312
313         /* Do we have real sources? */
314         if (num_sources == 1 && sources[0].fd == -1) {
315                 /* Dummy source, freewheel over it. */
316                 while (session->running)
317                         sources[0].cb(-1, 0, sources[0].user_data);
318         } else {
319                 /* Real sources, use g_poll() main loop. */
320                 sr_session_run_poll();
321         }
322
323         /* TODO: return SR_OK; */
324 }
325
326 /**
327  * Halt the current session.
328  *
329  * TODO: Return int.
330  *
331  * TODO.
332  */
333 void sr_session_halt(void)
334 {
335         if (!session) {
336                 sr_err("session: %s: session was NULL", __func__);
337                 return; /* TODO: SR_ERR; or SR_ERR_BUG? */
338         }
339
340         sr_info("session: halting");
341         session->running = FALSE;
342
343         /* TODO: return SR_OK; */
344 }
345
346 /**
347  * Stop the current session.
348  *
349  * TODO: Difference to halt?
350  *
351  * TODO.
352  */
353 void sr_session_stop(void)
354 {
355         struct sr_device *device;
356         GSList *l;
357
358         if (!session) {
359                 sr_err("session: %s: session was NULL", __func__);
360                 return; /* TODO: SR_ERR or SR_ERR_BUG? */
361         }
362
363         sr_info("session: stopping");
364         session->running = FALSE;
365         for (l = session->devices; l; l = l->next) {
366                 device = l->data;
367                 if (device->plugin && device->plugin->stop_acquisition)
368                         device->plugin->stop_acquisition(device->plugin_index, device);
369         }
370
371         /* TODO: return SR_OK; */
372 }
373
374 /**
375  * TODO.
376  *
377  * TODO: Should return int?
378  * TODO: Various error checks.
379  *
380  * @param packet TODO.
381  */
382 static void datafeed_dump(struct sr_datafeed_packet *packet)
383 {
384         struct sr_datafeed_logic *logic;
385
386         switch (packet->type) {
387         case SR_DF_HEADER:
388                 sr_dbg("bus: received SR_DF_HEADER");
389                 break;
390         case SR_DF_TRIGGER:
391                 sr_dbg("bus: received SR_DF_TRIGGER at %lu ms",
392                                 packet->timeoffset / 1000000);
393                 break;
394         case SR_DF_LOGIC:
395                 logic = packet->payload;
396                 sr_dbg("bus: received SR_DF_LOGIC at %f ms duration %f ms, "
397                        "%" PRIu64 " bytes", packet->timeoffset / 1000000.0,
398                        packet->duration / 1000000.0, logic->length);
399                 break;
400         case SR_DF_END:
401                 sr_dbg("bus: received SR_DF_END");
402                 break;
403         default:
404                 /* TODO: sr_err() and abort? */
405                 sr_dbg("bus: received unknown packet type %d", packet->type);
406                 break;
407         }
408 }
409
410 /**
411  * TODO.
412  *
413  * TODO: Should return int?
414  *
415  * @param device TODO.
416  * @param packet TODO.
417  */
418 void sr_session_bus(struct sr_device *device, struct sr_datafeed_packet *packet)
419 {
420         GSList *l;
421         sr_datafeed_callback cb;
422
423         if (!device) {
424                 sr_err("session: %s: device was NULL", __func__);
425                 return; /* TODO: SR_ERR_ARG */
426         }
427
428         if (!device->plugin) {
429                 sr_err("session: %s: device->plugin was NULL", __func__);
430                 return; /* TODO: SR_ERR_ARG */
431         }
432
433         /*
434          * TODO: Send packet through PD pipe, and send the output of that to
435          * the callbacks as well.
436          */
437         for (l = session->datafeed_callbacks; l; l = l->next) {
438                 cb = l->data;
439                 /* TODO: Check for cb != NULL. */
440                 datafeed_dump(packet);
441                 cb(device, packet);
442         }
443
444         /* TODO: return SR_OK; */
445 }
446
447 /**
448  * TODO.
449  *
450  * TODO: Should return int?
451  * TODO: Switch to g_try_malloc0() / g_free().
452  * TODO: More error checks etc.
453  *
454  * @param fd TODO.
455  * @param events TODO.
456  * @param timeout TODO.
457  * @param callback TODO.
458  * @param user_data TODO.
459  */
460 void sr_session_source_add(int fd, int events, int timeout,
461                 sr_receive_data_callback callback, void *user_data)
462 {
463         struct source *new_sources, *s;
464
465         if (!callback) {
466                 sr_err("session: %s: callback was NULL", __func__);
467                 return; /* TODO: SR_ERR_ARG */
468         }
469
470         /* Note: user_data can be NULL, that's not a bug. */
471
472         new_sources = calloc(1, sizeof(struct source) * (num_sources + 1));
473         if (!new_sources) {
474                 sr_err("session: %s: new_sources malloc failed", __func__);
475                 return; /* TODO: SR_ERR_MALLOC */
476         }
477
478         if (sources) {
479                 memcpy(new_sources, sources,
480                        sizeof(struct source) * num_sources);
481                 free(sources);
482         }
483
484         s = &new_sources[num_sources++];
485         s->fd = fd;
486         s->events = events;
487         s->timeout = timeout;
488         s->cb = callback;
489         s->user_data = user_data;
490         sources = new_sources;
491
492         if (timeout != source_timeout && timeout > 0
493             && (source_timeout == -1 || timeout < source_timeout))
494                 source_timeout = timeout;
495
496         /* TODO: return SR_OK; */
497 }
498
499 /**
500  * Remove the source belonging to the specified file descriptor.
501  *
502  * TODO: Should return int.
503  * TODO: More error checks.
504  * TODO: Switch to g_try_malloc0() / g_free().
505  *
506  * @param fd TODO.
507  */
508 void sr_session_source_remove(int fd)
509 {
510         struct source *new_sources;
511         int old, new;
512
513         /* TODO */
514         if (!sources)
515                 return;
516
517         new_sources = calloc(1, sizeof(struct source) * num_sources);
518         if (!new_sources) {
519                 sr_err("session: %s: new_sources malloc failed", __func__);
520                 return; /* TODO: SR_ERR_MALLOC */
521         }
522
523         for (old = 0, new = 0; old < num_sources; old++) {
524                 if (sources[old].fd != fd)
525                         memcpy(&new_sources[new++], &sources[old],
526                                sizeof(struct source));
527         }
528
529         if (old != new) {
530                 free(sources);
531                 sources = new_sources;
532                 num_sources--;
533         } else {
534                 /* Target fd was not found. */
535                 free(new_sources);
536         }
537 }