Restore uart PPS signal capture polarity to its historical norm, and add an

option to invert the polarity in software. Also add an option to capture
very narrow pulses by using the hardware's MSR delta-bit capability of
latching line state changes.

This effectively reverts the mistake I made in r286595 which was based on
empirical measurements made on hardware using TTL-level signaling, in which
the logic levels are inverted from RS-232. Thus, this re-syncs the polarity
with the requirements of RFC 2783, which is writen in terms of RS-232
signaling.

Narrow-pulse mode uses the ability of most ns8250 and similar chips to
provide a delta indication in the modem status register. The hardware is
able to notice and latch the change when the pulse width is shorter than
interrupt latency, which results in the signal no longer being asserted by
time the interrupt service code runs. When running in this mode we get
notified only that "a pulse happened" so the driver synthesizes both an
ASSERT and a CLEAR event (with the same timestamp for each). When the pulse
width is about equal to the interrupt latency the driver may intermittantly
see both edges of the pulse. To prevent generating spurious events, the
driver implements a half-second lockout period after generating an event
before it will generate another.

Differential Revision:	https://reviews.freebsd.org/D4477
This commit is contained in:
Ian Lepore 2016-01-12 18:42:00 +00:00
parent c1f892e0d1
commit fdfbb3f5b1
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=293781
5 changed files with 247 additions and 72 deletions

View File

@ -25,7 +25,7 @@
.\"
.\" $FreeBSD$
.\"
.Dd August 10, 2015
.Dd December 9, 2015
.Dt UART 4
.Os
.Sh NAME
@ -160,7 +160,9 @@ The API, accessed via
is available on the tty device.
To use the PPS capture feature with
.Xr ntpd 8 ,
symlink the tty device to
symlink the tty callout device
.Va /dev/cuau?
to
.Va /dev/pps0.
.Pp
The
@ -175,15 +177,54 @@ it can be set in
.Xr loader.conf 5
or
.Xr sysctl.conf 5 .
.Pp
The following capture modes are available:
.Bl -tag -compact -offset "mmmm" -width "mmmm"
.It 0
.It 0x00
Capture disabled.
.It 1
.It 0x01
Capture pulses on the CTS line.
.It 2
Capture pulses on the DCD line (default).
.It 0x02
Capture pulses on the DCD line.
.El
.Pp
The following values may be ORed with the capture mode to configure
capture processing options:
.Bl -tag -compact -offset "mmmm" -width "mmmm"
.It 0x10
Invert the pulse (RS-232 logic low = ASSERT, high = CLEAR).
.It 0x20
Attempt to capture narrow pulses.
.El
.Pp
Add the narrow pulse option when the incoming PPS pulse width is small
enough to prevent reliable capture in normal mode.
In narrow mode the driver uses the hardware's ability to latch a line
state change; not all hardware has this capability.
The hardware latch provides a reliable indication that a pulse occurred,
but prevents distinguishing between the CLEAR and ASSERT edges of the pulse.
For each detected pulse, the driver synthesizes both an ASSERT and a CLEAR
event, using the same timestamp for each.
To prevent spurious events when the hardware is intermittently able to
see both edges of a pulse, the driver will not generate a new pair of
events within a half second of the prior pair.
Both normal and narrow pulse modes work with
.Xr ntpd 8 .
.Pp
Add the invert option when the connection to the uart device uses TTL
level signals, or when the PPS source emits inverted pulses.
RFC 2783 defines an ASSERT event as a higher-voltage line level, and a CLEAR
event as a lower-voltage line level, in the context of the RS-232 protocol.
The modem control signals on a TTL-level connection are typically
inverted from the RS-232 levels.
For example, carrier presence is indicated by a high signal on an RS-232
DCD line, and by a low signal on a TTL DCD line.
This is due to the use of inverting line driver buffers to convert between
TTL and RS-232 line levels in most hardware designs.
Generally speaking, a connection to a DB-9 style connector is an RS-232
level signal at up to 12 volts.
A connection to header pins or an edge-connector on an embedded board
is typically a TTL signal at 3.3 or 5 volts.
.Sh FILES
.Bl -tag -width ".Pa /dev/ttyu?.init" -compact
.It Pa /dev/ttyu?

View File

