Add support for the new query-based PD v3 decoder API.
authorUwe Hermann <uwe@hermann-uwe.de>
Fri, 26 Aug 2016 13:09:17 +0000 (15:09 +0200)
committerUwe Hermann <uwe@hermann-uwe.de>
Wed, 7 Dec 2016 21:37:49 +0000 (22:37 +0100)
For the time being, both APIs (2 and 3) will remain supported until all
decoders have been converted to API version 3. Then, support for API
version 2 will be dropped.

Decoders using PD v3 API can benefit from both readability improvements
as well as performance improvements. Up to 10x speedup has been measured
in some situations (depends a lot on the decoder, the amount of data,
the amount of edges in the signals, the amount of oversampling etc. etc.).

This is only the first set of (basic) performance improvements for
libsigrokdecode, there are various additional opportunities for further
changes to improve performance.

This changeset has been tested to survive a run of all the test-cases in
the sigrok-test repo without issues (for the converted PDs), however it is
not very well-tested yet, so there might be regressions that need to be
addressed.

decoder.c
instance.c
libsigrokdecode-internal.h
libsigrokdecode.h
type_decoder.c
util.c

index 1151f057f2b5c3e2d4fa5f05ed0fda10cb7e7df6..8ad0c5a132e1bac021184bab8c9ee60e28aa5ff6 100644 (file)
--- a/decoder.c
+++ b/decoder.c
@@ -682,8 +682,8 @@ SRD_API int srd_decoder_load(const char *module_name)
         * PDs of different API versions are incompatible and cannot work.
         */
        apiver = srd_decoder_apiver(d);
