diff options
author | Marc Balmer <mbalmer@cvs.openbsd.org> | 2007-11-10 12:58:09 +0000 |
---|---|---|
committer | Marc Balmer <mbalmer@cvs.openbsd.org> | 2007-11-10 12:58:09 +0000 |
commit | b35eda5466cb4eb8488450856647b23d252450a7 (patch) | |
tree | 228d7fa194d63580f197d4c1e44cf3d5022661d1 | |
parent | 14c10cdc34f6308805d54fd8f0c38c73062240ef (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.usb | 7 | ||||
-rw-r--r-- | sys/dev/usb/umbg.c | 500 |
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; +} |