@ -113,6 +113,7 @@ struct uart_softc {
/* Pulse capturing support (PPS). */
struct pps_state sc_pps;
int sc_pps_mode;
sbintime_t sc_pps_captime;
/* Upper layer data. */
void *sc_softih;

View File

@ -48,6 +48,7 @@ __FBSDID("$FreeBSD$");
#include <dev/uart/uart.h>
#include <dev/uart/uart_bus.h>
#include <dev/uart/uart_cpu.h>
#include <dev/uart/uart_ppstypes.h>
#include "uart_if.h"
@ -70,47 +71,47 @@ static int uart_force_poll;
SYSCTL_INT(_debug, OID_AUTO, uart_force_poll, CTLFLAG_RDTUN, &uart_force_poll,
0, "Force UART polling");
#define PPS_MODE_DISABLED 0
#define PPS_MODE_CTS 1
#define PPS_MODE_DCD 2
static inline int
uart_pps_signal(int pps_mode)
{
switch(pps_mode) {
case PPS_MODE_CTS:
return (SER_CTS);
case PPS_MODE_DCD:
return (SER_DCD);
}
return (0);
}
static inline int
uart_pps_mode_valid(int pps_mode)
{
int opt;
switch(pps_mode) {
case PPS_MODE_DISABLED:
case PPS_MODE_CTS:
case PPS_MODE_DCD:
return (true);
switch(pps_mode & UART_PPS_SIGNAL_MASK) {
case UART_PPS_DISABLED:
case UART_PPS_CTS:
case UART_PPS_DCD:
break;
default:
return (false);
}
return (false);
opt = pps_mode & UART_PPS_OPTION_MASK;
if ((opt & ~(UART_PPS_INVERT_PULSE | UART_PPS_NARROW_PULSE)) != 0)
return (false);
return (true);
}
static const char *
uart_pps_mode_name(int pps_mode)
static void
uart_pps_print_mode(struct uart_softc *sc)
{
switch(pps_mode) {
case PPS_MODE_DISABLED:
return ("disabled");
case PPS_MODE_CTS:
return ("CTS");
case PPS_MODE_DCD:
return ("DCD");
device_printf(sc->sc_dev, "PPS capture mode: ");
switch(sc->sc_pps_mode) {
case UART_PPS_DISABLED:
printf("disabled");
case UART_PPS_CTS:
printf("CTS");
case UART_PPS_DCD:
printf("DCD");
default:
printf("invalid");
}
return ("invalid");
if (sc->sc_pps_mode & UART_PPS_INVERT_PULSE)
printf("-Inverted");
if (sc->sc_pps_mode & UART_PPS_NARROW_PULSE)
printf("-NarrowPulse");
printf("\n");
}
static int
@ -130,6 +131,55 @@ uart_pps_mode_sysctl(SYSCTL_HANDLER_ARGS)
return(0);
}
static void
uart_pps_process(struct uart_softc *sc, int ser_sig)
{
sbintime_t now;
int is_assert, pps_sig;
/* Which signal is configured as PPS? Early out if none. */
switch(sc->sc_pps_mode & UART_PPS_SIGNAL_MASK) {
case UART_PPS_CTS:
pps_sig = SER_CTS;
break;
case UART_PPS_DCD:
pps_sig = SER_DCD;
break;
default:
return;
}
/* Early out if there is no change in the signal configured as PPS. */
if ((ser_sig & SER_DELTA(pps_sig)) == 0)
return;
/*
* In narrow-pulse mode we need to synthesize both capture and clear
* events from a single "delta occurred" indication from the uart
* hardware because the pulse width is too narrow to reliably detect
* both edges. However, when the pulse width is close to our interrupt
* processing latency we might intermittantly catch both edges. To
* guard against generating spurious events when that happens, we use a
* separate timer to ensure at least half a second elapses before we
* generate another event.
*/
pps_capture(&sc->sc_pps);
if (sc->sc_pps_mode & UART_PPS_NARROW_PULSE) {
now = getsbinuptime();
if (now > sc->sc_pps_captime + 500 * SBT_1MS) {
sc->sc_pps_captime = now;
pps_event(&sc->sc_pps, PPS_CAPTUREASSERT);
pps_event(&sc->sc_pps, PPS_CAPTURECLEAR);
}
} else {
is_assert = ser_sig & pps_sig;
if (sc->sc_pps_mode & UART_PPS_INVERT_PULSE)
is_assert = !is_assert;
pps_event(&sc->sc_pps, is_assert ? PPS_CAPTUREASSERT :
PPS_CAPTURECLEAR);
}
}
static void
uart_pps_init(struct uart_softc *sc)
{
@ -147,23 +197,23 @@ uart_pps_init(struct uart_softc *sc)
* for one specific device.
*/
#ifdef UART_PPS_ON_CTS
sc->sc_pps_mode = PPS_MODE_CTS;
sc->sc_pps_mode = UART_PPS_CTS;
#else
sc->sc_pps_mode = PPS_MODE_DCD;
sc->sc_pps_mode = UART_PPS_DCD;
#endif
TUNABLE_INT_FETCH("hw.uart.pps_mode", &sc->sc_pps_mode);
SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "pps_mode",
CTLTYPE_INT | CTLFLAG_RWTUN, sc, 0, uart_pps_mode_sysctl, "I",
"pulse capturing mode - 0/1/2 - disabled/CTS/DCD");
"pulse mode: 0/1/2=disabled/CTS/DCD; "
"add 0x10 to invert, 0x20 for narrow pulse");
if (!uart_pps_mode_valid(sc->sc_pps_mode)) {
device_printf(sc->sc_dev,
"Invalid pps_mode %d configured; disabling PPS capture\n",
"Invalid pps_mode 0x%02x configured; disabling PPS capture\n",
sc->sc_pps_mode);
sc->sc_pps_mode = PPS_MODE_DISABLED;
sc->sc_pps_mode = UART_PPS_DISABLED;
} else if (bootverbose) {
device_printf(sc->sc_dev, "PPS capture mode %d (%s)\n",
sc->sc_pps_mode, uart_pps_mode_name(sc->sc_pps_mode));
uart_pps_print_mode(sc);
}
sc->sc_pps.ppscap = PPS_CAPTUREBOTH;
@ -313,23 +363,16 @@ static __inline int
uart_intr_sigchg(void *arg)
{
struct uart_softc *sc = arg;
int new, old, pps_sig, sig;
int new, old, sig;
sig = UART_GETSIG(sc);
/*
* 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.
* Time pulse counting support, invoked whenever the PPS parameters are
* currently set to capture either edge of the signal.
*/
if (sc->sc_pps.ppsparam.mode & PPS_CAPTUREBOTH) {
pps_sig = uart_pps_signal(sc->sc_pps_mode);
if (sig & SER_DELTA(pps_sig)) {
pps_capture(&sc->sc_pps);
pps_event(&sc->sc_pps, (sig & pps_sig) ?
PPS_CAPTURECLEAR : PPS_CAPTUREASSERT);
}
uart_pps_process(sc, sig);
}
/*

View File

@ -51,6 +51,7 @@ __FBSDID("$FreeBSD$");
#endif
#include <dev/uart/uart_bus.h>
#include <dev/uart/uart_dev_ns8250.h>
#include <dev/uart/uart_ppstypes.h>
#include <dev/ic/ns16550.h>
@ -401,11 +402,40 @@ static struct ofw_compat_data compat_data[] = {
UART_FDT_CLASS_AND_DEVICE(compat_data);
#endif
#define SIGCHG(c, i, s, d) \
if (c) { \
i |= (i & s) ? s : s | d; \
} else { \
i = (i & s) ? (i & ~s) | d : i; \
/* Use token-pasting to form SER_ and MSR_ named constants. */
#define SER(sig) SER_##sig
#define SERD(sig) SER_D##sig
#define MSR(sig) MSR_##sig
#define MSRD(sig) MSR_D##sig
/*
* Detect signal changes using software delta detection. The previous state of
* the signals is in 'var' the new hardware state is in 'msr', and 'sig' is the
* short name (DCD, CTS, etc) of the signal bit being processed; 'var' gets the
* new state of both the signal and the delta bits.
*/
#define SIGCHGSW(var, msr, sig) \
if ((msr) & MSR(sig)) { \
if ((var & SER(sig)) == 0) \
var |= SERD(sig) | SER(sig); \
} else { \
if ((var & SER(sig)) != 0) \
var = SERD(sig) | (var & ~SER(sig)); \
}
/*
* Detect signal changes using the hardware msr delta bits. This is currently
* used only when PPS timing information is being captured using the "narrow
* pulse" option. With a narrow PPS pulse the signal may not still be asserted
* by time the interrupt handler is invoked. The hardware will latch the fact
* that it changed in the delta bits.
*/
#define SIGCHGHW(var, msr, sig) \
if ((msr) & MSRD(sig)) { \
if (((msr) & MSR(sig)) != 0) \
var |= SERD(sig) | SER(sig); \
else \
var = SERD(sig) | (var & ~SER(sig)); \
}
int
@ -532,21 +562,37 @@ ns8250_bus_flush(struct uart_softc *sc, int what)
int
ns8250_bus_getsig(struct uart_softc *sc)
{
uint32_t new, old, sig;
uint32_t old, sig;
uint8_t msr;
/*
* The delta bits are reputed to be broken on some hardware, so use
* software delta detection by default. Use the hardware delta bits
* when capturing PPS pulses which are too narrow for software detection
* to see the edges. Hardware delta for RI doesn't work like the
* others, so always use software for it. Other threads may be changing
* other (non-MSR) bits in sc_hwsig, so loop until it can succesfully
* update without other changes happening. Note that the SIGCHGxx()
* macros carefully preserve the delta bits when we have to loop several
* times and a signal transitions between iterations.
*/
do {
old = sc->sc_hwsig;
sig = old;
uart_lock(sc->sc_hwmtx);
msr = uart_getreg(&sc->sc_bas, REG_MSR);
uart_unlock(sc->sc_hwmtx);
SIGCHG(msr & MSR_DSR, sig, SER_DSR, SER_DDSR);
SIGCHG(msr & MSR_CTS, sig, SER_CTS, SER_DCTS);
SIGCHG(msr & MSR_DCD, sig, SER_DCD, SER_DDCD);
SIGCHG(msr & MSR_RI, sig, SER_RI, SER_DRI);
new = sig & ~SER_MASK_DELTA;
} while (!atomic_cmpset_32(&sc->sc_hwsig, old, new));
if (sc->sc_pps_mode & UART_PPS_NARROW_PULSE) {
SIGCHGHW(sig, msr, DSR);
SIGCHGHW(sig, msr, CTS);
SIGCHGHW(sig, msr, DCD);
} else {
SIGCHGSW(sig, msr, DSR);
SIGCHGSW(sig, msr, CTS);
SIGCHGSW(sig, msr, DCD);
}
SIGCHGSW(sig, msr, RI);
} while (!atomic_cmpset_32(&sc->sc_hwsig, old, sig & ~SER_MASK_DELTA));
return (sig);
}
@ -900,12 +946,10 @@ ns8250_bus_setsig(struct uart_softc *sc, int sig)
old = sc->sc_hwsig;
new = old;
if (sig & SER_DDTR) {
SIGCHG(sig & SER_DTR, new, SER_DTR,
SER_DDTR);
new = (new & ~SER_DTR) | (sig & (SER_DTR | SER_DDTR));
}
if (sig & SER_DRTS) {
SIGCHG(sig & SER_RTS, new, SER_RTS,
SER_DRTS);
new = (new & ~SER_RTS) | (sig & (SER_RTS | SER_DRTS));
}
} while (!atomic_cmpset_32(&sc->sc_hwsig, old, new));
uart_lock(sc->sc_hwmtx);

View File

@ -0,0 +1,46 @@
/*-
* Copyright (c) 2015 Ian Lepore
* 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 ``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 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.
*
* $FreeBSD$
*/
#ifndef _DEV_UART_PPSTYPES_H_
#define _DEV_UART_PPSTYPES_H_
/*
* These constants are shared by several drivers including uart and usb_serial.
*/
#define UART_PPS_SIGNAL_MASK 0x0f
#define UART_PPS_OPTION_MASK 0xf0
#define UART_PPS_DISABLED 0x00
#define UART_PPS_CTS 0x01
#define UART_PPS_DCD 0x02
#define UART_PPS_INVERT_PULSE 0x10
#define UART_PPS_NARROW_PULSE 0x20
#endif /* _DEV_UART_PPSTYPES_H_ */