Implement basic functionalities for baylibre-acme. Add support for common config options, device detection and sample reading.
Signed-off-by: Bartosz Golaszewski <bgolaszew...@baylibre.com> --- src/hardware/baylibre-acme/api.c | 217 ++++++++++++++++--- src/hardware/baylibre-acme/protocol.c | 392 +++++++++++++++++++++++++++++++++- src/hardware/baylibre-acme/protocol.h | 54 ++++- 3 files changed, 621 insertions(+), 42 deletions(-) diff --git a/src/hardware/baylibre-acme/api.c b/src/hardware/baylibre-acme/api.c index b13e2e0..89d8b09 100644 --- a/src/hardware/baylibre-acme/api.c +++ b/src/hardware/baylibre-acme/api.c @@ -22,6 +22,21 @@ SR_PRIV struct sr_dev_driver baylibre_acme_driver_info; static struct sr_dev_driver *di = &baylibre_acme_driver_info; +static const uint32_t devopts[] = { + SR_CONF_CONTINUOUS | SR_CONF_SET, + SR_CONF_LIMIT_SAMPLES | SR_CONF_GET | SR_CONF_SET, + SR_CONF_LIMIT_MSEC | SR_CONF_GET | SR_CONF_SET, + SR_CONF_SAMPLERATE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, +}; + +#define MAX_SAMPLE_RATE 500 /* In Hz */ + +static const uint64_t samplerates[] = { + SR_HZ(1), + SR_HZ(MAX_SAMPLE_RATE), + SR_HZ(1), +}; + static int init(struct sr_context *sr_ctx) { return std_init(sr_ctx, di, LOG_PREFIX); @@ -30,18 +45,84 @@ static int init(struct sr_context *sr_ctx) static GSList *scan(GSList *options) { struct drv_context *drvc; + struct dev_context *devc; + struct sr_dev_inst *sdi; GSList *devices; + gboolean status; + int i; (void)options; - devices = NULL; drvc = di->priv; - drvc->instances = NULL; + devices = NULL; - /* TODO: scan for devices, either based on a SR_CONF_CONN option - * or on a USB scan. */ + devc = g_malloc0(sizeof(struct dev_context)); + devc->samplerate = SR_HZ(10); + + sdi = g_malloc0(sizeof(struct sr_dev_inst)); + sdi->status = SR_ST_INACTIVE; + sdi->model = g_strdup("Acme cape"); + sdi->driver = di; + sdi->priv = devc; + + status = bl_acme_is_sane(); + if (!status) + goto err_out; + + /* + * Iterate over all ACME connectors and check if any probes + * are present. + */ + for (i = 0; i < MAX_PROBES; i++) { + /* + * First check if there's an energy probe on this connector. If + * not, and we're already at the fifth probe - see if we can + * detect a temperature probe. + */ + status = bl_acme_detect_probe(bl_acme_get_enrg_addr(i), + PROBE_NUM(i), + ENRG_PROBE_NAME); + if (status) { + /* Energy probe detected. */ + status = bl_acme_register_probe(sdi, + PROBE_ENRG, + bl_acme_get_enrg_addr(i), + PROBE_NUM(i)); + if (!status) { + sr_err("Error registering power probe %d", + PROBE_NUM(i)); + continue; + } + + } else if (i >= TEMP_PRB_START_INDEX) { + status = bl_acme_detect_probe(bl_acme_get_temp_addr(i), + PROBE_NUM(i), + TEMP_PROBE_NAME); + if (status) { + /* Temperature probe detected. */ + status = bl_acme_register_probe(sdi,PROBE_TEMP, + bl_acme_get_temp_addr(i), + PROBE_NUM(i)); + if (!status) { + sr_err("Error registering temp " + "probe %d", PROBE_NUM(i)); + continue; + } + } + + } + } + + devices = g_slist_append(devices, sdi); + drvc->instances = g_slist_append(drvc->instances, sdi); return devices; + +err_out: + g_free(devc); + sr_dev_inst_free(sdi); + + return NULL; } static GSList *dev_list(void) @@ -58,8 +139,7 @@ static int dev_open(struct sr_dev_inst *sdi) { (void)sdi; - /* TODO: get handle from sdi->conn and open it. */ - + /* Nothing to do here. */ sdi->status = SR_ST_ACTIVE; return SR_OK; @@ -69,8 +149,7 @@ static int dev_close(struct sr_dev_inst *sdi) { (void)sdi; - /* TODO: get handle from sdi->conn and close it. */ - + /* Nothing to do here. */ sdi->status = SR_ST_INACTIVE; return SR_OK; @@ -80,23 +159,31 @@ static int cleanup(void) { dev_clear(); - /* TODO: free other driver resources, if any. */ - return SR_OK; } -static int config_get(uint32_t key, GVariant **data, const struct sr_dev_inst *sdi, - const struct sr_channel_group *cg) +static int config_get(uint32_t key, GVariant **data, + const struct sr_dev_inst *sdi, + const struct sr_channel_group *cg) { + struct dev_context *devc; int ret; - (void)sdi; - (void)data; (void)cg; + devc = sdi->priv; + ret = SR_OK; switch (key) { - /* TODO */ + case SR_CONF_LIMIT_SAMPLES: + *data = g_variant_new_uint64(devc->limit_samples); + break; + case SR_CONF_LIMIT_MSEC: + *data = g_variant_new_uint64(devc->limit_msec); + break; + case SR_CONF_SAMPLERATE: + *data = g_variant_new_uint64(devc->samplerate); + break; default: return SR_ERR_NA; } @@ -104,20 +191,44 @@ static int config_get(uint32_t key, GVariant **data, const struct sr_dev_inst *s return ret; } -static int config_set(uint32_t key, GVariant *data, const struct sr_dev_inst *sdi, - const struct sr_channel_group *cg) +static int config_set(uint32_t key, GVariant *data, + const struct sr_dev_inst *sdi, + const struct sr_channel_group *cg) { + struct dev_context *devc; + uint64_t samplerate; int ret; - (void)data; (void)cg; if (sdi->status != SR_ST_ACTIVE) return SR_ERR_DEV_CLOSED; + devc = sdi->priv; + ret = SR_OK; switch (key) { - /* TODO */ + case SR_CONF_LIMIT_SAMPLES: + devc->limit_samples = g_variant_get_uint64(data); + devc->limit_msec = 0; + sr_dbg("Setting sample limit to %" PRIu64, + devc->limit_samples); + break; + case SR_CONF_LIMIT_MSEC: + devc->limit_msec = g_variant_get_uint64(data) * 1000; + devc->limit_samples = 0; + sr_dbg("Setting time limit to %" PRIu64"ms", devc->limit_msec); + break; + case SR_CONF_SAMPLERATE: + samplerate = g_variant_get_uint64(data); + if (samplerate > MAX_SAMPLE_RATE) { + sr_err("Maximum sample rate is %d", MAX_SAMPLE_RATE); + ret = SR_ERR_SAMPLERATE; + break; + } + devc->samplerate = samplerate; + sr_dbg("Setting samplerate to %" PRIu64, devc->samplerate); + break; default: ret = SR_ERR_NA; } @@ -125,18 +236,34 @@ static int config_set(uint32_t key, GVariant *data, const struct sr_dev_inst *sd return ret; } -static int config_list(uint32_t key, GVariant **data, const struct sr_dev_inst *sdi, - const struct sr_channel_group *cg) +static int config_list(uint32_t key, GVariant **data, + const struct sr_dev_inst *sdi, + const struct sr_channel_group *cg) { + GVariant *gvar; + GVariantBuilder gvb; int ret; (void)sdi; - (void)data; (void)cg; ret = SR_OK; switch (key) { - /* TODO */ + case SR_CONF_DEVICE_OPTIONS: + *data = g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32, + devopts, + ARRAY_SIZE(devopts), + sizeof(uint32_t)); + break; + case SR_CONF_SAMPLERATE: + g_variant_builder_init(&gvb, G_VARIANT_TYPE("a{sv}")); + gvar = g_variant_new_fixed_array(G_VARIANT_TYPE("t"), + samplerates, + ARRAY_SIZE(samplerates), + sizeof(uint64_t)); + g_variant_builder_add(&gvb, "{sv}", "samplerate-steps", gvar); + *data = g_variant_builder_end(&gvb); + break; default: return SR_ERR_NA; } @@ -144,36 +271,66 @@ static int config_list(uint32_t key, GVariant **data, const struct sr_dev_inst * return ret; } -static int dev_acquisition_start(const struct sr_dev_inst *sdi, - void *cb_data) +static int dev_acquisition_start(const struct sr_dev_inst *sdi, void *cb_data) { - (void)sdi; + struct dev_context *devc; + (void)cb_data; if (sdi->status != SR_ST_ACTIVE) return SR_ERR_DEV_CLOSED; - /* TODO: configure hardware, reset acquisition state, set up - * callbacks and send header packet. */ + devc = sdi->priv; + devc->samples_read = 0; + + if (pipe(devc->pipe_fds)) { + sr_err("Error setting up pipe"); + return SR_ERR; + } + + devc->channel = g_io_channel_unix_new(devc->pipe_fds[0]); + g_io_channel_set_flags(devc->channel, G_IO_FLAG_NONBLOCK, NULL); + g_io_channel_set_encoding(devc->channel, NULL, NULL); + g_io_channel_set_buffered(devc->channel, FALSE); + + sr_session_source_add_channel(sdi->session, devc->channel, + G_IO_IN | G_IO_ERR, 1, + bl_acme_receive_data, (void *)sdi); + + /* Send header packet to the session bus. */ + std_session_send_df_header(sdi, LOG_PREFIX); + devc->start_time = g_get_monotonic_time(); return SR_OK; } static int dev_acquisition_stop(struct sr_dev_inst *sdi, void *cb_data) { + struct sr_datafeed_packet packet; + struct dev_context *devc; + (void)cb_data; + devc = sdi->priv; + if (sdi->status != SR_ST_ACTIVE) return SR_ERR_DEV_CLOSED; - /* TODO: stop acquisition. */ + sr_session_source_remove_channel(sdi->session, devc->channel); + g_io_channel_shutdown(devc->channel, FALSE, NULL); + g_io_channel_unref(devc->channel); + devc->channel = NULL; + + /* Send last packet. */ + packet.type = SR_DF_END; + sr_session_send(sdi, &packet); return SR_OK; } SR_PRIV struct sr_dev_driver baylibre_acme_driver_info = { .name = "baylibre-acme", - .longname = "baylibre-acme", + .longname = "BayLibre ACME (Another Cute Measurement Equipment)", .api_version = 1, .init = init, .cleanup = cleanup, diff --git a/src/hardware/baylibre-acme/protocol.c b/src/hardware/baylibre-acme/protocol.c index b9f8096..272a4ff 100644 --- a/src/hardware/baylibre-acme/protocol.c +++ b/src/hardware/baylibre-acme/protocol.c @@ -17,24 +17,404 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ +#include <string.h> +#include <stdlib.h> /* strtol() */ +#include <errno.h> +#include <fcntl.h> /* open(), etc... */ #include "protocol.h" -SR_PRIV int baylibre_acme_receive_data(int fd, int revents, void *cb_data) +struct channel_group_priv { + int hwmon_num; +}; + +struct channel_priv { + int ch_type; + struct channel_group_priv *probe; +}; + +static const uint8_t enrg_i2c_addrs[] = { + 0x40, 0x41, 0x44, 0x45, 0x42, 0x43, 0x46, 0x47, +}; + +static const uint8_t temp_i2c_addrs[] = { + 0x0, 0x0, 0x0, 0x0, 0x4c, 0x49, 0x4f, 0x4b, +}; + +SR_PRIV uint8_t bl_acme_get_enrg_addr(int index) +{ + return enrg_i2c_addrs[index]; +} + +SR_PRIV uint8_t bl_acme_get_temp_addr(int index) +{ + return temp_i2c_addrs[index]; +} + +SR_PRIV gboolean bl_acme_is_sane(void) +{ + gboolean status; + + /* + * We expect sysfs to be present and mounted at /sys, ina226 and + * tmp435 sensors detected by the system and their appropriate + * drivers loaded and functional. + */ + status = g_file_test("/sys", G_FILE_TEST_IS_DIR); + if (!status) { + sr_err("/sys/ directory not found - sysfs not mounted?"); + return FALSE; + } + + return TRUE; +} + +static void probe_name_path(unsigned int addr, GString *path) +{ + g_string_printf(path, + "/sys/class/i2c-adapter/i2c-1/1-00%02x/name", addr); +} + +/* + * For given address fill buf with the path to appropriate hwmon entry. + */ +static void probe_hwmon_path(unsigned int addr, GString *path) +{ + g_string_printf(path, + "/sys/class/i2c-adapter/i2c-1/1-00%02x/hwmon", addr); +} + +SR_PRIV gboolean bl_acme_detect_probe(unsigned int addr, + int prb_num, const char *prb_name) +{ + gboolean ret = FALSE, status; + char *buf = NULL; + GString *path = g_string_sized_new(64); + GError *err = NULL; + gsize size; + + probe_name_path(addr, path); + status = g_file_get_contents(path->str, &buf, &size, &err); + if (!status) { + sr_dbg("Name for probe %d can't be read: %s", + prb_num, err->message); + g_string_free(path, TRUE); + return ret; + + } + + if (strncmp(buf, prb_name, strlen(prb_name)) == 0) { + /* + * Correct driver registered on this address - but is + * there an actual probe connected? + */ + probe_hwmon_path(addr, path); + status = g_file_test(path->str, G_FILE_TEST_IS_DIR); + if (status) { + /* We have found an ACME probe. */ + ret = TRUE; + } + } + + g_free(buf); + g_string_free(path, TRUE); + + return ret; +} + +static int get_hwmon_index(unsigned int addr) +{ + int status, hwmon; + GString *path = g_string_sized_new(64); + GError *err = NULL; + GDir *dir; + + probe_hwmon_path(addr, path); + dir = g_dir_open(path->str, 0, &err); + if (dir == NULL) { + sr_err("Error opening %s: %s", path->str, err->message); + g_string_free(path, TRUE); + return -1; + } + + g_string_free(path, TRUE); + + /* + * The directory should contain a single file named hwmonX where X + * is the hwmon index. + */ + status = sscanf(g_dir_read_name(dir), "hwmon%d", &hwmon); + g_dir_close(dir); + if (status != 1) { + sr_err("Unable to determine the hwmon entry"); + return -1; + } + + return hwmon; +} + +static void append_channel(struct sr_dev_inst *sdi, + struct sr_channel_group *cg, + int index, int type) +{ + struct channel_priv *cp; + struct dev_context *devc; + struct sr_channel *ch; + char *name; + + devc = sdi->priv; + + switch (type) { + case ENRG_PWR: + name = g_strdup_printf("P%d_ENRG_PWR", index); + break; + case ENRG_CURR: + name = g_strdup_printf("P%d_ENRG_CURR", index); + break; + case ENRG_VOL: + name = g_strdup_printf("P%d_ENRG_VOL", index); + break; + case TEMP_IN: + name = g_strdup_printf("P%d_TEMP_IN", index); + break; + case TEMP_OUT: + name = g_strdup_printf("P%d_TEMP_OUT", index); + break; + default: + sr_err("Bug in the code: invalid channel type!"); + return; + } + + cp = g_malloc0(sizeof(struct channel_priv)); + cp->ch_type = type; + cp->probe = cg->priv; + + ch = sr_channel_new(devc->num_channels++, + SR_CHANNEL_ANALOG, TRUE, name); + g_free(name); + + ch->priv = cp; + cg->channels = g_slist_append(cg->channels, ch); + sdi->channels = g_slist_append(sdi->channels, ch); +} + +SR_PRIV gboolean bl_acme_register_probe(struct sr_dev_inst *sdi, int type, + unsigned int addr, int prb_num) +{ + struct sr_channel_group *cg; + struct channel_group_priv *cgp; + int hwmon; + + /* Obtain the hwmon index. */ + hwmon = get_hwmon_index(addr); + if (hwmon < 0) + return FALSE; + + cg = g_malloc0(sizeof(struct sr_channel_group)); + cgp = g_malloc0(sizeof(struct channel_group_priv)); + cgp->hwmon_num = hwmon; + cg->name = g_strdup_printf("Probe_%d", prb_num); + cg->priv = cgp; + + if (type == PROBE_ENRG) { + append_channel(sdi, cg, prb_num, ENRG_PWR); + append_channel(sdi, cg, prb_num, ENRG_CURR); + append_channel(sdi, cg, prb_num, ENRG_VOL); + } else if (type == PROBE_TEMP) { + append_channel(sdi, cg, prb_num, TEMP_IN); + append_channel(sdi, cg, prb_num, TEMP_OUT); + } else { + sr_err("Bug in the code: invalid probe type!"); + } + + sdi->channel_groups = g_slist_append(sdi->channel_groups, cg); + + return TRUE; +} + +static int channel_to_mq(struct sr_channel *ch) { - const struct sr_dev_inst *sdi; + struct channel_priv *chp; + + chp = ch->priv; + + switch (chp->ch_type) { + case ENRG_PWR: + return SR_MQ_POWER; + case ENRG_CURR: + return SR_MQ_CURRENT; + case ENRG_VOL: + return SR_MQ_VOLTAGE; + case TEMP_IN: + case TEMP_OUT: + return SR_MQ_TEMPERATURE; + default: + return -1; + } +} + +static int channel_to_unit(struct sr_channel *ch) +{ + struct channel_priv *chp; + + chp = ch->priv; + + switch (chp->ch_type) { + case ENRG_PWR: + return SR_UNIT_WATT; + case ENRG_CURR: + return SR_UNIT_AMPERE; + case ENRG_VOL: + return SR_UNIT_VOLT; + case TEMP_IN: + case TEMP_OUT: + return SR_UNIT_CELSIUS; + default: + return -1; + } +} + +/* We need to scale measurements down from the units used by the drivers. */ +static float adjust_data(int val, int type) +{ + switch (type) { + case ENRG_PWR: + return ((float)val) / 1000000.0; + case ENRG_CURR: + case ENRG_VOL: + case TEMP_IN: + case TEMP_OUT: + return ((float)val) / 1000.0; + default: + return 0.0; + } +} + +static float read_sample(struct sr_channel *ch) +{ + struct channel_priv *chp; + char path[64], *file, buf[16]; + ssize_t len; + int fd; + + chp = ch->priv; + + switch (chp->ch_type) { + case ENRG_PWR: file = "power1_input"; break; + case ENRG_CURR: file = "curr1_input"; break; + case ENRG_VOL: file = "in1_input"; break; + case TEMP_IN: file = "temp1_input"; break; + case TEMP_OUT: file = "temp2_input"; break; + default: + sr_err("Bug in the code: invalid channel type!"); + return -1.0; + } + + snprintf(path, sizeof(path), + "/sys/class/hwmon/hwmon%d/%s", + chp->probe->hwmon_num, file); + fd = open(path, O_RDONLY); + if (fd < 0) { + sr_err("Error opening %s: %s", path, strerror(errno)); + ch->enabled = FALSE; + return -1.0; + } + + len = read(fd, buf, sizeof(buf)); + close(fd); + if (len < 0) { + sr_err("error reading from %s: %s", path, strerror(errno)); + ch->enabled = FALSE; + return -1.0; + } + + return adjust_data(strtol(buf, NULL, 10), chp->ch_type); +} + +SR_PRIV int bl_acme_receive_data(int fd, int revents, void *cb_data) +{ + uint32_t cur_time, elapsed_time, diff_time; + int64_t time_to_sleep; + struct sr_datafeed_packet packet, framep; + struct sr_datafeed_analog analog; + struct sr_dev_inst *sdi; + struct sr_channel *ch; struct dev_context *devc; + GSList *chl, chonly; + float valf; (void)fd; + (void)revents; + + sdi = cb_data; + if (!sdi) + return TRUE; - if (!(sdi = cb_data)) + devc = sdi->priv; + if (!devc) return TRUE; - if (!(devc = sdi->priv)) + packet.type = SR_DF_ANALOG; + packet.payload = &analog; + memset(&analog, 0, sizeof(analog)); + analog.data = &valf; + + /* + * Reading from sysfs takes some time - try to + * keep up with samplerate. + */ + if (devc->samples_read) { + cur_time = g_get_monotonic_time(); + diff_time = cur_time - devc->last_sample_fin; + time_to_sleep = G_USEC_PER_SEC / devc->samplerate - diff_time; + if (time_to_sleep > 0) + g_usleep(time_to_sleep); + } + + framep.type = SR_DF_FRAME_BEGIN; + sr_session_send(cb_data, &framep); + + /* + * Due to different units used in each channel we're sending + * samples one-by-one. + */ + for (chl = sdi->channels; chl; chl = chl->next) { + ch = chl->data; + if (!ch->enabled) + continue; + chonly.next = NULL; + chonly.data = ch; + analog.channels = &chonly; + analog.num_samples = 1; + analog.mq = channel_to_mq(chl->data); + analog.unit = channel_to_unit(ch); + + valf = read_sample(ch); + + sr_session_send(cb_data, &packet); + } + + framep.type = SR_DF_FRAME_END; + sr_session_send(cb_data, &framep); + + devc->samples_read++; + if (devc->limit_samples > 0 && + devc->samples_read >= devc->limit_samples) { + sr_info("Requested number of samples reached."); + sdi->driver->dev_acquisition_stop(sdi, cb_data); + devc->last_sample_fin = g_get_monotonic_time(); return TRUE; + } else if (devc->limit_msec > 0) { + cur_time = g_get_monotonic_time(); + elapsed_time = cur_time - devc->start_time; - if (revents == G_IO_IN) { - /* TODO */ + if (elapsed_time >= devc->limit_msec) { + sr_info("Sampling time limit reached."); + sdi->driver->dev_acquisition_stop(sdi, cb_data); + devc->last_sample_fin = g_get_monotonic_time(); + return TRUE; + } } + devc->last_sample_fin = g_get_monotonic_time(); return TRUE; } diff --git a/src/hardware/baylibre-acme/protocol.h b/src/hardware/baylibre-acme/protocol.h index 599fdd9..5252118 100644 --- a/src/hardware/baylibre-acme/protocol.h +++ b/src/hardware/baylibre-acme/protocol.h @@ -22,23 +22,65 @@ #include <stdint.h> #include <glib.h> +#include <unistd.h> /* pipe() */ #include "libsigrok.h" #include "libsigrok-internal.h" #define LOG_PREFIX "baylibre-acme" +/* We support up to 8 energy/temperature probes. */ +#define MAX_PROBES 8 + +/* + * Temperature probes can be connected to the last four ports on the + * ACME cape. When scanning, first look for temperature probes starting + * from this index. + */ +#define TEMP_PRB_START_INDEX 4 + +#define ENRG_PROBE_NAME "ina226" +#define TEMP_PROBE_NAME "tmp435" + +/* For the user we number the probes starting from 1. */ +#define PROBE_NUM(n) ((n)+1) + +enum probe_type { + PROBE_ENRG = 1, + PROBE_TEMP, +}; + +enum channel_type { + ENRG_PWR = 1, + ENRG_CURR, + ENRG_VOL, + TEMP_IN, + TEMP_OUT, +}; + /** Private, per-device-instance driver context. */ struct dev_context { - /* Model-specific information */ + uint64_t samplerate; + uint64_t limit_samples; + uint64_t limit_msec; - /* Acquisition settings */ + uint32_t num_channels; + uint64_t samples_read; + int64_t start_time; + int64_t last_sample_fin; + int pipe_fds[2]; + GIOChannel *channel; +}; - /* Operational state */ +SR_PRIV uint8_t bl_acme_get_enrg_addr(int index); +SR_PRIV uint8_t bl_acme_get_temp_addr(int index); - /* Temporary state across callbacks */ +SR_PRIV gboolean bl_acme_is_sane(void); -}; +SR_PRIV gboolean bl_acme_detect_probe(unsigned int addr, + int prb_num, const char *prb_name); +SR_PRIV gboolean bl_acme_register_probe(struct sr_dev_inst *sdi, int type, + unsigned int addr, int prb_num); -SR_PRIV int baylibre_acme_receive_data(int fd, int revents, void *cb_data); +SR_PRIV int bl_acme_receive_data(int fd, int revents, void *cb_data); #endif -- 2.1.4 ------------------------------------------------------------------------------ Dive into the World of Parallel Programming. The Go Parallel Website, sponsored by Intel and developed in partnership with Slashdot Media, is your hub for all things parallel software development, from weekly thought leadership blogs to news, videos, case studies, tutorials and more. Take a look and join the conversation now. http://goparallel.sourceforge.net/ _______________________________________________ sigrok-devel mailing list sigrok-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/sigrok-devel