GPS-DO support for icE1usb hardware

This adds support for monitoring the GPS-DO that is built-in to the
icE1usb device.  It assumes a very recent firmware with GPS-DO control
moved to a separate USB interface, i.e. after osmo-e1-hardware.git
Change-Id Icd6555a14896c38626fb147b78af44ff719f2254 is merged.

Change-Id: If5e2a6b2dae0290ce3186009e68f618049ebf5ff
This commit is contained in:
Harald Welte 2022-01-30 18:21:49 +01:00
parent f5362e9217
commit 7bc8404f63
4 changed files with 316 additions and 2 deletions

View File

@ -23,8 +23,54 @@ enum e1usb_dev_capability {
ICE1USB_DEV_CAP_GPSDO,
};
/***********************************************************************
* Control Endpoint / GPS-DO Interface Requests
***********************************************************************/
/* Interface Requests */
#define ICE1USB_INTF_GET_GPSDO_STATUS 0x10
#define ICE1USB_INTF_GET_GPSDO_MODE 0x12 /*!< uint8_t */
#define ICE1USB_INTF_SET_GPSDO_MODE 0x13 /*!< wValue = mode */
#define ICE1USB_INTF_GET_GPSDO_TUNE 0x14 /*!< data = struct e1usb_gpsdo_tune */
#define ICE1USB_INTF_SET_GPSDO_TUNE 0x15 /*!< data = struct e1usb_gpsdo_tune */
enum ice1usb_gpsdo_mode {
ICE1USB_GPSDO_MODE_DISABLED = 0,
ICE1USB_GPSDO_MODE_AUTO = 1,
};
enum ice1usb_gpsdo_antenna_state {
ICE1USB_GPSDO_ANT_UNKNOWN = 0,
ICE1USB_GPSDO_ANT_OK = 1,
ICE1USB_GPSDO_ANT_OPEN = 2,
ICE1USB_GPSDO_ANT_SHORT = 3,
};
enum ice1usb_gpsdo_state {
ICE1USB_GPSDO_STATE_DISABLED = 0,
ICE1USB_GPSDO_STATE_CALIBRATE = 1,
ICE1USB_GPSDO_STATE_HOLD_OVER = 2,
ICE1USB_GPSDO_STATE_TUNE_COARSE = 3,
ICE1USB_GPSDO_STATE_TUNE_FINE = 4,
};
struct e1usb_gpsdo_tune {
uint16_t coarse;
uint16_t fine;
} __attribute__((packed));
struct e1usb_gpsdo_status {
uint8_t state;
uint8_t antenna_state; /*!< Antenna state */
uint8_t valid_fix; /*!< Valid GPS Fix (0/1) */
uint8_t mode; /*!< Current configured operating mode */
struct e1usb_gpsdo_tune tune; /*!< Current VCXO tuning values */
uint32_t freq_est; /*!< Latest frequency estimate measurement */
} __attribute__((packed));
/***********************************************************************
* Control Endpoint / E1 Interface Requests
***********************************************************************/
/*! returns a bit-mask of optional device capabilities (see enum e1usb_intf_capability) */
#define ICE1USB_INTF_GET_CAPABILITIES 0x01
@ -32,6 +78,7 @@ enum e1usb_dev_capability {
#define ICE1USB_INTF_GET_TX_CFG 0x03 /*!< struct ice1usb_tx_config */
#define ICE1USB_INTF_SET_RX_CFG 0x04 /*!< struct ice1usb_rx_config */
#define ICE1USB_INTF_GET_RX_CFG 0x05 /*!< struct ice1usb_rx_config */
#define ICE1USB_INTF_GET_ERRORS 0x06 /*!< struct ice1usb_irq_err */
//enum e1usb_intf_capability { };

250
src/usb.c
View File

@ -30,6 +30,7 @@
#include <osmocom/core/isdnhdlc.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/bit32gen.h>
#include <osmocom/usb/libusb.h>
#include <libusb.h>
@ -84,6 +85,15 @@ struct e1_usb_line_data {
struct e1_usb_intf_data {
libusb_device_handle *devh;
struct {
uint8_t if_num;
struct osmo_timer_list poll_timer;
struct e1usb_gpsdo_status last_status;
} gpsdo;
/* list of in-progress CTRL operations */
struct llist_head ctrl_inprogress;
};
@ -415,7 +425,7 @@ static int resubmit_irq(struct e1_line *line)
}
// ---------------------------------------------------------------------------
// Control transfers
// Control transfers (USB interface == E1 line level)
// ---------------------------------------------------------------------------
struct e1_usb_ctrl_xfer {
@ -445,6 +455,10 @@ ctrl_xfer_compl_cb(struct libusb_transfer *xfr)
libusb_free_transfer(xfr);
}
// ---------------------------------------------------------------------------
// Control transfers (USB device == E1 interface level)
// ---------------------------------------------------------------------------
/* generic helper for async transmission of control endpoint requests */
static int
_e1_usb_line_send_ctrl(struct e1_line *line, uint8_t bmReqType, uint8_t bReq, uint16_t wValue,
@ -518,6 +532,224 @@ e1_usb_ctrl_set_rx_cfg(struct e1_line *line, enum ice1usb_rx_mode mode)
sizeof(rx_cfg));
}
struct e1_usb_ctrl_xfer_intf {
struct e1_intf *intf;
struct llist_head list;
/* 8 bytes control setup packet, remainder for data */
uint8_t buffer[8 + sizeof(struct e1usb_gpsdo_status)];
};
static void _e1_usb_intf_gpsdo_status_cb(struct e1_intf *intf, const uint8_t *data, size_t len);
static void
ctrl_xfer_intf_compl_cb(struct libusb_transfer *xfr)
{
struct e1_usb_ctrl_xfer_intf *ucx = xfr->user_data;
struct libusb_control_setup *setup;
switch (xfr->status) {
case LIBUSB_TRANSFER_COMPLETED:
setup = (struct libusb_control_setup *) ucx->buffer;
LOGPIF(ucx->intf, DE1D, LOGL_DEBUG, "CTRL transfer completed successfully: %s\n",
osmo_hexdump(ucx->buffer, 8+xfr->actual_length));
switch (setup->bRequest) {
case ICE1USB_INTF_GET_GPSDO_STATUS:
_e1_usb_intf_gpsdo_status_cb(ucx->intf, ucx->buffer+8, xfr->actual_length);
break;
default:
break;
}
break;
default:
LOGPIF(ucx->intf, DE1D, LOGL_ERROR, "CTRL transfer completed unsuccessfully %d\n",
xfr->status);
break;
}
llist_del(&ucx->list);
talloc_free(ucx);
libusb_free_transfer(xfr);
}
/* generic helper for async transmission of control endpoint requests */
static int
_e1_usb_intf_send_ctrl(struct e1_intf *intf, uint8_t bmReqType, uint8_t bReq, uint16_t wValue,
const uint8_t *data, size_t data_len)
{
struct e1_usb_ctrl_xfer_intf *ucx = talloc_zero(intf, struct e1_usb_ctrl_xfer_intf);
struct e1_usb_intf_data *id = (struct e1_usb_intf_data *) intf->drv_data;
struct libusb_transfer *xfr;
int rc;
if (!ucx)
return -ENOMEM;
OSMO_ASSERT(sizeof(ucx->buffer) >= 8+data_len);
ucx->intf = intf;
libusb_fill_control_setup(ucx->buffer, bmReqType, bReq, wValue, id->gpsdo.if_num, data_len);
if (data && data_len)
memcpy(ucx->buffer+8, data, data_len);
xfr = libusb_alloc_transfer(0);
if (!xfr) {
rc = -ENOMEM;
goto free_ucx;
}
libusb_fill_control_transfer(xfr, id->devh, ucx->buffer, ctrl_xfer_intf_compl_cb, ucx, 3000);
rc = libusb_submit_transfer(xfr);
if (rc != 0)
goto free_xfr;
llist_add_tail(&ucx->list, &id->ctrl_inprogress);
return 0;
free_xfr:
libusb_free_transfer(xfr);
free_ucx:
talloc_free(ucx);
return rc;
}
int
e1_usb_ctrl_set_gpsdo_mode(struct e1_intf *intf, enum ice1usb_gpsdo_mode gpsdo_mode)
{
const uint16_t bmReqType = LIBUSB_RECIPIENT_INTERFACE | LIBUSB_REQUEST_TYPE_VENDOR |
LIBUSB_ENDPOINT_OUT;
return _e1_usb_intf_send_ctrl(intf, bmReqType, ICE1USB_INTF_SET_GPSDO_MODE, 0,
(uint8_t *)&gpsdo_mode, sizeof(gpsdo_mode));
}
int
e1_usb_ctrl_set_gpsdo_tune(struct e1_intf *intf, const struct e1usb_gpsdo_tune *gpsdo_tune)
{
const uint16_t bmReqType = LIBUSB_RECIPIENT_INTERFACE | LIBUSB_REQUEST_TYPE_VENDOR |
LIBUSB_ENDPOINT_OUT;
return _e1_usb_intf_send_ctrl(intf, bmReqType, ICE1USB_INTF_SET_GPSDO_TUNE, 0,
(uint8_t *)gpsdo_tune, sizeof(gpsdo_tune));
}
int
e1_usb_ctrl_get_gpsdo_status(struct e1_intf *intf)
{
const uint16_t bmReqType = LIBUSB_RECIPIENT_INTERFACE | LIBUSB_REQUEST_TYPE_VENDOR |
LIBUSB_ENDPOINT_IN;
return _e1_usb_intf_send_ctrl(intf, bmReqType, ICE1USB_INTF_GET_GPSDO_STATUS, 0,
NULL, sizeof(struct e1usb_gpsdo_status));
}
// ---------------------------------------------------------------------------
// GPS-DO
// ---------------------------------------------------------------------------
static const struct value_string ice1usb_gpsdo_mode_str[] = {
{ ICE1USB_GPSDO_MODE_DISABLED, "DISABLED" },
{ ICE1USB_GPSDO_MODE_AUTO, "AUTO" },
{ 0, NULL }
};
static const struct value_string ice1usb_gpsdo_antenna_state_str[] = {
{ ICE1USB_GPSDO_ANT_UNKNOWN, "UNKNOWN" },
{ ICE1USB_GPSDO_ANT_OK, "OK" },
{ ICE1USB_GPSDO_ANT_OPEN, "OPEN" },
{ ICE1USB_GPSDO_ANT_SHORT, "SHORT" },
{ 0, NULL }
};
static const struct value_string ice1usb_gpsdo_state_str[] = {
{ ICE1USB_GPSDO_STATE_DISABLED, "DISABLED" },
{ ICE1USB_GPSDO_STATE_CALIBRATE, "CALIBRATE" },
{ ICE1USB_GPSDO_STATE_HOLD_OVER, "HOLD_OVER" },
{ ICE1USB_GPSDO_STATE_TUNE_COARSE, "TUNE_COARSE" },
{ ICE1USB_GPSDO_STATE_TUNE_FINE, "TUNE_FINE" },
{ 0, NULL }
};
int
e1_usb_intf_gpsdo_state_string(char *buf, size_t len, const struct e1_intf *intf)
{
struct e1_usb_intf_data *id = intf->drv_data;
struct e1usb_gpsdo_status *last_st = &id->gpsdo.last_status;
OSMO_ASSERT(intf->drv == E1_DRIVER_USB);
return snprintf(buf, len, "mode=%s, fix=%s, state=%s antenna=%s, tune=%u/%u, freq_est=%u",
get_value_string(ice1usb_gpsdo_mode_str, last_st->mode),
last_st->valid_fix ? "TRUE" : "FALSE",
get_value_string(ice1usb_gpsdo_state_str, last_st->state),
get_value_string(ice1usb_gpsdo_antenna_state_str, last_st->antenna_state),
libusb_le16_to_cpu(last_st->tune.coarse), libusb_le16_to_cpu(last_st->tune.fine),
osmo_load32le(&last_st->freq_est));
}
static void
_e1_usb_intf_gpsdo_status_cb(struct e1_intf *intf, const uint8_t *data, size_t len)
{
struct e1_usb_intf_data *id = intf->drv_data;
struct e1usb_gpsdo_status *last_st = &id->gpsdo.last_status;
const struct e1usb_gpsdo_status *st;
if (len < sizeof(*st)) {
LOGPIF(intf, DE1D, LOGL_ERROR, "GPSDO status %zu < %zu!\n", len, sizeof(*st));
return;
}
st = (const struct e1usb_gpsdo_status *) data;
if (st->state != last_st->state) {
LOGPIF(intf, DE1D, LOGL_NOTICE, "GPSDO state change: %s -> %s\n",
get_value_string(ice1usb_gpsdo_state_str, last_st->state),
get_value_string(ice1usb_gpsdo_state_str, st->state));
}
if (st->antenna_state != last_st->antenna_state) {
int level = LOGL_NOTICE;
switch (st->antenna_state) {
case ICE1USB_GPSDO_ANT_OPEN:
case ICE1USB_GPSDO_ANT_SHORT:
level = LOGL_ERROR;
break;
default:
level = LOGL_NOTICE;
}
LOGPIF(intf, DE1D, level, "GPS antenna status change: %s -> %s\n",
get_value_string(ice1usb_gpsdo_antenna_state_str, last_st->antenna_state),
get_value_string(ice1usb_gpsdo_antenna_state_str, st->antenna_state));
}
if (st->valid_fix != last_st->valid_fix) {
if (st->valid_fix)
LOGPIF(intf, DE1D, LOGL_NOTICE, "GPS Fix achieved\n");
else
LOGPIF(intf, DE1D, LOGL_ERROR, "GPS Fix LOST\n");
}
/* update our state */
memcpy(last_st, st, sizeof(*last_st));
}
static void
_e1_usb_gpsdo_poll_cb(void *data)
{
struct e1_intf *intf = (struct e1_intf *) data;
struct e1_usb_intf_data *id = intf->drv_data;
/* issue a control endpoint request, further processing is when it completes */
e1_usb_ctrl_get_gpsdo_status(intf);
osmo_timer_schedule(&id->gpsdo.poll_timer, 1, 0);
}
static void
_e1_usb_gpsdo_init(struct e1_intf *intf)
{
struct e1_usb_intf_data *id = intf->drv_data;
osmo_timer_setup(&id->gpsdo.poll_timer, &_e1_usb_gpsdo_poll_cb, intf);
osmo_timer_schedule(&id->gpsdo.poll_timer, 1, 0);
}
// ---------------------------------------------------------------------------
// Init / Probing
// ---------------------------------------------------------------------------
@ -587,6 +819,8 @@ _e1_usb_open_device(struct e1_daemon *e1d, struct libusb_device *dev)
osmo_talloc_replace_string(intf, &intf->usb.serial_str, serial_str);
}
INIT_LLIST_HEAD(&intf_data->ctrl_inprogress);
ret = libusb_get_active_config_descriptor(dev, &cd);
if (ret) {
LOGP(DE1D, LOGL_ERROR, "Failed to talk to usb device\n");
@ -690,6 +924,20 @@ next_interface:
line_nr++;
}
/* find the GPS-DO interface (if any) */
for (i = 0; i < cd->bNumInterfaces; i++) {
if (cd->interface[i].num_altsetting != 1)
continue;
id = &cd->interface[i].altsetting[0];
if ((id->bInterfaceClass == 0xff) && (id->bInterfaceSubClass == 0xe1) &&
(id->bInterfaceProtocol == 0xd0)) {
intf_data->gpsdo.if_num = id->bInterfaceNumber;
_e1_usb_gpsdo_init(intf);
break;
}
}
return 0;
}

