Index: head/sys/dev/usb/serial/usb_serial.c =================================================================== --- head/sys/dev/usb/serial/usb_serial.c (revision 282423) +++ head/sys/dev/usb/serial/usb_serial.c (revision 282424) @@ -1,1714 +1,1715 @@ /* $NetBSD: ucom.c,v 1.40 2001/11/13 06:24:54 lukem Exp $ */ /*- * Copyright (c) 2001-2003, 2005, 2008 * Shunsuke Akiyama . * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); /*- * Copyright (c) 1998, 2000 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Lennart Augustsson (lennart@augustsson.net) at * Carlstedt Research & Technology. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define USB_DEBUG_VAR ucom_debug #include #include #include #include #include "opt_gdb.h" static SYSCTL_NODE(_hw_usb, OID_AUTO, ucom, CTLFLAG_RW, 0, "USB ucom"); static int ucom_pps_mode; SYSCTL_INT(_hw_usb_ucom, OID_AUTO, pps_mode, CTLFLAG_RWTUN, &ucom_pps_mode, 0, "pulse capturing mode - 0/1/2 - disabled/CTS/DCD"); #ifdef USB_DEBUG static int ucom_debug = 0; SYSCTL_INT(_hw_usb_ucom, OID_AUTO, debug, CTLFLAG_RWTUN, &ucom_debug, 0, "ucom debug level"); #endif #define UCOM_CONS_BUFSIZE 1024 static uint8_t ucom_cons_rx_buf[UCOM_CONS_BUFSIZE]; static uint8_t ucom_cons_tx_buf[UCOM_CONS_BUFSIZE]; static unsigned int ucom_cons_rx_low = 0; static unsigned int ucom_cons_rx_high = 0; static unsigned int ucom_cons_tx_low = 0; static unsigned int ucom_cons_tx_high = 0; static int ucom_cons_unit = -1; static int ucom_cons_subunit = 0; static int ucom_cons_baud = 9600; static struct ucom_softc *ucom_cons_softc = NULL; SYSCTL_INT(_hw_usb_ucom, OID_AUTO, cons_unit, CTLFLAG_RWTUN, &ucom_cons_unit, 0, "console unit number"); SYSCTL_INT(_hw_usb_ucom, OID_AUTO, cons_subunit, CTLFLAG_RWTUN, &ucom_cons_subunit, 0, "console subunit number"); SYSCTL_INT(_hw_usb_ucom, OID_AUTO, cons_baud, CTLFLAG_RWTUN, &ucom_cons_baud, 0, "console baud rate"); static usb_proc_callback_t ucom_cfg_start_transfers; static usb_proc_callback_t ucom_cfg_open; static usb_proc_callback_t ucom_cfg_close; static usb_proc_callback_t ucom_cfg_line_state; static usb_proc_callback_t ucom_cfg_status_change; static usb_proc_callback_t ucom_cfg_param; static int ucom_unit_alloc(void); static void ucom_unit_free(int); static int ucom_attach_tty(struct ucom_super_softc *, struct ucom_softc *); static void ucom_detach_tty(struct ucom_super_softc *, struct ucom_softc *); static void ucom_queue_command(struct ucom_softc *, usb_proc_callback_t *, struct termios *pt, struct usb_proc_msg *t0, struct usb_proc_msg *t1); static void ucom_shutdown(struct ucom_softc *); static void ucom_ring(struct ucom_softc *, uint8_t); static void ucom_break(struct ucom_softc *, uint8_t); static void ucom_dtr(struct ucom_softc *, uint8_t); static void ucom_rts(struct ucom_softc *, uint8_t); static tsw_open_t ucom_open; static tsw_close_t ucom_close; static tsw_ioctl_t ucom_ioctl; static tsw_modem_t ucom_modem; static tsw_param_t ucom_param; static tsw_outwakeup_t ucom_outwakeup; static tsw_inwakeup_t ucom_inwakeup; static tsw_free_t ucom_free; static struct ttydevsw ucom_class = { .tsw_flags = TF_INITLOCK | TF_CALLOUT, .tsw_open = ucom_open, .tsw_close = ucom_close, .tsw_outwakeup = ucom_outwakeup, .tsw_inwakeup = ucom_inwakeup, .tsw_ioctl = ucom_ioctl, .tsw_param = ucom_param, .tsw_modem = ucom_modem, .tsw_free = ucom_free, }; MODULE_DEPEND(ucom, usb, 1, 1, 1); MODULE_VERSION(ucom, 1); #define UCOM_UNIT_MAX 128 /* maximum number of units */ #define UCOM_TTY_PREFIX "U" static struct unrhdr *ucom_unrhdr; static struct mtx ucom_mtx; static int ucom_close_refs; static void ucom_init(void *arg) { DPRINTF("\n"); ucom_unrhdr = new_unrhdr(0, UCOM_UNIT_MAX - 1, NULL); mtx_init(&ucom_mtx, "UCOM MTX", NULL, MTX_DEF); } SYSINIT(ucom_init, SI_SUB_KLD - 1, SI_ORDER_ANY, ucom_init, NULL); static void ucom_uninit(void *arg) { struct unrhdr *hdr; hdr = ucom_unrhdr; ucom_unrhdr = NULL; DPRINTF("\n"); if (hdr != NULL) delete_unrhdr(hdr); mtx_destroy(&ucom_mtx); } SYSUNINIT(ucom_uninit, SI_SUB_KLD - 3, SI_ORDER_ANY, ucom_uninit, NULL); /* * Mark a unit number (the X in cuaUX) as in use. * * Note that devices using a different naming scheme (see ucom_tty_name() * callback) still use this unit allocation. */ static int ucom_unit_alloc(void) { int unit; /* sanity checks */ if (ucom_unrhdr == NULL) { DPRINTF("ucom_unrhdr is NULL\n"); return (-1); } unit = alloc_unr(ucom_unrhdr); DPRINTF("unit %d is allocated\n", unit); return (unit); } /* * Mark the unit number as not in use. */ static void ucom_unit_free(int unit) { /* sanity checks */ if (unit < 0 || unit >= UCOM_UNIT_MAX || ucom_unrhdr == NULL) { DPRINTF("cannot free unit number\n"); return; } DPRINTF("unit %d is freed\n", unit); free_unr(ucom_unrhdr, unit); } /* * Setup a group of one or more serial ports. * * The mutex pointed to by "mtx" is applied before all * callbacks are called back. Also "mtx" must be applied * before calling into the ucom-layer! */ int ucom_attach(struct ucom_super_softc *ssc, struct ucom_softc *sc, int subunits, void *parent, const struct ucom_callback *callback, struct mtx *mtx) { int subunit; int error = 0; if ((sc == NULL) || (subunits <= 0) || (callback == NULL) || (mtx == NULL)) { return (EINVAL); } /* allocate a uniq unit number */ ssc->sc_unit = ucom_unit_alloc(); if (ssc->sc_unit == -1) return (ENOMEM); /* generate TTY name string */ snprintf(ssc->sc_ttyname, sizeof(ssc->sc_ttyname), UCOM_TTY_PREFIX "%d", ssc->sc_unit); /* create USB request handling process */ error = usb_proc_create(&ssc->sc_tq, mtx, "ucom", USB_PRI_MED); if (error) { ucom_unit_free(ssc->sc_unit); return (error); } ssc->sc_subunits = subunits; ssc->sc_flag = UCOM_FLAG_ATTACHED | UCOM_FLAG_FREE_UNIT; if (callback->ucom_free == NULL) ssc->sc_flag |= UCOM_FLAG_WAIT_REFS; /* increment reference count */ ucom_ref(ssc); for (subunit = 0; subunit < ssc->sc_subunits; subunit++) { sc[subunit].sc_subunit = subunit; sc[subunit].sc_super = ssc; sc[subunit].sc_mtx = mtx; sc[subunit].sc_parent = parent; sc[subunit].sc_callback = callback; error = ucom_attach_tty(ssc, &sc[subunit]); if (error) { ucom_detach(ssc, &sc[0]); return (error); } /* increment reference count */ ucom_ref(ssc); /* set subunit attached */ sc[subunit].sc_flag |= UCOM_FLAG_ATTACHED; } DPRINTF("tp = %p, unit = %d, subunits = %d\n", sc->sc_tty, ssc->sc_unit, ssc->sc_subunits); return (0); } /* * The following function will do nothing if the structure pointed to * by "ssc" and "sc" is zero or has already been detached. */ void ucom_detach(struct ucom_super_softc *ssc, struct ucom_softc *sc) { int subunit; if (!(ssc->sc_flag & UCOM_FLAG_ATTACHED)) return; /* not initialized */ if (ssc->sc_sysctl_ttyname != NULL) { sysctl_remove_oid(ssc->sc_sysctl_ttyname, 1, 0); ssc->sc_sysctl_ttyname = NULL; } if (ssc->sc_sysctl_ttyports != NULL) { sysctl_remove_oid(ssc->sc_sysctl_ttyports, 1, 0); ssc->sc_sysctl_ttyports = NULL; } usb_proc_drain(&ssc->sc_tq); for (subunit = 0; subunit < ssc->sc_subunits; subunit++) { if (sc[subunit].sc_flag & UCOM_FLAG_ATTACHED) { ucom_detach_tty(ssc, &sc[subunit]); /* avoid duplicate detach */ sc[subunit].sc_flag &= ~UCOM_FLAG_ATTACHED; } } usb_proc_free(&ssc->sc_tq); ucom_unref(ssc); if (ssc->sc_flag & UCOM_FLAG_WAIT_REFS) ucom_drain(ssc); /* make sure we don't detach twice */ ssc->sc_flag &= ~UCOM_FLAG_ATTACHED; } void ucom_drain(struct ucom_super_softc *ssc) { mtx_lock(&ucom_mtx); while (ssc->sc_refs > 0) { printf("ucom: Waiting for a TTY device to close.\n"); usb_pause_mtx(&ucom_mtx, hz); } mtx_unlock(&ucom_mtx); } void ucom_drain_all(void *arg) { mtx_lock(&ucom_mtx); while (ucom_close_refs > 0) { printf("ucom: Waiting for all detached TTY " "devices to have open fds closed.\n"); usb_pause_mtx(&ucom_mtx, hz); } mtx_unlock(&ucom_mtx); } static int ucom_attach_tty(struct ucom_super_softc *ssc, struct ucom_softc *sc) { struct tty *tp; char buf[32]; /* temporary TTY device name buffer */ tp = tty_alloc_mutex(&ucom_class, sc, sc->sc_mtx); if (tp == NULL) return (ENOMEM); /* Check if the client has a custom TTY name */ buf[0] = '\0'; if (sc->sc_callback->ucom_tty_name) { sc->sc_callback->ucom_tty_name(sc, buf, sizeof(buf), ssc->sc_unit, sc->sc_subunit); } if (buf[0] == 0) { /* Use default TTY name */ if (ssc->sc_subunits > 1) { /* multiple modems in one */ snprintf(buf, sizeof(buf), UCOM_TTY_PREFIX "%u.%u", ssc->sc_unit, sc->sc_subunit); } else { /* single modem */ snprintf(buf, sizeof(buf), UCOM_TTY_PREFIX "%u", ssc->sc_unit); } } tty_makedev(tp, NULL, "%s", buf); sc->sc_tty = tp; sc->sc_pps.ppscap = PPS_CAPTUREBOTH; - sc->sc_pps.mtx = sc->sc_mtx; - pps_init(&sc->sc_pps); + sc->sc_pps.driver_abi = PPS_ABI_VERSION; + sc->sc_pps.driver_mtx = sc->sc_mtx; + pps_init_abi(&sc->sc_pps); DPRINTF("ttycreate: %s\n", buf); /* Check if this device should be a console */ if ((ucom_cons_softc == NULL) && (ssc->sc_unit == ucom_cons_unit) && (sc->sc_subunit == ucom_cons_subunit)) { DPRINTF("unit %d subunit %d is console", ssc->sc_unit, sc->sc_subunit); ucom_cons_softc = sc; tty_init_console(tp, ucom_cons_baud); UCOM_MTX_LOCK(ucom_cons_softc); ucom_cons_rx_low = 0; ucom_cons_rx_high = 0; ucom_cons_tx_low = 0; ucom_cons_tx_high = 0; sc->sc_flag |= UCOM_FLAG_CONSOLE; ucom_open(ucom_cons_softc->sc_tty); ucom_param(ucom_cons_softc->sc_tty, &tp->t_termios_init_in); UCOM_MTX_UNLOCK(ucom_cons_softc); } return (0); } static void ucom_detach_tty(struct ucom_super_softc *ssc, struct ucom_softc *sc) { struct tty *tp = sc->sc_tty; DPRINTF("sc = %p, tp = %p\n", sc, sc->sc_tty); if (sc->sc_flag & UCOM_FLAG_CONSOLE) { UCOM_MTX_LOCK(ucom_cons_softc); ucom_close(ucom_cons_softc->sc_tty); sc->sc_flag &= ~UCOM_FLAG_CONSOLE; UCOM_MTX_UNLOCK(ucom_cons_softc); ucom_cons_softc = NULL; } /* the config thread has been stopped when we get here */ UCOM_MTX_LOCK(sc); sc->sc_flag |= UCOM_FLAG_GONE; sc->sc_flag &= ~(UCOM_FLAG_HL_READY | UCOM_FLAG_LL_READY); UCOM_MTX_UNLOCK(sc); if (tp) { mtx_lock(&ucom_mtx); ucom_close_refs++; mtx_unlock(&ucom_mtx); tty_lock(tp); ucom_close(tp); /* close, if any */ tty_rel_gone(tp); UCOM_MTX_LOCK(sc); /* * make sure that read and write transfers are stopped */ if (sc->sc_callback->ucom_stop_read) (sc->sc_callback->ucom_stop_read) (sc); if (sc->sc_callback->ucom_stop_write) (sc->sc_callback->ucom_stop_write) (sc); UCOM_MTX_UNLOCK(sc); } } void ucom_set_pnpinfo_usb(struct ucom_super_softc *ssc, device_t dev) { char buf[64]; uint8_t iface_index; struct usb_attach_arg *uaa; snprintf(buf, sizeof(buf), "ttyname=" UCOM_TTY_PREFIX "%d ttyports=%d", ssc->sc_unit, ssc->sc_subunits); /* Store the PNP info in the first interface for the device */ uaa = device_get_ivars(dev); iface_index = uaa->info.bIfaceIndex; if (usbd_set_pnpinfo(uaa->device, iface_index, buf) != 0) device_printf(dev, "Could not set PNP info\n"); /* * The following information is also replicated in the PNP-info * string which is registered above: */ if (ssc->sc_sysctl_ttyname == NULL) { ssc->sc_sysctl_ttyname = SYSCTL_ADD_STRING(NULL, SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "ttyname", CTLFLAG_RD, ssc->sc_ttyname, 0, "TTY device basename"); } if (ssc->sc_sysctl_ttyports == NULL) { ssc->sc_sysctl_ttyports = SYSCTL_ADD_INT(NULL, SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "ttyports", CTLFLAG_RD, NULL, ssc->sc_subunits, "Number of ports"); } } static void ucom_queue_command(struct ucom_softc *sc, usb_proc_callback_t *fn, struct termios *pt, struct usb_proc_msg *t0, struct usb_proc_msg *t1) { struct ucom_super_softc *ssc = sc->sc_super; struct ucom_param_task *task; UCOM_MTX_ASSERT(sc, MA_OWNED); if (usb_proc_is_gone(&ssc->sc_tq)) { DPRINTF("proc is gone\n"); return; /* nothing to do */ } /* * NOTE: The task cannot get executed before we drop the * "sc_mtx" mutex. It is safe to update fields in the message * structure after that the message got queued. */ task = (struct ucom_param_task *) usb_proc_msignal(&ssc->sc_tq, t0, t1); /* Setup callback and softc pointers */ task->hdr.pm_callback = fn; task->sc = sc; /* * Make a copy of the termios. This field is only present if * the "pt" field is not NULL. */ if (pt != NULL) task->termios_copy = *pt; /* * Closing the device should be synchronous. */ if (fn == ucom_cfg_close) usb_proc_mwait(&ssc->sc_tq, t0, t1); /* * In case of multiple configure requests, * keep track of the last one! */ if (fn == ucom_cfg_start_transfers) sc->sc_last_start_xfer = &task->hdr; } static void ucom_shutdown(struct ucom_softc *sc) { struct tty *tp = sc->sc_tty; UCOM_MTX_ASSERT(sc, MA_OWNED); DPRINTF("\n"); /* * Hang up if necessary: */ if (tp->t_termios.c_cflag & HUPCL) { ucom_modem(tp, 0, SER_DTR); } } /* * Return values: * 0: normal * else: taskqueue is draining or gone */ uint8_t ucom_cfg_is_gone(struct ucom_softc *sc) { struct ucom_super_softc *ssc = sc->sc_super; return (usb_proc_is_gone(&ssc->sc_tq)); } static void ucom_cfg_start_transfers(struct usb_proc_msg *_task) { struct ucom_cfg_task *task = (struct ucom_cfg_task *)_task; struct ucom_softc *sc = task->sc; if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) { return; } if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { /* TTY device closed */ return; } if (_task == sc->sc_last_start_xfer) sc->sc_flag |= UCOM_FLAG_GP_DATA; if (sc->sc_callback->ucom_start_read) { (sc->sc_callback->ucom_start_read) (sc); } if (sc->sc_callback->ucom_start_write) { (sc->sc_callback->ucom_start_write) (sc); } } static void ucom_start_transfers(struct ucom_softc *sc) { if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { return; } /* * Make sure that data transfers are started in both * directions: */ if (sc->sc_callback->ucom_start_read) { (sc->sc_callback->ucom_start_read) (sc); } if (sc->sc_callback->ucom_start_write) { (sc->sc_callback->ucom_start_write) (sc); } } static void ucom_cfg_open(struct usb_proc_msg *_task) { struct ucom_cfg_task *task = (struct ucom_cfg_task *)_task; struct ucom_softc *sc = task->sc; DPRINTF("\n"); if (sc->sc_flag & UCOM_FLAG_LL_READY) { /* already opened */ } else { sc->sc_flag |= UCOM_FLAG_LL_READY; if (sc->sc_callback->ucom_cfg_open) { (sc->sc_callback->ucom_cfg_open) (sc); /* wait a little */ usb_pause_mtx(sc->sc_mtx, hz / 10); } } } static int ucom_open(struct tty *tp) { struct ucom_softc *sc = tty_softc(tp); int error; UCOM_MTX_ASSERT(sc, MA_OWNED); if (sc->sc_flag & UCOM_FLAG_GONE) { return (ENXIO); } if (sc->sc_flag & UCOM_FLAG_HL_READY) { /* already opened */ return (0); } DPRINTF("tp = %p\n", tp); if (sc->sc_callback->ucom_pre_open) { /* * give the lower layer a chance to disallow TTY open, for * example if the device is not present: */ error = (sc->sc_callback->ucom_pre_open) (sc); if (error) { return (error); } } sc->sc_flag |= UCOM_FLAG_HL_READY; /* Disable transfers */ sc->sc_flag &= ~UCOM_FLAG_GP_DATA; sc->sc_lsr = 0; sc->sc_msr = 0; sc->sc_mcr = 0; /* reset programmed line state */ sc->sc_pls_curr = 0; sc->sc_pls_set = 0; sc->sc_pls_clr = 0; /* reset jitter buffer */ sc->sc_jitterbuf_in = 0; sc->sc_jitterbuf_out = 0; ucom_queue_command(sc, ucom_cfg_open, NULL, &sc->sc_open_task[0].hdr, &sc->sc_open_task[1].hdr); /* Queue transfer enable command last */ ucom_queue_command(sc, ucom_cfg_start_transfers, NULL, &sc->sc_start_task[0].hdr, &sc->sc_start_task[1].hdr); ucom_modem(tp, SER_DTR | SER_RTS, 0); ucom_ring(sc, 0); ucom_break(sc, 0); ucom_status_change(sc); return (0); } static void ucom_cfg_close(struct usb_proc_msg *_task) { struct ucom_cfg_task *task = (struct ucom_cfg_task *)_task; struct ucom_softc *sc = task->sc; DPRINTF("\n"); if (sc->sc_flag & UCOM_FLAG_LL_READY) { sc->sc_flag &= ~UCOM_FLAG_LL_READY; if (sc->sc_callback->ucom_cfg_close) (sc->sc_callback->ucom_cfg_close) (sc); } else { /* already closed */ } } static void ucom_close(struct tty *tp) { struct ucom_softc *sc = tty_softc(tp); UCOM_MTX_ASSERT(sc, MA_OWNED); DPRINTF("tp=%p\n", tp); if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { DPRINTF("tp=%p already closed\n", tp); return; } ucom_shutdown(sc); ucom_queue_command(sc, ucom_cfg_close, NULL, &sc->sc_close_task[0].hdr, &sc->sc_close_task[1].hdr); sc->sc_flag &= ~(UCOM_FLAG_HL_READY | UCOM_FLAG_RTS_IFLOW); if (sc->sc_callback->ucom_stop_read) { (sc->sc_callback->ucom_stop_read) (sc); } } static void ucom_inwakeup(struct tty *tp) { struct ucom_softc *sc = tty_softc(tp); uint16_t pos; if (sc == NULL) return; UCOM_MTX_ASSERT(sc, MA_OWNED); DPRINTF("tp=%p\n", tp); if (ttydisc_can_bypass(tp) != 0 || (sc->sc_flag & UCOM_FLAG_HL_READY) == 0 || (sc->sc_flag & UCOM_FLAG_INWAKEUP) != 0) { return; } /* prevent recursion */ sc->sc_flag |= UCOM_FLAG_INWAKEUP; pos = sc->sc_jitterbuf_out; while (sc->sc_jitterbuf_in != pos) { int c; c = (char)sc->sc_jitterbuf[pos]; if (ttydisc_rint(tp, c, 0) == -1) break; pos++; if (pos >= UCOM_JITTERBUF_SIZE) pos -= UCOM_JITTERBUF_SIZE; } sc->sc_jitterbuf_out = pos; /* clear RTS in async fashion */ if ((sc->sc_jitterbuf_in == pos) && (sc->sc_flag & UCOM_FLAG_RTS_IFLOW)) ucom_rts(sc, 0); sc->sc_flag &= ~UCOM_FLAG_INWAKEUP; } static int ucom_ioctl(struct tty *tp, u_long cmd, caddr_t data, struct thread *td) { struct ucom_softc *sc = tty_softc(tp); int error; UCOM_MTX_ASSERT(sc, MA_OWNED); if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { return (EIO); } DPRINTF("cmd = 0x%08lx\n", cmd); switch (cmd) { #if 0 case TIOCSRING: ucom_ring(sc, 1); error = 0; break; case TIOCCRING: ucom_ring(sc, 0); error = 0; break; #endif case TIOCSBRK: ucom_break(sc, 1); error = 0; break; case TIOCCBRK: ucom_break(sc, 0); error = 0; break; default: if (sc->sc_callback->ucom_ioctl) { error = (sc->sc_callback->ucom_ioctl) (sc, cmd, data, 0, td); } else { error = ENOIOCTL; } if (error == ENOIOCTL) error = pps_ioctl(cmd, data, &sc->sc_pps); break; } return (error); } static int ucom_modem(struct tty *tp, int sigon, int sigoff) { struct ucom_softc *sc = tty_softc(tp); uint8_t onoff; UCOM_MTX_ASSERT(sc, MA_OWNED); if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { return (0); } if ((sigon == 0) && (sigoff == 0)) { if (sc->sc_mcr & SER_DTR) { sigon |= SER_DTR; } if (sc->sc_mcr & SER_RTS) { sigon |= SER_RTS; } if (sc->sc_msr & SER_CTS) { sigon |= SER_CTS; } if (sc->sc_msr & SER_DCD) { sigon |= SER_DCD; } if (sc->sc_msr & SER_DSR) { sigon |= SER_DSR; } if (sc->sc_msr & SER_RI) { sigon |= SER_RI; } return (sigon); } if (sigon & SER_DTR) { sc->sc_mcr |= SER_DTR; } if (sigoff & SER_DTR) { sc->sc_mcr &= ~SER_DTR; } if (sigon & SER_RTS) { sc->sc_mcr |= SER_RTS; } if (sigoff & SER_RTS) { sc->sc_mcr &= ~SER_RTS; } onoff = (sc->sc_mcr & SER_DTR) ? 1 : 0; ucom_dtr(sc, onoff); onoff = (sc->sc_mcr & SER_RTS) ? 1 : 0; ucom_rts(sc, onoff); return (0); } static void ucom_cfg_line_state(struct usb_proc_msg *_task) { struct ucom_cfg_task *task = (struct ucom_cfg_task *)_task; struct ucom_softc *sc = task->sc; uint8_t notch_bits; uint8_t any_bits; uint8_t prev_value; uint8_t last_value; uint8_t mask; if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) { return; } mask = 0; /* compute callback mask */ if (sc->sc_callback->ucom_cfg_set_dtr) mask |= UCOM_LS_DTR; if (sc->sc_callback->ucom_cfg_set_rts) mask |= UCOM_LS_RTS; if (sc->sc_callback->ucom_cfg_set_break) mask |= UCOM_LS_BREAK; if (sc->sc_callback->ucom_cfg_set_ring) mask |= UCOM_LS_RING; /* compute the bits we are to program */ notch_bits = (sc->sc_pls_set & sc->sc_pls_clr) & mask; any_bits = (sc->sc_pls_set | sc->sc_pls_clr) & mask; prev_value = sc->sc_pls_curr ^ notch_bits; last_value = sc->sc_pls_curr; /* reset programmed line state */ sc->sc_pls_curr = 0; sc->sc_pls_set = 0; sc->sc_pls_clr = 0; /* ensure that we don't lose any levels */ if (notch_bits & UCOM_LS_DTR) sc->sc_callback->ucom_cfg_set_dtr(sc, (prev_value & UCOM_LS_DTR) ? 1 : 0); if (notch_bits & UCOM_LS_RTS) sc->sc_callback->ucom_cfg_set_rts(sc, (prev_value & UCOM_LS_RTS) ? 1 : 0); if (notch_bits & UCOM_LS_BREAK) sc->sc_callback->ucom_cfg_set_break(sc, (prev_value & UCOM_LS_BREAK) ? 1 : 0); if (notch_bits & UCOM_LS_RING) sc->sc_callback->ucom_cfg_set_ring(sc, (prev_value & UCOM_LS_RING) ? 1 : 0); /* set last value */ if (any_bits & UCOM_LS_DTR) sc->sc_callback->ucom_cfg_set_dtr(sc, (last_value & UCOM_LS_DTR) ? 1 : 0); if (any_bits & UCOM_LS_RTS) sc->sc_callback->ucom_cfg_set_rts(sc, (last_value & UCOM_LS_RTS) ? 1 : 0); if (any_bits & UCOM_LS_BREAK) sc->sc_callback->ucom_cfg_set_break(sc, (last_value & UCOM_LS_BREAK) ? 1 : 0); if (any_bits & UCOM_LS_RING) sc->sc_callback->ucom_cfg_set_ring(sc, (last_value & UCOM_LS_RING) ? 1 : 0); } static void ucom_line_state(struct ucom_softc *sc, uint8_t set_bits, uint8_t clear_bits) { UCOM_MTX_ASSERT(sc, MA_OWNED); if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { return; } DPRINTF("on=0x%02x, off=0x%02x\n", set_bits, clear_bits); /* update current programmed line state */ sc->sc_pls_curr |= set_bits; sc->sc_pls_curr &= ~clear_bits; sc->sc_pls_set |= set_bits; sc->sc_pls_clr |= clear_bits; /* defer driver programming */ ucom_queue_command(sc, ucom_cfg_line_state, NULL, &sc->sc_line_state_task[0].hdr, &sc->sc_line_state_task[1].hdr); } static void ucom_ring(struct ucom_softc *sc, uint8_t onoff) { DPRINTF("onoff = %d\n", onoff); if (onoff) ucom_line_state(sc, UCOM_LS_RING, 0); else ucom_line_state(sc, 0, UCOM_LS_RING); } static void ucom_break(struct ucom_softc *sc, uint8_t onoff) { DPRINTF("onoff = %d\n", onoff); if (onoff) ucom_line_state(sc, UCOM_LS_BREAK, 0); else ucom_line_state(sc, 0, UCOM_LS_BREAK); } static void ucom_dtr(struct ucom_softc *sc, uint8_t onoff) { DPRINTF("onoff = %d\n", onoff); if (onoff) ucom_line_state(sc, UCOM_LS_DTR, 0); else ucom_line_state(sc, 0, UCOM_LS_DTR); } static void ucom_rts(struct ucom_softc *sc, uint8_t onoff) { DPRINTF("onoff = %d\n", onoff); if (onoff) ucom_line_state(sc, UCOM_LS_RTS, 0); else ucom_line_state(sc, 0, UCOM_LS_RTS); } static void ucom_cfg_status_change(struct usb_proc_msg *_task) { struct ucom_cfg_task *task = (struct ucom_cfg_task *)_task; struct ucom_softc *sc = task->sc; struct tty *tp; uint8_t new_msr; uint8_t new_lsr; uint8_t msr_delta; uint8_t lsr_delta; tp = sc->sc_tty; UCOM_MTX_ASSERT(sc, MA_OWNED); if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) { return; } if (sc->sc_callback->ucom_cfg_get_status == NULL) { return; } /* get status */ new_msr = 0; new_lsr = 0; (sc->sc_callback->ucom_cfg_get_status) (sc, &new_lsr, &new_msr); if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { /* TTY device closed */ return; } msr_delta = (sc->sc_msr ^ new_msr); lsr_delta = (sc->sc_lsr ^ new_lsr); sc->sc_msr = new_msr; sc->sc_lsr = new_lsr; /* * Time pulse counting support. Note that both CTS and DCD are * active-low signals. The status bit is high to indicate that * the signal on the line is low, which corresponds to a PPS * clear event. */ switch(ucom_pps_mode) { case 1: if ((sc->sc_pps.ppsparam.mode & PPS_CAPTUREBOTH) && (msr_delta & SER_CTS)) { pps_capture(&sc->sc_pps); pps_event(&sc->sc_pps, (sc->sc_msr & SER_CTS) ? PPS_CAPTURECLEAR : PPS_CAPTUREASSERT); } break; case 2: if ((sc->sc_pps.ppsparam.mode & PPS_CAPTUREBOTH) && (msr_delta & SER_DCD)) { pps_capture(&sc->sc_pps); pps_event(&sc->sc_pps, (sc->sc_msr & SER_DCD) ? PPS_CAPTURECLEAR : PPS_CAPTUREASSERT); } break; default: break; } if (msr_delta & SER_DCD) { int onoff = (sc->sc_msr & SER_DCD) ? 1 : 0; DPRINTF("DCD changed to %d\n", onoff); ttydisc_modem(tp, onoff); } if ((lsr_delta & ULSR_BI) && (sc->sc_lsr & ULSR_BI)) { DPRINTF("BREAK detected\n"); ttydisc_rint(tp, 0, TRE_BREAK); ttydisc_rint_done(tp); } if ((lsr_delta & ULSR_FE) && (sc->sc_lsr & ULSR_FE)) { DPRINTF("Frame error detected\n"); ttydisc_rint(tp, 0, TRE_FRAMING); ttydisc_rint_done(tp); } if ((lsr_delta & ULSR_PE) && (sc->sc_lsr & ULSR_PE)) { DPRINTF("Parity error detected\n"); ttydisc_rint(tp, 0, TRE_PARITY); ttydisc_rint_done(tp); } } void ucom_status_change(struct ucom_softc *sc) { UCOM_MTX_ASSERT(sc, MA_OWNED); if (sc->sc_flag & UCOM_FLAG_CONSOLE) return; /* not supported */ if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { return; } DPRINTF("\n"); ucom_queue_command(sc, ucom_cfg_status_change, NULL, &sc->sc_status_task[0].hdr, &sc->sc_status_task[1].hdr); } static void ucom_cfg_param(struct usb_proc_msg *_task) { struct ucom_param_task *task = (struct ucom_param_task *)_task; struct ucom_softc *sc = task->sc; if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) { return; } if (sc->sc_callback->ucom_cfg_param == NULL) { return; } (sc->sc_callback->ucom_cfg_param) (sc, &task->termios_copy); /* wait a little */ usb_pause_mtx(sc->sc_mtx, hz / 10); } static int ucom_param(struct tty *tp, struct termios *t) { struct ucom_softc *sc = tty_softc(tp); uint8_t opened; int error; UCOM_MTX_ASSERT(sc, MA_OWNED); opened = 0; error = 0; if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { /* XXX the TTY layer should call "open()" first! */ /* * Not quite: Its ordering is partly backwards, but * some parameters must be set early in ttydev_open(), * possibly before calling ttydevsw_open(). */ error = ucom_open(tp); if (error) goto done; opened = 1; } DPRINTF("sc = %p\n", sc); /* Check requested parameters. */ if (t->c_ispeed && (t->c_ispeed != t->c_ospeed)) { /* XXX c_ospeed == 0 is perfectly valid. */ DPRINTF("mismatch ispeed and ospeed\n"); error = EINVAL; goto done; } t->c_ispeed = t->c_ospeed; if (sc->sc_callback->ucom_pre_param) { /* Let the lower layer verify the parameters */ error = (sc->sc_callback->ucom_pre_param) (sc, t); if (error) { DPRINTF("callback error = %d\n", error); goto done; } } /* Disable transfers */ sc->sc_flag &= ~UCOM_FLAG_GP_DATA; /* Queue baud rate programming command first */ ucom_queue_command(sc, ucom_cfg_param, t, &sc->sc_param_task[0].hdr, &sc->sc_param_task[1].hdr); /* Queue transfer enable command last */ ucom_queue_command(sc, ucom_cfg_start_transfers, NULL, &sc->sc_start_task[0].hdr, &sc->sc_start_task[1].hdr); if (t->c_cflag & CRTS_IFLOW) { sc->sc_flag |= UCOM_FLAG_RTS_IFLOW; } else if (sc->sc_flag & UCOM_FLAG_RTS_IFLOW) { sc->sc_flag &= ~UCOM_FLAG_RTS_IFLOW; ucom_modem(tp, SER_RTS, 0); } done: if (error) { if (opened) { ucom_close(tp); } } return (error); } static void ucom_outwakeup(struct tty *tp) { struct ucom_softc *sc = tty_softc(tp); UCOM_MTX_ASSERT(sc, MA_OWNED); DPRINTF("sc = %p\n", sc); if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { /* The higher layer is not ready */ return; } ucom_start_transfers(sc); } /*------------------------------------------------------------------------* * ucom_get_data * * Return values: * 0: No data is available. * Else: Data is available. *------------------------------------------------------------------------*/ uint8_t ucom_get_data(struct ucom_softc *sc, struct usb_page_cache *pc, uint32_t offset, uint32_t len, uint32_t *actlen) { struct usb_page_search res; struct tty *tp = sc->sc_tty; uint32_t cnt; uint32_t offset_orig; UCOM_MTX_ASSERT(sc, MA_OWNED); if (sc->sc_flag & UCOM_FLAG_CONSOLE) { unsigned int temp; /* get total TX length */ temp = ucom_cons_tx_high - ucom_cons_tx_low; temp %= UCOM_CONS_BUFSIZE; /* limit TX length */ if (temp > (UCOM_CONS_BUFSIZE - ucom_cons_tx_low)) temp = (UCOM_CONS_BUFSIZE - ucom_cons_tx_low); if (temp > len) temp = len; /* copy in data */ usbd_copy_in(pc, offset, ucom_cons_tx_buf + ucom_cons_tx_low, temp); /* update counters */ ucom_cons_tx_low += temp; ucom_cons_tx_low %= UCOM_CONS_BUFSIZE; /* store actual length */ *actlen = temp; return (temp ? 1 : 0); } if (tty_gone(tp) || !(sc->sc_flag & UCOM_FLAG_GP_DATA)) { actlen[0] = 0; return (0); /* multiport device polling */ } offset_orig = offset; while (len != 0) { usbd_get_page(pc, offset, &res); if (res.length > len) { res.length = len; } /* copy data directly into USB buffer */ cnt = ttydisc_getc(tp, res.buffer, res.length); offset += cnt; len -= cnt; if (cnt < res.length) { /* end of buffer */ break; } } actlen[0] = offset - offset_orig; DPRINTF("cnt=%d\n", actlen[0]); if (actlen[0] == 0) { return (0); } return (1); } void ucom_put_data(struct ucom_softc *sc, struct usb_page_cache *pc, uint32_t offset, uint32_t len) { struct usb_page_search res; struct tty *tp = sc->sc_tty; char *buf; uint32_t cnt; UCOM_MTX_ASSERT(sc, MA_OWNED); if (sc->sc_flag & UCOM_FLAG_CONSOLE) { unsigned int temp; /* get maximum RX length */ temp = (UCOM_CONS_BUFSIZE - 1) - ucom_cons_rx_high + ucom_cons_rx_low; temp %= UCOM_CONS_BUFSIZE; /* limit RX length */ if (temp > (UCOM_CONS_BUFSIZE - ucom_cons_rx_high)) temp = (UCOM_CONS_BUFSIZE - ucom_cons_rx_high); if (temp > len) temp = len; /* copy out data */ usbd_copy_out(pc, offset, ucom_cons_rx_buf + ucom_cons_rx_high, temp); /* update counters */ ucom_cons_rx_high += temp; ucom_cons_rx_high %= UCOM_CONS_BUFSIZE; return; } if (tty_gone(tp)) return; /* multiport device polling */ if (len == 0) return; /* no data */ /* set a flag to prevent recursation ? */ while (len > 0) { usbd_get_page(pc, offset, &res); if (res.length > len) { res.length = len; } len -= res.length; offset += res.length; /* pass characters to tty layer */ buf = res.buffer; cnt = res.length; /* first check if we can pass the buffer directly */ if (ttydisc_can_bypass(tp)) { /* clear any jitter buffer */ sc->sc_jitterbuf_in = 0; sc->sc_jitterbuf_out = 0; if (ttydisc_rint_bypass(tp, buf, cnt) != cnt) { DPRINTF("tp=%p, data lost\n", tp); } continue; } /* need to loop */ for (cnt = 0; cnt != res.length; cnt++) { if (sc->sc_jitterbuf_in != sc->sc_jitterbuf_out || ttydisc_rint(tp, buf[cnt], 0) == -1) { uint16_t end; uint16_t pos; pos = sc->sc_jitterbuf_in; end = sc->sc_jitterbuf_out + UCOM_JITTERBUF_SIZE - 1; if (end >= UCOM_JITTERBUF_SIZE) end -= UCOM_JITTERBUF_SIZE; for (; cnt != res.length; cnt++) { if (pos == end) break; sc->sc_jitterbuf[pos] = buf[cnt]; pos++; if (pos >= UCOM_JITTERBUF_SIZE) pos -= UCOM_JITTERBUF_SIZE; } sc->sc_jitterbuf_in = pos; /* set RTS in async fashion */ if (sc->sc_flag & UCOM_FLAG_RTS_IFLOW) ucom_rts(sc, 1); DPRINTF("tp=%p, lost %d " "chars\n", tp, res.length - cnt); break; } } } ttydisc_rint_done(tp); } static void ucom_free(void *xsc) { struct ucom_softc *sc = xsc; if (sc->sc_callback->ucom_free != NULL) sc->sc_callback->ucom_free(sc); else ucom_unref(sc->sc_super); mtx_lock(&ucom_mtx); ucom_close_refs--; mtx_unlock(&ucom_mtx); } static cn_probe_t ucom_cnprobe; static cn_init_t ucom_cninit; static cn_term_t ucom_cnterm; static cn_getc_t ucom_cngetc; static cn_putc_t ucom_cnputc; static cn_grab_t ucom_cngrab; static cn_ungrab_t ucom_cnungrab; CONSOLE_DRIVER(ucom); static void ucom_cnprobe(struct consdev *cp) { if (ucom_cons_unit != -1) cp->cn_pri = CN_NORMAL; else cp->cn_pri = CN_DEAD; strlcpy(cp->cn_name, "ucom", sizeof(cp->cn_name)); } static void ucom_cninit(struct consdev *cp) { } static void ucom_cnterm(struct consdev *cp) { } static void ucom_cngrab(struct consdev *cp) { } static void ucom_cnungrab(struct consdev *cp) { } static int ucom_cngetc(struct consdev *cd) { struct ucom_softc *sc = ucom_cons_softc; int c; if (sc == NULL) return (-1); UCOM_MTX_LOCK(sc); if (ucom_cons_rx_low != ucom_cons_rx_high) { c = ucom_cons_rx_buf[ucom_cons_rx_low]; ucom_cons_rx_low ++; ucom_cons_rx_low %= UCOM_CONS_BUFSIZE; } else { c = -1; } /* start USB transfers */ ucom_outwakeup(sc->sc_tty); UCOM_MTX_UNLOCK(sc); /* poll if necessary */ if (kdb_active && sc->sc_callback->ucom_poll) (sc->sc_callback->ucom_poll) (sc); return (c); } static void ucom_cnputc(struct consdev *cd, int c) { struct ucom_softc *sc = ucom_cons_softc; unsigned int temp; if (sc == NULL) return; repeat: UCOM_MTX_LOCK(sc); /* compute maximum TX length */ temp = (UCOM_CONS_BUFSIZE - 1) - ucom_cons_tx_high + ucom_cons_tx_low; temp %= UCOM_CONS_BUFSIZE; if (temp) { ucom_cons_tx_buf[ucom_cons_tx_high] = c; ucom_cons_tx_high ++; ucom_cons_tx_high %= UCOM_CONS_BUFSIZE; } /* start USB transfers */ ucom_outwakeup(sc->sc_tty); UCOM_MTX_UNLOCK(sc); /* poll if necessary */ if (kdb_active && sc->sc_callback->ucom_poll) { (sc->sc_callback->ucom_poll) (sc); /* simple flow control */ if (temp == 0) goto repeat; } } /*------------------------------------------------------------------------* * ucom_ref * * This function will increment the super UCOM reference count. *------------------------------------------------------------------------*/ void ucom_ref(struct ucom_super_softc *ssc) { mtx_lock(&ucom_mtx); ssc->sc_refs++; mtx_unlock(&ucom_mtx); } /*------------------------------------------------------------------------* * ucom_free_unit * * This function will free the super UCOM's allocated unit * number. This function can be called on a zero-initialized * structure. This function can be called multiple times. *------------------------------------------------------------------------*/ static void ucom_free_unit(struct ucom_super_softc *ssc) { if (!(ssc->sc_flag & UCOM_FLAG_FREE_UNIT)) return; ucom_unit_free(ssc->sc_unit); ssc->sc_flag &= ~UCOM_FLAG_FREE_UNIT; } /*------------------------------------------------------------------------* * ucom_unref * * This function will decrement the super UCOM reference count. * * Return values: * 0: UCOM structures are still referenced. * Else: UCOM structures are no longer referenced. *------------------------------------------------------------------------*/ int ucom_unref(struct ucom_super_softc *ssc) { int retval; mtx_lock(&ucom_mtx); retval = (ssc->sc_refs < 2); ssc->sc_refs--; mtx_unlock(&ucom_mtx); if (retval) ucom_free_unit(ssc); return (retval); } #if defined(GDB) #include static gdb_probe_f ucom_gdbprobe; static gdb_init_f ucom_gdbinit; static gdb_term_f ucom_gdbterm; static gdb_getc_f ucom_gdbgetc; static gdb_putc_f ucom_gdbputc; GDB_DBGPORT(sio, ucom_gdbprobe, ucom_gdbinit, ucom_gdbterm, ucom_gdbgetc, ucom_gdbputc); static int ucom_gdbprobe(void) { return ((ucom_cons_softc != NULL) ? 0 : -1); } static void ucom_gdbinit(void) { } static void ucom_gdbterm(void) { } static void ucom_gdbputc(int c) { ucom_cnputc(NULL, c); } static int ucom_gdbgetc(void) { return (ucom_cngetc(NULL)); } #endif Index: head/sys/kern/kern_tc.c =================================================================== --- head/sys/kern/kern_tc.c (revision 282423) +++ head/sys/kern/kern_tc.c (revision 282424) @@ -1,2041 +1,2072 @@ /*- * ---------------------------------------------------------------------------- * "THE BEER-WARE LICENSE" (Revision 42): * wrote this file. As long as you retain this notice you * can do whatever you want with this stuff. If we meet some day, and you think * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp * ---------------------------------------------------------------------------- * * Copyright (c) 2011 The FreeBSD Foundation * All rights reserved. * * Portions of this software were developed by Julien Ridoux at the University * of Melbourne under sponsorship from the FreeBSD Foundation. */ #include __FBSDID("$FreeBSD$"); #include "opt_compat.h" #include "opt_ntp.h" #include "opt_ffclock.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * A large step happens on boot. This constant detects such steps. * It is relatively small so that ntp_update_second gets called enough * in the typical 'missed a couple of seconds' case, but doesn't loop * forever when the time step is large. */ #define LARGE_STEP 200 /* * Implement a dummy timecounter which we can use until we get a real one * in the air. This allows the console and other early stuff to use * time services. */ static u_int dummy_get_timecount(struct timecounter *tc) { static u_int now; return (++now); } static struct timecounter dummy_timecounter = { dummy_get_timecount, 0, ~0u, 1000000, "dummy", -1000000 }; struct timehands { /* These fields must be initialized by the driver. */ struct timecounter *th_counter; int64_t th_adjustment; uint64_t th_scale; u_int th_offset_count; struct bintime th_offset; struct timeval th_microtime; struct timespec th_nanotime; /* Fields not to be copied in tc_windup start with th_generation. */ volatile u_int th_generation; struct timehands *th_next; }; static struct timehands th0; static struct timehands th9 = { NULL, 0, 0, 0, {0, 0}, {0, 0}, {0, 0}, 0, &th0}; static struct timehands th8 = { NULL, 0, 0, 0, {0, 0}, {0, 0}, {0, 0}, 0, &th9}; static struct timehands th7 = { NULL, 0, 0, 0, {0, 0}, {0, 0}, {0, 0}, 0, &th8}; static struct timehands th6 = { NULL, 0, 0, 0, {0, 0}, {0, 0}, {0, 0}, 0, &th7}; static struct timehands th5 = { NULL, 0, 0, 0, {0, 0}, {0, 0}, {0, 0}, 0, &th6}; static struct timehands th4 = { NULL, 0, 0, 0, {0, 0}, {0, 0}, {0, 0}, 0, &th5}; static struct timehands th3 = { NULL, 0, 0, 0, {0, 0}, {0, 0}, {0, 0}, 0, &th4}; static struct timehands th2 = { NULL, 0, 0, 0, {0, 0}, {0, 0}, {0, 0}, 0, &th3}; static struct timehands th1 = { NULL, 0, 0, 0, {0, 0}, {0, 0}, {0, 0}, 0, &th2}; static struct timehands th0 = { &dummy_timecounter, 0, (uint64_t)-1 / 1000000, 0, {1, 0}, {0, 0}, {0, 0}, 1, &th1 }; static struct timehands *volatile timehands = &th0; struct timecounter *timecounter = &dummy_timecounter; static struct timecounter *timecounters = &dummy_timecounter; int tc_min_ticktock_freq = 1; volatile time_t time_second = 1; volatile time_t time_uptime = 1; struct bintime boottimebin; struct timeval boottime; static int sysctl_kern_boottime(SYSCTL_HANDLER_ARGS); SYSCTL_PROC(_kern, KERN_BOOTTIME, boottime, CTLTYPE_STRUCT|CTLFLAG_RD, NULL, 0, sysctl_kern_boottime, "S,timeval", "System boottime"); SYSCTL_NODE(_kern, OID_AUTO, timecounter, CTLFLAG_RW, 0, ""); static SYSCTL_NODE(_kern_timecounter, OID_AUTO, tc, CTLFLAG_RW, 0, ""); static int timestepwarnings; SYSCTL_INT(_kern_timecounter, OID_AUTO, stepwarnings, CTLFLAG_RW, ×tepwarnings, 0, "Log time steps"); struct bintime bt_timethreshold; struct bintime bt_tickthreshold; sbintime_t sbt_timethreshold; sbintime_t sbt_tickthreshold; struct bintime tc_tick_bt; sbintime_t tc_tick_sbt; int tc_precexp; int tc_timepercentage = TC_DEFAULTPERC; static int sysctl_kern_timecounter_adjprecision(SYSCTL_HANDLER_ARGS); SYSCTL_PROC(_kern_timecounter, OID_AUTO, alloweddeviation, CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, 0, 0, sysctl_kern_timecounter_adjprecision, "I", "Allowed time interval deviation in percents"); static void tc_windup(void); static void cpu_tick_calibrate(int); void dtrace_getnanotime(struct timespec *tsp); static int sysctl_kern_boottime(SYSCTL_HANDLER_ARGS) { #ifndef __mips__ #ifdef SCTL_MASK32 int tv[2]; if (req->flags & SCTL_MASK32) { tv[0] = boottime.tv_sec; tv[1] = boottime.tv_usec; return SYSCTL_OUT(req, tv, sizeof(tv)); } else #endif #endif return SYSCTL_OUT(req, &boottime, sizeof(boottime)); } static int sysctl_kern_timecounter_get(SYSCTL_HANDLER_ARGS) { u_int ncount; struct timecounter *tc = arg1; ncount = tc->tc_get_timecount(tc); return sysctl_handle_int(oidp, &ncount, 0, req); } static int sysctl_kern_timecounter_freq(SYSCTL_HANDLER_ARGS) { uint64_t freq; struct timecounter *tc = arg1; freq = tc->tc_frequency; return sysctl_handle_64(oidp, &freq, 0, req); } /* * Return the difference between the timehands' counter value now and what * was when we copied it to the timehands' offset_count. */ static __inline u_int tc_delta(struct timehands *th) { struct timecounter *tc; tc = th->th_counter; return ((tc->tc_get_timecount(tc) - th->th_offset_count) & tc->tc_counter_mask); } /* * Functions for reading the time. We have to loop until we are sure that * the timehands that we operated on was not updated under our feet. See * the comment in for a description of these 12 functions. */ #ifdef FFCLOCK void fbclock_binuptime(struct bintime *bt) { struct timehands *th; unsigned int gen; do { th = timehands; gen = th->th_generation; *bt = th->th_offset; bintime_addx(bt, th->th_scale * tc_delta(th)); } while (gen == 0 || gen != th->th_generation); } void fbclock_nanouptime(struct timespec *tsp) { struct bintime bt; fbclock_binuptime(&bt); bintime2timespec(&bt, tsp); } void fbclock_microuptime(struct timeval *tvp) { struct bintime bt; fbclock_binuptime(&bt); bintime2timeval(&bt, tvp); } void fbclock_bintime(struct bintime *bt) { fbclock_binuptime(bt); bintime_add(bt, &boottimebin); } void fbclock_nanotime(struct timespec *tsp) { struct bintime bt; fbclock_bintime(&bt); bintime2timespec(&bt, tsp); } void fbclock_microtime(struct timeval *tvp) { struct bintime bt; fbclock_bintime(&bt); bintime2timeval(&bt, tvp); } void fbclock_getbinuptime(struct bintime *bt) { struct timehands *th; unsigned int gen; do { th = timehands; gen = th->th_generation; *bt = th->th_offset; } while (gen == 0 || gen != th->th_generation); } void fbclock_getnanouptime(struct timespec *tsp) { struct timehands *th; unsigned int gen; do { th = timehands; gen = th->th_generation; bintime2timespec(&th->th_offset, tsp); } while (gen == 0 || gen != th->th_generation); } void fbclock_getmicrouptime(struct timeval *tvp) { struct timehands *th; unsigned int gen; do { th = timehands; gen = th->th_generation; bintime2timeval(&th->th_offset, tvp); } while (gen == 0 || gen != th->th_generation); } void fbclock_getbintime(struct bintime *bt) { struct timehands *th; unsigned int gen; do { th = timehands; gen = th->th_generation; *bt = th->th_offset; } while (gen == 0 || gen != th->th_generation); bintime_add(bt, &boottimebin); } void fbclock_getnanotime(struct timespec *tsp) { struct timehands *th; unsigned int gen; do { th = timehands; gen = th->th_generation; *tsp = th->th_nanotime; } while (gen == 0 || gen != th->th_generation); } void fbclock_getmicrotime(struct timeval *tvp) { struct timehands *th; unsigned int gen; do { th = timehands; gen = th->th_generation; *tvp = th->th_microtime; } while (gen == 0 || gen != th->th_generation); } #else /* !FFCLOCK */ void binuptime(struct bintime *bt) { struct timehands *th; u_int gen; do { th = timehands; gen = th->th_generation; *bt = th->th_offset; bintime_addx(bt, th->th_scale * tc_delta(th)); } while (gen == 0 || gen != th->th_generation); } void nanouptime(struct timespec *tsp) { struct bintime bt; binuptime(&bt); bintime2timespec(&bt, tsp); } void microuptime(struct timeval *tvp) { struct bintime bt; binuptime(&bt); bintime2timeval(&bt, tvp); } void bintime(struct bintime *bt) { binuptime(bt); bintime_add(bt, &boottimebin); } void nanotime(struct timespec *tsp) { struct bintime bt; bintime(&bt); bintime2timespec(&bt, tsp); } void microtime(struct timeval *tvp) { struct bintime bt; bintime(&bt); bintime2timeval(&bt, tvp); } void getbinuptime(struct bintime *bt) { struct timehands *th; u_int gen; do { th = timehands; gen = th->th_generation; *bt = th->th_offset; } while (gen == 0 || gen != th->th_generation); } void getnanouptime(struct timespec *tsp) { struct timehands *th; u_int gen; do { th = timehands; gen = th->th_generation; bintime2timespec(&th->th_offset, tsp); } while (gen == 0 || gen != th->th_generation); } void getmicrouptime(struct timeval *tvp) { struct timehands *th; u_int gen; do { th = timehands; gen = th->th_generation; bintime2timeval(&th->th_offset, tvp); } while (gen == 0 || gen != th->th_generation); } void getbintime(struct bintime *bt) { struct timehands *th; u_int gen; do { th = timehands; gen = th->th_generation; *bt = th->th_offset; } while (gen == 0 || gen != th->th_generation); bintime_add(bt, &boottimebin); } void getnanotime(struct timespec *tsp) { struct timehands *th; u_int gen; do { th = timehands; gen = th->th_generation; *tsp = th->th_nanotime; } while (gen == 0 || gen != th->th_generation); } void getmicrotime(struct timeval *tvp) { struct timehands *th; u_int gen; do { th = timehands; gen = th->th_generation; *tvp = th->th_microtime; } while (gen == 0 || gen != th->th_generation); } #endif /* FFCLOCK */ #ifdef FFCLOCK /* * Support for feed-forward synchronization algorithms. This is heavily inspired * by the timehands mechanism but kept independent from it. *_windup() functions * have some connection to avoid accessing the timecounter hardware more than * necessary. */ /* Feed-forward clock estimates kept updated by the synchronization daemon. */ struct ffclock_estimate ffclock_estimate; struct bintime ffclock_boottime; /* Feed-forward boot time estimate. */ uint32_t ffclock_status; /* Feed-forward clock status. */ int8_t ffclock_updated; /* New estimates are available. */ struct mtx ffclock_mtx; /* Mutex on ffclock_estimate. */ struct fftimehands { struct ffclock_estimate cest; struct bintime tick_time; struct bintime tick_time_lerp; ffcounter tick_ffcount; uint64_t period_lerp; volatile uint8_t gen; struct fftimehands *next; }; #define NUM_ELEMENTS(x) (sizeof(x) / sizeof(*x)) static struct fftimehands ffth[10]; static struct fftimehands *volatile fftimehands = ffth; static void ffclock_init(void) { struct fftimehands *cur; struct fftimehands *last; memset(ffth, 0, sizeof(ffth)); last = ffth + NUM_ELEMENTS(ffth) - 1; for (cur = ffth; cur < last; cur++) cur->next = cur + 1; last->next = ffth; ffclock_updated = 0; ffclock_status = FFCLOCK_STA_UNSYNC; mtx_init(&ffclock_mtx, "ffclock lock", NULL, MTX_DEF); } /* * Reset the feed-forward clock estimates. Called from inittodr() to get things * kick started and uses the timecounter nominal frequency as a first period * estimate. Note: this function may be called several time just after boot. * Note: this is the only function that sets the value of boot time for the * monotonic (i.e. uptime) version of the feed-forward clock. */ void ffclock_reset_clock(struct timespec *ts) { struct timecounter *tc; struct ffclock_estimate cest; tc = timehands->th_counter; memset(&cest, 0, sizeof(struct ffclock_estimate)); timespec2bintime(ts, &ffclock_boottime); timespec2bintime(ts, &(cest.update_time)); ffclock_read_counter(&cest.update_ffcount); cest.leapsec_next = 0; cest.period = ((1ULL << 63) / tc->tc_frequency) << 1; cest.errb_abs = 0; cest.errb_rate = 0; cest.status = FFCLOCK_STA_UNSYNC; cest.leapsec_total = 0; cest.leapsec = 0; mtx_lock(&ffclock_mtx); bcopy(&cest, &ffclock_estimate, sizeof(struct ffclock_estimate)); ffclock_updated = INT8_MAX; mtx_unlock(&ffclock_mtx); printf("ffclock reset: %s (%llu Hz), time = %ld.%09lu\n", tc->tc_name, (unsigned long long)tc->tc_frequency, (long)ts->tv_sec, (unsigned long)ts->tv_nsec); } /* * Sub-routine to convert a time interval measured in RAW counter units to time * in seconds stored in bintime format. * NOTE: bintime_mul requires u_int, but the value of the ffcounter may be * larger than the max value of u_int (on 32 bit architecture). Loop to consume * extra cycles. */ static void ffclock_convert_delta(ffcounter ffdelta, uint64_t period, struct bintime *bt) { struct bintime bt2; ffcounter delta, delta_max; delta_max = (1ULL << (8 * sizeof(unsigned int))) - 1; bintime_clear(bt); do { if (ffdelta > delta_max) delta = delta_max; else delta = ffdelta; bt2.sec = 0; bt2.frac = period; bintime_mul(&bt2, (unsigned int)delta); bintime_add(bt, &bt2); ffdelta -= delta; } while (ffdelta > 0); } /* * Update the fftimehands. * Push the tick ffcount and time(s) forward based on current clock estimate. * The conversion from ffcounter to bintime relies on the difference clock * principle, whose accuracy relies on computing small time intervals. If a new * clock estimate has been passed by the synchronisation daemon, make it * current, and compute the linear interpolation for monotonic time if needed. */ static void ffclock_windup(unsigned int delta) { struct ffclock_estimate *cest; struct fftimehands *ffth; struct bintime bt, gap_lerp; ffcounter ffdelta; uint64_t frac; unsigned int polling; uint8_t forward_jump, ogen; /* * Pick the next timehand, copy current ffclock estimates and move tick * times and counter forward. */ forward_jump = 0; ffth = fftimehands->next; ogen = ffth->gen; ffth->gen = 0; cest = &ffth->cest; bcopy(&fftimehands->cest, cest, sizeof(struct ffclock_estimate)); ffdelta = (ffcounter)delta; ffth->period_lerp = fftimehands->period_lerp; ffth->tick_time = fftimehands->tick_time; ffclock_convert_delta(ffdelta, cest->period, &bt); bintime_add(&ffth->tick_time, &bt); ffth->tick_time_lerp = fftimehands->tick_time_lerp; ffclock_convert_delta(ffdelta, ffth->period_lerp, &bt); bintime_add(&ffth->tick_time_lerp, &bt); ffth->tick_ffcount = fftimehands->tick_ffcount + ffdelta; /* * Assess the status of the clock, if the last update is too old, it is * likely the synchronisation daemon is dead and the clock is free * running. */ if (ffclock_updated == 0) { ffdelta = ffth->tick_ffcount - cest->update_ffcount; ffclock_convert_delta(ffdelta, cest->period, &bt); if (bt.sec > 2 * FFCLOCK_SKM_SCALE) ffclock_status |= FFCLOCK_STA_UNSYNC; } /* * If available, grab updated clock estimates and make them current. * Recompute time at this tick using the updated estimates. The clock * estimates passed the feed-forward synchronisation daemon may result * in time conversion that is not monotonically increasing (just after * the update). time_lerp is a particular linear interpolation over the * synchronisation algo polling period that ensures monotonicity for the * clock ids requesting it. */ if (ffclock_updated > 0) { bcopy(&ffclock_estimate, cest, sizeof(struct ffclock_estimate)); ffdelta = ffth->tick_ffcount - cest->update_ffcount; ffth->tick_time = cest->update_time; ffclock_convert_delta(ffdelta, cest->period, &bt); bintime_add(&ffth->tick_time, &bt); /* ffclock_reset sets ffclock_updated to INT8_MAX */ if (ffclock_updated == INT8_MAX) ffth->tick_time_lerp = ffth->tick_time; if (bintime_cmp(&ffth->tick_time, &ffth->tick_time_lerp, >)) forward_jump = 1; else forward_jump = 0; bintime_clear(&gap_lerp); if (forward_jump) { gap_lerp = ffth->tick_time; bintime_sub(&gap_lerp, &ffth->tick_time_lerp); } else { gap_lerp = ffth->tick_time_lerp; bintime_sub(&gap_lerp, &ffth->tick_time); } /* * The reset from the RTC clock may be far from accurate, and * reducing the gap between real time and interpolated time * could take a very long time if the interpolated clock insists * on strict monotonicity. The clock is reset under very strict * conditions (kernel time is known to be wrong and * synchronization daemon has been restarted recently. * ffclock_boottime absorbs the jump to ensure boot time is * correct and uptime functions stay consistent. */ if (((ffclock_status & FFCLOCK_STA_UNSYNC) == FFCLOCK_STA_UNSYNC) && ((cest->status & FFCLOCK_STA_UNSYNC) == 0) && ((cest->status & FFCLOCK_STA_WARMUP) == FFCLOCK_STA_WARMUP)) { if (forward_jump) bintime_add(&ffclock_boottime, &gap_lerp); else bintime_sub(&ffclock_boottime, &gap_lerp); ffth->tick_time_lerp = ffth->tick_time; bintime_clear(&gap_lerp); } ffclock_status = cest->status; ffth->period_lerp = cest->period; /* * Compute corrected period used for the linear interpolation of * time. The rate of linear interpolation is capped to 5000PPM * (5ms/s). */ if (bintime_isset(&gap_lerp)) { ffdelta = cest->update_ffcount; ffdelta -= fftimehands->cest.update_ffcount; ffclock_convert_delta(ffdelta, cest->period, &bt); polling = bt.sec; bt.sec = 0; bt.frac = 5000000 * (uint64_t)18446744073LL; bintime_mul(&bt, polling); if (bintime_cmp(&gap_lerp, &bt, >)) gap_lerp = bt; /* Approximate 1 sec by 1-(1/2^64) to ease arithmetic */ frac = 0; if (gap_lerp.sec > 0) { frac -= 1; frac /= ffdelta / gap_lerp.sec; } frac += gap_lerp.frac / ffdelta; if (forward_jump) ffth->period_lerp += frac; else ffth->period_lerp -= frac; } ffclock_updated = 0; } if (++ogen == 0) ogen = 1; ffth->gen = ogen; fftimehands = ffth; } /* * Adjust the fftimehands when the timecounter is changed. Stating the obvious, * the old and new hardware counter cannot be read simultaneously. tc_windup() * does read the two counters 'back to back', but a few cycles are effectively * lost, and not accumulated in tick_ffcount. This is a fairly radical * operation for a feed-forward synchronization daemon, and it is its job to not * pushing irrelevant data to the kernel. Because there is no locking here, * simply force to ignore pending or next update to give daemon a chance to * realize the counter has changed. */ static void ffclock_change_tc(struct timehands *th) { struct fftimehands *ffth; struct ffclock_estimate *cest; struct timecounter *tc; uint8_t ogen; tc = th->th_counter; ffth = fftimehands->next; ogen = ffth->gen; ffth->gen = 0; cest = &ffth->cest; bcopy(&(fftimehands->cest), cest, sizeof(struct ffclock_estimate)); cest->period = ((1ULL << 63) / tc->tc_frequency ) << 1; cest->errb_abs = 0; cest->errb_rate = 0; cest->status |= FFCLOCK_STA_UNSYNC; ffth->tick_ffcount = fftimehands->tick_ffcount; ffth->tick_time_lerp = fftimehands->tick_time_lerp; ffth->tick_time = fftimehands->tick_time; ffth->period_lerp = cest->period; /* Do not lock but ignore next update from synchronization daemon. */ ffclock_updated--; if (++ogen == 0) ogen = 1; ffth->gen = ogen; fftimehands = ffth; } /* * Retrieve feed-forward counter and time of last kernel tick. */ void ffclock_last_tick(ffcounter *ffcount, struct bintime *bt, uint32_t flags) { struct fftimehands *ffth; uint8_t gen; /* * No locking but check generation has not changed. Also need to make * sure ffdelta is positive, i.e. ffcount > tick_ffcount. */ do { ffth = fftimehands; gen = ffth->gen; if ((flags & FFCLOCK_LERP) == FFCLOCK_LERP) *bt = ffth->tick_time_lerp; else *bt = ffth->tick_time; *ffcount = ffth->tick_ffcount; } while (gen == 0 || gen != ffth->gen); } /* * Absolute clock conversion. Low level function to convert ffcounter to * bintime. The ffcounter is converted using the current ffclock period estimate * or the "interpolated period" to ensure monotonicity. * NOTE: this conversion may have been deferred, and the clock updated since the * hardware counter has been read. */ void ffclock_convert_abs(ffcounter ffcount, struct bintime *bt, uint32_t flags) { struct fftimehands *ffth; struct bintime bt2; ffcounter ffdelta; uint8_t gen; /* * No locking but check generation has not changed. Also need to make * sure ffdelta is positive, i.e. ffcount > tick_ffcount. */ do { ffth = fftimehands; gen = ffth->gen; if (ffcount > ffth->tick_ffcount) ffdelta = ffcount - ffth->tick_ffcount; else ffdelta = ffth->tick_ffcount - ffcount; if ((flags & FFCLOCK_LERP) == FFCLOCK_LERP) { *bt = ffth->tick_time_lerp; ffclock_convert_delta(ffdelta, ffth->period_lerp, &bt2); } else { *bt = ffth->tick_time; ffclock_convert_delta(ffdelta, ffth->cest.period, &bt2); } if (ffcount > ffth->tick_ffcount) bintime_add(bt, &bt2); else bintime_sub(bt, &bt2); } while (gen == 0 || gen != ffth->gen); } /* * Difference clock conversion. * Low level function to Convert a time interval measured in RAW counter units * into bintime. The difference clock allows measuring small intervals much more * reliably than the absolute clock. */ void ffclock_convert_diff(ffcounter ffdelta, struct bintime *bt) { struct fftimehands *ffth; uint8_t gen; /* No locking but check generation has not changed. */ do { ffth = fftimehands; gen = ffth->gen; ffclock_convert_delta(ffdelta, ffth->cest.period, bt); } while (gen == 0 || gen != ffth->gen); } /* * Access to current ffcounter value. */ void ffclock_read_counter(ffcounter *ffcount) { struct timehands *th; struct fftimehands *ffth; unsigned int gen, delta; /* * ffclock_windup() called from tc_windup(), safe to rely on * th->th_generation only, for correct delta and ffcounter. */ do { th = timehands; gen = th->th_generation; ffth = fftimehands; delta = tc_delta(th); *ffcount = ffth->tick_ffcount; } while (gen == 0 || gen != th->th_generation); *ffcount += delta; } void binuptime(struct bintime *bt) { binuptime_fromclock(bt, sysclock_active); } void nanouptime(struct timespec *tsp) { nanouptime_fromclock(tsp, sysclock_active); } void microuptime(struct timeval *tvp) { microuptime_fromclock(tvp, sysclock_active); } void bintime(struct bintime *bt) { bintime_fromclock(bt, sysclock_active); } void nanotime(struct timespec *tsp) { nanotime_fromclock(tsp, sysclock_active); } void microtime(struct timeval *tvp) { microtime_fromclock(tvp, sysclock_active); } void getbinuptime(struct bintime *bt) { getbinuptime_fromclock(bt, sysclock_active); } void getnanouptime(struct timespec *tsp) { getnanouptime_fromclock(tsp, sysclock_active); } void getmicrouptime(struct timeval *tvp) { getmicrouptime_fromclock(tvp, sysclock_active); } void getbintime(struct bintime *bt) { getbintime_fromclock(bt, sysclock_active); } void getnanotime(struct timespec *tsp) { getnanotime_fromclock(tsp, sysclock_active); } void getmicrotime(struct timeval *tvp) { getmicrouptime_fromclock(tvp, sysclock_active); } #endif /* FFCLOCK */ /* * This is a clone of getnanotime and used for walltimestamps. * The dtrace_ prefix prevents fbt from creating probes for * it so walltimestamp can be safely used in all fbt probes. */ void dtrace_getnanotime(struct timespec *tsp) { struct timehands *th; u_int gen; do { th = timehands; gen = th->th_generation; *tsp = th->th_nanotime; } while (gen == 0 || gen != th->th_generation); } /* * System clock currently providing time to the system. Modifiable via sysctl * when the FFCLOCK option is defined. */ int sysclock_active = SYSCLOCK_FBCK; /* Internal NTP status and error estimates. */ extern int time_status; extern long time_esterror; /* * Take a snapshot of sysclock data which can be used to compare system clocks * and generate timestamps after the fact. */ void sysclock_getsnapshot(struct sysclock_snap *clock_snap, int fast) { struct fbclock_info *fbi; struct timehands *th; struct bintime bt; unsigned int delta, gen; #ifdef FFCLOCK ffcounter ffcount; struct fftimehands *ffth; struct ffclock_info *ffi; struct ffclock_estimate cest; ffi = &clock_snap->ff_info; #endif fbi = &clock_snap->fb_info; delta = 0; do { th = timehands; gen = th->th_generation; fbi->th_scale = th->th_scale; fbi->tick_time = th->th_offset; #ifdef FFCLOCK ffth = fftimehands; ffi->tick_time = ffth->tick_time_lerp; ffi->tick_time_lerp = ffth->tick_time_lerp; ffi->period = ffth->cest.period; ffi->period_lerp = ffth->period_lerp; clock_snap->ffcount = ffth->tick_ffcount; cest = ffth->cest; #endif if (!fast) delta = tc_delta(th); } while (gen == 0 || gen != th->th_generation); clock_snap->delta = delta; clock_snap->sysclock_active = sysclock_active; /* Record feedback clock status and error. */ clock_snap->fb_info.status = time_status; /* XXX: Very crude estimate of feedback clock error. */ bt.sec = time_esterror / 1000000; bt.frac = ((time_esterror - bt.sec) * 1000000) * (uint64_t)18446744073709ULL; clock_snap->fb_info.error = bt; #ifdef FFCLOCK if (!fast) clock_snap->ffcount += delta; /* Record feed-forward clock leap second adjustment. */ ffi->leapsec_adjustment = cest.leapsec_total; if (clock_snap->ffcount > cest.leapsec_next) ffi->leapsec_adjustment -= cest.leapsec; /* Record feed-forward clock status and error. */ clock_snap->ff_info.status = cest.status; ffcount = clock_snap->ffcount - cest.update_ffcount; ffclock_convert_delta(ffcount, cest.period, &bt); /* 18446744073709 = int(2^64/1e12), err_bound_rate in [ps/s]. */ bintime_mul(&bt, cest.errb_rate * (uint64_t)18446744073709ULL); /* 18446744073 = int(2^64 / 1e9), since err_abs in [ns]. */ bintime_addx(&bt, cest.errb_abs * (uint64_t)18446744073ULL); clock_snap->ff_info.error = bt; #endif } /* * Convert a sysclock snapshot into a struct bintime based on the specified * clock source and flags. */ int sysclock_snap2bintime(struct sysclock_snap *cs, struct bintime *bt, int whichclock, uint32_t flags) { #ifdef FFCLOCK struct bintime bt2; uint64_t period; #endif switch (whichclock) { case SYSCLOCK_FBCK: *bt = cs->fb_info.tick_time; /* If snapshot was created with !fast, delta will be >0. */ if (cs->delta > 0) bintime_addx(bt, cs->fb_info.th_scale * cs->delta); if ((flags & FBCLOCK_UPTIME) == 0) bintime_add(bt, &boottimebin); break; #ifdef FFCLOCK case SYSCLOCK_FFWD: if (flags & FFCLOCK_LERP) { *bt = cs->ff_info.tick_time_lerp; period = cs->ff_info.period_lerp; } else { *bt = cs->ff_info.tick_time; period = cs->ff_info.period; } /* If snapshot was created with !fast, delta will be >0. */ if (cs->delta > 0) { ffclock_convert_delta(cs->delta, period, &bt2); bintime_add(bt, &bt2); } /* Leap second adjustment. */ if (flags & FFCLOCK_LEAPSEC) bt->sec -= cs->ff_info.leapsec_adjustment; /* Boot time adjustment, for uptime/monotonic clocks. */ if (flags & FFCLOCK_UPTIME) bintime_sub(bt, &ffclock_boottime); break; #endif default: return (EINVAL); break; } return (0); } /* * Initialize a new timecounter and possibly use it. */ void tc_init(struct timecounter *tc) { u_int u; struct sysctl_oid *tc_root; u = tc->tc_frequency / tc->tc_counter_mask; /* XXX: We need some margin here, 10% is a guess */ u *= 11; u /= 10; if (u > hz && tc->tc_quality >= 0) { tc->tc_quality = -2000; if (bootverbose) { printf("Timecounter \"%s\" frequency %ju Hz", tc->tc_name, (uintmax_t)tc->tc_frequency); printf(" -- Insufficient hz, needs at least %u\n", u); } } else if (tc->tc_quality >= 0 || bootverbose) { printf("Timecounter \"%s\" frequency %ju Hz quality %d\n", tc->tc_name, (uintmax_t)tc->tc_frequency, tc->tc_quality); } tc->tc_next = timecounters; timecounters = tc; /* * Set up sysctl tree for this counter. */ tc_root = SYSCTL_ADD_NODE(NULL, SYSCTL_STATIC_CHILDREN(_kern_timecounter_tc), OID_AUTO, tc->tc_name, CTLFLAG_RW, 0, "timecounter description"); SYSCTL_ADD_UINT(NULL, SYSCTL_CHILDREN(tc_root), OID_AUTO, "mask", CTLFLAG_RD, &(tc->tc_counter_mask), 0, "mask for implemented bits"); SYSCTL_ADD_PROC(NULL, SYSCTL_CHILDREN(tc_root), OID_AUTO, "counter", CTLTYPE_UINT | CTLFLAG_RD, tc, sizeof(*tc), sysctl_kern_timecounter_get, "IU", "current timecounter value"); SYSCTL_ADD_PROC(NULL, SYSCTL_CHILDREN(tc_root), OID_AUTO, "frequency", CTLTYPE_U64 | CTLFLAG_RD, tc, sizeof(*tc), sysctl_kern_timecounter_freq, "QU", "timecounter frequency"); SYSCTL_ADD_INT(NULL, SYSCTL_CHILDREN(tc_root), OID_AUTO, "quality", CTLFLAG_RD, &(tc->tc_quality), 0, "goodness of time counter"); /* * Never automatically use a timecounter with negative quality. * Even though we run on the dummy counter, switching here may be * worse since this timecounter may not be monotonous. */ if (tc->tc_quality < 0) return; if (tc->tc_quality < timecounter->tc_quality) return; if (tc->tc_quality == timecounter->tc_quality && tc->tc_frequency < timecounter->tc_frequency) return; (void)tc->tc_get_timecount(tc); (void)tc->tc_get_timecount(tc); timecounter = tc; } /* Report the frequency of the current timecounter. */ uint64_t tc_getfrequency(void) { return (timehands->th_counter->tc_frequency); } /* * Step our concept of UTC. This is done by modifying our estimate of * when we booted. * XXX: not locked. */ void tc_setclock(struct timespec *ts) { struct timespec tbef, taft; struct bintime bt, bt2; cpu_tick_calibrate(1); nanotime(&tbef); timespec2bintime(ts, &bt); binuptime(&bt2); bintime_sub(&bt, &bt2); bintime_add(&bt2, &boottimebin); boottimebin = bt; bintime2timeval(&bt, &boottime); /* XXX fiddle all the little crinkly bits around the fiords... */ tc_windup(); nanotime(&taft); if (timestepwarnings) { log(LOG_INFO, "Time stepped from %jd.%09ld to %jd.%09ld (%jd.%09ld)\n", (intmax_t)tbef.tv_sec, tbef.tv_nsec, (intmax_t)taft.tv_sec, taft.tv_nsec, (intmax_t)ts->tv_sec, ts->tv_nsec); } cpu_tick_calibrate(1); } /* * Initialize the next struct timehands in the ring and make * it the active timehands. Along the way we might switch to a different * timecounter and/or do seconds processing in NTP. Slightly magic. */ static void tc_windup(void) { struct bintime bt; struct timehands *th, *tho; uint64_t scale; u_int delta, ncount, ogen; int i; time_t t; /* * Make the next timehands a copy of the current one, but do not * overwrite the generation or next pointer. While we update * the contents, the generation must be zero. */ tho = timehands; th = tho->th_next; ogen = th->th_generation; th->th_generation = 0; bcopy(tho, th, offsetof(struct timehands, th_generation)); /* * Capture a timecounter delta on the current timecounter and if * changing timecounters, a counter value from the new timecounter. * Update the offset fields accordingly. */ delta = tc_delta(th); if (th->th_counter != timecounter) ncount = timecounter->tc_get_timecount(timecounter); else ncount = 0; #ifdef FFCLOCK ffclock_windup(delta); #endif th->th_offset_count += delta; th->th_offset_count &= th->th_counter->tc_counter_mask; while (delta > th->th_counter->tc_frequency) { /* Eat complete unadjusted seconds. */ delta -= th->th_counter->tc_frequency; th->th_offset.sec++; } if ((delta > th->th_counter->tc_frequency / 2) && (th->th_scale * delta < ((uint64_t)1 << 63))) { /* The product th_scale * delta just barely overflows. */ th->th_offset.sec++; } bintime_addx(&th->th_offset, th->th_scale * delta); /* * Hardware latching timecounters may not generate interrupts on * PPS events, so instead we poll them. There is a finite risk that * the hardware might capture a count which is later than the one we * got above, and therefore possibly in the next NTP second which might * have a different rate than the current NTP second. It doesn't * matter in practice. */ if (tho->th_counter->tc_poll_pps) tho->th_counter->tc_poll_pps(tho->th_counter); /* * Deal with NTP second processing. The for loop normally * iterates at most once, but in extreme situations it might * keep NTP sane if timeouts are not run for several seconds. * At boot, the time step can be large when the TOD hardware * has been read, so on really large steps, we call * ntp_update_second only twice. We need to call it twice in * case we missed a leap second. */ bt = th->th_offset; bintime_add(&bt, &boottimebin); i = bt.sec - tho->th_microtime.tv_sec; if (i > LARGE_STEP) i = 2; for (; i > 0; i--) { t = bt.sec; ntp_update_second(&th->th_adjustment, &bt.sec); if (bt.sec != t) boottimebin.sec += bt.sec - t; } /* Update the UTC timestamps used by the get*() functions. */ /* XXX shouldn't do this here. Should force non-`get' versions. */ bintime2timeval(&bt, &th->th_microtime); bintime2timespec(&bt, &th->th_nanotime); /* Now is a good time to change timecounters. */ if (th->th_counter != timecounter) { #ifndef __arm__ if ((timecounter->tc_flags & TC_FLAGS_C2STOP) != 0) cpu_disable_c2_sleep++; if ((th->th_counter->tc_flags & TC_FLAGS_C2STOP) != 0) cpu_disable_c2_sleep--; #endif th->th_counter = timecounter; th->th_offset_count = ncount; tc_min_ticktock_freq = max(1, timecounter->tc_frequency / (((uint64_t)timecounter->tc_counter_mask + 1) / 3)); #ifdef FFCLOCK ffclock_change_tc(th); #endif } /*- * Recalculate the scaling factor. We want the number of 1/2^64 * fractions of a second per period of the hardware counter, taking * into account the th_adjustment factor which the NTP PLL/adjtime(2) * processing provides us with. * * The th_adjustment is nanoseconds per second with 32 bit binary * fraction and we want 64 bit binary fraction of second: * * x = a * 2^32 / 10^9 = a * 4.294967296 * * The range of th_adjustment is +/- 5000PPM so inside a 64bit int * we can only multiply by about 850 without overflowing, that * leaves no suitably precise fractions for multiply before divide. * * Divide before multiply with a fraction of 2199/512 results in a * systematic undercompensation of 10PPM of th_adjustment. On a * 5000PPM adjustment this is a 0.05PPM error. This is acceptable. * * We happily sacrifice the lowest of the 64 bits of our result * to the goddess of code clarity. * */ scale = (uint64_t)1 << 63; scale += (th->th_adjustment / 1024) * 2199; scale /= th->th_counter->tc_frequency; th->th_scale = scale * 2; /* * Now that the struct timehands is again consistent, set the new * generation number, making sure to not make it zero. */ if (++ogen == 0) ogen = 1; th->th_generation = ogen; /* Go live with the new struct timehands. */ #ifdef FFCLOCK switch (sysclock_active) { case SYSCLOCK_FBCK: #endif time_second = th->th_microtime.tv_sec; time_uptime = th->th_offset.sec; #ifdef FFCLOCK break; case SYSCLOCK_FFWD: time_second = fftimehands->tick_time_lerp.sec; time_uptime = fftimehands->tick_time_lerp.sec - ffclock_boottime.sec; break; } #endif timehands = th; timekeep_push_vdso(); } /* Report or change the active timecounter hardware. */ static int sysctl_kern_timecounter_hardware(SYSCTL_HANDLER_ARGS) { char newname[32]; struct timecounter *newtc, *tc; int error; tc = timecounter; strlcpy(newname, tc->tc_name, sizeof(newname)); error = sysctl_handle_string(oidp, &newname[0], sizeof(newname), req); if (error != 0 || req->newptr == NULL || strcmp(newname, tc->tc_name) == 0) return (error); for (newtc = timecounters; newtc != NULL; newtc = newtc->tc_next) { if (strcmp(newname, newtc->tc_name) != 0) continue; /* Warm up new timecounter. */ (void)newtc->tc_get_timecount(newtc); (void)newtc->tc_get_timecount(newtc); timecounter = newtc; /* * The vdso timehands update is deferred until the next * 'tc_windup()'. * * This is prudent given that 'timekeep_push_vdso()' does not * use any locking and that it can be called in hard interrupt * context via 'tc_windup()'. */ return (0); } return (EINVAL); } SYSCTL_PROC(_kern_timecounter, OID_AUTO, hardware, CTLTYPE_STRING | CTLFLAG_RW, 0, 0, sysctl_kern_timecounter_hardware, "A", "Timecounter hardware selected"); /* Report or change the active timecounter hardware. */ static int sysctl_kern_timecounter_choice(SYSCTL_HANDLER_ARGS) { struct sbuf sb; struct timecounter *tc; int error; sbuf_new_for_sysctl(&sb, NULL, 0, req); for (tc = timecounters; tc != NULL; tc = tc->tc_next) { if (tc != timecounters) sbuf_putc(&sb, ' '); sbuf_printf(&sb, "%s(%d)", tc->tc_name, tc->tc_quality); } error = sbuf_finish(&sb); sbuf_delete(&sb); return (error); } SYSCTL_PROC(_kern_timecounter, OID_AUTO, choice, CTLTYPE_STRING | CTLFLAG_RD, 0, 0, sysctl_kern_timecounter_choice, "A", "Timecounter hardware detected"); /* * RFC 2783 PPS-API implementation. */ +/* + * Return true if the driver is aware of the abi version extensions in the + * pps_state structure, and it supports at least the given abi version number. + */ +static inline int +abi_aware(struct pps_state *pps, int vers) +{ + + return ((pps->kcmode & KCMODE_ABIFLAG) && pps->driver_abi >= vers); +} + static int pps_fetch(struct pps_fetch_args *fapi, struct pps_state *pps) { int err, timo; pps_seq_t aseq, cseq; struct timeval tv; if (fapi->tsformat && fapi->tsformat != PPS_TSFMT_TSPEC) return (EINVAL); /* * If no timeout is requested, immediately return whatever values were * most recently captured. If timeout seconds is -1, that's a request * to block without a timeout. WITNESS won't let us sleep forever * without a lock (we really don't need a lock), so just repeatedly * sleep a long time. */ if (fapi->timeout.tv_sec || fapi->timeout.tv_nsec) { if (fapi->timeout.tv_sec == -1) timo = 0x7fffffff; else { tv.tv_sec = fapi->timeout.tv_sec; tv.tv_usec = fapi->timeout.tv_nsec / 1000; timo = tvtohz(&tv); } aseq = pps->ppsinfo.assert_sequence; cseq = pps->ppsinfo.clear_sequence; while (aseq == pps->ppsinfo.assert_sequence && cseq == pps->ppsinfo.clear_sequence) { - if (pps->mtx != NULL) - err = msleep(pps, pps->mtx, PCATCH, "ppsfch", timo); - else + if (abi_aware(pps, 1) && pps->driver_mtx != NULL) { + if (pps->flags & PPSFLAG_MTX_SPIN) { + err = msleep_spin(pps, pps->driver_mtx, + "ppsfch", timo); + } else { + err = msleep(pps, pps->driver_mtx, PCATCH, + "ppsfch", timo); + } + } else { err = tsleep(pps, PCATCH, "ppsfch", timo); + } if (err == EWOULDBLOCK && fapi->timeout.tv_sec == -1) { continue; } else if (err != 0) { return (err); } } } pps->ppsinfo.current_mode = pps->ppsparam.mode; fapi->pps_info_buf = pps->ppsinfo; return (0); } int pps_ioctl(u_long cmd, caddr_t data, struct pps_state *pps) { pps_params_t *app; struct pps_fetch_args *fapi; #ifdef FFCLOCK struct pps_fetch_ffc_args *fapi_ffc; #endif #ifdef PPS_SYNC struct pps_kcbind_args *kapi; #endif KASSERT(pps != NULL, ("NULL pps pointer in pps_ioctl")); switch (cmd) { case PPS_IOC_CREATE: return (0); case PPS_IOC_DESTROY: return (0); case PPS_IOC_SETPARAMS: app = (pps_params_t *)data; if (app->mode & ~pps->ppscap) return (EINVAL); #ifdef FFCLOCK /* Ensure only a single clock is selected for ffc timestamp. */ if ((app->mode & PPS_TSCLK_MASK) == PPS_TSCLK_MASK) return (EINVAL); #endif pps->ppsparam = *app; return (0); case PPS_IOC_GETPARAMS: app = (pps_params_t *)data; *app = pps->ppsparam; app->api_version = PPS_API_VERS_1; return (0); case PPS_IOC_GETCAP: *(int*)data = pps->ppscap; return (0); case PPS_IOC_FETCH: fapi = (struct pps_fetch_args *)data; return (pps_fetch(fapi, pps)); #ifdef FFCLOCK case PPS_IOC_FETCH_FFCOUNTER: fapi_ffc = (struct pps_fetch_ffc_args *)data; if (fapi_ffc->tsformat && fapi_ffc->tsformat != PPS_TSFMT_TSPEC) return (EINVAL); if (fapi_ffc->timeout.tv_sec || fapi_ffc->timeout.tv_nsec) return (EOPNOTSUPP); pps->ppsinfo_ffc.current_mode = pps->ppsparam.mode; fapi_ffc->pps_info_buf_ffc = pps->ppsinfo_ffc; /* Overwrite timestamps if feedback clock selected. */ switch (pps->ppsparam.mode & PPS_TSCLK_MASK) { case PPS_TSCLK_FBCK: fapi_ffc->pps_info_buf_ffc.assert_timestamp = pps->ppsinfo.assert_timestamp; fapi_ffc->pps_info_buf_ffc.clear_timestamp = pps->ppsinfo.clear_timestamp; break; case PPS_TSCLK_FFWD: break; default: break; } return (0); #endif /* FFCLOCK */ case PPS_IOC_KCBIND: #ifdef PPS_SYNC kapi = (struct pps_kcbind_args *)data; /* XXX Only root should be able to do this */ if (kapi->tsformat && kapi->tsformat != PPS_TSFMT_TSPEC) return (EINVAL); if (kapi->kernel_consumer != PPS_KC_HARDPPS) return (EINVAL); if (kapi->edge & ~pps->ppscap) return (EINVAL); - pps->kcmode = kapi->edge; + pps->kcmode = (kapi->edge & KCMODE_EDGEMASK) | + (pps->kcmode & KCMODE_ABIFLAG); return (0); #else return (EOPNOTSUPP); #endif default: return (ENOIOCTL); } } void pps_init(struct pps_state *pps) { pps->ppscap |= PPS_TSFMT_TSPEC | PPS_CANWAIT; if (pps->ppscap & PPS_CAPTUREASSERT) pps->ppscap |= PPS_OFFSETASSERT; if (pps->ppscap & PPS_CAPTURECLEAR) pps->ppscap |= PPS_OFFSETCLEAR; #ifdef FFCLOCK pps->ppscap |= PPS_TSCLK_MASK; #endif + pps->kcmode &= ~KCMODE_ABIFLAG; +} + +void +pps_init_abi(struct pps_state *pps) +{ + + pps_init(pps); + if (pps->driver_abi > 0) { + pps->kcmode |= KCMODE_ABIFLAG; + pps->kernel_abi = PPS_ABI_VERSION; + } } void pps_capture(struct pps_state *pps) { struct timehands *th; KASSERT(pps != NULL, ("NULL pps pointer in pps_capture")); th = timehands; pps->capgen = th->th_generation; pps->capth = th; #ifdef FFCLOCK pps->capffth = fftimehands; #endif pps->capcount = th->th_counter->tc_get_timecount(th->th_counter); if (pps->capgen != th->th_generation) pps->capgen = 0; } void pps_event(struct pps_state *pps, int event) { struct bintime bt; struct timespec ts, *tsp, *osp; u_int tcount, *pcount; int foff, fhard; pps_seq_t *pseq; #ifdef FFCLOCK struct timespec *tsp_ffc; pps_seq_t *pseq_ffc; ffcounter *ffcount; #endif KASSERT(pps != NULL, ("NULL pps pointer in pps_event")); /* If the timecounter was wound up underneath us, bail out. */ if (pps->capgen == 0 || pps->capgen != pps->capth->th_generation) return; /* Things would be easier with arrays. */ if (event == PPS_CAPTUREASSERT) { tsp = &pps->ppsinfo.assert_timestamp; osp = &pps->ppsparam.assert_offset; foff = pps->ppsparam.mode & PPS_OFFSETASSERT; fhard = pps->kcmode & PPS_CAPTUREASSERT; pcount = &pps->ppscount[0]; pseq = &pps->ppsinfo.assert_sequence; #ifdef FFCLOCK ffcount = &pps->ppsinfo_ffc.assert_ffcount; tsp_ffc = &pps->ppsinfo_ffc.assert_timestamp; pseq_ffc = &pps->ppsinfo_ffc.assert_sequence; #endif } else { tsp = &pps->ppsinfo.clear_timestamp; osp = &pps->ppsparam.clear_offset; foff = pps->ppsparam.mode & PPS_OFFSETCLEAR; fhard = pps->kcmode & PPS_CAPTURECLEAR; pcount = &pps->ppscount[1]; pseq = &pps->ppsinfo.clear_sequence; #ifdef FFCLOCK ffcount = &pps->ppsinfo_ffc.clear_ffcount; tsp_ffc = &pps->ppsinfo_ffc.clear_timestamp; pseq_ffc = &pps->ppsinfo_ffc.clear_sequence; #endif } /* * If the timecounter changed, we cannot compare the count values, so * we have to drop the rest of the PPS-stuff until the next event. */ if (pps->ppstc != pps->capth->th_counter) { pps->ppstc = pps->capth->th_counter; *pcount = pps->capcount; pps->ppscount[2] = pps->capcount; return; } /* Convert the count to a timespec. */ tcount = pps->capcount - pps->capth->th_offset_count; tcount &= pps->capth->th_counter->tc_counter_mask; bt = pps->capth->th_offset; bintime_addx(&bt, pps->capth->th_scale * tcount); bintime_add(&bt, &boottimebin); bintime2timespec(&bt, &ts); /* If the timecounter was wound up underneath us, bail out. */ if (pps->capgen != pps->capth->th_generation) return; *pcount = pps->capcount; (*pseq)++; *tsp = ts; if (foff) { timespecadd(tsp, osp); if (tsp->tv_nsec < 0) { tsp->tv_nsec += 1000000000; tsp->tv_sec -= 1; } } #ifdef FFCLOCK *ffcount = pps->capffth->tick_ffcount + tcount; bt = pps->capffth->tick_time; ffclock_convert_delta(tcount, pps->capffth->cest.period, &bt); bintime_add(&bt, &pps->capffth->tick_time); bintime2timespec(&bt, &ts); (*pseq_ffc)++; *tsp_ffc = ts; #endif #ifdef PPS_SYNC if (fhard) { uint64_t scale; /* * Feed the NTP PLL/FLL. * The FLL wants to know how many (hardware) nanoseconds * elapsed since the previous event. */ tcount = pps->capcount - pps->ppscount[2]; pps->ppscount[2] = pps->capcount; tcount &= pps->capth->th_counter->tc_counter_mask; scale = (uint64_t)1 << 63; scale /= pps->capth->th_counter->tc_frequency; scale *= 2; bt.sec = 0; bt.frac = 0; bintime_addx(&bt, scale * tcount); bintime2timespec(&bt, &ts); hardpps(tsp, ts.tv_nsec + 1000000000 * ts.tv_sec); } #endif /* Wakeup anyone sleeping in pps_fetch(). */ wakeup(pps); } /* * Timecounters need to be updated every so often to prevent the hardware * counter from overflowing. Updating also recalculates the cached values * used by the get*() family of functions, so their precision depends on * the update frequency. */ static int tc_tick; SYSCTL_INT(_kern_timecounter, OID_AUTO, tick, CTLFLAG_RD, &tc_tick, 0, "Approximate number of hardclock ticks in a millisecond"); void tc_ticktock(int cnt) { static int count; count += cnt; if (count < tc_tick) return; count = 0; tc_windup(); } static void __inline tc_adjprecision(void) { int t; if (tc_timepercentage > 0) { t = (99 + tc_timepercentage) / tc_timepercentage; tc_precexp = fls(t + (t >> 1)) - 1; FREQ2BT(hz / tc_tick, &bt_timethreshold); FREQ2BT(hz, &bt_tickthreshold); bintime_shift(&bt_timethreshold, tc_precexp); bintime_shift(&bt_tickthreshold, tc_precexp); } else { tc_precexp = 31; bt_timethreshold.sec = INT_MAX; bt_timethreshold.frac = ~(uint64_t)0; bt_tickthreshold = bt_timethreshold; } sbt_timethreshold = bttosbt(bt_timethreshold); sbt_tickthreshold = bttosbt(bt_tickthreshold); } static int sysctl_kern_timecounter_adjprecision(SYSCTL_HANDLER_ARGS) { int error, val; val = tc_timepercentage; error = sysctl_handle_int(oidp, &val, 0, req); if (error != 0 || req->newptr == NULL) return (error); tc_timepercentage = val; if (cold) goto done; tc_adjprecision(); done: return (0); } static void inittimecounter(void *dummy) { u_int p; int tick_rate; /* * Set the initial timeout to * max(1, ). * People should probably not use the sysctl to set the timeout * to smaller than its inital value, since that value is the * smallest reasonable one. If they want better timestamps they * should use the non-"get"* functions. */ if (hz > 1000) tc_tick = (hz + 500) / 1000; else tc_tick = 1; tc_adjprecision(); FREQ2BT(hz, &tick_bt); tick_sbt = bttosbt(tick_bt); tick_rate = hz / tc_tick; FREQ2BT(tick_rate, &tc_tick_bt); tc_tick_sbt = bttosbt(tc_tick_bt); p = (tc_tick * 1000000) / hz; printf("Timecounters tick every %d.%03u msec\n", p / 1000, p % 1000); #ifdef FFCLOCK ffclock_init(); #endif /* warm up new timecounter (again) and get rolling. */ (void)timecounter->tc_get_timecount(timecounter); (void)timecounter->tc_get_timecount(timecounter); tc_windup(); } SYSINIT(timecounter, SI_SUB_CLOCKS, SI_ORDER_SECOND, inittimecounter, NULL); /* Cpu tick handling -------------------------------------------------*/ static int cpu_tick_variable; static uint64_t cpu_tick_frequency; static uint64_t tc_cpu_ticks(void) { static uint64_t base; static unsigned last; unsigned u; struct timecounter *tc; tc = timehands->th_counter; u = tc->tc_get_timecount(tc) & tc->tc_counter_mask; if (u < last) base += (uint64_t)tc->tc_counter_mask + 1; last = u; return (u + base); } void cpu_tick_calibration(void) { static time_t last_calib; if (time_uptime != last_calib && !(time_uptime & 0xf)) { cpu_tick_calibrate(0); last_calib = time_uptime; } } /* * This function gets called every 16 seconds on only one designated * CPU in the system from hardclock() via cpu_tick_calibration()(). * * Whenever the real time clock is stepped we get called with reset=1 * to make sure we handle suspend/resume and similar events correctly. */ static void cpu_tick_calibrate(int reset) { static uint64_t c_last; uint64_t c_this, c_delta; static struct bintime t_last; struct bintime t_this, t_delta; uint32_t divi; if (reset) { /* The clock was stepped, abort & reset */ t_last.sec = 0; return; } /* we don't calibrate fixed rate cputicks */ if (!cpu_tick_variable) return; getbinuptime(&t_this); c_this = cpu_ticks(); if (t_last.sec != 0) { c_delta = c_this - c_last; t_delta = t_this; bintime_sub(&t_delta, &t_last); /* * Headroom: * 2^(64-20) / 16[s] = * 2^(44) / 16[s] = * 17.592.186.044.416 / 16 = * 1.099.511.627.776 [Hz] */ divi = t_delta.sec << 20; divi |= t_delta.frac >> (64 - 20); c_delta <<= 20; c_delta /= divi; if (c_delta > cpu_tick_frequency) { if (0 && bootverbose) printf("cpu_tick increased to %ju Hz\n", c_delta); cpu_tick_frequency = c_delta; } } c_last = c_this; t_last = t_this; } void set_cputicker(cpu_tick_f *func, uint64_t freq, unsigned var) { if (func == NULL) { cpu_ticks = tc_cpu_ticks; } else { cpu_tick_frequency = freq; cpu_tick_variable = var; cpu_ticks = func; } } uint64_t cpu_tickrate(void) { if (cpu_ticks == tc_cpu_ticks) return (tc_getfrequency()); return (cpu_tick_frequency); } /* * We need to be slightly careful converting cputicks to microseconds. * There is plenty of margin in 64 bits of microseconds (half a million * years) and in 64 bits at 4 GHz (146 years), but if we do a multiply * before divide conversion (to retain precision) we find that the * margin shrinks to 1.5 hours (one millionth of 146y). * With a three prong approach we never lose significant bits, no * matter what the cputick rate and length of timeinterval is. */ uint64_t cputick2usec(uint64_t tick) { if (tick > 18446744073709551LL) /* floor(2^64 / 1000) */ return (tick / (cpu_tickrate() / 1000000LL)); else if (tick > 18446744073709LL) /* floor(2^64 / 1000000) */ return ((tick * 1000LL) / (cpu_tickrate() / 1000LL)); else return ((tick * 1000000LL) / cpu_tickrate()); } cpu_tick_f *cpu_ticks = tc_cpu_ticks; static int vdso_th_enable = 1; static int sysctl_fast_gettime(SYSCTL_HANDLER_ARGS) { int old_vdso_th_enable, error; old_vdso_th_enable = vdso_th_enable; error = sysctl_handle_int(oidp, &old_vdso_th_enable, 0, req); if (error != 0) return (error); vdso_th_enable = old_vdso_th_enable; return (0); } SYSCTL_PROC(_kern_timecounter, OID_AUTO, fast_gettime, CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, NULL, 0, sysctl_fast_gettime, "I", "Enable fast time of day"); uint32_t tc_fill_vdso_timehands(struct vdso_timehands *vdso_th) { struct timehands *th; uint32_t enabled; th = timehands; vdso_th->th_algo = VDSO_TH_ALGO_1; vdso_th->th_scale = th->th_scale; vdso_th->th_offset_count = th->th_offset_count; vdso_th->th_counter_mask = th->th_counter->tc_counter_mask; vdso_th->th_offset = th->th_offset; vdso_th->th_boottime = boottimebin; enabled = cpu_fill_vdso_timehands(vdso_th, th->th_counter); if (!vdso_th_enable) enabled = 0; return (enabled); } #ifdef COMPAT_FREEBSD32 uint32_t tc_fill_vdso_timehands32(struct vdso_timehands32 *vdso_th32) { struct timehands *th; uint32_t enabled; th = timehands; vdso_th32->th_algo = VDSO_TH_ALGO_1; *(uint64_t *)&vdso_th32->th_scale[0] = th->th_scale; vdso_th32->th_offset_count = th->th_offset_count; vdso_th32->th_counter_mask = th->th_counter->tc_counter_mask; vdso_th32->th_offset.sec = th->th_offset.sec; *(uint64_t *)&vdso_th32->th_offset.frac[0] = th->th_offset.frac; vdso_th32->th_boottime.sec = boottimebin.sec; *(uint64_t *)&vdso_th32->th_boottime.frac[0] = boottimebin.frac; enabled = cpu_fill_vdso_timehands32(vdso_th32, th->th_counter); if (!vdso_th_enable) enabled = 0; return (enabled); } #endif Index: head/sys/sys/timepps.h =================================================================== --- head/sys/sys/timepps.h (revision 282423) +++ head/sys/sys/timepps.h (revision 282424) @@ -1,254 +1,266 @@ /*- * ---------------------------------------------------------------------------- * "THE BEER-WARE LICENSE" (Revision 42): * wrote this file. As long as you retain this notice you * can do whatever you want with this stuff. If we meet some day, and you think * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp * ---------------------------------------------------------------------------- * * Copyright (c) 2011 The FreeBSD Foundation * All rights reserved. * * Portions of this software were developed by Julien Ridoux at the University * of Melbourne under sponsorship from the FreeBSD Foundation. * * $FreeBSD$ * * The is a FreeBSD version of the RFC 2783 API for Pulse Per Second * timing interfaces. */ #ifndef _SYS_TIMEPPS_H_ #define _SYS_TIMEPPS_H_ #include #include #include #define PPS_API_VERS_1 1 typedef int pps_handle_t; typedef unsigned pps_seq_t; typedef struct ntp_fp { unsigned int integral; unsigned int fractional; } ntp_fp_t; typedef union pps_timeu { struct timespec tspec; ntp_fp_t ntpfp; unsigned long longpad[3]; } pps_timeu_t; typedef struct { pps_seq_t assert_sequence; /* assert event seq # */ pps_seq_t clear_sequence; /* clear event seq # */ pps_timeu_t assert_tu; pps_timeu_t clear_tu; int current_mode; /* current mode bits */ } pps_info_t; typedef struct { pps_seq_t assert_sequence; /* assert event seq # */ pps_seq_t clear_sequence; /* clear event seq # */ pps_timeu_t assert_tu; pps_timeu_t clear_tu; ffcounter assert_ffcount; /* ffcounter on assert event */ ffcounter clear_ffcount; /* ffcounter on clear event */ int current_mode; /* current mode bits */ } pps_info_ffc_t; #define assert_timestamp assert_tu.tspec #define clear_timestamp clear_tu.tspec #define assert_timestamp_ntpfp assert_tu.ntpfp #define clear_timestamp_ntpfp clear_tu.ntpfp typedef struct { int api_version; /* API version # */ int mode; /* mode bits */ pps_timeu_t assert_off_tu; pps_timeu_t clear_off_tu; } pps_params_t; #define assert_offset assert_off_tu.tspec #define clear_offset clear_off_tu.tspec #define assert_offset_ntpfp assert_off_tu.ntpfp #define clear_offset_ntpfp clear_off_tu.ntpfp #define PPS_CAPTUREASSERT 0x01 #define PPS_CAPTURECLEAR 0x02 #define PPS_CAPTUREBOTH 0x03 #define PPS_OFFSETASSERT 0x10 #define PPS_OFFSETCLEAR 0x20 #define PPS_ECHOASSERT 0x40 #define PPS_ECHOCLEAR 0x80 #define PPS_CANWAIT 0x100 #define PPS_CANPOLL 0x200 #define PPS_TSFMT_TSPEC 0x1000 #define PPS_TSFMT_NTPFP 0x2000 #define PPS_TSCLK_FBCK 0x10000 #define PPS_TSCLK_FFWD 0x20000 #define PPS_TSCLK_MASK 0x30000 #define PPS_KC_HARDPPS 0 #define PPS_KC_HARDPPS_PLL 1 #define PPS_KC_HARDPPS_FLL 2 struct pps_fetch_args { int tsformat; pps_info_t pps_info_buf; struct timespec timeout; }; struct pps_fetch_ffc_args { int tsformat; pps_info_ffc_t pps_info_buf_ffc; struct timespec timeout; }; struct pps_kcbind_args { int kernel_consumer; int edge; int tsformat; }; #define PPS_IOC_CREATE _IO('1', 1) #define PPS_IOC_DESTROY _IO('1', 2) #define PPS_IOC_SETPARAMS _IOW('1', 3, pps_params_t) #define PPS_IOC_GETPARAMS _IOR('1', 4, pps_params_t) #define PPS_IOC_GETCAP _IOR('1', 5, int) #define PPS_IOC_FETCH _IOWR('1', 6, struct pps_fetch_args) #define PPS_IOC_KCBIND _IOW('1', 7, struct pps_kcbind_args) #define PPS_IOC_FETCH_FFCOUNTER _IOWR('1', 8, struct pps_fetch_ffc_args) #ifdef _KERNEL struct mtx; +#define KCMODE_EDGEMASK 0x03 +#define KCMODE_ABIFLAG 0x80000000 /* Internal use: abi-aware driver. */ + +#define PPS_ABI_VERSION 1 + +#define PPSFLAG_MTX_SPIN 0x01 /* Driver mtx is MTX_SPIN type. */ + struct pps_state { /* Capture information. */ struct timehands *capth; struct fftimehands *capffth; unsigned capgen; unsigned capcount; - /* pointer to mutex protecting this state, if any */ - struct mtx *mtx; - /* State information. */ pps_params_t ppsparam; pps_info_t ppsinfo; pps_info_ffc_t ppsinfo_ffc; int kcmode; int ppscap; struct timecounter *ppstc; unsigned ppscount[3]; + /* + * The following fields are valid if the driver calls pps_init_abi(). + */ + uint16_t driver_abi; /* Driver sets before pps_init_abi(). */ + uint16_t kernel_abi; /* Kernel sets during pps_init_abi(). */ + struct mtx *driver_mtx; /* Optional, valid if non-NULL. */ + uint32_t flags; }; void pps_capture(struct pps_state *pps); void pps_event(struct pps_state *pps, int event); void pps_init(struct pps_state *pps); +void pps_init_abi(struct pps_state *pps); int pps_ioctl(unsigned long cmd, caddr_t data, struct pps_state *pps); void hardpps(struct timespec *tsp, long nsec); #else /* !_KERNEL */ static __inline int time_pps_create(int filedes, pps_handle_t *handle) { int error; *handle = -1; error = ioctl(filedes, PPS_IOC_CREATE, 0); if (error < 0) return (-1); *handle = filedes; return (0); } static __inline int time_pps_destroy(pps_handle_t handle) { return (ioctl(handle, PPS_IOC_DESTROY, 0)); } static __inline int time_pps_setparams(pps_handle_t handle, const pps_params_t *ppsparams) { return (ioctl(handle, PPS_IOC_SETPARAMS, ppsparams)); } static __inline int time_pps_getparams(pps_handle_t handle, pps_params_t *ppsparams) { return (ioctl(handle, PPS_IOC_GETPARAMS, ppsparams)); } static __inline int time_pps_getcap(pps_handle_t handle, int *mode) { return (ioctl(handle, PPS_IOC_GETCAP, mode)); } static __inline int time_pps_fetch(pps_handle_t handle, const int tsformat, pps_info_t *ppsinfobuf, const struct timespec *timeout) { int error; struct pps_fetch_args arg; arg.tsformat = tsformat; if (timeout == NULL) { arg.timeout.tv_sec = -1; arg.timeout.tv_nsec = -1; } else arg.timeout = *timeout; error = ioctl(handle, PPS_IOC_FETCH, &arg); *ppsinfobuf = arg.pps_info_buf; return (error); } static __inline int time_pps_fetch_ffc(pps_handle_t handle, const int tsformat, pps_info_ffc_t *ppsinfobuf, const struct timespec *timeout) { struct pps_fetch_ffc_args arg; int error; arg.tsformat = tsformat; if (timeout == NULL) { arg.timeout.tv_sec = -1; arg.timeout.tv_nsec = -1; } else { arg.timeout = *timeout; } error = ioctl(handle, PPS_IOC_FETCH_FFCOUNTER, &arg); *ppsinfobuf = arg.pps_info_buf_ffc; return (error); } static __inline int time_pps_kcbind(pps_handle_t handle, const int kernel_consumer, const int edge, const int tsformat) { struct pps_kcbind_args arg; arg.kernel_consumer = kernel_consumer; arg.edge = edge; arg.tsformat = tsformat; return (ioctl(handle, PPS_IOC_KCBIND, &arg)); } #endif /* KERNEL */ #endif /* !_SYS_TIMEPPS_H_ */