-       if (apiver != 2) {
-               srd_exception_catch("Only PD API version 2 is supported, "
+       if (apiver != 2 && apiver != 3) {
+               srd_exception_catch("Only PD API version 2/3 is supported, "
                        "decoder %s has version %ld", module_name, apiver);
                fail_txt = "API version mismatch";
                goto err_out;
index e4cf0bfbf1b9118ac874cbbea1301f9af6ca871b..b56a3e662400938839a89d99d8ce0bd8ebd0dfd1 100644 (file)
@@ -354,6 +354,18 @@ SRD_API struct srd_decoder_inst *srd_inst_new(struct srd_session *sess,
                return NULL;
        }
 
+       di->condition_list = NULL;
+       di->match_array = NULL;
+       di->start_samplenum = 0;
+       di->end_samplenum = 0;
+       di->inbuf = NULL;
+       di->inbuflen = 0;
+       di->cur_samplenum = 0;
+       di->old_pins_array = NULL;
+       di->thread_handle = NULL;
+       di->got_new_samples = FALSE;
+       di->handled_all_samples = FALSE;
+
        /* Instance takes input from a frontend by default. */
        sess->di_list = g_slist_append(sess->di_list, di);
 
@@ -493,6 +505,56 @@ SRD_PRIV struct srd_decoder_inst *srd_inst_find_by_obj(const GSList *stack,
        return di;
 }
 
+/**
+ * Set the list of initial (assumed) pin values.
+ *
+ * If the list already exists, do nothing.
+ *
+ * @param di Decoder instance to use. Must not be NULL.
+ *
+ * @private
+ */
+static void set_initial_pin_values(struct srd_decoder_inst *di)
+{
+       int i;
+       GString *s;
+       PyObject *py_initial_pins;
+
+       if (!di || !di->py_inst) {
+               srd_err("Invalid decoder instance.");
+               return;
+       }
+
+       /* Nothing to do if di->old_pins_array is already != NULL. */
+       if (di->old_pins_array) {
+               srd_dbg("Initial pins already set, nothing to do.");
+               return;
+       }
+
+       /* Create an array of old (previous sample) pins, init to 0. */
+       di->old_pins_array = g_array_sized_new(FALSE, TRUE, sizeof(uint8_t), di->dec_num_channels);
+       g_array_set_size(di->old_pins_array, di->dec_num_channels);
+
+       /* Check if the decoder has set self.initial_pins. */
+       if (!PyObject_HasAttrString(di->py_inst, "initial_pins")) {
+               srd_dbg("Initial pins: all 0 (self.initial_pins not set).");
+               return;
+       }
+
+       /* Get self.initial_pins. */
+       py_initial_pins = PyObject_GetAttrString(di->py_inst, "initial_pins");
+
+       /* Fill di->old_pins_array based on self.initial_pins. */
+       s = g_string_sized_new(100);
+       for (i = 0; i < di->dec_num_channels; i++) {
+               di->old_pins_array->data[i] = PyLong_AsLong(PyList_GetItem(py_initial_pins, i));
+               g_string_append_printf(s, "%d, ", di->old_pins_array->data[i]);
+       }
+       s = g_string_truncate(s, s->len - 2);
+       srd_dbg("Initial pins: %s.", s->str);
+       g_string_free(s, TRUE);
+}
+
 /** @private */
 SRD_PRIV int srd_inst_start(struct srd_decoder_inst *di)
 {
@@ -504,6 +566,7 @@ SRD_PRIV int srd_inst_start(struct srd_decoder_inst *di)
        srd_dbg("Calling start() method on protocol decoder instance %s.",
                        di->inst_id);
 
+       /* Run self.start(). */
        if (!(py_res = PyObject_CallMethod(di->py_inst, "start", NULL))) {
                srd_exception_catch("Protocol decoder instance %s",
                                di->inst_id);
@@ -511,6 +574,15 @@ SRD_PRIV int srd_inst_start(struct srd_decoder_inst *di)
        }
        Py_DecRef(py_res);
 
+       /* Set the initial pins based on self.initial_pins. */
+       set_initial_pin_values(di);
+
+       /* Set self.samplenum to 0. */
+       PyObject_SetAttrString(di->py_inst, "samplenum", PyLong_FromLong(0));
+
+       /* Set self.matches to None. */
+       PyObject_SetAttrString(di->py_inst, "matches", Py_None);
+
        /* Start all the PDs stacked on top of this one. */
        for (l = di->next_di; l; l = l->next) {
                next_di = l->data;
@@ -521,6 +593,304 @@ SRD_PRIV int srd_inst_start(struct srd_decoder_inst *di)
        return SRD_OK;
 }
 
+/**
+ * Check whether the specified sample matches the specified term.
+ *
+ * In the case of SRD_TERM_SKIP, this function can modify
+ * term->num_samples_already_skipped.
+ *
+ * @param old_sample The value of the previous sample (0/1).
+ * @param sample The value of the current sample (0/1).
+ * @param term The term that should be checked for a match. Must not be NULL.
+ *
+ * @retval TRUE The current sample matches the specified term.
+ * @retval FALSE The current sample doesn't match the specified term, or an
+ *               invalid term was provided.
+ *
+ * @private
+ */
+static gboolean sample_matches(uint8_t old_sample, uint8_t sample, struct srd_term *term)
+{
+       if (!term)
+               return FALSE;
+
+       switch (term->type) {
+       case SRD_TERM_HIGH:
+               if (sample == 1)
+                       return TRUE;
+               break;
+       case SRD_TERM_LOW:
+               if (sample == 0)
+                       return TRUE;
+               break;
+       case SRD_TERM_RISING_EDGE:
+               if (old_sample == 0 && sample == 1)
+                       return TRUE;
+               break;
+       case SRD_TERM_FALLING_EDGE:
+               if (old_sample == 1 && sample == 0)
+                       return TRUE;
+               break;
+       case SRD_TERM_EITHER_EDGE:
+               if ((old_sample == 1 && sample == 0) || (old_sample == 0 && sample == 1))
+                       return TRUE;
+               break;
+       case SRD_TERM_NO_EDGE:
+               if ((old_sample == 0 && sample == 0) || (old_sample == 1 && sample == 1))
+                       return TRUE;
+               break;
+       case SRD_TERM_SKIP:
+               if (term->num_samples_already_skipped == term->num_samples_to_skip)
+                       return TRUE;
+               term->num_samples_already_skipped++;
+               break;
+       default:
+               srd_err("Unknown term type %d.", term->type);
+               break;
+       }
+
+       return FALSE;
+}
+
+SRD_PRIV void match_array_free(struct srd_decoder_inst *di)
+{
+       if (!di || !di->match_array)
+               return;
+
+       g_array_free(di->match_array, TRUE);
+       di->match_array = NULL;
+}
+
+SRD_PRIV void condition_list_free(struct srd_decoder_inst *di)
+{
+       GSList *l, *ll;
+
+       if (!di)
+               return;
+
+       for (l = di->condition_list; l; l = l->next) {
+               ll = l->data;
+               if (ll)
+                       g_slist_free_full(ll, g_free);
+       }
+
+       di->condition_list = NULL;
+}
+
+static gboolean have_non_null_conds(const struct srd_decoder_inst *di)
+{
+       GSList *l, *cond;
+
+       if (!di)
+               return FALSE;
+
+       for (l = di->condition_list; l; l = l->next) {
+               cond = l->data;
+               if (cond)
+                       return TRUE;
+       }
+
+       return FALSE;
+}
+
+static void update_old_pins_array(struct srd_decoder_inst *di,
+               const uint8_t *sample_pos)
+{
+       uint8_t sample;
+       int i, byte_offset, bit_offset;
+
+       if (!di || !di->dec_channelmap || !sample_pos)
+               return;
+
+       for (i = 0; i < di->dec_num_channels; i++) {
+               byte_offset = di->dec_channelmap[i] / 8;
+               bit_offset = di->dec_channelmap[i] % 8;
+               sample = *(sample_pos + byte_offset) & (1 << bit_offset) ? 1 : 0;
+               di->old_pins_array->data[i] = sample;
+       }
+}
+
+static gboolean term_matches(const struct srd_decoder_inst *di,
+               struct srd_term *term, const uint8_t *sample_pos)
+{
+       uint8_t old_sample, sample;
+       int byte_offset, bit_offset, ch;
+
+       if (!di || !di->dec_channelmap || !term || !sample_pos)
+               return FALSE;
+
+       /* Overwritten below (or ignored for SRD_TERM_SKIP). */
+       old_sample = sample = 0;
+
+       if (term->type != SRD_TERM_SKIP) {
+               ch = term->channel;
+               byte_offset = di->dec_channelmap[ch] / 8;
+               bit_offset = di->dec_channelmap[ch] % 8;
+               sample = *(sample_pos + byte_offset) & (1 << bit_offset) ? 1 : 0;
+               old_sample = di->old_pins_array->data[ch];
+       }
+
+       return sample_matches(old_sample, sample, term);
+}
+
+static gboolean all_terms_match(const struct srd_decoder_inst *di,
+               const GSList *cond, const uint8_t *sample_pos)
+{
+       const GSList *l;
+       struct srd_term *term;
+
+       if (!di || !cond || !sample_pos)
+               return FALSE;
+
+       for (l = cond; l; l = l->next) {
+               term = l->data;
+               if (!term_matches(di, term, sample_pos))
+                       return FALSE;
+       }
+
+       return TRUE;
+}
+
+static gboolean at_least_one_condition_matched(
+               const struct srd_decoder_inst *di, unsigned int num_conditions)
+{
+       unsigned int i;
+
+       if (!di)
+               return FALSE;
+
+       for (i = 0; i < num_conditions; i++) {
+               if (di->match_array->data[i])
+                       return TRUE;
+       }
+
+       return FALSE;
+}
+
+static gboolean find_match(struct srd_decoder_inst *di)
+{
+       static uint64_t s = 0;
+       uint64_t i, j, num_samples_to_process;
+       GSList *l, *cond;
+       const uint8_t *sample_pos;
+       unsigned int num_conditions;
+
+       /* Check whether the condition list is NULL/empty. */
+       if (!di->condition_list) {
+               srd_dbg("NULL/empty condition list, automatic match.");
+               return TRUE;
+       }
+
+       /* Check whether we have any non-NULL conditions. */
+       if (!have_non_null_conds(di)) {
+               srd_dbg("Only NULL conditions in list, automatic match.");
+               return TRUE;
+       }
+
+       num_samples_to_process = di->end_samplenum - di->cur_samplenum;
+       num_conditions = g_slist_length(di->condition_list);
+
+       /* di->match_array is NULL here. Create a new GArray. */
+       di->match_array = g_array_sized_new(FALSE, TRUE, sizeof(gboolean), num_conditions);
+       g_array_set_size(di->match_array, num_conditions);
+
+       for (i = 0, s = 0; i < num_samples_to_process; i++, s++, (di->cur_samplenum)++) {
+
+               sample_pos = di->inbuf + ((di->cur_samplenum - di->start_samplenum) * di->data_unitsize);
+
+               /* Check whether the current sample matches at least one of the conditions (logical OR). */
+               /* IMPORTANT: We need to check all conditions, even if there was a match already! */
+               for (l = di->condition_list, j = 0; l; l = l->next, j++) {
+                       cond = l->data;
+                       if (!cond)
+                               continue;
+                       /* All terms in 'cond' must match (logical AND). */
+                       di->match_array->data[j] = all_terms_match(di, cond, sample_pos);
+               }
+
+               update_old_pins_array(di, sample_pos);
+
+               /* If at least one condition matched we're done. */
+               if (at_least_one_condition_matched(di, num_conditions))
+                       return TRUE;
+       }
+
+       return FALSE;
+}
+
+/**
+ * Process available samples and check if they match the defined conditions.
+ *
+ * This function returns if there is an error, or when a match is found, or
+ * when all samples have been processed (whether a match was found or not).
+ *
+ * @param di The decoder instance to use. Must not be NULL.
+ * @param found_match Will be set to TRUE if at least one condition matched,
+ *                    FALSE otherwise. Must not be NULL.
+ *
+ * @retval SRD_OK No errors occured, see found_match for the result.
+ * @retval SRD_ERR_ARG Invalid arguments.
+ *
+ * @private
+ */
+SRD_PRIV int process_samples_until_condition_match(struct srd_decoder_inst *di, gboolean *found_match)
+{
+       if (!di || !found_match)
+               return SRD_ERR_ARG;
+
+       /* Check if any of the current condition(s) match. */
+       while (TRUE) {
+               /* Feed the (next chunk of the) buffer to find_match(). */
+               *found_match = find_match(di);
+
+               /* Did we handle all samples yet? */
+               if (di->cur_samplenum >= di->end_samplenum) {
+                       srd_dbg("Done, handled all samples (%" PRIu64 "/%" PRIu64 ").",
+                               di->cur_samplenum, di->end_samplenum);
+                       return SRD_OK;
+               }
+
+               /* If we didn't find a match, continue looking. */
+               if (!(*found_match))
+                       continue;
+
+               /* At least one condition matched, return. */
+               return SRD_OK;
+       }
+
+       return SRD_OK;
+}
+
+/**
+ * Worker thread (per PD-stack).
+ *
+ * @param data Pointer to the lowest-level PD's device instance.
+ *             Must not be NULL.
+ *
+ * @return NULL if there was an error.
+ */
+static gpointer di_thread(gpointer data)
+{
+       PyObject *py_res;
+       struct srd_decoder_inst *di;
+
+       if (!data)
+               return NULL;
+
+       di = data;
+
+       /* Call self.decode(). Only returns if the PD throws an exception. */
+       Py_IncRef(di->py_inst);
+       if (!(py_res = PyObject_CallMethod(di->py_inst, "decode", NULL))) {
+               srd_exception_catch("Protocol decoder instance %s: ", di->inst_id);
+               exit(1); /* TODO: Proper shutdown. This is a hack. */
+               return NULL;
+       }
+       Py_DecRef(py_res);
+
+       return NULL;
+}
+
 /**
  * Decode a chunk of samples.
  *
@@ -537,7 +907,7 @@ SRD_PRIV int srd_inst_start(struct srd_decoder_inst *di)
  *
  * @private
  */
-SRD_PRIV int srd_inst_decode(const struct srd_decoder_inst *di,
+SRD_PRIV int srd_inst_decode(struct srd_decoder_inst *di,
                uint64_t start_samplenum, uint64_t end_samplenum,
                const uint8_t *inbuf, uint64_t inbuflen, uint64_t unitsize)
 {
@@ -563,7 +933,7 @@ SRD_PRIV int srd_inst_decode(const struct srd_decoder_inst *di,
                return SRD_ERR_ARG;
        }
 
-       ((struct srd_decoder_inst *)di)->data_unitsize = unitsize;
+       di->data_unitsize = unitsize;
 
        srd_dbg("Decoding: start sample %" PRIu64 ", end sample %"
                PRIu64 " (%" PRIu64 " samples, %" PRIu64 " bytes, unitsize = "
@@ -596,6 +966,30 @@ SRD_PRIV int srd_inst_decode(const struct srd_decoder_inst *di,
                        return SRD_ERR_PYTHON;
                }
                Py_DecRef(py_res);
+       } else {
+               /* If this is the first call, start the worker thread. */
+               if (!di->thread_handle)
+                       di->thread_handle = g_thread_new("di_thread",
+                                                        di_thread, di);
+
+               /* Push the new sample chunk to the worker thread. */
+               g_mutex_lock(&di->data_mutex);
+               di->start_samplenum = start_samplenum;
+               di->end_samplenum = end_samplenum;
+               di->inbuf = inbuf;
+               di->inbuflen = inbuflen;
+               di->got_new_samples = TRUE;
+               di->handled_all_samples = FALSE;
+
+               /* Signal the thread that we have new data. */
+               g_cond_signal(&di->got_new_samples_cond);
+               g_mutex_unlock(&di->data_mutex);
+
+               /* When all samples in this chunk were handled, return. */
+               g_mutex_lock(&di->data_mutex);
+               while (!di->handled_all_samples)
+                       g_cond_wait(&di->handled_all_samples_cond, &di->data_mutex);
+               g_mutex_unlock(&di->data_mutex);
        }
 
        return SRD_OK;
index 8259b97935a9de1c98a3619f14d8d6ebe68f77fe..c006574d0c97f8826dfbf110c7cbe0186a0f4e33 100644 (file)
 #include <Python.h> /* First, so we avoid a _POSIX_C_SOURCE warning. */
 #include "libsigrokdecode.h"
 
+enum {
+       SRD_TERM_HIGH,
+       SRD_TERM_LOW,
+       SRD_TERM_RISING_EDGE,
+       SRD_TERM_FALLING_EDGE,
+       SRD_TERM_EITHER_EDGE,
+       SRD_TERM_NO_EDGE,
+       SRD_TERM_SKIP,
+};
+
+struct srd_term {
+       int type;
+       int channel;
+       uint64_t num_samples_to_skip;
+       uint64_t num_samples_already_skipped;
+};
+
 /* Custom Python types: */
 
 typedef struct {
@@ -62,9 +79,12 @@ SRD_PRIV struct srd_pd_callback *srd_pd_output_callback_find(struct srd_session
 SRD_PRIV struct srd_decoder_inst *srd_inst_find_by_obj( const GSList *stack,
                const PyObject *obj);
 SRD_PRIV int srd_inst_start(struct srd_decoder_inst *di);
-SRD_PRIV int srd_inst_decode(const struct srd_decoder_inst *di,
+SRD_PRIV void match_array_free(struct srd_decoder_inst *di);
+SRD_PRIV void condition_list_free(struct srd_decoder_inst *di);
+SRD_PRIV int srd_inst_decode(struct srd_decoder_inst *di,
                uint64_t start_samplenum, uint64_t end_samplenum,
                const uint8_t *inbuf, uint64_t inbuflen, uint64_t unitsize);
+SRD_PRIV int process_samples_until_condition_match(struct srd_decoder_inst *di, gboolean *found_match);
 SRD_PRIV void srd_inst_free(struct srd_decoder_inst *di);
 SRD_PRIV void srd_inst_free_all(struct srd_session *sess, GSList *stack);
 
@@ -102,6 +122,8 @@ PyMODINIT_FUNC PyInit_sigrokdecode(void);
 SRD_PRIV PyObject *py_import_by_name(const char *name);
 SRD_PRIV int py_attr_as_str(PyObject *py_obj, const char *attr, char **outstr);
 SRD_PRIV int py_dictitem_as_str(PyObject *py_obj, const char *key, char **outstr);
+SRD_PRIV int py_pydictitem_as_str(PyObject *py_obj, PyObject *py_key, char **outstr);
+SRD_PRIV int py_pydictitem_as_long(PyObject *py_obj, PyObject *py_key, uint64_t *out);
 SRD_PRIV int py_str_as_str(PyObject *py_str, char **outstr);
 SRD_PRIV int py_strseq_to_char(PyObject *py_strseq, char ***out_strv);
 SRD_PRIV GVariant *py_obj_to_variant(PyObject *py_obj);
index 03a5de096b18ebd7d2c4cf3fbf9ba103a3894ffe..a4d28d40b943db1a73da1e3e50a89643994a583f 100644 (file)
@@ -228,6 +228,43 @@ struct srd_decoder_inst {
        int data_unitsize;
        uint8_t *channel_samples;
        GSList *next_di;
+
+       /** List of conditions a PD wants to wait for. */
+       GSList *condition_list;
+
+       /** Array of booleans denoting which conditions matched. */
+       GArray *match_array;
+
+       /** Absolute start sample number. */
+       uint64_t start_samplenum;
+
+       /** Absolute end sample number. */
+       uint64_t end_samplenum;
+
+       /** Pointer to the buffer/chunk of input samples. */
+       const uint8_t *inbuf;
+
+       /** Length (in bytes) of the input sample buffer. */
+       uint64_t inbuflen;
+
+       /** Absolute current samplenumber. */
+       uint64_t cur_samplenum;
+
+       /** Array of "old" (previous sample) pin values. */
+       GArray *old_pins_array;
+
+       /** Handle for this PD stack's worker thread. */
+       GThread *thread_handle;
+
+       /** Indicates whether new samples are available for processing. */
+       gboolean got_new_samples;
+
+       /** Indicates whether the worker thread has handled all samples. */
+       gboolean handled_all_samples;
+
+       GCond got_new_samples_cond;
+       GCond handled_all_samples_cond;
+       GMutex data_mutex;
 };
 
 struct srd_pd_output {
index 228cd444c42fc208279472a34c678ddc83739484..dda0de28a2aab2fd9c6a991604eabeb031d49ed2 100644 (file)
@@ -369,16 +369,352 @@ static PyObject *Decoder_register(PyObject *self, PyObject *args,
        return py_new_output_id;
 }
 
+static int get_term_type(const char *v)
+{
+       switch (v[0]) {
+       case 'h':
+               return SRD_TERM_HIGH;
+       case 'l':
+               return SRD_TERM_LOW;
+       case 'r':
+               return SRD_TERM_RISING_EDGE;
+       case 'f':
+               return SRD_TERM_FALLING_EDGE;
+       case 'e':
+               return SRD_TERM_EITHER_EDGE;
+       case 'n':
+               return SRD_TERM_NO_EDGE;
+       }
+
+       return -1;
+}
+
+/**
+ * Get the pin values at the current sample number.
+ *
+ * @param di The decoder instance to use. Must not be NULL.
+ *           The number of channels must be >= 1.
+ *
+ * @return A newly allocated PyTuple containing the pin values at the
+ *         current sample number.
+ */
+static PyObject *get_current_pinvalues(const struct srd_decoder_inst *di)
+{
+       int i;
+       uint8_t sample;
+       const uint8_t *sample_pos;
+       int byte_offset, bit_offset;
+       PyObject *py_pinvalues;
+
+       if (!di) {
+               srd_err("Invalid decoder instance.");
+               return NULL;
+       }
+
+       py_pinvalues = PyTuple_New(di->dec_num_channels);
+
+       for (i = 0; i < di->dec_num_channels; i++) {
+               /* A channelmap value of -1 means "unused optional channel". */
+               if (di->dec_channelmap[i] == -1) {
+                       /* Value of unused channel is 0xff, instead of 0 or 1. */
+                       PyTuple_SetItem(py_pinvalues, i, PyLong_FromLong(0xff));
+               } else {
+                       sample_pos = di->inbuf + ((di->cur_samplenum - di->start_samplenum) * di->data_unitsize);
+                       byte_offset = di->dec_channelmap[i] / 8;
+                       bit_offset = di->dec_channelmap[i] % 8;
+                       sample = *(sample_pos + byte_offset) & (1 << bit_offset) ? 1 : 0;
+                       PyTuple_SetItem(py_pinvalues, i, PyLong_FromLong(sample));
+               }
+       }
+
+       Py_IncRef(py_pinvalues);
+
+       return py_pinvalues;
+}
+
+/**
+ * Create a list of terms in the specified condition.
+ *
+ * If there are no terms in the condition, 'term_list' will be NULL.
+ *
+ * @param py_dict A Python dict containing terms. Must not be NULL.
+ * @param term_list Pointer to a GSList which will be set to the newly
+ *                  created list of terms. Must not be NULL.
+ *
+ * @return SRD_OK upon success, a negative error code otherwise.
+ */
+static int create_term_list(PyObject *py_dict, GSList **term_list)
+{
+       Py_ssize_t pos = 0;
+       PyObject *py_key, *py_value;
+       struct srd_term *term;
+       uint64_t num_samples_to_skip;
+       char *term_str;
+
+       if (!py_dict || !term_list)
+               return SRD_ERR_ARG;
+
+       /* "Create" an empty GSList of terms. */
+       *term_list = NULL;
+
+       /* Iterate over all items in the current dict. */
+       while (PyDict_Next(py_dict, &pos, &py_key, &py_value)) {
+               /* Check whether the current key is a string or a number. */
+               if (PyLong_Check(py_key)) {
+                       /* The key is a number. */
+                       /* TODO: Check if the number is a valid channel. */
+                       /* Get the value string. */
+                       if ((py_pydictitem_as_str(py_dict, py_key, &term_str)) != SRD_OK) {
+                               srd_err("Failed to get the value.");
+                               return SRD_ERR;
+                       }
+                       term = g_malloc0(sizeof(struct srd_term));
+                       term->type = get_term_type(term_str);
+                       term->channel = PyLong_AsLong(py_key);
+                       g_free(term_str);
+               } else if (PyUnicode_Check(py_key)) {
+                       /* The key is a string. */
+                       /* TODO: Check if it's "skip". */
+                       if ((py_pydictitem_as_long(py_dict, py_key, &num_samples_to_skip)) != SRD_OK) {
+                               srd_err("Failed to get number of samples to skip.");
+                               return SRD_ERR;
+                       }
+                       term = g_malloc0(sizeof(struct srd_term));
+                       term->type = SRD_TERM_SKIP;
+                       term->num_samples_to_skip = num_samples_to_skip;
+                       term->num_samples_already_skipped = 0;
+               } else {
+                       srd_err("Term key is neither a string nor a number.");
+                       return SRD_ERR;
+               }
+
+               /* Add the term to the list of terms. */
+               *term_list = g_slist_append(*term_list, term);
+       }
+
+       return SRD_OK;
+}
+
+/**
+ * Replace the current condition list with the new one.
+ *
+ * @param self TODO. Must not be NULL.
+ * @param args TODO. Must not be NULL.
+ *
+ * @retval SRD_OK The new condition list was set successfully.
+ * @retval SRD_ERR There was an error setting the new condition list.
+ *                 The contents of di->condition_list are undefined.
+ * @retval 9999 TODO.
+ */
+static int set_new_condition_list(PyObject *self, PyObject *args)
+{
+       struct srd_decoder_inst *di;
+       GSList *term_list;
+       PyObject *py_conditionlist, *py_conds, *py_dict;
+       int i, num_conditions, ret;
+
+       if (!self || !args)
+               return SRD_ERR_ARG;
+
+       /* Get the decoder instance. */
+       if (!(di = srd_inst_find_by_obj(NULL, self))) {
+               PyErr_SetString(PyExc_Exception, "decoder instance not found");
+               return SRD_ERR;
+       }
+
+       /* Parse the argument of self.wait() into 'py_conds'. */
+       if (!PyArg_ParseTuple(args, "O", &py_conds)) {
+               /* Let Python raise this exception. */
+               return SRD_ERR;
+       }
+
+       /* Check whether 'py_conds' is a dict or a list. */
+       if (PyList_Check(py_conds)) {
+               /* 'py_conds' is a list. */
+               py_conditionlist = py_conds;
+               num_conditions = PyList_Size(py_conditionlist);
+               if (num_conditions == 0)
+                       return 9999; /* The PD invoked self.wait([]). */
+       } else if (PyDict_Check(py_conds)) {
+               /* 'py_conds' is a dict. */
+               if (PyDict_Size(py_conds) == 0)
+                       return 9999; /* The PD invoked self.wait({}). */
+               /* Make a list and put the dict in there for convenience. */
+               py_conditionlist = PyList_New(1);
+               PyList_SetItem(py_conditionlist, 0, py_conds);
+               num_conditions = 1;
+       } else {
+               srd_err("Condition list is neither a list nor a dict.");
+               return SRD_ERR;
+       }
+
+       /* Free the old condition list. */
+       condition_list_free(di);
+
+       ret = SRD_OK;
+
+       /* Iterate over the conditions, set di->condition_list accordingly. */
+       for (i = 0; i < num_conditions; i++) {
+               /* Get a condition (dict) from the condition list. */
+               py_dict = PyList_GetItem(py_conditionlist, i);
+               if (!PyDict_Check(py_dict)) {
+                       srd_err("Condition is not a dict.");
+                       ret = SRD_ERR;
+                       break;
+               }
+
+               /* Create the list of terms in this condition. */
+               if ((ret = create_term_list(py_dict, &term_list)) < 0)
+                       break;
+
+               /* Add the new condition to the PD instance's condition list. */
+               di->condition_list = g_slist_append(di->condition_list, term_list);
+       }
+
+       Py_DecRef(py_conditionlist);
+
+       return ret;
+}
+
+static PyObject *Decoder_wait(PyObject *self, PyObject *args)
+{
+       int ret;
+       unsigned int i;
+       gboolean found_match;
+       struct srd_decoder_inst *di;
+       PyObject *py_pinvalues, *py_matched;
+
+       if (!self || !args)
+               return NULL;
+
+       if (!(di = srd_inst_find_by_obj(NULL, self))) {
+               PyErr_SetString(PyExc_Exception, "decoder instance not found");
+               Py_RETURN_NONE;
+       }
+
+       ret = set_new_condition_list(self, args);
+
+       if (ret == 9999) {
+               /* Empty condition list, automatic match. */
+               PyObject_SetAttrString(di->py_inst, "matched", Py_None);
+               /* Leave self.samplenum unchanged (== di->cur_samplenum). */
+               return get_current_pinvalues(di);
+       }
+
+       while (1) {
+               /* Wait for new samples to process. */
+               g_mutex_lock(&di->data_mutex);
+               while (!di->got_new_samples)
+                       g_cond_wait(&di->got_new_samples_cond, &di->data_mutex);
+
+               /* Check whether any of the current condition(s) match. */
+               ret = process_samples_until_condition_match(di, &found_match);
+
+               /* If there's a match, set self.samplenum etc. and return. */
+               if (found_match) {
+                       /* Set self.samplenum to the (absolute) sample number that matched. */
+                       PyObject_SetAttrString(di->py_inst, "samplenum",
+                               PyLong_FromLong(di->cur_samplenum));
+
+                       if (di->match_array && di->match_array->len > 0) {
+                               py_matched = PyTuple_New(di->match_array->len);
+                               for (i = 0; i < di->match_array->len; i++)
+                                       PyTuple_SetItem(py_matched, i, PyBool_FromLong(di->match_array->data[i]));
+                               PyObject_SetAttrString(di->py_inst, "matched", py_matched);
+                               match_array_free(di);
+                       } else {
+                               PyObject_SetAttrString(di->py_inst, "matched", Py_None);
+                       }
+       
+                       py_pinvalues = get_current_pinvalues(di);
+
+                       g_mutex_unlock(&di->data_mutex);
+
+                       return py_pinvalues;
+               }
+
+               /* No match, reset state for the next chunk. */
+               di->got_new_samples = FALSE;
+               di->handled_all_samples = TRUE;
+               di->start_samplenum = 0;
+               di->end_samplenum = 0;
+               di->inbuf = NULL;
+               di->inbuflen = 0;
+
+               /* Signal the main thread that we handled all samples. */
+               g_cond_signal(&di->handled_all_samples_cond);
+
+               g_mutex_unlock(&di->data_mutex);
+       }
+
+       Py_RETURN_NONE;
+}
+
+/**
+ * Return whether the specified channel was supplied to the decoder.
+ *
+ * @param self TODO. Must not be NULL.
+ * @param args TODO. Must not be NULL.
+ *
+ * @retval Py_True The channel has been supplied by the frontend.
+ * @retval Py_False The channel has been supplied by the frontend.
+ * @retval NULL An error occurred.
+ */
+static PyObject *Decoder_has_channel(PyObject *self, PyObject *args)
+{
+       int idx, max_idx;
+       struct srd_decoder_inst *di;
+       PyObject *py_channel;
+
+       if (!self || !args)
+               return NULL;
+
+       if (!(di = srd_inst_find_by_obj(NULL, self))) {
+               PyErr_SetString(PyExc_Exception, "decoder instance not found");
+               return NULL;
+       }
+
+       /* Parse the argument of self.has_channel() into 'py_channel'. */
+       if (!PyArg_ParseTuple(args, "O", &py_channel)) {
+               /* Let Python raise this exception. */
+               return NULL;
+       }
+
+       if (!PyLong_Check(py_channel)) {
+               PyErr_SetString(PyExc_Exception, "channel index not a number");
+               return NULL;
+       }
+
+       idx = PyLong_AsLong(py_channel);
+       max_idx = g_slist_length(di->decoder->channels)
+               + g_slist_length(di->decoder->opt_channels) - 1;
+
+       if (idx < 0 || idx > max_idx) {
+               srd_err("Invalid channel index %d/%d.", idx, max_idx);
+               PyErr_SetString(PyExc_Exception, "invalid channel");
+               return NULL;
+       }
+
+       return (di->dec_channelmap[idx] == -1) ? Py_False : Py_True;
+}
+
 static PyMethodDef Decoder_methods[] = {
        {"put", Decoder_put, METH_VARARGS,
         "Accepts a dictionary with the following keys: startsample, endsample, data"},
        {"register", (PyCFunction)Decoder_register, METH_VARARGS|METH_KEYWORDS,
                        "Register a new output stream"},
+       {"wait", Decoder_wait, METH_VARARGS,
+                       "Wait for one or more conditions to occur"},
+       {"has_channel", Decoder_has_channel, METH_VARARGS,
+                       "Report whether a channel was supplied"},
        {NULL, NULL, 0, NULL}
 };
 
-/** Create the sigrokdecode.Decoder type.
+/**
+ * Create the sigrokdecode.Decoder type.
+ *
  * @return The new type object.
+ *
  * @private
  */
 SRD_PRIV PyObject *srd_Decoder_type_new(void)
diff --git a/util.c b/util.c
index c3b84ab6463fbc4435735ae4c9f10e584d057595..bb353d0a694ba072cd61ac8025ea9bddddd4896e 100644 (file)
--- a/util.c
+++ b/util.c
@@ -112,6 +112,84 @@ SRD_PRIV int py_dictitem_as_str(PyObject *py_obj, const char *key,
        return py_str_as_str(py_value, outstr);
 }
 
+/**
+ * Get the value of a Python dictionary item, returned as a newly
+ * allocated char *.
+ *
+ * @param py_obj The dictionary to probe.
+ * @param py_key Key of the item to retrieve.
+ * @param outstr Pointer to char * storage to be filled in.
+ *
+ * @return SRD_OK upon success, a (negative) error code otherwise.
+ *         The 'outstr' argument points to a malloc()ed string upon success.
+ *
+ * @private
+ */
+SRD_PRIV int py_pydictitem_as_str(PyObject *py_obj, PyObject *py_key,
+                               char **outstr)
+{
+       PyObject *py_value;
+
+       if (!py_obj || !py_key || !outstr)
+               return SRD_ERR_ARG;
+
+       if (!PyDict_Check(py_obj)) {
+               srd_dbg("Object is not a dictionary.");
+               return SRD_ERR_PYTHON;
+       }
+
+       if (!(py_value = PyDict_GetItem(py_obj, py_key))) {
+               srd_dbg("Dictionary has no such key.");
+               return SRD_ERR_PYTHON;
+       }
+
+       if (!PyUnicode_Check(py_value)) {
+               srd_dbg("Dictionary value should be a string.");
+               return SRD_ERR_PYTHON;
+       }
+
+       return py_str_as_str(py_value, outstr);
+}
+
+/**
+ * Get the value of a Python dictionary item, returned as a newly
+ * allocated char *.
+ *
+ * @param py_obj The dictionary to probe.
+ * @param py_key Key of the item to retrieve.
+ * @param out TODO.
+ *
+ * @return SRD_OK upon success, a (negative) error code otherwise.
+ *
+ * @private
+ */
+SRD_PRIV int py_pydictitem_as_long(PyObject *py_obj, PyObject *py_key, uint64_t *out)
+{
+       PyObject *py_value;
+
+       if (!py_obj || !py_key || !out)
+               return SRD_ERR_ARG;
+
+       if (!PyDict_Check(py_obj)) {
+               srd_dbg("Object is not a dictionary.");
+               return SRD_ERR_PYTHON;
+       }
+
+       if (!(py_value = PyDict_GetItem(py_obj, py_key))) {
+               srd_dbg("Dictionary has no such key.");
+               return SRD_ERR_PYTHON;
+       }
+
+       if (!PyLong_Check(py_value)) {
+               srd_dbg("Dictionary value should be a long.");
+               return SRD_ERR_PYTHON;
+       }
+
+       *out = PyLong_AsUnsignedLongLong(py_value);
+
+       return SRD_OK;
+}
+
 /**
  * Get the value of a Python unicode string object, returned as a newly
  * allocated char *.