View File

@ -12,4 +12,10 @@ int e1_usb_ctrl_set_tx_cfg(struct e1_line *line, enum ice1usb_tx_mode mode,
int e1_usb_ctrl_set_rx_cfg(struct e1_line *line, enum ice1usb_rx_mode mode);
int e1_usb_ctrl_set_gpsdo_mode(struct e1_intf *intf, enum ice1usb_gpsdo_mode gpsdo_mode);
int e1_usb_ctrl_set_gpsdo_tune(struct e1_intf *intf, const struct e1usb_gpsdo_tune *gpsdo_tune);
int e1_usb_ctrl_get_gpsdo_status(struct e1_intf *intf);
int e1_usb_intf_gpsdo_state_string(char *buf, size_t len, const struct e1_intf *intf);
int e1_usb_probe(struct e1_daemon *e1d);

View File

@ -38,6 +38,7 @@
#include <osmocom/e1d/proto.h>
#include "e1d.h"
#include "usb.h"
struct e1_daemon *vty_e1d;
@ -75,8 +76,20 @@ static const char *intf_serno(const struct e1_intf *intf)
static void vty_dump_intf(struct vty *vty, const struct e1_intf *intf)
{
char buf[128];
vty_out(vty, "Interface #%u (%s), Driver: %s%s", intf->id, intf_serno(intf),
get_value_string(e1_driver_names, intf->drv), VTY_NEWLINE);
/* TODO: put this behind some call-back */
switch (intf->drv) {
case E1_DRIVER_USB:
e1_usb_intf_gpsdo_state_string(buf, sizeof(buf), intf);
vty_out(vty, " GPS-DO: %s%s", buf, VTY_NEWLINE);
break;
default:
break;
}
}
DEFUN(show_intf, show_intf_cmd, "show interface [<0-255>]",