scpi: Synchronize read, write and write+read operations.
This ensures that SCPI read/write/write+read operations are thread safe. F.e.: If a write operation expects a return value (in other words: a read operation), it is not allowed to be interrupted by another write operation. To simplify things, the SCPI helper functions are moved from scpi/helpers.c to scpi/scpi.c and also are renamed to fit the naming scheme. libgpib in particular will abort the program execution in case of concurrent operations.
This commit is contained in:
parent
1c5adc5ff7
commit
fd20e59caa
|
@ -108,7 +108,6 @@ libsigrok_la_SOURCES += \
|
||||||
libsigrok_la_SOURCES += \
|
libsigrok_la_SOURCES += \
|
||||||
src/scpi.h \
|
src/scpi.h \
|
||||||
src/scpi/scpi.c \
|
src/scpi/scpi.c \
|
||||||
src/scpi/helpers.c \
|
|
||||||
src/scpi/scpi_tcp.c
|
src/scpi/scpi_tcp.c
|
||||||
if NEED_RPC
|
if NEED_RPC
|
||||||
libsigrok_la_SOURCES += \
|
libsigrok_la_SOURCES += \
|
||||||
|
|
|
@ -99,6 +99,7 @@ struct sr_scpi_dev_inst {
|
||||||
void *priv;
|
void *priv;
|
||||||
/* Only used for quirk workarounds, notably the Rigol DS1000 series. */
|
/* Only used for quirk workarounds, notably the Rigol DS1000 series. */
|
||||||
uint64_t firmware_version;
|
uint64_t firmware_version;
|
||||||
|
GMutex scpi_mutex;
|
||||||
};
|
};
|
||||||
|
|
||||||
SR_PRIV GSList *sr_scpi_scan(struct drv_context *drvc, GSList *options,
|
SR_PRIV GSList *sr_scpi_scan(struct drv_context *drvc, GSList *options,
|
||||||
|
@ -148,10 +149,10 @@ SR_PRIV int sr_scpi_get_hw_id(struct sr_scpi_dev_inst *scpi,
|
||||||
SR_PRIV void sr_scpi_hw_info_free(struct sr_scpi_hw_info *hw_info);
|
SR_PRIV void sr_scpi_hw_info_free(struct sr_scpi_hw_info *hw_info);
|
||||||
|
|
||||||
SR_PRIV const char *sr_vendor_alias(const char *raw_vendor);
|
SR_PRIV const char *sr_vendor_alias(const char *raw_vendor);
|
||||||
SR_PRIV const char *scpi_cmd_get(const struct scpi_command *cmdtable, int command);
|
SR_PRIV const char *sr_scpi_cmd_get(const struct scpi_command *cmdtable, int command);
|
||||||
SR_PRIV int scpi_cmd(const struct sr_dev_inst *sdi,
|
SR_PRIV int sr_scpi_cmd(const struct sr_dev_inst *sdi,
|
||||||
const struct scpi_command *cmdtable, int command, ...);
|
const struct scpi_command *cmdtable, int command, ...);
|
||||||
SR_PRIV int scpi_cmd_resp(const struct sr_dev_inst *sdi,
|
SR_PRIV int sr_scpi_cmd_resp(const struct sr_dev_inst *sdi,
|
||||||
const struct scpi_command *cmdtable,
|
const struct scpi_command *cmdtable,
|
||||||
GVariant **gvar, const GVariantType *gvtype, int command, ...);
|
GVariant **gvar, const GVariantType *gvtype, int command, ...);
|
||||||
|
|
||||||
|
|
|
@ -1,136 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of the libsigrok project.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2015 Bert Vermeulen <bert@biot.com>
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <config.h>
|
|
||||||
#include <strings.h>
|
|
||||||
#include <libsigrok/libsigrok.h>
|
|
||||||
#include "libsigrok-internal.h"
|
|
||||||
#include "scpi.h"
|
|
||||||
|
|
||||||
#define LOG_PREFIX "scpi/helpers"
|
|
||||||
|
|
||||||
static const char *scpi_vendors[][2] = {
|
|
||||||
{ "HEWLETT-PACKARD", "HP" },
|
|
||||||
{ "Agilent Technologies", "Agilent" },
|
|
||||||
{ "RIGOL TECHNOLOGIES", "Rigol" },
|
|
||||||
{ "PHILIPS", "Philips" },
|
|
||||||
{ "CHROMA", "Chroma" },
|
|
||||||
{ "Chroma ATE", "Chroma" },
|
|
||||||
};
|
|
||||||
|
|
||||||
SR_PRIV const char *sr_vendor_alias(const char *raw_vendor)
|
|
||||||
{
|
|
||||||
unsigned int i;
|
|
||||||
|
|
||||||
for (i = 0; i < ARRAY_SIZE(scpi_vendors); i++) {
|
|
||||||
if (!g_ascii_strcasecmp(raw_vendor, scpi_vendors[i][0]))
|
|
||||||
return scpi_vendors[i][1];
|
|
||||||
}
|
|
||||||
|
|
||||||
return raw_vendor;
|
|
||||||
}
|
|
||||||
|
|
||||||
SR_PRIV const char *scpi_cmd_get(const struct scpi_command *cmdtable, int command)
|
|
||||||
{
|
|
||||||
unsigned int i;
|
|
||||||
const char *cmd;
|
|
||||||
|
|
||||||
if (!cmdtable)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
cmd = NULL;
|
|
||||||
for (i = 0; cmdtable[i].string; i++) {
|
|
||||||
if (cmdtable[i].command == command) {
|
|
||||||
cmd = cmdtable[i].string;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return cmd;
|
|
||||||
}
|
|
||||||
|
|
||||||
SR_PRIV int scpi_cmd(const struct sr_dev_inst *sdi, const struct scpi_command *cmdtable,
|
|
||||||
int command, ...)
|
|
||||||
{
|
|
||||||
struct sr_scpi_dev_inst *scpi;
|
|
||||||
va_list args;
|
|
||||||
int ret;
|
|
||||||
const char *cmd;
|
|
||||||
|
|
||||||
if (!(cmd = scpi_cmd_get(cmdtable, command))) {
|
|
||||||
/* Device does not implement this command, that's OK. */
|
|
||||||
return SR_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
scpi = sdi->conn;
|
|
||||||
va_start(args, command);
|
|
||||||
ret = sr_scpi_send_variadic(scpi, cmd, args);
|
|
||||||
va_end(args);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
SR_PRIV int scpi_cmd_resp(const struct sr_dev_inst *sdi, const struct scpi_command *cmdtable,
|
|
||||||
GVariant **gvar, const GVariantType *gvtype, int command, ...)
|
|
||||||
{
|
|
||||||
struct sr_scpi_dev_inst *scpi;
|
|
||||||
va_list args;
|
|
||||||
double d;
|
|
||||||
int ret;
|
|
||||||
char *s;
|
|
||||||
const char *cmd;
|
|
||||||
|
|
||||||
if (!(cmd = scpi_cmd_get(cmdtable, command))) {
|
|
||||||
/* Device does not implement this command. */
|
|
||||||
return SR_ERR_NA;
|
|
||||||
}
|
|
||||||
|
|
||||||
scpi = sdi->conn;
|
|
||||||
va_start(args, command);
|
|
||||||
ret = sr_scpi_send_variadic(scpi, cmd, args);
|
|
||||||
va_end(args);
|
|
||||||
if (ret != SR_OK)
|
|
||||||
return ret;
|
|
||||||
|
|
||||||
/* Straight SCPI getters to GVariant types. */
|
|
||||||
if (g_variant_type_equal(gvtype, G_VARIANT_TYPE_BOOLEAN)) {
|
|
||||||
if ((ret = sr_scpi_get_string(scpi, NULL, &s)) != SR_OK)
|
|
||||||
return ret;
|
|
||||||
if (!g_ascii_strcasecmp(s, "ON") || !g_ascii_strcasecmp(s, "1")
|
|
||||||
|| !g_ascii_strcasecmp(s, "YES"))
|
|
||||||
*gvar = g_variant_new_boolean(TRUE);
|
|
||||||
else if (!g_ascii_strcasecmp(s, "OFF") || !g_ascii_strcasecmp(s, "0")
|
|
||||||
|| !g_ascii_strcasecmp(s, "NO"))
|
|
||||||
*gvar = g_variant_new_boolean(FALSE);
|
|
||||||
else
|
|
||||||
ret = SR_ERR;
|
|
||||||
g_free(s);
|
|
||||||
} else if (g_variant_type_equal(gvtype, G_VARIANT_TYPE_DOUBLE)) {
|
|
||||||
if ((ret = sr_scpi_get_double(scpi, NULL, &d)) == SR_OK)
|
|
||||||
*gvar = g_variant_new_double(d);
|
|
||||||
} else if (g_variant_type_equal(gvtype, G_VARIANT_TYPE_STRING)) {
|
|
||||||
if ((ret = sr_scpi_get_string(scpi, NULL, &s)) == SR_OK)
|
|
||||||
*gvar = g_variant_new_string(s);
|
|
||||||
} else {
|
|
||||||
sr_err("Unable to convert to desired GVariant type.");
|
|
||||||
ret = SR_ERR_NA;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
450
src/scpi/scpi.c
450
src/scpi/scpi.c
|
@ -2,6 +2,7 @@
|
||||||
* This file is part of the libsigrok project.
|
* This file is part of the libsigrok project.
|
||||||
*
|
*
|
||||||
* Copyright (C) 2013 poljar (Damir Jelić) <poljarinho@gmail.com>
|
* Copyright (C) 2013 poljar (Damir Jelić) <poljarinho@gmail.com>
|
||||||
|
* Copyright (C) 2015 Bert Vermeulen <bert@biot.com>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -29,6 +30,15 @@
|
||||||
#define SCPI_READ_RETRIES 100
|
#define SCPI_READ_RETRIES 100
|
||||||
#define SCPI_READ_RETRY_TIMEOUT_US (10 * 1000)
|
#define SCPI_READ_RETRY_TIMEOUT_US (10 * 1000)
|
||||||
|
|
||||||
|
static const char *scpi_vendors[][2] = {
|
||||||
|
{ "HEWLETT-PACKARD", "HP" },
|
||||||
|
{ "Agilent Technologies", "Agilent" },
|
||||||
|
{ "RIGOL TECHNOLOGIES", "Rigol" },
|
||||||
|
{ "PHILIPS", "Philips" },
|
||||||
|
{ "CHROMA", "Chroma" },
|
||||||
|
{ "Chroma ATE", "Chroma" },
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse a string representation of a boolean-like value into a gboolean.
|
* Parse a string representation of a boolean-like value into a gboolean.
|
||||||
* Similar to sr_parse_boolstring but rejects strings which do not represent
|
* Similar to sr_parse_boolstring but rejects strings which do not represent
|
||||||
|
@ -122,6 +132,178 @@ static struct sr_dev_inst *sr_scpi_scan_resource(struct drv_context *drvc,
|
||||||
return sdi;
|
return sdi;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a SCPI command with a variadic argument list without mutex.
|
||||||
|
*
|
||||||
|
* @param scpi Previously initialized SCPI device structure.
|
||||||
|
* @param format Format string.
|
||||||
|
* @param args Argument list.
|
||||||
|
*
|
||||||
|
* @return SR_OK on success, SR_ERR on failure.
|
||||||
|
*/
|
||||||
|
static int scpi_send_variadic(struct sr_scpi_dev_inst *scpi,
|
||||||
|
const char *format, va_list args)
|
||||||
|
{
|
||||||
|
va_list args_copy;
|
||||||
|
char *buf;
|
||||||
|
int len, ret;
|
||||||
|
|
||||||
|
/* Get length of buffer required. */
|
||||||
|
va_copy(args_copy, args);
|
||||||
|
len = vsnprintf(NULL, 0, format, args_copy);
|
||||||
|
va_end(args_copy);
|
||||||
|
|
||||||
|
/* Allocate buffer and write out command. */
|
||||||
|
buf = g_malloc0(len + 2);
|
||||||
|
vsprintf(buf, format, args);
|
||||||
|
if (buf[len - 1] != '\n')
|
||||||
|
buf[len] = '\n';
|
||||||
|
|
||||||
|
/* Send command. */
|
||||||
|
ret = scpi->send(scpi->priv, buf);
|
||||||
|
|
||||||
|
/* Free command buffer. */
|
||||||
|
g_free(buf);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a SCPI command without mutex.
|
||||||
|
*
|
||||||
|
* @param scpi Previously initialized SCPI device structure.
|
||||||
|
* @param format Format string, to be followed by any necessary arguments.
|
||||||
|
*
|
||||||
|
* @return SR_OK on success, SR_ERR on failure.
|
||||||
|
*/
|
||||||
|
static int scpi_send(struct sr_scpi_dev_inst *scpi, const char *format,
|
||||||
|
va_list args)
|
||||||
|
{
|
||||||
|
return scpi_send_variadic(scpi, format, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send data to SCPI device without mutex.
|
||||||
|
*
|
||||||
|
* TODO: This is only implemented in TcpRaw, but never used.
|
||||||
|
* TODO: Use Mutex at all?
|
||||||
|
*
|
||||||
|
* @param scpi Previously initialised SCPI device structure.
|
||||||
|
* @param buf Buffer with data to send.
|
||||||
|
* @param len Number of bytes to send.
|
||||||
|
*
|
||||||
|
* @return Number of bytes read, or SR_ERR upon failure.
|
||||||
|
*/
|
||||||
|
static int scpi_write_data(struct sr_scpi_dev_inst *scpi, char *buf, int maxlen)
|
||||||
|
{
|
||||||
|
return scpi->write_data(scpi->priv, buf, maxlen);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read part of a response from SCPI device without mutex.
|
||||||
|
*
|
||||||
|
* @param scpi Previously initialised SCPI device structure.
|
||||||
|
* @param buf Buffer to store result.
|
||||||
|
* @param maxlen Maximum number of bytes to read.
|
||||||
|
*
|
||||||
|
* @return Number of bytes read, or SR_ERR upon failure.
|
||||||
|
*/
|
||||||
|
static int scpi_read_data(struct sr_scpi_dev_inst *scpi, char *buf, int maxlen)
|
||||||
|
{
|
||||||
|
return scpi->read_data(scpi->priv, buf, maxlen);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do a non-blocking read of up to the allocated length, and
|
||||||
|
* check if a timeout has occured, without mutex.
|
||||||
|
*
|
||||||
|
* @param scpi Previously initialised SCPI device structure.
|
||||||
|
* @param response Buffer to which the response is appended.
|
||||||
|
* @param abs_timeout_us Absolute timeout in microseconds
|
||||||
|
*
|
||||||
|
* @return read length on success, SR_ERR* on failure.
|
||||||
|
*/
|
||||||
|
static int scpi_read_response(struct sr_scpi_dev_inst *scpi,
|
||||||
|
GString *response, gint64 abs_timeout_us)
|
||||||
|
{
|
||||||
|
int len, space;
|
||||||
|
|
||||||
|
space = response->allocated_len - response->len;
|
||||||
|
len = scpi->read_data(scpi->priv, &response->str[response->len], space);
|
||||||
|
|
||||||
|
if (len < 0) {
|
||||||
|
sr_err("Incompletely read SCPI response.");
|
||||||
|
return SR_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len > 0) {
|
||||||
|
g_string_set_size(response, response->len + len);
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_get_monotonic_time() > abs_timeout_us) {
|
||||||
|
sr_err("Timed out waiting for SCPI response.");
|
||||||
|
return SR_ERR_TIMEOUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a SCPI command, receive the reply and store the reply in
|
||||||
|
* scpi_response, without mutex.
|
||||||
|
*
|
||||||
|
* @param scpi Previously initialised SCPI device structure.
|
||||||
|
* @param command The SCPI command to send to the device.
|
||||||
|
* @param scpi_response Pointer where to store the SCPI response.
|
||||||
|
*
|
||||||
|
* @return SR_OK on success, SR_ERR on failure.
|
||||||
|
*/
|
||||||
|
static int scpi_get_data(struct sr_scpi_dev_inst *scpi,
|
||||||
|
const char *command, GString **scpi_response)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
GString *response;
|
||||||
|
int space;
|
||||||
|
gint64 timeout;
|
||||||
|
va_list empty_va_list;
|
||||||
|
|
||||||
|
/* Optionally send caller provided command. */
|
||||||
|
if (command) {
|
||||||
|
if (scpi_send(scpi, command, empty_va_list) != SR_OK)
|
||||||
|
return SR_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Initiate SCPI read operation. */
|
||||||
|
if (sr_scpi_read_begin(scpi) != SR_OK)
|
||||||
|
return SR_ERR;
|
||||||
|
|
||||||
|
/* Keep reading until completion or until timeout. */
|
||||||
|
timeout = g_get_monotonic_time() + scpi->read_timeout_us;
|
||||||
|
|
||||||
|
response = *scpi_response;
|
||||||
|
|
||||||
|
while (!sr_scpi_read_complete(scpi)) {
|
||||||
|
/* Resize the buffer when free space drops below a threshold. */
|
||||||
|
space = response->allocated_len - response->len;
|
||||||
|
if (space < 128) {
|
||||||
|
int oldlen = response->len;
|
||||||
|
g_string_set_size(response, oldlen + 1024);
|
||||||
|
g_string_set_size(response, oldlen);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read another chunk of the response. */
|
||||||
|
ret = scpi_read_response(scpi, response, timeout);
|
||||||
|
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
if (ret > 0)
|
||||||
|
timeout = g_get_monotonic_time() + scpi->read_timeout_us;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
SR_PRIV GSList *sr_scpi_scan(struct drv_context *drvc, GSList *options,
|
SR_PRIV GSList *sr_scpi_scan(struct drv_context *drvc, GSList *options,
|
||||||
struct sr_dev_inst *(*probe_device)(struct sr_scpi_dev_inst *scpi))
|
struct sr_dev_inst *(*probe_device)(struct sr_scpi_dev_inst *scpi))
|
||||||
{
|
{
|
||||||
|
@ -214,6 +396,8 @@ SR_PRIV struct sr_scpi_dev_inst *scpi_dev_inst_new(struct drv_context *drvc,
|
||||||
*/
|
*/
|
||||||
SR_PRIV int sr_scpi_open(struct sr_scpi_dev_inst *scpi)
|
SR_PRIV int sr_scpi_open(struct sr_scpi_dev_inst *scpi)
|
||||||
{
|
{
|
||||||
|
g_mutex_init(&scpi->scpi_mutex);
|
||||||
|
|
||||||
return scpi->open(scpi);
|
return scpi->open(scpi);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -268,7 +452,9 @@ SR_PRIV int sr_scpi_send(struct sr_scpi_dev_inst *scpi,
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
va_start(args, format);
|
va_start(args, format);
|
||||||
ret = sr_scpi_send_variadic(scpi, format, args);
|
g_mutex_lock(&scpi->scpi_mutex);
|
||||||
|
ret = scpi_send_variadic(scpi, format, args);
|
||||||
|
g_mutex_unlock(&scpi->scpi_mutex);
|
||||||
va_end(args);
|
va_end(args);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -286,26 +472,11 @@ SR_PRIV int sr_scpi_send(struct sr_scpi_dev_inst *scpi,
|
||||||
SR_PRIV int sr_scpi_send_variadic(struct sr_scpi_dev_inst *scpi,
|
SR_PRIV int sr_scpi_send_variadic(struct sr_scpi_dev_inst *scpi,
|
||||||
const char *format, va_list args)
|
const char *format, va_list args)
|
||||||
{
|
{
|
||||||
va_list args_copy;
|
int ret;
|
||||||
char *buf;
|
|
||||||
int len, ret;
|
|
||||||
|
|
||||||
/* Get length of buffer required. */
|
g_mutex_lock(&scpi->scpi_mutex);
|
||||||
va_copy(args_copy, args);
|
ret = scpi_send_variadic(scpi, format, args);
|
||||||
len = vsnprintf(NULL, 0, format, args_copy);
|
g_mutex_unlock(&scpi->scpi_mutex);
|
||||||
va_end(args_copy);
|
|
||||||
|
|
||||||
/* Allocate buffer and write out command. */
|
|
||||||
buf = g_malloc0(len + 2);
|
|
||||||
vsprintf(buf, format, args);
|
|
||||||
if (buf[len - 1] != '\n')
|
|
||||||
buf[len] = '\n';
|
|
||||||
|
|
||||||
/* Send command. */
|
|
||||||
ret = scpi->send(scpi->priv, buf);
|
|
||||||
|
|
||||||
/* Free command buffer. */
|
|
||||||
g_free(buf);
|
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -334,12 +505,21 @@ SR_PRIV int sr_scpi_read_begin(struct sr_scpi_dev_inst *scpi)
|
||||||
SR_PRIV int sr_scpi_read_data(struct sr_scpi_dev_inst *scpi,
|
SR_PRIV int sr_scpi_read_data(struct sr_scpi_dev_inst *scpi,
|
||||||
char *buf, int maxlen)
|
char *buf, int maxlen)
|
||||||
{
|
{
|
||||||
return scpi->read_data(scpi->priv, buf, maxlen);
|
int ret;
|
||||||
|
|
||||||
|
g_mutex_lock(&scpi->scpi_mutex);
|
||||||
|
ret = scpi_read_data(scpi, buf, maxlen);
|
||||||
|
g_mutex_unlock(&scpi->scpi_mutex);
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send data to SCPI device.
|
* Send data to SCPI device.
|
||||||
*
|
*
|
||||||
|
* TODO: This is only implemented in TcpRaw, but never used.
|
||||||
|
* TODO: Use Mutex at all?
|
||||||
|
*
|
||||||
* @param scpi Previously initialised SCPI device structure.
|
* @param scpi Previously initialised SCPI device structure.
|
||||||
* @param buf Buffer with data to send.
|
* @param buf Buffer with data to send.
|
||||||
* @param len Number of bytes to send.
|
* @param len Number of bytes to send.
|
||||||
|
@ -349,7 +529,13 @@ SR_PRIV int sr_scpi_read_data(struct sr_scpi_dev_inst *scpi,
|
||||||
SR_PRIV int sr_scpi_write_data(struct sr_scpi_dev_inst *scpi,
|
SR_PRIV int sr_scpi_write_data(struct sr_scpi_dev_inst *scpi,
|
||||||
char *buf, int maxlen)
|
char *buf, int maxlen)
|
||||||
{
|
{
|
||||||
return scpi->write_data(scpi->priv, buf, maxlen);
|
int ret;
|
||||||
|
|
||||||
|
g_mutex_lock(&scpi->scpi_mutex);
|
||||||
|
ret = scpi_write_data(scpi, buf, maxlen);
|
||||||
|
g_mutex_unlock(&scpi->scpi_mutex);
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -373,7 +559,14 @@ SR_PRIV int sr_scpi_read_complete(struct sr_scpi_dev_inst *scpi)
|
||||||
*/
|
*/
|
||||||
SR_PRIV int sr_scpi_close(struct sr_scpi_dev_inst *scpi)
|
SR_PRIV int sr_scpi_close(struct sr_scpi_dev_inst *scpi)
|
||||||
{
|
{
|
||||||
return scpi->close(scpi);
|
int ret;
|
||||||
|
|
||||||
|
g_mutex_lock(&scpi->scpi_mutex);
|
||||||
|
ret = scpi->close(scpi);
|
||||||
|
g_mutex_unlock(&scpi->scpi_mutex);
|
||||||
|
g_mutex_clear(&scpi->scpi_mutex);
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -442,71 +635,25 @@ SR_PRIV int sr_scpi_get_string(struct sr_scpi_dev_inst *scpi,
|
||||||
SR_PRIV int sr_scpi_read_response(struct sr_scpi_dev_inst *scpi,
|
SR_PRIV int sr_scpi_read_response(struct sr_scpi_dev_inst *scpi,
|
||||||
GString *response, gint64 abs_timeout_us)
|
GString *response, gint64 abs_timeout_us)
|
||||||
{
|
{
|
||||||
int len, space;
|
int ret;
|
||||||
|
|
||||||
space = response->allocated_len - response->len;
|
g_mutex_lock(&scpi->scpi_mutex);
|
||||||
len = sr_scpi_read_data(scpi, &response->str[response->len], space);
|
ret = scpi_read_response(scpi, response, abs_timeout_us);
|
||||||
|
g_mutex_unlock(&scpi->scpi_mutex);
|
||||||
|
|
||||||
if (len < 0) {
|
return ret;
|
||||||
sr_err("Incompletely read SCPI response.");
|
|
||||||
return SR_ERR;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (len > 0) {
|
|
||||||
g_string_set_size(response, response->len + len);
|
|
||||||
return len;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (g_get_monotonic_time() > abs_timeout_us) {
|
|
||||||
sr_err("Timed out waiting for SCPI response.");
|
|
||||||
return SR_ERR_TIMEOUT;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SR_PRIV int sr_scpi_get_data(struct sr_scpi_dev_inst *scpi,
|
SR_PRIV int sr_scpi_get_data(struct sr_scpi_dev_inst *scpi,
|
||||||
const char *command, GString **scpi_response)
|
const char *command, GString **scpi_response)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
GString *response;
|
|
||||||
int space;
|
|
||||||
gint64 timeout;
|
|
||||||
|
|
||||||
/* Optionally send caller provided command. */
|
g_mutex_lock(&scpi->scpi_mutex);
|
||||||
if (command) {
|
ret = scpi_get_data(scpi, command, scpi_response);
|
||||||
if (sr_scpi_send(scpi, command) != SR_OK)
|
g_mutex_unlock(&scpi->scpi_mutex);
|
||||||
return SR_ERR;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Initiate SCPI read operation. */
|
return ret;
|
||||||
if (sr_scpi_read_begin(scpi) != SR_OK)
|
|
||||||
return SR_ERR;
|
|
||||||
|
|
||||||
/* Keep reading until completion or until timeout. */
|
|
||||||
timeout = g_get_monotonic_time() + scpi->read_timeout_us;
|
|
||||||
|
|
||||||
response = *scpi_response;
|
|
||||||
|
|
||||||
while (!sr_scpi_read_complete(scpi)) {
|
|
||||||
/* Resize the buffer when free space drops below a threshold. */
|
|
||||||
space = response->allocated_len - response->len;
|
|
||||||
if (space < 128) {
|
|
||||||
int oldlen = response->len;
|
|
||||||
g_string_set_size(response, oldlen + 1024);
|
|
||||||
g_string_set_size(response, oldlen);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Read another chunk of the response. */
|
|
||||||
ret = sr_scpi_read_response(scpi, response, timeout);
|
|
||||||
|
|
||||||
if (ret < 0)
|
|
||||||
return ret;
|
|
||||||
if (ret > 0)
|
|
||||||
timeout = g_get_monotonic_time() + scpi->read_timeout_us;
|
|
||||||
}
|
|
||||||
|
|
||||||
return SR_OK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -796,13 +943,20 @@ SR_PRIV int sr_scpi_get_block(struct sr_scpi_dev_inst *scpi,
|
||||||
long llen;
|
long llen;
|
||||||
long datalen;
|
long datalen;
|
||||||
gint64 timeout;
|
gint64 timeout;
|
||||||
|
va_list empty_va_list;
|
||||||
|
|
||||||
|
g_mutex_lock(&scpi->scpi_mutex);
|
||||||
|
|
||||||
if (command)
|
if (command)
|
||||||
if (sr_scpi_send(scpi, command) != SR_OK)
|
if (scpi_send(scpi, command, empty_va_list) != SR_OK) {
|
||||||
|
g_mutex_unlock(&scpi->scpi_mutex);
|
||||||
return SR_ERR;
|
return SR_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
if (sr_scpi_read_begin(scpi) != SR_OK)
|
if (sr_scpi_read_begin(scpi) != SR_OK) {
|
||||||
|
g_mutex_unlock(&scpi->scpi_mutex);
|
||||||
return SR_ERR;
|
return SR_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Assume an initial maximum length, optionally gets adjusted below.
|
* Assume an initial maximum length, optionally gets adjusted below.
|
||||||
|
@ -816,8 +970,9 @@ SR_PRIV int sr_scpi_get_block(struct sr_scpi_dev_inst *scpi,
|
||||||
|
|
||||||
/* Get (the first chunk of) the response. */
|
/* Get (the first chunk of) the response. */
|
||||||
while (response->len < 2) {
|
while (response->len < 2) {
|
||||||
ret = sr_scpi_read_response(scpi, response, timeout);
|
ret = scpi_read_response(scpi, response, timeout);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
|
g_mutex_unlock(&scpi->scpi_mutex);
|
||||||
g_string_free(response, TRUE);
|
g_string_free(response, TRUE);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -835,6 +990,7 @@ SR_PRIV int sr_scpi_get_block(struct sr_scpi_dev_inst *scpi,
|
||||||
* the input buffer, leaving just the data bytes.
|
* the input buffer, leaving just the data bytes.
|
||||||
*/
|
*/
|
||||||
if (response->str[0] != '#') {
|
if (response->str[0] != '#') {
|
||||||
|
g_mutex_unlock(&scpi->scpi_mutex);
|
||||||
g_string_free(response, TRUE);
|
g_string_free(response, TRUE);
|
||||||
return SR_ERR_DATA;
|
return SR_ERR_DATA;
|
||||||
}
|
}
|
||||||
|
@ -842,13 +998,15 @@ SR_PRIV int sr_scpi_get_block(struct sr_scpi_dev_inst *scpi,
|
||||||
buf[1] = '\0';
|
buf[1] = '\0';
|
||||||
ret = sr_atol(buf, &llen);
|
ret = sr_atol(buf, &llen);
|
||||||
if ((ret != SR_OK) || (llen == 0)) {
|
if ((ret != SR_OK) || (llen == 0)) {
|
||||||
|
g_mutex_unlock(&scpi->scpi_mutex);
|
||||||
g_string_free(response, TRUE);
|
g_string_free(response, TRUE);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (response->len < (unsigned long)(2 + llen)) {
|
while (response->len < (unsigned long)(2 + llen)) {
|
||||||
ret = sr_scpi_read_response(scpi, response, timeout);
|
ret = scpi_read_response(scpi, response, timeout);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
|
g_mutex_unlock(&scpi->scpi_mutex);
|
||||||
g_string_free(response, TRUE);
|
g_string_free(response, TRUE);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -858,6 +1016,7 @@ SR_PRIV int sr_scpi_get_block(struct sr_scpi_dev_inst *scpi,
|
||||||
buf[llen] = '\0';
|
buf[llen] = '\0';
|
||||||
ret = sr_atol(buf, &datalen);
|
ret = sr_atol(buf, &datalen);
|
||||||
if ((ret != SR_OK) || (datalen == 0)) {
|
if ((ret != SR_OK) || (datalen == 0)) {
|
||||||
|
g_mutex_unlock(&scpi->scpi_mutex);
|
||||||
g_string_free(response, TRUE);
|
g_string_free(response, TRUE);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -875,8 +1034,9 @@ SR_PRIV int sr_scpi_get_block(struct sr_scpi_dev_inst *scpi,
|
||||||
}
|
}
|
||||||
|
|
||||||
while (response->len < (unsigned long)(datalen)) {
|
while (response->len < (unsigned long)(datalen)) {
|
||||||
ret = sr_scpi_read_response(scpi, response, timeout);
|
ret = scpi_read_response(scpi, response, timeout);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
|
g_mutex_unlock(&scpi->scpi_mutex);
|
||||||
g_string_free(response, TRUE);
|
g_string_free(response, TRUE);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -884,6 +1044,8 @@ SR_PRIV int sr_scpi_get_block(struct sr_scpi_dev_inst *scpi,
|
||||||
timeout = g_get_monotonic_time() + scpi->read_timeout_us;
|
timeout = g_get_monotonic_time() + scpi->read_timeout_us;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
g_mutex_unlock(&scpi->scpi_mutex);
|
||||||
|
|
||||||
/* Convert received data to byte array. */
|
/* Convert received data to byte array. */
|
||||||
*scpi_response = g_byte_array_new_take(
|
*scpi_response = g_byte_array_new_take(
|
||||||
(guint8*)g_string_free(response, FALSE), datalen);
|
(guint8*)g_string_free(response, FALSE), datalen);
|
||||||
|
@ -971,3 +1133,125 @@ SR_PRIV void sr_scpi_hw_info_free(struct sr_scpi_hw_info *hw_info)
|
||||||
g_free(hw_info->firmware_version);
|
g_free(hw_info->firmware_version);
|
||||||
g_free(hw_info);
|
g_free(hw_info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SR_PRIV const char *sr_vendor_alias(const char *raw_vendor)
|
||||||
|
{
|
||||||
|
unsigned int i;
|
||||||
|
|
||||||
|
for (i = 0; i < ARRAY_SIZE(scpi_vendors); i++) {
|
||||||
|
if (!g_ascii_strcasecmp(raw_vendor, scpi_vendors[i][0]))
|
||||||
|
return scpi_vendors[i][1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return raw_vendor;
|
||||||
|
}
|
||||||
|
|
||||||
|
SR_PRIV const char *sr_scpi_cmd_get(const struct scpi_command *cmdtable, int command)
|
||||||
|
{
|
||||||
|
unsigned int i;
|
||||||
|
const char *cmd;
|
||||||
|
|
||||||
|
if (!cmdtable)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
cmd = NULL;
|
||||||
|
for (i = 0; cmdtable[i].string; i++) {
|
||||||
|
if (cmdtable[i].command == command) {
|
||||||
|
cmd = cmdtable[i].string;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
SR_PRIV int sr_scpi_cmd(const struct sr_dev_inst *sdi, const struct scpi_command *cmdtable,
|
||||||
|
int command, ...)
|
||||||
|
{
|
||||||
|
struct sr_scpi_dev_inst *scpi;
|
||||||
|
va_list args;
|
||||||
|
int ret;
|
||||||
|
const char *cmd;
|
||||||
|
|
||||||
|
if (!(cmd = sr_scpi_cmd_get(cmdtable, command))) {
|
||||||
|
/* Device does not implement this command, that's OK. */
|
||||||
|
return SR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
scpi = sdi->conn;
|
||||||
|
va_start(args, command);
|
||||||
|
ret = sr_scpi_send_variadic(scpi, cmd, args);
|
||||||
|
va_end(args);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
SR_PRIV int sr_scpi_cmd_resp(const struct sr_dev_inst *sdi,
|
||||||
|
const struct scpi_command *cmdtable,
|
||||||
|
GVariant **gvar, const GVariantType *gvtype, int command, ...)
|
||||||
|
{
|
||||||
|
struct sr_scpi_dev_inst *scpi;
|
||||||
|
va_list args;
|
||||||
|
const char *cmd;
|
||||||
|
GString *response;
|
||||||
|
char *s;
|
||||||
|
gboolean b;
|
||||||
|
double d;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
scpi = sdi->conn;
|
||||||
|
|
||||||
|
if (!(cmd = sr_scpi_cmd_get(cmdtable, command))) {
|
||||||
|
/* Device does not implement this command. */
|
||||||
|
return SR_ERR_NA;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_mutex_lock(&scpi->scpi_mutex);
|
||||||
|
|
||||||
|
va_start(args, command);
|
||||||
|
ret = scpi_send_variadic(scpi, cmd, args);
|
||||||
|
va_end(args);
|
||||||
|
if (ret != SR_OK) {
|
||||||
|
g_mutex_unlock(&scpi->scpi_mutex);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
response = g_string_sized_new(1024);
|
||||||
|
ret = scpi_get_data(scpi, NULL, &response);
|
||||||
|
if (ret != SR_OK) {
|
||||||
|
g_mutex_unlock(&scpi->scpi_mutex);
|
||||||
|
if (response)
|
||||||
|
g_string_free(response, TRUE);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_mutex_unlock(&scpi->scpi_mutex);
|
||||||
|
|
||||||
|
/* Get rid of trailing linefeed if present */
|
||||||
|
if (response->len >= 1 && response->str[response->len - 1] == '\n')
|
||||||
|
g_string_truncate(response, response->len - 1);
|
||||||
|
|
||||||
|
/* Get rid of trailing carriage return if present */
|
||||||
|
if (response->len >= 1 && response->str[response->len - 1] == '\r')
|
||||||
|
g_string_truncate(response, response->len - 1);
|
||||||
|
|
||||||
|
s = g_string_free(response, FALSE);
|
||||||
|
|
||||||
|
ret = SR_OK;
|
||||||
|
if (g_variant_type_equal(gvtype, G_VARIANT_TYPE_BOOLEAN)) {
|
||||||
|
if ((ret = parse_strict_bool(s, &b)) == SR_OK)
|
||||||
|
*gvar = g_variant_new_boolean(b);
|
||||||
|
} else if (g_variant_type_equal(gvtype, G_VARIANT_TYPE_DOUBLE)) {
|
||||||
|
if ((ret = sr_atod_ascii(s, &d)) == SR_OK)
|
||||||
|
*gvar = g_variant_new_double(d);
|
||||||
|
} else if (g_variant_type_equal(gvtype, G_VARIANT_TYPE_STRING)) {
|
||||||
|
*gvar = g_variant_new_string(s);
|
||||||
|
} else {
|
||||||
|
sr_err("Unable to convert to desired GVariant type.");
|
||||||
|
ret = SR_ERR_NA;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_free(s);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue