+ return SR_OK;
+}
+
+/**
+ * Returns the next enabled channel, wrapping around if necessary.
+ *
+ * @param[in] sdi The device instance the channel is connected to.
+ * Must not be NULL.
+ * @param[in] cur_channel The current channel.
+ *
+ * @return A pointer to the next enabled channel of this device.
+ *
+ * @private
+ */
+SR_PRIV struct sr_channel *sr_next_enabled_channel(const struct sr_dev_inst *sdi,
+ struct sr_channel *cur_channel)
+{
+ struct sr_channel *next_channel;
+ GSList *l;
+
+ next_channel = cur_channel;
+ do {
+ l = g_slist_find(sdi->channels, next_channel);
+ if (l && l->next)
+ next_channel = l->next->data;
+ else
+ next_channel = sdi->channels->data;
+ } while (!next_channel->enabled);
+
+ return next_channel;
+}
+
+/**
+ * Compare two channels, return whether they differ.
+ *
+ * The channels' names and types are checked. The enabled state is not
+ * considered a condition for difference. The test is motivated by the
+ * desire to detect changes in the configuration of acquisition setups
+ * between re-reads of an input file.
+ *
+ * @param[in] ch1 First channel.
+ * @param[in] ch2 Second channel.
+ *
+ * @return TRUE upon differences or unexpected input, FALSE otherwise.
+ *
+ * @private
+ */
+SR_PRIV gboolean sr_channels_differ(struct sr_channel *ch1, struct sr_channel *ch2)
+{
+ if (!ch1 || !ch2)
+ return TRUE;
+
+ if (ch1->type != ch2->type)
+ return TRUE;
+ if (strcmp(ch1->name, ch2->name))
+ return TRUE;
+
+ return FALSE;
+}
+
+/**
+ * Compare two channel lists, return whether they differ.
+ *
+ * Listing the same set of channels but in a different order is considered
+ * a difference in the lists.
+ *
+ * @param[in] l1 First channel list.
+ * @param[in] l2 Second channel list.
+ *
+ * @return TRUE upon differences or unexpected input, FALSE otherwise.
+ *
+ * @private
+ */
+SR_PRIV gboolean sr_channel_lists_differ(GSList *l1, GSList *l2)
+{
+ struct sr_channel *ch1, *ch2;
+
+ while (l1 && l2) {
+ ch1 = l1->data;
+ ch2 = l2->data;
+ l1 = l1->next;
+ l2 = l2->next;
+ if (!ch1 || !ch2)
+ return TRUE;
+ if (sr_channels_differ(ch1, ch2))
+ return TRUE;
+ if (ch1->index != ch2->index)
+ return TRUE;
+ }
+ if (l1 || l2)
+ return TRUE;
+
+ return FALSE;
+}
+
+/**
+ * Allocate and initialize a new channel group, and add it to sdi.
+ *
+ * @param[in] sdi The device instance the channel group is connected to.
+ * Optional, can be NULL.
+ * @param[in] name @copydoc sr_channel_group::name
+ * @param[in] priv @copydoc sr_channel_group::priv
+ *
+ * @return A pointer to a new struct sr_channel_group, NULL upon error.
+ *
+ * @private
+ */
+SR_PRIV struct sr_channel_group *sr_channel_group_new(struct sr_dev_inst *sdi,
+ const char *name, void *priv)
+{
+ struct sr_channel_group *cg;
+
+ cg = g_malloc0(sizeof(*cg));
+ if (name && *name)
+ cg->name = g_strdup(name);
+ cg->priv = priv;
+
+ if (sdi)
+ sdi->channel_groups = g_slist_append(sdi->channel_groups, cg);
+
+ return cg;
+}
+
+/**
+ * Release a previously allocated struct sr_channel_group.
+ *
+ * @param[in] cg Pointer to struct sr_channel_group.
+ *
+ * @private
+ */
+SR_PRIV void sr_channel_group_free(struct sr_channel_group *cg)
+{
+ if (!cg)
+ return;
+
+ g_free(cg->name);
+ g_slist_free(cg->channels);
+ g_free(cg->priv);
+ g_free(cg);
+}
+
+/**
+ * Wrapper around sr_channel_group_free(), suitable for glib iterators.
+ *
+ * @private
+ */
+SR_PRIV void sr_channel_group_free_cb(void *cg)
+{
+ return sr_channel_group_free(cg);