From 6496b5d0e0469e7fd3f9750cd3f9753bc8579815 Mon Sep 17 00:00:00 2001 From: Yaroslav Kolomiiets Date: Tue, 22 Oct 2024 09:08:06 -0700 Subject: [PATCH] =?UTF-8?q?time:=20switch=20to=20use=20upstreamed=20unix.P?= =?UTF-8?q?tp=E2=80=A6=20(#416)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Convert the phc package to use the upstreamed PTP ioctls, functions and constants from golang.org/x/sys/unix. As before, the new things are placed in the phc/unix shim until a new version tag for that repo is cut (we expect v0.27.0). Reviewed By: leoleovich, abulimov Differential Revision: D64723712 --- cmd/ptpcheck/cmd/pins.go | 74 +++++++++++-- phc/device.go | 217 +++++++++++++++--------------------- phc/device_test.go | 32 ------ phc/helper_386.go | 33 ------ phc/helper_64bit.go | 33 ------ phc/offset.go | 41 ++++--- phc/offset_test.go | 67 ++++++------ phc/phc.go | 231 ++++----------------------------------- phc/phc_test.go | 19 ++-- phc/pps_source.go | 54 ++++----- phc/pps_source_mocks.go | 6 +- phc/pps_source_test.go | 35 +++--- phc/unix/linux.go | 37 ++++++- 13 files changed, 325 insertions(+), 554 deletions(-) delete mode 100644 phc/device_test.go delete mode 100644 phc/helper_386.go delete mode 100644 phc/helper_64bit.go diff --git a/cmd/ptpcheck/cmd/pins.go b/cmd/ptpcheck/cmd/pins.go index 36c518c8..a401c01d 100644 --- a/cmd/ptpcheck/cmd/pins.go +++ b/cmd/ptpcheck/cmd/pins.go @@ -19,8 +19,9 @@ package cmd import ( "fmt" "os" + "strings" - "github.com/facebook/time/phc" + "github.com/facebook/time/phc/unix" // a temporary shim for "golang.org/x/sys/unix" until v0.27.0 is cut log "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -28,7 +29,7 @@ import ( // flags var devPath string var pinName string -var pinFunc phc.PinFunc +var pinFunc PinFunc var setMode bool func init() { @@ -60,21 +61,74 @@ func doListPins(device string) error { return fmt.Errorf("opening device %q: %w", device, err) } defer f.Close() - dev := phc.FromFile(f) - pins, err := dev.ReadPins() + caps, err := unix.IoctlPtpClockGetcaps(int(f.Fd())) if err != nil { return err } - for _, p := range pins { - if setMode && pinName == p.Name { - if err := p.SetFunc(pinFunc); err != nil { - log.Fatal(err) + npins := int(caps.N_pins) + + pins := make([]*unix.PtpPinDesc, npins) + names := make([]string, npins) + for i := 0; i < npins; i++ { + pin, err := unix.IoctlPtpPinGetfunc(int(f.Fd()), uint(i)) //#nosec G115 + if err != nil { + return err + } + pins[i] = pin + names[i] = unix.ByteSliceToString(pin.Name[:]) + } + + for i, pin := range pins { + if setMode && pinName == names[i] { + pin.Func = uint32(pinFunc) + if err := unix.IoctlPtpPinSetfunc(int(f.Fd()), pin); err != nil { + return fmt.Errorf("%s: IoctlPtpPinSetfunc: %w", f.Name(), err) } } - if pinName == "" || pinName == p.Name { - fmt.Printf("%s: pin %d function %-7[3]s (%[3]d) chan %d\n", p.Name, p.Index, p.Func, p.Chan) + if pinName == "" || pinName == names[i] { + fmt.Printf("%s: pin %d function %-7[3]s (%[3]d) chan %d\n", + pin.Name, pin.Index, PinFunc(pin.Func), pin.Chan) } } return nil } + +// PinFunc type represents the pin function values. +type PinFunc uint32 + +// Type implements cobra.Value +func (pf *PinFunc) Type() string { return "{ PPS-In | PPS-Out | PhySync | None }" } + +// String implements flags.Value +func (pf PinFunc) String() string { + switch pf { + case unix.PTP_PF_NONE: + return "None" + case unix.PTP_PF_EXTTS: + return "PPS-In" // user friendly + case unix.PTP_PF_PEROUT: + return "PPS-Out" // user friendly + case unix.PTP_PF_PHYSYNC: + return "PhySync" + default: + return fmt.Sprintf("!(PinFunc=%d)", int(pf)) + } +} + +// Set implements flags.Value +func (pf *PinFunc) Set(s string) error { + switch strings.ToLower(s) { + case "none", "-": + *pf = unix.PTP_PF_NONE + case "pps-in", "ppsin", "extts": + *pf = unix.PTP_PF_EXTTS + case "pps-out", "ppsout", "perout": + *pf = unix.PTP_PF_PEROUT + case "phy-sync", "physync", "sync": + *pf = unix.PTP_PF_PHYSYNC + default: + return fmt.Errorf("use either of: %s", pf.Type()) + } + return nil +} diff --git a/phc/device.go b/phc/device.go index a31219dd..2447741d 100644 --- a/phc/device.go +++ b/phc/device.go @@ -18,166 +18,127 @@ package phc import ( "fmt" - "unsafe" + "os" + "syscall" + "time" "github.com/facebook/time/phc/unix" // a temporary shim for "golang.org/x/sys/unix" until v0.27.0 is cut - "github.com/vtolstov/go-ioctl" ) -// Missing from sys/unix package, defined in Linux include/uapi/linux/ptp_clock.h -const ( - ptpMaxSamples = 25 - ptpClkMagic = '=' - nsPerSec = uint32(1000000000) -) +// Device represents a PHC device +type Device os.File -// ioctlPTPSysOffsetExtended is an IOCTL to get extended offset -var ioctlPTPSysOffsetExtended = ioctl.IOWR(ptpClkMagic, 9, unsafe.Sizeof(PTPSysOffsetExtended{})) +// FromFile returns a *Device corresponding to an *os.File +func FromFile(file *os.File) *Device { return (*Device)(file) } -// ioctlPTPSysOffsetPrecise is an IOCTL to get precise offset -var ioctlPTPSysOffsetPrecise = ioctl.IOWR(ptpClkMagic, 8, unsafe.Sizeof(PTPSysOffsetPrecise{})) +// File returns the underlying *os.File +func (dev *Device) File() *os.File { return (*os.File)(dev) } -// ioctlPTPClockGetCaps is an IOCTL to get PTP clock capabilities -var ioctlPTPClockGetcaps = ioctl.IOR(ptpClkMagic, 1, unsafe.Sizeof(PTPClockCaps{})) +// Fd returns the underlying file descriptor +func (dev *Device) Fd() uintptr { return dev.File().Fd() } -// iocPinGetfunc is an IOCTL req corresponding to PTP_PIN_GETFUNC in linux/ptp_clock.h -var iocPinGetfunc = ioctl.IOWR(ptpClkMagic, 6, unsafe.Sizeof(rawPinDesc{})) +// ClockID derives the clock ID from the file descriptor number - +// see clock_gettime(3), FD_TO_CLOCKID macros +func (dev *Device) ClockID() int32 { return unix.FdToClockID(int(dev.Fd())) } -// iocPinSetfunc is an IOCTL req corresponding to PTP_PIN_SETFUNC in linux/ptp_clock.h -var iocPinSetfunc = ioctl.IOW(ptpClkMagic, 7, unsafe.Sizeof(rawPinDesc{})) +// Time returns time from the PTP device using the clock_gettime syscall +func (dev *Device) Time() (time.Time, error) { + var ts unix.Timespec + if err := unix.ClockGettime(dev.ClockID(), &ts); err != nil { + return time.Time{}, fmt.Errorf("failed clock_gettime: %w", err) + } + return time.Unix(ts.Unix()), nil +} -// iocPinSetfunc is an IOCTL req corresponding to PTP_PIN_SETFUNC2 in linux/ptp_clock.h -var iocPinSetfunc2 = ioctl.IOW(ptpClkMagic, 16, unsafe.Sizeof(rawPinDesc{})) +// ReadSysoffExtended reads the precise time from the PHC along with SYS time to measure the call delay. +// The nsamples parameter is set to ExtendedNumProbes. +func (dev *Device) ReadSysoffExtended() (*PTPSysOffsetExtended, error) { + return dev.readSysoffExtended(ExtendedNumProbes) +} -// ioctlPTPPeroutRequest2 is an IOCTL req corresponding to PTP_PEROUT_REQUEST2 in linux/ptp_clock.h -var ioctlPTPPeroutRequest2 = ioctl.IOW(ptpClkMagic, 12, unsafe.Sizeof(PTPPeroutRequest{})) +// ReadSysoffExtended1 reads the precise time from the PHC along with SYS time to measure the call delay. +// The samples parameter is set to 1. +func (dev *Device) ReadSysoffExtended1() (*PTPSysOffsetExtended, error) { + return dev.readSysoffExtended(1) +} -// ioctlExtTTSRequest2 is an IOCTL req corresponding to PTP_EXTTS_REQUEST2 in linux/ptp_clock.h -var ioctlExtTTSRequest2 = ioctl.IOW(ptpClkMagic, 11, unsafe.Sizeof(PTPExtTTSRequest{})) +// ReadSysoffPrecise reads the precise time from the PHC along with SYS time to measure the call delay. +func (dev *Device) ReadSysoffPrecise() (*PTPSysOffsetPrecise, error) { + return dev.readSysoffPrecise() +} -// PTPSysOffsetExtended as defined in linux/ptp_clock.h -type PTPSysOffsetExtended struct { - NSamples uint32 /* Desired number of measurements. */ - Reserved [3]uint32 /* Reserved for future use. */ - /* - * Array of [system, phc, system] time stamps. The kernel will provide - * 3*n_samples time stamps. - * - system time right before reading the lowest bits of the PHC timestamp - * - PHC time - * - system time immediately after reading the lowest bits of the PHC timestamp - */ - TS [ptpMaxSamples][3]PTPClockTime +func (dev *Device) readSysoffExtended(samples uint) (*PTPSysOffsetExtended, error) { + value, err := unix.IoctlPtpSysOffsetExtended(int(dev.Fd()), samples) + if err != nil { + return nil, err + } + our := PTPSysOffsetExtended(*value) + return &our, nil } -// PTPSysOffsetPrecise as defined in linux/ptp_clock.h -type PTPSysOffsetPrecise struct { - Device PTPClockTime - SysRealTime PTPClockTime - SysMonoRaw PTPClockTime - Reserved [4]uint32 /* Reserved for future use. */ +func (dev *Device) readSysoffPrecise() (*PTPSysOffsetPrecise, error) { + value, err := unix.IoctlPtpSysOffsetPrecise(int(dev.Fd())) + if err != nil { + return nil, err + } + our := PTPSysOffsetPrecise(*value) + return &our, nil } -// PinDesc represents the C struct ptp_pin_desc as defined in linux/ptp_clock.h -type PinDesc struct { - Name string // Hardware specific human readable pin name - Index uint // Pin index in the range of zero to ptp_clock_caps.n_pins - 1 - Func PinFunc // Which of the PTP_PF_xxx functions to use on this pin - Chan uint // The specific channel to use for this function. - // private fields - dev *Device +// readCaps reads PTP capabilities using ioctl +func (dev *Device) readCaps() (*PtpClockCaps, error) { + return unix.IoctlPtpClockGetcaps(int(dev.Fd())) } -// SetFunc uses an ioctl to change the pin function -func (pd *PinDesc) SetFunc(pf PinFunc) error { - if err := pd.dev.setPinFunc(pd.Index, pf, pd.Chan); err != nil { - return err +// setPinFunc sets the function on a single PTP pin descriptor +func (dev *Device) setPinFunc(index uint, pf int, ch uint) error { + raw := unix.PtpPinDesc{ + Index: uint32(index), //#nosec G115 + Func: uint32(pf), //#nosec G115 + Chan: uint32(ch), //#nosec G115 + } + if err := unix.IoctlPtpPinSetfunc(int(dev.Fd()), &raw); err != nil { + return fmt.Errorf("%s: ioctl(PTP_PIN_SETFUNC) failed: %w", dev.File().Name(), err) } - pd.Func = pf return nil } -type rawPinDesc struct { - Name [64]byte // Hardware specific human readable pin name - Index uint32 // Pin index in the range of zero to ptp_clock_caps.n_pins - 1 - Func uint32 // Which of the PTP_PF_xxx functions to use on this pin - Chan uint32 // The specific channel to use for this function. - Rsv [5]uint32 // Reserved for future use. +// MaxFreqAdjPPB reads max value for frequency adjustments (in PPB) from ptp device +func (dev *Device) MaxFreqAdjPPB() (maxFreq float64, err error) { + caps, err := dev.readCaps() + if err != nil { + return 0, err + } + return maxAdj(caps), nil } -// PTPPeroutRequest as defined in linux/ptp_clock.h -type PTPPeroutRequest struct { - // * Represents either absolute start time or phase offset. - // * Absolute start time if (flags & PTP_PEROUT_PHASE) is unset. - // * Phase offset if (flags & PTP_PEROUT_PHASE) is set. - // * If set the signal should start toggling at an - // * unspecified integer multiple of the period, plus this value. - // * The start time should be "as soon as possible". - StartOrPhase PTPClockTime - Period PTPClockTime // Desired period, zero means disable - Index uint32 // Which channel to configure - Flags uint32 // Configuration flags - On PTPClockTime // "On" time of the signal. Must be lower than the period. Valid only if (flags & PTP_PEROUT_DUTY_CYCLE) is set. +func maxAdj(caps *PtpClockCaps) float64 { + if caps == nil || caps.Max_adj == 0 { + return DefaultMaxClockFreqPPB + } + return float64(caps.Max_adj) } -// Bits of the ptp_extts_request.flags field: -const ( - PTPEnableFeature uint32 = 1 << 0 // Enable feature - PTPRisingEdge uint32 = 1 << 1 // Rising edge - PTPFallingEdge uint32 = 1 << 2 // Falling edge - PTPStrictFlags uint32 = 1 << 3 // Strict flags - PTPExtOffset uint32 = 1 << 4 // External offset -) - -// PTPExtTTSRequest as defined in linux/ptp_clock.h -type PTPExtTTSRequest struct { - index uint32 - flags uint32 - rsv [2]uint32 +func (dev *Device) setPTPPerout(req *PtpPeroutRequest) error { + return unix.IoctlPtpPeroutRequest(int(dev.Fd()), req) } -// PTPExtTTS as defined in linux/ptp_clock.h -type PTPExtTTS struct { - T PTPClockTime /* Time when event occurred. */ - Index uint32 /* Which channel produced the event. Corresponds to the 'index' field of the PTP_EXTTS_REQUEST and PTP_PEROUT_REQUEST ioctls.*/ - Flags uint32 /* Event flags */ - Rsv [2]uint32 /* Reserved for future use. */ +func (dev *Device) extTTSRequest(req *PtpExttsRequest) error { + return unix.IoctlPtpExttsRequest(int(dev.Fd()), req) } -// PTPClockTime as defined in linux/ptp_clock.h -type PTPClockTime struct { - Sec int64 /* seconds */ - NSec uint32 /* nanoseconds */ - Reserved uint32 -} +// FreqPPB reads PHC device frequency in PPB (parts per billion) +func (dev *Device) FreqPPB() (freqPPB float64, err error) { return freqPPBFromDevice(dev) } -// PTPClockCaps as defined in linux/ptp_clock.h -type PTPClockCaps struct { - MaxAdj int32 /* Maximum frequency adjustment in parts per billon. */ - NAalarm int32 /* Number of programmable alarms. */ - NExtTs int32 /* Number of external time stamp channels. */ - NPerOut int32 /* Number of programmable periodic signals. */ - PPS int32 /* Whether the clock supports a PPS callback. */ - NPins int32 /* Number of input/output pins. */ - /* Whether the clock supports precise system-device cross timestamps */ - CrossTimestamping int32 - /* Whether the clock supports adjust phase */ - AdjustPhase int32 - Rsv [12]int32 /* Reserved for future use. */ -} +// AdjFreq adjusts the PHC clock frequency in PPB +func (dev *Device) AdjFreq(freqPPB float64) error { return clockAdjFreq(dev, freqPPB) } -func (caps *PTPClockCaps) maxAdj() float64 { - if caps == nil || caps.MaxAdj == 0 { - return DefaultMaxClockFreqPPB - } - return float64(caps.MaxAdj) -} +// Step steps the PHC clock by given duration +func (dev *Device) Step(step time.Duration) error { return clockStep(dev, step) } -// IfaceInfo uses an ioctl to get information for the named nic, e.g. eth0. -func IfaceInfo(iface string) (*unix.EthtoolTsInfo, error) { - fd, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0) - if err != nil { - return nil, fmt.Errorf("failed to create socket for ioctl: %w", err) - } - defer unix.Close(fd) - return unix.IoctlGetEthtoolTsInfo(fd, iface) +// SetTime sets the time of the PHC clock +func (dev *Device) SetTime(t time.Time) error { return clockSetTime(dev, t) } + +func (dev *Device) Read(buffer []byte) (int, error) { + return syscall.Read(int(dev.Fd()), buffer) } diff --git a/phc/device_test.go b/phc/device_test.go deleted file mode 100644 index 51104e0b..00000000 --- a/phc/device_test.go +++ /dev/null @@ -1,32 +0,0 @@ -/* -Copyright (c) Facebook, Inc. and its affiliates. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package phc - -import ( - "testing" - "unsafe" - - "github.com/stretchr/testify/require" -) - -func TestIoctlValues(t *testing.T) { - require.Equal(t, iocPinGetfunc, uintptr(3227532550)) - require.Equal(t, iocPinSetfunc, uintptr(1080048903)) - require.Equal(t, ioctlPTPPeroutRequest2, uintptr(0x40383d0c)) - require.Equal(t, iocPinSetfunc2, uintptr(0x40603d10)) - require.Equal(t, unsafe.Sizeof(rawPinDesc{}), uintptr(96)) -} diff --git a/phc/helper_386.go b/phc/helper_386.go deleted file mode 100644 index fdc7fc76..00000000 --- a/phc/helper_386.go +++ /dev/null @@ -1,33 +0,0 @@ -//go:build 386 && !darwin - -/* -Copyright (c) Facebook, Inc. and its affiliates. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package phc - -import ( - "time" - - "golang.org/x/sys/unix" -) - -func timeToTimespec(t time.Time) unix.Timespec { - return unix.Timespec{Sec: int32(t.Unix()), Nsec: int32(t.Nanosecond())} -} - -func ptpClockTimeToTime(t PTPClockTime) time.Time { - return time.Unix(int64(t.Sec), int64(t.NSec)) -} diff --git a/phc/helper_64bit.go b/phc/helper_64bit.go deleted file mode 100644 index 7ead39b9..00000000 --- a/phc/helper_64bit.go +++ /dev/null @@ -1,33 +0,0 @@ -//go:build !386 && !darwin - -/* -Copyright (c) Facebook, Inc. and its affiliates. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package phc - -import ( - "time" - - "golang.org/x/sys/unix" -) - -func timeToTimespec(t time.Time) unix.Timespec { - return unix.Timespec{Sec: t.Unix(), Nsec: int64(t.Nanosecond())} -} - -func ptpClockTimeToTime(t PTPClockTime) time.Time { - return time.Unix(t.Sec, int64(t.NSec)) -} diff --git a/phc/offset.go b/phc/offset.go index eabc9422..c94f216f 100644 --- a/phc/offset.go +++ b/phc/offset.go @@ -18,16 +18,23 @@ package phc import ( "fmt" - "golang.org/x/sys/unix" "os" "time" + + "github.com/facebook/time/phc/unix" // a temporary shim for "golang.org/x/sys/unix" until v0.27.0 is cut ) const ( - // ExtendedNumProbes is the number of samples we request for IOCTL SYS_OFFSET_EXTENDED + // ExtendedNumProbes is the number of samples we request for IoctlPtpSysOffsetExtended ExtendedNumProbes = 9 ) +// PTPSysOffsetExtended wraps unix.PtpSysOffsetExtended to add methods +type PTPSysOffsetExtended unix.PtpSysOffsetExtended + +// PTPSysOffsetPrecise wraps unix.PtpSysOffsetPrecise to add methods +type PTPSysOffsetPrecise unix.PtpSysOffsetPrecise + // SysoffResult is a result of PHC time measurement with related data type SysoffResult struct { Offset time.Duration @@ -37,7 +44,7 @@ type SysoffResult struct { } // based on sysoff_estimate from ptp4l sysoff.c -func sysoffFromExtendedTS(extendedTS [3]PTPClockTime) SysoffResult { +func sysoffFromExtendedTS(extendedTS [3]PtpClockTime) SysoffResult { t1 := extendedTS[0].Time() tp := extendedTS[1].Time() t2 := extendedTS[2].Time() @@ -54,9 +61,9 @@ func sysoffFromExtendedTS(extendedTS [3]PTPClockTime) SysoffResult { // SysoffFromPrecise returns SysoffResult from *PTPSysOffsetPrecise . Code based on sysoff_precise from ptp4l sysoff.c func SysoffFromPrecise(precise *PTPSysOffsetPrecise) SysoffResult { - offset := precise.SysRealTime.Time().Sub(precise.Device.Time()) + offset := precise.Realtime.Time().Sub(precise.Device.Time()) return SysoffResult{ - SysTime: precise.SysRealTime.Time(), + SysTime: precise.Realtime.Time(), PHCTime: precise.Device.Time(), Delay: 0, // They are measured at the same time Offset: offset, @@ -80,9 +87,9 @@ func SysoffEstimateBasic(ts1, rt, ts2 time.Time) SysoffResult { // BestSample finds a sample which took the least time to be read; // the logic is loosely based on sysoff_estimate from ptp4l sysoff.c func (extended *PTPSysOffsetExtended) BestSample() SysoffResult { - best := sysoffFromExtendedTS(extended.TS[0]) - for i := 1; i < int(extended.NSamples); i++ { - sysoff := sysoffFromExtendedTS(extended.TS[i]) + best := sysoffFromExtendedTS(extended.Ts[0]) + for i := 1; i < int(extended.Samples); i++ { + sysoff := sysoffFromExtendedTS(extended.Ts[i]) if sysoff.Delay < best.Delay { best = sysoff } @@ -149,20 +156,20 @@ func (extended *PTPSysOffsetExtended) Sub(a *PTPSysOffsetExtended) time.Duration } // Sub returns the estimated difference between two PHC SYS_OFFSET_PRECISE readings -func (extended *PTPSysOffsetPrecise) Sub(a *PTPSysOffsetPrecise) time.Duration { - return offsetBetweenPreciseReadings(a, extended) +func (precise *PTPSysOffsetPrecise) Sub(a *PTPSysOffsetPrecise) time.Duration { + return offsetBetweenPreciseReadings(a, precise) } // offsetBetweenExtendedReadings returns estimated difference between two PHC SYS_OFFSET_EXTENDED readings func offsetBetweenExtendedReadings(extendedA, extendedB *PTPSysOffsetExtended) time.Duration { // we expect both probes to have same number of measures - numProbes := int(extendedA.NSamples) - if int(extendedB.NSamples) < numProbes { - numProbes = int(extendedB.NSamples) + numProbes := int(extendedA.Samples) + if int(extendedB.Samples) < numProbes { + numProbes = int(extendedB.Samples) } // calculate sys time midpoint from both samples - sysoffA := sysoffFromExtendedTS(extendedA.TS[0]) - sysoffB := sysoffFromExtendedTS(extendedB.TS[0]) + sysoffA := sysoffFromExtendedTS(extendedA.Ts[0]) + sysoffB := sysoffFromExtendedTS(extendedB.Ts[0]) // offset between sys time midpoints sysOffset := sysoffB.SysTime.Sub(sysoffA.SysTime) // compensate difference between PHC time by difference in system time @@ -170,8 +177,8 @@ func offsetBetweenExtendedReadings(extendedA, extendedB *PTPSysOffsetExtended) t shortest := phcOffset // look for smallest difference between system time midpoints for i := 1; i < numProbes; i++ { - sysoffA = sysoffFromExtendedTS(extendedA.TS[i]) - sysoffB = sysoffFromExtendedTS(extendedB.TS[i]) + sysoffA = sysoffFromExtendedTS(extendedA.Ts[i]) + sysoffB = sysoffFromExtendedTS(extendedB.Ts[i]) sysOffset = sysoffB.SysTime.Sub(sysoffA.SysTime) phcOffset = sysoffB.PHCTime.Sub(sysoffA.PHCTime) - sysOffset diff --git a/phc/offset_test.go b/phc/offset_test.go index 0fda3ce4..a095537f 100644 --- a/phc/offset_test.go +++ b/phc/offset_test.go @@ -20,6 +20,7 @@ import ( "testing" "time" + "github.com/facebook/time/phc/unix" // a temporary shim for "golang.org/x/sys/unix" until v0.27.0 is cut "github.com/stretchr/testify/require" ) @@ -39,11 +40,11 @@ func TestSysoffEstimateBasic(t *testing.T) { func TestSysoffBestSample(t *testing.T) { extended := &PTPSysOffsetExtended{ - NSamples: 3, - TS: [ptpMaxSamples][3]PTPClockTime{ - {{Sec: 1667818190, NSec: 552297411}, {Sec: 1667818153, NSec: 552297462}, {Sec: 1667818190, NSec: 552297522}}, - {{Sec: 1667818190, NSec: 552297533}, {Sec: 1667818153, NSec: 552297582}, {Sec: 1667818190, NSec: 552297622}}, - {{Sec: 1667818190, NSec: 552297644}, {Sec: 1667818153, NSec: 552297661}, {Sec: 1667818190, NSec: 552297722}}, + Samples: 3, + Ts: [unix.PTP_MAX_SAMPLES][3]PtpClockTime{ + {{Sec: 1667818190, Nsec: 552297411}, {Sec: 1667818153, Nsec: 552297462}, {Sec: 1667818190, Nsec: 552297522}}, + {{Sec: 1667818190, Nsec: 552297533}, {Sec: 1667818153, Nsec: 552297582}, {Sec: 1667818190, Nsec: 552297622}}, + {{Sec: 1667818190, Nsec: 552297644}, {Sec: 1667818153, Nsec: 552297661}, {Sec: 1667818190, Nsec: 552297722}}, }, } got := extended.BestSample() @@ -57,10 +58,10 @@ func TestSysoffBestSample(t *testing.T) { } func TestSysoffFromExtendedTS(t *testing.T) { - extendedTS := [3]PTPClockTime{ - {Sec: 1667818190, NSec: 552297411}, - {Sec: 1667818153, NSec: 552297462}, - {Sec: 1667818190, NSec: 552297522}, + extendedTS := [3]PtpClockTime{ + {Sec: 1667818190, Nsec: 552297411}, + {Sec: 1667818153, Nsec: 552297462}, + {Sec: 1667818190, Nsec: 552297522}, } sysoff := sysoffFromExtendedTS(extendedTS) want := SysoffResult{ @@ -74,9 +75,9 @@ func TestSysoffFromExtendedTS(t *testing.T) { func TestSysoffFromPrecise(t *testing.T) { preciseTs := &PTPSysOffsetPrecise{ - SysRealTime: PTPClockTime{Sec: 1667818190, NSec: 552297411}, - Device: PTPClockTime{Sec: 1667818153, NSec: 552297462}, - SysMonoRaw: PTPClockTime{Sec: 1667818190, NSec: 552297522}, + Realtime: PtpClockTime{Sec: 1667818190, Nsec: 552297411}, + Device: PtpClockTime{Sec: 1667818153, Nsec: 552297462}, + Monoraw: PtpClockTime{Sec: 1667818190, Nsec: 552297522}, } sysoff := SysoffFromPrecise(preciseTs) want := SysoffResult{ @@ -90,14 +91,14 @@ func TestSysoffFromPrecise(t *testing.T) { func TestOffsetBetweenPreciseReadings(t *testing.T) { preciseA := &PTPSysOffsetPrecise{ - SysRealTime: PTPClockTime{Sec: 1667818190, NSec: 552297411}, - Device: PTPClockTime{Sec: 1667818153, NSec: 552297462}, - SysMonoRaw: PTPClockTime{Sec: 1667818190, NSec: 552297522}, + Realtime: PtpClockTime{Sec: 1667818190, Nsec: 552297411}, + Device: PtpClockTime{Sec: 1667818153, Nsec: 552297462}, + Monoraw: PtpClockTime{Sec: 1667818190, Nsec: 552297522}, } preciseB := &PTPSysOffsetPrecise{ - SysRealTime: PTPClockTime{Sec: 1667818190, NSec: 552297666}, // 255ns later than A - Device: PTPClockTime{Sec: 1667818153, NSec: 552297462}, - SysMonoRaw: PTPClockTime{Sec: 1667818190, NSec: 552297522}, + Realtime: PtpClockTime{Sec: 1667818190, Nsec: 552297666}, // 255ns later than A + Device: PtpClockTime{Sec: 1667818153, Nsec: 552297462}, + Monoraw: PtpClockTime{Sec: 1667818190, Nsec: 552297522}, } offset := preciseB.Sub(preciseA) require.Equal(t, time.Duration(-255), offset) @@ -105,25 +106,25 @@ func TestOffsetBetweenPreciseReadings(t *testing.T) { func TestOffsetBetweenExtendedReadings(t *testing.T) { extendedA := &PTPSysOffsetExtended{ - NSamples: 6, - TS: [ptpMaxSamples][3]PTPClockTime{ - {{Sec: 1667818190, NSec: 552297411}, {Sec: 1667818153, NSec: 552297462}, {Sec: 1667818190, NSec: 552297522}}, - {{Sec: 1667818190, NSec: 552297533}, {Sec: 1667818153, NSec: 552297582}, {Sec: 1667818190, NSec: 552297602}}, - {{Sec: 1667818190, NSec: 552297644}, {Sec: 1667818153, NSec: 552297661}, {Sec: 1667818190, NSec: 552297722}}, - {{Sec: 1667818190, NSec: 552297755}, {Sec: 1667818153, NSec: 552297782}, {Sec: 1667818190, NSec: 552297822}}, - {{Sec: 1667818190, NSec: 552297866}, {Sec: 1667818153, NSec: 552297861}, {Sec: 1667818190, NSec: 552297922}}, - {{Sec: 1667818190, NSec: 552297966}, {Sec: 1667818153, NSec: 552297961}, {Sec: 1667818190, NSec: 552298022}}, + Samples: 6, + Ts: [unix.PTP_MAX_SAMPLES][3]PtpClockTime{ + {{Sec: 1667818190, Nsec: 552297411}, {Sec: 1667818153, Nsec: 552297462}, {Sec: 1667818190, Nsec: 552297522}}, + {{Sec: 1667818190, Nsec: 552297533}, {Sec: 1667818153, Nsec: 552297582}, {Sec: 1667818190, Nsec: 552297602}}, + {{Sec: 1667818190, Nsec: 552297644}, {Sec: 1667818153, Nsec: 552297661}, {Sec: 1667818190, Nsec: 552297722}}, + {{Sec: 1667818190, Nsec: 552297755}, {Sec: 1667818153, Nsec: 552297782}, {Sec: 1667818190, Nsec: 552297822}}, + {{Sec: 1667818190, Nsec: 552297866}, {Sec: 1667818153, Nsec: 552297861}, {Sec: 1667818190, Nsec: 552297922}}, + {{Sec: 1667818190, Nsec: 552297966}, {Sec: 1667818153, Nsec: 552297961}, {Sec: 1667818190, Nsec: 552298022}}, }, } extendedB := &PTPSysOffsetExtended{ - NSamples: 5, - TS: [ptpMaxSamples][3]PTPClockTime{ - {{Sec: 1667818191, NSec: 552298311}, {Sec: 1667818154, NSec: 552297452}, {Sec: 1667818191, NSec: 552298512}}, - {{Sec: 1667818191, NSec: 552298033}, {Sec: 1667818154, NSec: 552297572}, {Sec: 1667818191, NSec: 552298712}}, - {{Sec: 1667818191, NSec: 552299644}, {Sec: 1667818154, NSec: 552297691}, {Sec: 1667818191, NSec: 552308702}}, - {{Sec: 1667818191, NSec: 552300755}, {Sec: 1667818154, NSec: 552297782}, {Sec: 1667818191, NSec: 552309812}}, - {{Sec: 1667818191, NSec: 552301866}, {Sec: 1667818154, NSec: 552297861}, {Sec: 1667818191, NSec: 552328912}}, + Samples: 5, + Ts: [unix.PTP_MAX_SAMPLES][3]PtpClockTime{ + {{Sec: 1667818191, Nsec: 552298311}, {Sec: 1667818154, Nsec: 552297452}, {Sec: 1667818191, Nsec: 552298512}}, + {{Sec: 1667818191, Nsec: 552298033}, {Sec: 1667818154, Nsec: 552297572}, {Sec: 1667818191, Nsec: 552298712}}, + {{Sec: 1667818191, Nsec: 552299644}, {Sec: 1667818154, Nsec: 552297691}, {Sec: 1667818191, Nsec: 552308702}}, + {{Sec: 1667818191, Nsec: 552300755}, {Sec: 1667818154, Nsec: 552297782}, {Sec: 1667818191, Nsec: 552309812}}, + {{Sec: 1667818191, Nsec: 552301866}, {Sec: 1667818154, Nsec: 552297861}, {Sec: 1667818191, Nsec: 552328912}}, }, } offset := extendedB.Sub(extendedA) diff --git a/phc/phc.go b/phc/phc.go index 6728546b..b5e0ff98 100644 --- a/phc/phc.go +++ b/phc/phc.go @@ -19,22 +19,14 @@ package phc import ( "fmt" "os" - "strings" - "syscall" "time" - "unsafe" - "golang.org/x/sys/unix" + "github.com/facebook/time/phc/unix" // a temporary shim for "golang.org/x/sys/unix" until v0.27.0 is cut ) // DefaultMaxClockFreqPPB value came from linuxptp project (clockadj.c) const DefaultMaxClockFreqPPB = 500000.0 -// Time returns PTPClockTime as time.Time -func (t PTPClockTime) Time() time.Time { - return time.Unix(t.Sec, int64(t.NSec)) -} - // TimeMethod is method we use to get time type TimeMethod string @@ -45,56 +37,27 @@ const ( MethodIoctlSysOffsetPrecise TimeMethod = "ioctl_PTP_SYS_OFFSET_PRECISE" ) -// PinFunc type represents the pin function values. -type PinFunc int - -// Type implements cobra.Value -func (pf *PinFunc) Type() string { return "{ PPS-In | PPS-Out | PhySync | None }" } - -// String implements flags.Value -func (pf PinFunc) String() string { - switch pf { - case PinFuncNone: - return "None" - case PinFuncExtTS: - return "PPS-In" // user friendly - case PinFuncPerOut: - return "PPS-Out" // user friendly - case PinFuncPhySync: - return "PhySync" - default: - return fmt.Sprintf("!(PinFunc=%d)", int(pf)) - } -} - -// Set implements flags.Value -func (pf *PinFunc) Set(s string) error { - switch strings.ToLower(s) { - case "none", "-": - *pf = PinFuncNone - case "pps-in", "ppsin", "extts": - *pf = PinFuncExtTS - case "pps-out", "ppsout", "perout": - *pf = PinFuncPerOut - case "phy-sync", "physync", "sync": - *pf = PinFuncPhySync - default: - return fmt.Errorf("use either of: %s", pf.Type()) - } - return nil -} - -// Pin functions corresponding to `enum ptp_pin_function` in linux/ptp_clock.h -const ( - PinFuncNone PinFunc = iota // PTP_PF_NONE - PinFuncExtTS // PTP_PF_EXTTS - PinFuncPerOut // PTP_PF_PEROUT - PinFuncPhySync // PTP_PF_PHYSYNC +type ( + // PtpPeroutRequest is an alias + PtpPeroutRequest = unix.PtpPeroutRequest + // PtpExttsRequest is an alias + PtpExttsRequest = unix.PtpExttsRequest + // PtpExttsEvent is an alias + PtpExttsEvent = unix.PtpExttsEvent + // PtpClockTime is an alias + PtpClockTime = unix.PtpClockTime + // PtpClockCaps is an alias + PtpClockCaps = unix.PtpClockCaps ) // IfaceToPHCDevice returns path to PHC device associated with given network card iface func IfaceToPHCDevice(iface string) (string, error) { - info, err := IfaceInfo(iface) + fd, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0) + if err != nil { + return "", fmt.Errorf("failed to create socket for ioctl: %w", err) + } + defer unix.Close(fd) + info, err := unix.IoctlGetEthtoolTsInfo(fd, iface) if err != nil { return "", fmt.Errorf("getting interface %s info: %w", iface, err) } @@ -126,7 +89,7 @@ func Time(iface string, method TimeMethod) (time.Time, error) { if err != nil { return time.Time{}, err } - latest := extended.TS[extended.NSamples-1] + latest := extended.Ts[extended.Samples-1] return latest[1].Time(), nil case MethodIoctlSysOffsetPrecise: precise, err := dev.ReadSysoffPrecise() @@ -138,159 +101,3 @@ func Time(iface string, method TimeMethod) (time.Time, error) { return time.Time{}, fmt.Errorf("unknown method to get PHC time %q", method) } } - -// Device represents a PHC device -type Device os.File - -// FromFile returns a *Device corresponding to an *os.File -func FromFile(file *os.File) *Device { return (*Device)(file) } - -// File returns the underlying *os.File -func (dev *Device) File() *os.File { return (*os.File)(dev) } - -// Fd returns the underlying file descriptor -func (dev *Device) Fd() uintptr { return dev.File().Fd() } - -// ClockID derives the clock ID from the file descriptor number - see clock_gettime(3), FD_TO_CLOCKID macros -func (dev *Device) ClockID() int32 { return int32((int(^dev.Fd()) << 3) | 3) } - -// Time returns time from the PTP device using the clock_gettime syscall -func (dev *Device) Time() (time.Time, error) { - var ts unix.Timespec - if err := unix.ClockGettime(dev.ClockID(), &ts); err != nil { - return time.Time{}, fmt.Errorf("failed clock_gettime: %w", err) - } - return time.Unix(ts.Unix()), nil -} - -// ioctl makes a unis.SYS_IOCTL unix.Syscall with the given device, request and argument -func (dev *Device) ioctl(req uintptr, arg unsafe.Pointer) (err error) { - _, _, errno := unix.Syscall(unix.SYS_IOCTL, dev.Fd(), req, uintptr(arg)) - if errno != 0 { - err = fmt.Errorf("errno %w during IOCTL %d on FD %s", errno, req, dev.File().Name()) - } - return err -} - -// ReadSysoffExtended reads the precise time from the PHC along with SYS time to measure the call delay. -// The nsamples parameter is set to ExtendedNumProbes. -func (dev *Device) ReadSysoffExtended() (*PTPSysOffsetExtended, error) { - return dev.readSysoffExtended(ExtendedNumProbes) -} - -// ReadSysoffExtended1 reads the precise time from the PHC along with SYS time to measure the call delay. -// The nsamples parameter is set to 1. -func (dev *Device) ReadSysoffExtended1() (*PTPSysOffsetExtended, error) { - return dev.readSysoffExtended(1) -} - -// ReadSysoffPrecise reads the precise time from the PHC along with SYS time to measure the call delay. -func (dev *Device) ReadSysoffPrecise() (*PTPSysOffsetPrecise, error) { - return dev.readSysoffPrecise() -} - -func (dev *Device) readSysoffExtended(nsamples int) (*PTPSysOffsetExtended, error) { - res := &PTPSysOffsetExtended{ - NSamples: uint32(nsamples), - } - err := dev.ioctl(ioctlPTPSysOffsetExtended, unsafe.Pointer(res)) - if err != nil { - return nil, fmt.Errorf("failed PTP_SYS_OFFSET_EXTENDED: %w", err) - } - return res, nil -} - -func (dev *Device) readSysoffPrecise() (*PTPSysOffsetPrecise, error) { - res := &PTPSysOffsetPrecise{} - if err := dev.ioctl(ioctlPTPSysOffsetPrecise, unsafe.Pointer(res)); err != nil { - return nil, fmt.Errorf("failed PTP_SYS_OFFSET_PRECISE: %w", err) - } - return res, nil -} - -// readCaps reads PTP capabilities using ioctl -func (dev *Device) readCaps() (*PTPClockCaps, error) { - caps := &PTPClockCaps{} - if err := dev.ioctl(ioctlPTPClockGetcaps, unsafe.Pointer(caps)); err != nil { - return nil, fmt.Errorf("clock didn't respond properly: %w", err) - } - return caps, nil -} - -// readPinDesc reads a single PTP pin descriptor -func (dev *Device) readPinDesc(index int, desc *PinDesc) error { - var raw rawPinDesc - - raw.Index = uint32(index) - if err := dev.ioctl(iocPinGetfunc, unsafe.Pointer(&raw)); err != nil { - return fmt.Errorf("%s: ioctl(PTP_PIN_GETFUNC) failed: %w", dev.File().Name(), err) - } - desc.Name = unix.ByteSliceToString(raw.Name[:]) - desc.Index = uint(raw.Index) - desc.Func = PinFunc(raw.Func) - desc.Chan = uint(raw.Chan) - desc.dev = dev - return nil -} - -// readPinDesc reads a single PTP pin descriptor -func (dev *Device) setPinFunc(index uint, pf PinFunc, ch uint) error { - var raw rawPinDesc - - raw.Index = uint32(index) //#nosec G115 - raw.Func = uint32(pf) //#nosec G115 - raw.Chan = uint32(ch) //#nosec G115 - if err := dev.ioctl(iocPinSetfunc, unsafe.Pointer(&raw)); err != nil { - return fmt.Errorf("%s: ioctl(PTP_PIN_SETFUNC) failed: %w", dev.File().Name(), err) - } - return nil -} - -// ReadPins reads all PTP pin descriptors -func (dev *Device) ReadPins() ([]PinDesc, error) { - caps, err := dev.readCaps() - if err != nil { - return nil, err - } - npins := int(caps.NPins) - desc := make([]PinDesc, npins) - for i := 0; i < npins; i++ { - if err := dev.readPinDesc(i, &desc[i]); err != nil { - return nil, err - } - } - return desc, nil -} - -// MaxFreqAdjPPB reads max value for frequency adjustments (in PPB) from ptp device -func (dev *Device) MaxFreqAdjPPB() (maxFreq float64, err error) { - caps, err := dev.readCaps() - if err != nil { - return 0, err - } - return caps.maxAdj(), nil -} - -func (dev *Device) setPTPPerout(req PTPPeroutRequest) error { - return dev.ioctl(ioctlPTPPeroutRequest2, unsafe.Pointer(&req)) -} - -func (dev *Device) extTTSRequest(req PTPExtTTSRequest) error { - return dev.ioctl(ioctlExtTTSRequest2, unsafe.Pointer(&req)) -} - -// FreqPPB reads PHC device frequency in PPB (parts per billion) -func (dev *Device) FreqPPB() (freqPPB float64, err error) { return freqPPBFromDevice(dev) } - -// AdjFreq adjusts the PHC clock frequency in PPB -func (dev *Device) AdjFreq(freqPPB float64) error { return clockAdjFreq(dev, freqPPB) } - -// Step steps the PHC clock by given duration -func (dev *Device) Step(step time.Duration) error { return clockStep(dev, step) } - -// SetTime sets the time of the PHC clock -func (dev *Device) SetTime(t time.Time) error { return clockSetTime(dev, t) } - -func (dev *Device) Read(buffer []byte) (int, error) { - return syscall.Read(int(dev.Fd()), buffer) -} diff --git a/phc/phc_test.go b/phc/phc_test.go index 27033119..9b79cfe6 100644 --- a/phc/phc_test.go +++ b/phc/phc_test.go @@ -23,16 +23,19 @@ import ( ) func TestMaxAdjFreq(t *testing.T) { - caps := &PTPClockCaps{ - MaxAdj: 1000000000, + var testcases = []struct { + in int32 + targ float64 + }{ + {in: 1000000000, targ: 1000000000.0}, + {in: 0, targ: 500000.0}, } - got := caps.maxAdj() - require.InEpsilon(t, 1000000000.0, got, 0.00001) - - caps.MaxAdj = 0 - got = caps.maxAdj() - require.InEpsilon(t, 500000.0, got, 0.00001) + for _, tc := range testcases { + caps := &PtpClockCaps{Max_adj: tc.in} + got := maxAdj(caps) + require.InEpsilon(t, tc.targ, got, 0.00001) + } } func TestIfaceToPHCDeviceNotSupported(t *testing.T) { diff --git a/phc/pps_source.go b/phc/pps_source.go index d6fdcf88..800d6307 100644 --- a/phc/pps_source.go +++ b/phc/pps_source.go @@ -26,8 +26,8 @@ import ( "time" "unsafe" + "github.com/facebook/time/phc/unix" // a temporary shim for "golang.org/x/sys/unix" until v0.27.0 is cut "github.com/facebook/time/servo" - "golang.org/x/sys/unix" ) // PPSSource represents a PPS source @@ -78,14 +78,14 @@ type Timestamper interface { // DeviceController defines a subset of functions to interact with a phc device. Enables mocking. type DeviceController interface { Time() (time.Time, error) - setPinFunc(index uint, pf PinFunc, ch uint) error - setPTPPerout(req PTPPeroutRequest) error + setPinFunc(index uint, pf int, ch uint) error + setPTPPerout(req *PtpPeroutRequest) error File() *os.File AdjFreq(freq float64) error Step(offset time.Duration) error Read(buf []byte) (int, error) Fd() uintptr - extTTSRequest(req PTPExtTTSRequest) error + extTTSRequest(req *PtpExttsRequest) error } // PPSPoller represents a device which can be polled for PPS events @@ -111,11 +111,7 @@ type PPSSink struct { // ActivatePPSSource configures the PHC device to be a PPS timestamp source func ActivatePPSSource(dev DeviceController, pinIndex uint) (*PPSSource, error) { - // Initialize the PTPPeroutRequest struct - peroutRequest := PTPPeroutRequest{} - - err := dev.setPinFunc(pinIndex, PinFuncPerOut, defaultTs2PhcChannel) - + err := dev.setPinFunc(pinIndex, unix.PTP_PF_PEROUT, defaultTs2PhcChannel) if err != nil { log.Printf("Failed to set PPS Perout on pin index %d, channel %d, PHC %s. Error: %s. Continuing bravely on...", pinIndex, defaultTs2PhcChannel, dev.File().Name(), err) @@ -126,21 +122,27 @@ func ActivatePPSSource(dev DeviceController, pinIndex uint) (*PPSSource, error) return nil, fmt.Errorf("failed (clock_gettime) on %s", dev.File().Name()) } - // Set the index and period - // nolint:gosec - peroutRequest.Index = uint32(defaultTs2PhcChannel) - peroutRequest.Period = PTPClockTime{Sec: 1, NSec: 0} + // Initialize the PTPPeroutRequest struct + peroutRequest := &PtpPeroutRequest{} + peroutRequest.Index = uint32(defaultTs2PhcChannel) // nolint:gosec + peroutRequest.Period = PtpClockTime{Sec: 1, Nsec: 0} // Set flags and pulse width pulsewidth := defaultPulseWidth // TODO: skip this block if pulsewidth unset once pulsewidth is configurable peroutRequest.Flags |= ptpPeroutDutyCycle - peroutRequest.On = PTPClockTime{Sec: int64(pulsewidth / nsPerSec), NSec: pulsewidth % nsPerSec} + peroutRequest.On = PtpClockTime{ + Sec: int64(pulsewidth / 1e9), + Nsec: pulsewidth % 1e9, + } // Set phase or start time // TODO: reintroduce peroutPhase != -1 condition once peroutPhase is configurable - peroutRequest.StartOrPhase = PTPClockTime{Sec: int64(ts.Second() + ppsStartDelay), NSec: 0} + peroutRequest.StartOrPhase = PtpClockTime{ + Sec: int64(ts.Second() + ppsStartDelay), + Nsec: 0, + } err = dev.setPTPPerout(peroutRequest) @@ -171,7 +173,10 @@ func (ppsSource *PPSSource) Timestamp() (*time.Time, error) { // subtract device perout phase from current time to get the time of the last perout output edge // TODO: optimize section below using binary operations instead of type conversions currTime = currTime.Add(-time.Duration(ppsSource.peroutPhase)) - sourceTs := timeToTimespec(currTime) + sourceTs, err := unix.TimeToTimespec(currTime) + if err != nil { + return nil, err + } sourceTs.Nsec = 0 //nolint:unconvert @@ -249,20 +254,19 @@ func PPSSinkFromDevice(targetDevice DeviceController, pinIndex uint) (*PPSSink, } ppsSink := PPSSink{ InputPin: pinIndex, - Polarity: PTPRisingEdge, + Polarity: unix.PTP_RISING_EDGE, PulseWidth: defaultPulseWidth, Device: targetDevice, CollectedEvents: []*time.Time{}, pollDescriptor: pfd, } - req := PTPExtTTSRequest{ - flags: PTPEnableFeature | ppsSink.Polarity, - //nolint:gosec - index: uint32(ppsSink.InputPin), + req := &PtpExttsRequest{ + Flags: unix.PTP_ENABLE_FEATURE | ppsSink.Polarity, + Index: uint32(ppsSink.InputPin), //nolint:gosec } - err := targetDevice.setPinFunc(pinIndex, PinFuncExtTS, defaultTs2PhcChannel) + err := targetDevice.setPinFunc(pinIndex, unix.PTP_PF_EXTTS, defaultTs2PhcChannel) if err != nil { return nil, fmt.Errorf("error setting extts input pin for device %s: %w", targetDevice.File().Name(), err) } @@ -277,18 +281,18 @@ func PPSSinkFromDevice(targetDevice DeviceController, pinIndex uint) (*PPSSink, // getPPSEventTimestamp reads the first PPS event from the sink file descriptor and returns the timestamp of the event func (ppsSink *PPSSink) getPPSEventTimestamp() (time.Time, error) { - var event PTPExtTTS + var event PtpExttsEvent buf := make([]byte, binary.Size(event)) _, err := ppsSink.Device.Read(buf) if err != nil { return time.Time{}, fmt.Errorf("error reading from sink %v: %w", ppsSink.Device.File().Name(), err) } - event = *(*PTPExtTTS)(unsafe.Pointer(&buf[0])) + event = *(*PtpExttsEvent)(unsafe.Pointer(&buf[0])) // urgh... if uint(event.Index) != ppsSink.InputPin { return time.Time{}, fmt.Errorf("extts on unexpected pin index %d, expected %d", event.Index, ppsSink.InputPin) } - eventTime := ptpClockTimeToTime(event.T) + eventTime := event.T.Time() return eventTime, nil } diff --git a/phc/pps_source_mocks.go b/phc/pps_source_mocks.go index f8264d0d..2e448b51 100644 --- a/phc/pps_source_mocks.go +++ b/phc/pps_source_mocks.go @@ -215,7 +215,7 @@ func (mr *MockDeviceControllerMockRecorder) Time() *gomock.Call { } // extTTSRequest mocks base method. -func (m *MockDeviceController) extTTSRequest(req PTPExtTTSRequest) error { +func (m *MockDeviceController) extTTSRequest(req *PtpExttsRequest) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "extTTSRequest", req) ret0, _ := ret[0].(error) @@ -229,7 +229,7 @@ func (mr *MockDeviceControllerMockRecorder) extTTSRequest(req interface{}) *gomo } // setPTPPerout mocks base method. -func (m *MockDeviceController) setPTPPerout(req PTPPeroutRequest) error { +func (m *MockDeviceController) setPTPPerout(req *PtpPeroutRequest) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "setPTPPerout", req) ret0, _ := ret[0].(error) @@ -243,7 +243,7 @@ func (mr *MockDeviceControllerMockRecorder) setPTPPerout(req interface{}) *gomoc } // setPinFunc mocks base method. -func (m *MockDeviceController) setPinFunc(index uint, pf PinFunc, ch uint) error { +func (m *MockDeviceController) setPinFunc(index uint, pf int, ch uint) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "setPinFunc", index, pf, ch) ret0, _ := ret[0].(error) diff --git a/phc/pps_source_test.go b/phc/pps_source_test.go index 060786ad..7c686730 100644 --- a/phc/pps_source_test.go +++ b/phc/pps_source_test.go @@ -27,9 +27,9 @@ import ( "github.com/facebook/time/hostendian" "github.com/facebook/time/servo" + "github.com/facebook/time/phc/unix" // a temporary shim for "golang.org/x/sys/unix" until v0.27.0 is cut "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" - "golang.org/x/sys/unix" ) type Finisher func() @@ -61,22 +61,22 @@ func TestActivatePPSSource(t *testing.T) { // Prepare _, _, mockDeviceController, finish := SetupMocks(t) defer finish() - var actualPeroutRequest PTPPeroutRequest + var actualPeroutRequest *PtpPeroutRequest gomock.InOrder( // Should set default pin to PPS - mockDeviceController.EXPECT().setPinFunc(uint(4), PinFuncPerOut, uint(0)).Return(nil), + mockDeviceController.EXPECT().setPinFunc(uint(4), unix.PTP_PF_PEROUT, uint(0)).Return(nil), // Should call Time once mockDeviceController.EXPECT().Time().Return(time.Unix(1075896000, 500000000), nil), - mockDeviceController.EXPECT().setPTPPerout(gomock.Any()).Return(nil).Do(func(arg PTPPeroutRequest) { actualPeroutRequest = arg }), + mockDeviceController.EXPECT().setPTPPerout(gomock.Any()).Return(nil).Do(func(arg *PtpPeroutRequest) { actualPeroutRequest = arg }), ) // Should call setPTPPerout with correct parameters - expectedPeroutRequest := PTPPeroutRequest{ + expectedPeroutRequest := &PtpPeroutRequest{ Index: uint32(0), Flags: uint32(2), - StartOrPhase: PTPClockTime{Sec: 2}, - Period: PTPClockTime{Sec: 1}, - On: PTPClockTime{NSec: 500000000}, + StartOrPhase: PtpClockTime{Sec: 2}, + Period: PtpClockTime{Sec: 1}, + On: PtpClockTime{Nsec: 500000000}, } // Act @@ -112,7 +112,7 @@ func TestActivatePPSSourceSetPTPPeroutFailure(t *testing.T) { // Prepare _, _, mockDeviceController, finish := SetupMocks(t) defer finish() - var actualPeroutRequest PTPPeroutRequest + var actualPeroutRequest *PtpPeroutRequest gomock.InOrder( mockDeviceController.EXPECT().setPinFunc(gomock.Any(), gomock.Any(), gomock.Any()).Return(fmt.Errorf("error")), mockDeviceController.EXPECT().File().Return(os.NewFile(3, "mock_file")), @@ -120,14 +120,14 @@ func TestActivatePPSSourceSetPTPPeroutFailure(t *testing.T) { // If first attempt to set PTPPerout fails mockDeviceController.EXPECT().setPTPPerout(gomock.Any()).Return(fmt.Errorf("error")), // Should retry setPTPPerout with backward compatible flag - mockDeviceController.EXPECT().setPTPPerout(gomock.Any()).Return(nil).Do(func(arg PTPPeroutRequest) { actualPeroutRequest = arg }), + mockDeviceController.EXPECT().setPTPPerout(gomock.Any()).Return(nil).Do(func(arg *PtpPeroutRequest) { actualPeroutRequest = arg }), ) - expectedPeroutRequest := PTPPeroutRequest{ + expectedPeroutRequest := &PtpPeroutRequest{ Index: uint32(0), Flags: uint32(0x0), - StartOrPhase: PTPClockTime{Sec: 2}, - Period: PTPClockTime{Sec: 1}, - On: PTPClockTime{NSec: 500000000}, + StartOrPhase: PtpClockTime{Sec: 2}, + Period: PtpClockTime{Sec: 1}, + On: PtpClockTime{Nsec: 500000000}, } // Act @@ -220,7 +220,8 @@ func TestGetPPSTimestampUnphased(t *testing.T) { func TestTimeToTimespec(t *testing.T) { someTime := time.Unix(1075896000, 500000000) - result := timeToTimespec(someTime) + result, err := unix.TimeToTimespec(someTime) + require.NoError(t, err, "TimeToTimespec") require.Equal(t, result, unix.Timespec{Sec: 1075896000, Nsec: 500000000}) } @@ -544,7 +545,7 @@ func TestPPSSink_getPPSEventTimestamp(t *testing.T) { // Test cases t.Run("successful read", func(t *testing.T) { // Prepare - event := PTPExtTTS{Index: 1, T: PTPClockTime{Sec: 1}} + event := PtpExttsEvent{Index: 1, T: PtpClockTime{Sec: 1}} mockDevice.EXPECT().Read(gomock.Any()).Return(1, nil).Do(func(buf []byte) { var intBuffer bytes.Buffer @@ -577,7 +578,7 @@ func TestPPSSink_getPPSEventTimestamp(t *testing.T) { t.Run("unexpected channel", func(t *testing.T) { // Prepare - event := PTPExtTTS{Index: 2, T: PTPClockTime{Sec: 1}} + event := PtpExttsEvent{Index: 2, T: PtpClockTime{Sec: 1}} mockDevice.EXPECT().Read(gomock.Any()).Return(1, nil).Do(func(buf []byte) { var intBuffer bytes.Buffer diff --git a/phc/unix/linux.go b/phc/unix/linux.go index 696cc0ff..ecc56315 100644 --- a/phc/unix/linux.go +++ b/phc/unix/linux.go @@ -261,6 +261,34 @@ func IoctlPtpExttsRequest(fd int, r *PtpExttsRequest) error { return ioctlPtr(fd, PTP_EXTTS_REQUEST2, unsafe.Pointer(r)) } +// https://go-review.googlesource.com/c/sys/+/621735 + +const ( + PTP_PF_NONE = iota //nolint:revive + PTP_PF_EXTTS //nolint:revive + PTP_PF_PEROUT //nolint:revive + PTP_PF_PHYSYNC //nolint:revive +) + +// https://go-review.googlesource.com/c/sys/+/621498 + +// TimeToPtpClockTime returns t as PtpClockTime +func TimeToPtpClockTime(t time.Time) PtpClockTime { + sec := t.Unix() + nsec := uint32(t.Nanosecond()) + return PtpClockTime{Sec: sec, Nsec: nsec} +} + +// Time returns PTPClockTime as time.Time +func (t *PtpClockTime) Time() time.Time { + return time.Unix(t.Sec, int64(t.Nsec)) +} + +// Unix returns the time stored in t as seconds plus nanoseconds. +func (t *PtpClockTime) Unix() (sec int64, nsec int64) { + return t.Sec, int64(t.Nsec) +} + // bridging to upstream type Cmsghdr = unix.Cmsghdr @@ -277,6 +305,7 @@ type Utsname = unix.Utsname func ByteSliceToString(b []byte) string { return unix.ByteSliceToString(b) } func ClockAdjtime(c int32, t *Timex) (int, error) { return unix.ClockAdjtime(c, t) } +func ClockGettime(c int32, t *Timespec) error { return unix.ClockGettime(c, t) } func Close(fd int) (err error) { return unix.Close(fd) } func ErrnoName(e syscall.Errno) string { return unix.ErrnoName(e) } func Poll(f []PollFd, t int) (int, error) { return unix.Poll(f, t) } @@ -299,9 +328,11 @@ const ( IFNAMSIZ = unix.IFNAMSIZ //nolint:revive MSG_ERRQUEUE = unix.MSG_ERRQUEUE //nolint:revive POLLERR = unix.POLLERR //nolint:revive - SIOCETHTOOL = unix.SIOCETHTOOL //nolint:revive - SIOCGHWTSTAMP = unix.SIOCGHWTSTAMP //nolint:revive - SIOCSHWTSTAMP = unix.SIOCSHWTSTAMP //nolint:revive + POLLIN = unix.POLLIN + POLLPRI = unix.POLLPRI + SIOCETHTOOL = unix.SIOCETHTOOL //nolint:revive + SIOCGHWTSTAMP = unix.SIOCGHWTSTAMP //nolint:revive + SIOCSHWTSTAMP = unix.SIOCSHWTSTAMP //nolint:revive SizeofPtr = unix.SizeofPtr SizeofSockaddrInet4 = unix.SizeofSockaddrInet4 SOCK_DGRAM = unix.SOCK_DGRAM //nolint:revive