summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarc Balmer <mbalmer@cvs.openbsd.org>2007-11-10 12:58:09 +0000
committerMarc Balmer <mbalmer@cvs.openbsd.org>2007-11-10 12:58:09 +0000
commitb35eda5466cb4eb8488450856647b23d252450a7 (patch)
tree228d7fa194d63580f197d4c1e44cf3d5022661d1
parent14c10cdc34f6308805d54fd8f0c38c73062240ef (diff)
umbg(4) is a driver to support the USB attached Meinberg USB5131 DCF77
radio clock. umbg(4) implements a timedelta sensor and reports the signal quality in the Signal sensor as percentage. The signal sensor status further indicates if the clock is free running (WARN). ok dlg, jsg
-rw-r--r--sys/dev/usb/files.usb7
-rw-r--r--sys/dev/usb/umbg.c500
2 files changed, 506 insertions, 1 deletions
diff --git a/sys/dev/usb/files.usb b/sys/dev/usb/files.usb
index 680f2c22fb1..3f4440a9006 100644
--- a/sys/dev/usb/files.usb
+++ b/sys/dev/usb/files.usb
@@ -1,4 +1,4 @@
-# $OpenBSD: files.usb,v 1.71 2007/09/08 02:15:52 jsg Exp $
+# $OpenBSD: files.usb,v 1.72 2007/11/10 12:58:08 mbalmer Exp $
# $NetBSD: files.usb,v 1.16 2000/02/14 20:29:54 augustss Exp $
#
# Config file and device description for machine-independent USB code.
@@ -111,6 +111,11 @@ device udcf
attach udcf at uhub
file dev/usb/udcf.c udcf
+# Meinberg USB5131 DCF77 radio clock
+device umbg
+attach umbg at uhub
+file dev/usb/umbg.c umbg
+
# Diamond Multimedia Rio 500
device urio
attach urio at uhub
diff --git a/sys/dev/usb/umbg.c b/sys/dev/usb/umbg.c
new file mode 100644
index 00000000000..9ef4e3d2dc3
--- /dev/null
+++ b/sys/dev/usb/umbg.c
@@ -0,0 +1,500 @@
+/* $OpenBSD: umbg.c,v 1.1 2007/11/10 12:58:08 mbalmer Exp $ */
+
+/*
+ * Copyright (c) 2007 Marc Balmer <mbalmer@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/conf.h>
+#include <sys/file.h>
+#include <sys/select.h>
+#include <sys/proc.h>
+#include <sys/vnode.h>
+#include <sys/device.h>
+#include <sys/poll.h>
+#include <sys/syslog.h>
+#include <sys/time.h>
+#include <sys/sensors.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include <dev/usb/usbdevs.h>
+
+#ifdef UMBG_DEBUG
+#define DPRINTFN(n, x) do { if (umbgdebug > (n)) printf x; } while (0)
+int umbgdebug = 0;
+#else
+#define DPRINTFN(n, x)
+#endif
+#define DPRINTF(x) DPRINTFN(0, x)
+
+#ifdef UMBG_DEBUG
+#define TRUSTTIME ((long) 60)
+#else
+#define TRUSTTIME ((long) 12 * 60 * 60) /* degrade OK > WARN > CRIT */
+#endif
+
+struct umbg_softc {
+ struct device sc_dev; /* base device */
+ usbd_device_handle sc_udev; /* USB device */
+ usbd_interface_handle sc_iface; /* data interface */
+ u_char sc_dying; /* disconnecting */
+
+ int sc_bulkin_no;
+ usbd_pipe_handle sc_bulkin_pipe;
+ int sc_bulkout_no;
+ usbd_pipe_handle sc_bulkout_pipe;
+
+ struct timeout sc_to; /* get time from device */
+ struct usb_task sc_task;
+
+ struct timeout sc_it_to; /* invalidate sensor */
+ struct usb_task sc_it_task;
+
+ usb_device_request_t sc_req;
+
+ struct ksensor sc_timedelta; /* timedelta */
+ struct ksensor sc_signal; /* signal quality and status */
+ struct ksensordev sc_sensordev;
+};
+
+struct mbg_time {
+ u_int8_t hundreds;
+ u_int8_t sec;
+ u_int8_t min;
+ u_int8_t hour;
+ u_int8_t mday;
+ u_int8_t wday; /* 1 (monday) - 7 (sunday) */
+ u_int8_t mon;
+ u_int8_t year; /* 0 - 99 */
+ u_int8_t status;
+ u_int8_t signal;
+ int8_t utc_off;
+};
+
+struct mbg_time_hr {
+ u_int32_t sec; /* seconds since the epoch */
+ u_int32_t frac; /* fractions of second */
+ int32_t utc_off; /* UTC offset in seconds */
+ u_int16_t status;
+ u_int8_t signal;
+};
+
+/* mbg_time.status bits */
+#define MBG_FREERUN 0x01 /* clock running on xtal */
+#define MBG_DST_ENA 0x02 /* DST enabled */
+#define MBG_SYNC 0x04 /* clock synced at least once */
+#define MBG_DST_CHG 0x08 /* DST change announcement */
+#define MBG_UTC 0x10 /* special UTC firmware is installed */
+#define MBG_LEAP 0x20 /* announcement of a leap second */
+#define MBG_IFTM 0x40 /* current time was set from host */
+#define MBG_INVALID 0x80 /* time is invalid */
+
+/* commands */
+#define MBG_GET_TIME 0x00
+#define MBG_GET_SYNC_TIME 0x02
+#define MBG_GET_TIME_HR 0x03
+#define MBG_SET_TIME 0x10
+#define MBG_GET_TZCODE 0x32
+#define MBG_SET_TZCODE 0x33
+#define MBG_GET_FW_ID_1 0x40
+#define MBG_GET_FW_ID_2 0x41
+#define MBG_GET_SERNUM 0x42
+
+/* timezone codes (for MBG_{GET|SET}_TZCODE) */
+#define MBG_TZCODE_CET_CEST 0x00
+#define MBG_TZCODE_CET 0x01
+#define MBG_TZCODE_UTC 0x02
+#define MBG_TZCODE_EET_EEST 0x03
+
+/* misc. constants */
+#define MBG_FIFO_LEN 16
+#define MBG_ID_LEN (2 * MBG_FIFO_LEN + 1)
+#define MBG_BUSY 0x01
+#define MBG_SIG_BIAS 55
+#define MBG_SIG_MAX 68
+#define NSECPERSEC 1000000000LL /* nanoseconds per second */
+#define HRDIVISOR 0x100000000LL /* for hi-res timestamp */
+
+static int t_wait, t_trust;
+
+void umbg_intr(void *);
+void umbg_it_intr(void *);
+void umbg_it_probe(void *);
+
+int umbg_match(struct device *, void *, void *);
+void umbg_attach(struct device *, struct device *, void *);
+int umbg_detach(struct device *, int);
+int umbg_activate(struct device *, enum devact);
+
+void umbg_task(void *);
+
+int umbg_read(struct umbg_softc *, u_int8_t cmd, char *buf, size_t len,
+ struct timespec *tstamp);
+
+struct cfdriver umbg_cd = {
+ NULL, "umbg", DV_DULL
+};
+
+const struct cfattach umbg_ca = {
+ sizeof(struct umbg_softc),
+ umbg_match,
+ umbg_attach,
+ umbg_detach,
+ umbg_activate,
+};
+
+int
+umbg_match(struct device *parent, void *match, void *aux)
+{
+ struct usb_attach_arg *uaa = aux;
+
+ if (uaa->iface != NULL)
+ return UMATCH_NONE;
+
+ return uaa->vendor == USB_VENDOR_MEINBERG &&
+ uaa->product == USB_PRODUCT_MEINBERG_USB5131 ?
+ UMATCH_VENDOR_PRODUCT : UMATCH_NONE;
+}
+
+void
+umbg_attach(struct device *parent, struct device *self, void *aux)
+{
+ struct umbg_softc *sc = (struct umbg_softc *)self;
+ struct usb_attach_arg *uaa = aux;
+ usbd_device_handle dev = uaa->device;
+ usbd_interface_handle iface = uaa->iface;
+ struct timeval t;
+ struct mbg_time tframe;
+ usb_endpoint_descriptor_t *ed;
+ usbd_status err;
+ int signal;
+#ifdef UMBG_DEBUG
+ char fw_id[MBG_ID_LEN];
+#endif
+
+ if ((err = usbd_set_config_index(dev, 0, 1))) {
+ DPRINTF(("%s: failed to set configuration, err=%s\n",
+ sc->sc_dev.dv_xname, usbd_errstr(err)));
+ goto fishy;
+ }
+
+ if ((err = usbd_device2interface_handle(dev, 0, &iface))) {
+ DPRINTF(("%s: failed to get interface, err=%s\n",
+ sc->sc_dev.dv_xname, usbd_errstr(err)));
+ goto fishy;
+ }
+
+ ed = usbd_interface2endpoint_descriptor(iface, 0);
+ sc->sc_bulkin_no = ed->bEndpointAddress;
+ ed = usbd_interface2endpoint_descriptor(iface, 1);
+ sc->sc_bulkout_no = ed->bEndpointAddress;
+
+ sc->sc_udev = dev;
+ sc->sc_iface = iface;
+
+ err = usbd_open_pipe(sc->sc_iface, sc->sc_bulkin_no,
+ USBD_EXCLUSIVE_USE, &sc->sc_bulkin_pipe);
+ if (err) {
+ printf("%s: open rx pipe failed: %s\n", sc->sc_dev.dv_xname,
+ usbd_errstr(err));
+ goto fishy;
+ }
+
+ err = usbd_open_pipe(sc->sc_iface, sc->sc_bulkout_no,
+ USBD_EXCLUSIVE_USE, &sc->sc_bulkout_pipe);
+ if (err) {
+ printf("%s: open tx pipe failed: %s\n", sc->sc_dev.dv_xname,
+ usbd_errstr(err));
+ goto fishy;
+ }
+
+ strlcpy(sc->sc_sensordev.xname, sc->sc_dev.dv_xname,
+ sizeof(sc->sc_sensordev.xname));
+
+ sc->sc_timedelta.type = SENSOR_TIMEDELTA;
+ sc->sc_timedelta.status = SENSOR_S_UNKNOWN;
+ sc->sc_timedelta.value = 0LL;
+ sc->sc_timedelta.flags = 0;
+ strlcpy(sc->sc_timedelta.desc, "USB5131",
+ sizeof(sc->sc_timedelta.desc));
+ sensor_attach(&sc->sc_sensordev, &sc->sc_timedelta);
+
+ sc->sc_signal.type = SENSOR_PERCENT;
+ sc->sc_signal.value = 0LL;
+ sc->sc_signal.flags = 0;
+ strlcpy(sc->sc_signal.desc, "Signal",
+ sizeof(sc->sc_signal.desc));
+ sensor_attach(&sc->sc_sensordev, &sc->sc_signal);
+
+ printf("%s: ", sc->sc_dev.dv_xname);
+ if (umbg_read(sc, MBG_GET_TIME, (char *)&tframe,
+ sizeof(struct mbg_time), NULL)) {
+ sc->sc_signal.status = SENSOR_S_CRIT;
+ printf("unknown status");
+ } else {
+ sc->sc_signal.status = SENSOR_S_OK;
+ signal = tframe.signal - MBG_SIG_BIAS;
+ if (signal < 0)
+ signal = 0;
+ else if (signal > MBG_SIG_MAX)
+ signal = MBG_SIG_MAX;
+ sc->sc_signal.value = signal;
+
+ if (tframe.status & MBG_SYNC)
+ printf("synchronized");
+ else
+ printf("not synchronized");
+ if (tframe.status & MBG_FREERUN) {
+ sc->sc_signal.status = SENSOR_S_WARN;
+ printf(", freerun");
+ }
+ if (tframe.status & MBG_IFTM)
+ printf(", time set from host");
+ }
+#ifdef UMBG_DEBUG
+ if (umbg_read(sc, MBG_GET_FW_ID_1, fw_id, MBG_FIFO_LEN, NULL) ||
+ umbg_read(sc, MBG_GET_FW_ID_2, &fw_id[MBG_FIFO_LEN], MBG_FIFO_LEN,
+ NULL))
+ printf(", firmware unknown");
+ else {
+ fw_id[MBG_ID_LEN - 1] = '\0';
+ printf(", firmware %s", fw_id);
+ }
+#endif
+ printf("\n");
+
+ sensordev_install(&sc->sc_sensordev);
+
+ usbd_add_drv_event(USB_EVENT_DRIVER_ATTACH, sc->sc_udev,
+ &sc->sc_dev);
+
+ usb_init_task(&sc->sc_task, umbg_task, sc);
+ timeout_set(&sc->sc_to, umbg_intr, sc);
+
+ usb_init_task(&sc->sc_it_task, umbg_it_probe, sc);
+ timeout_set(&sc->sc_it_to, umbg_it_intr, sc);
+
+ /* convert timevals to hz */
+ t.tv_sec = 5L;
+ t.tv_usec = 0L;
+ t_wait = tvtohz(&t);
+
+ t.tv_sec = TRUSTTIME;
+ t_trust = tvtohz(&t);
+
+ usb_add_task(sc->sc_udev, &sc->sc_task);
+ return;
+
+fishy:
+ DPRINTF(("umbg_attach failed\n"));
+ sc->sc_dying = 1;
+}
+
+int
+umbg_detach(struct device *self, int flags)
+{
+ struct umbg_softc *sc = (struct umbg_softc *)self;
+ usbd_status err;
+
+ sc->sc_dying = 1;
+
+ timeout_del(&sc->sc_to);
+ timeout_del(&sc->sc_it_to);
+
+ if (sc->sc_bulkin_pipe != NULL) {
+ err = usbd_abort_pipe(sc->sc_bulkin_pipe);
+ if (err)
+ printf("%s: abort rx pipe failed: %s\n",
+ sc->sc_dev.dv_xname, usbd_errstr(err));
+ err = usbd_close_pipe(sc->sc_bulkin_pipe);
+ if (err)
+ printf("%s: close rx pipe failed: %s\n",
+ sc->sc_dev.dv_xname, usbd_errstr(err));
+ sc->sc_bulkin_pipe = NULL;
+ }
+ if (sc->sc_bulkout_pipe != NULL) {
+ err = usbd_abort_pipe(sc->sc_bulkout_pipe);
+ if (err)
+ printf("%s: abort tx pipe failed: %s\n",
+ sc->sc_dev.dv_xname, usbd_errstr(err));
+ err = usbd_close_pipe(sc->sc_bulkout_pipe);
+ if (err)
+ printf("%s: close tx pipe failed: %s\n",
+ sc->sc_dev.dv_xname, usbd_errstr(err));
+ sc->sc_bulkout_pipe = NULL;
+ }
+
+ usb_rem_task(sc->sc_udev, &sc->sc_task);
+ usb_rem_task(sc->sc_udev, &sc->sc_it_task);
+
+ /* Unregister the clock with the kernel */
+ sensordev_deinstall(&sc->sc_sensordev);
+
+ usbd_add_drv_event(USB_EVENT_DRIVER_DETACH, sc->sc_udev,
+ &sc->sc_dev);
+ return 0;
+}
+
+void
+umbg_intr(void *xsc)
+{
+ struct umbg_softc *sc = xsc;
+ usb_add_task(sc->sc_udev, &sc->sc_task);
+}
+
+/* umbg_task_hr() read a high resolution timestamp from the device. */
+void
+umbg_task(void *arg)
+{
+ struct umbg_softc *sc = (struct umbg_softc *)arg;
+ struct mbg_time_hr tframe;
+ struct timespec tstamp;
+ int64_t tlocal, trecv;
+ int signal;
+
+ if (sc->sc_dying)
+ return;
+
+ if (umbg_read(sc, MBG_GET_TIME_HR, (char *)&tframe, sizeof(tframe),
+ &tstamp)) {
+ DPRINTF(("%s: error reading hi-res time\n",
+ sc->sc_dev.dv_xname));
+ sc->sc_signal.status = SENSOR_S_CRIT;
+ goto bail_out;
+ }
+ if (tframe.status & MBG_INVALID) {
+ DPRINTF(("%s: invalid time, battery was disconnected\n",
+ sc->sc_dev.dv_xname));
+ sc->sc_signal.status = SENSOR_S_CRIT;
+ goto bail_out;
+ }
+
+ tlocal = tstamp.tv_sec * NSECPERSEC + tstamp.tv_nsec;
+ trecv = (letoh32(tframe.sec) - letoh32(tframe.utc_off)) * NSECPERSEC +
+ (letoh32(tframe.frac) * NSECPERSEC >> 32);
+
+ sc->sc_timedelta.value = tlocal - trecv;
+ if (sc->sc_timedelta.status == SENSOR_S_UNKNOWN ||
+ !(letoh16(tframe.status) & MBG_FREERUN)) {
+ sc->sc_timedelta.status = SENSOR_S_OK;
+ timeout_add(&sc->sc_it_to, t_trust);
+ }
+
+ sc->sc_timedelta.tv.tv_sec = tstamp.tv_sec;
+ sc->sc_timedelta.tv.tv_usec = tstamp.tv_nsec / 1000;
+
+ signal = tframe.signal - MBG_SIG_BIAS;
+ if (signal < 0)
+ signal = 0;
+ else if (signal > MBG_SIG_MAX)
+ signal = MBG_SIG_MAX;
+
+ sc->sc_signal.value = signal * 100000 / MBG_SIG_MAX;
+ sc->sc_signal.status = letoh16(tframe.status) & MBG_FREERUN ?
+ SENSOR_S_WARN : SENSOR_S_OK;
+ sc->sc_signal.tv.tv_sec = sc->sc_timedelta.tv.tv_sec;
+ sc->sc_signal.tv.tv_usec = sc->sc_timedelta.tv.tv_usec;
+
+bail_out:
+ timeout_add(&sc->sc_to, t_wait);
+
+}
+
+/* send a command and read back results */
+int
+umbg_read(struct umbg_softc *sc, u_int8_t cmd, char *buf, size_t len,
+ struct timespec *tstamp)
+{
+ usbd_status err;
+ usbd_xfer_handle xfer;
+
+ xfer = usbd_alloc_xfer(sc->sc_udev);
+ if (xfer == NULL) {
+ DPRINTF(("%s: alloc xfer failed\n", sc->sc_dev.dv_xname));
+ return -1;
+ }
+
+ usbd_setup_xfer(xfer, sc->sc_bulkout_pipe, NULL, &cmd, sizeof(cmd),
+ USBD_SHORT_XFER_OK, USBD_DEFAULT_TIMEOUT, NULL);
+ if (tstamp)
+ nanotime(tstamp);
+ err = usbd_sync_transfer(xfer);
+ if (err) {
+ DPRINTF(("%s: sending of command failed: %s\n",
+ sc->sc_dev.dv_xname, usbd_errstr(err)));
+ usbd_free_xfer(xfer);
+ return -1;
+ }
+
+ usbd_setup_xfer(xfer, sc->sc_bulkin_pipe, NULL, buf, len,
+ USBD_SHORT_XFER_OK, USBD_DEFAULT_TIMEOUT, NULL);
+
+ err = usbd_sync_transfer(xfer);
+ usbd_free_xfer(xfer);
+ if (err) {
+ DPRINTF(("%s: reading data failed: %s\n",
+ sc->sc_dev.dv_xname, usbd_errstr(err)));
+ return -1;
+ }
+ return 0;
+}
+
+/* degrade the sensor */
+void
+umbg_it_intr(void *xsc)
+{
+ struct umbg_softc *sc = xsc;
+ usb_add_task(sc->sc_udev, &sc->sc_it_task);
+}
+
+void
+umbg_it_probe(void *xsc)
+{
+ struct umbg_softc *sc = xsc;
+
+ if (sc->sc_dying)
+ return;
+
+ DPRINTF(("\ndegrading sensor state\n"));
+
+ if (sc->sc_timedelta.status == SENSOR_S_OK) {
+ sc->sc_timedelta.status = SENSOR_S_WARN;
+ /*
+ * further degrade in TRUSTTIME seconds if the clocks remains
+ * free running.
+ */
+ timeout_add(&sc->sc_it_to, t_trust);
+ } else
+ sc->sc_timedelta.status = SENSOR_S_CRIT;
+}
+
+int
+umbg_activate(struct device *self, enum devact act)
+{
+ struct umbg_softc *sc = (struct umbg_softc *)self;
+
+ switch (act) {
+ case DVACT_ACTIVATE:
+ break;
+ case DVACT_DEACTIVATE:
+ sc->sc_dying = 1;
+ break;
+ }
+ return 0;
+}