Skip to content

Commit

Permalink
Implement ts2phc (#399)
Browse files Browse the repository at this point in the history
Summary:

WHAT?

Implements minimal version of ts2phc command

WHY?

Remove dependency on linuxptp

CONSIDERATIONS:

 - Why not test getPPSSourceFromPath and getPHCDeviceFromName?

   - Trivial implementations, mostly os related, testing seems counterproductive

  - Why not test PPSSinkFromDevice and pollPPSSink ?
    - Can be done in the future, would not add much at the moment.

Differential Revision: D63384165
  • Loading branch information
crmdias authored and facebook-github-bot committed Oct 3, 2024
1 parent dfddf10 commit 930eefa
Show file tree
Hide file tree
Showing 9 changed files with 759 additions and 71 deletions.
119 changes: 119 additions & 0 deletions cmd/ptpcheck/cmd/ts2phc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
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 cmd

import (
"fmt"
"os"
"time"

"github.com/facebook/time/phc"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)

var (
srcDeviceTS2PHCFlag string
dstDeviceTS2PHCFlag string
intervalTS2PHCFlag time.Duration
firstStepTS2PHCFlag time.Duration
srcPinTS2PHCFlag uint
)

func init() {
RootCmd.AddCommand(ts2phcCmd)
ts2phcCmd.Flags().StringVarP(&srcDeviceTS2PHCFlag, "source", "s", "/dev/ptp_tcard", "Source for Time of Day (ToD) data. Only PHC devices are supported at the moment")
ts2phcCmd.Flags().StringVarP(&dstDeviceTS2PHCFlag, "destination", "d", "eth0", "PHC to be synchronized. The clock may be identified by its character device (like /dev/ptp0) or its associated network interface (like eth0).")
ts2phcCmd.Flags().DurationVarP(&intervalTS2PHCFlag, "interval", "i", time.Second, "Interval between syncs in nanosseconds")
ts2phcCmd.Flags().DurationVarP(&firstStepTS2PHCFlag, "first_step", "f", 20*time.Microsecond, "The maximum offset, specified in seconds, that the servo will correct by changing the clock frequency instead of stepping the clock. This is only applied on the first update. When set to 0.0, the servo will not step the clock on start. The default is 0.00002 (20 microseconds).")
ts2phcCmd.Flags().UintVarP(&srcPinTS2PHCFlag, "pin", "p", phc.DefaultTs2PhcIndex, "output pin number of the PPS signal on source device. Defaults to Pin 3")
}

func ts2phcRun(srcDevicePath string, dstDeviceName string, interval time.Duration, stepth time.Duration, srcPinIndex uint) error {
ppsSource, err := getPPSSourceFromPath(srcDevicePath, srcPinIndex)
if err != nil {
return fmt.Errorf("error opening source phc device: %w", err)
}
dstDevice, err := phcDeviceFromName(dstDeviceName)
if err != nil {
return fmt.Errorf("error opening target phc device: %w", err)
}
ppsSink, err := phc.PPSSinkFromDevice(dstDevice)
if err != nil {
return fmt.Errorf("error setting target device as PPS sink: %w", err)
}
pi, err := phc.NewPiServo(interval, stepth, dstDevice)
if err != nil {
return fmt.Errorf("error getting servo: %w", err)
}
ticker := time.NewTicker(interval)

for range ticker.C {
eventTime, err := phc.PollLatestPPSEvent(ppsSink)
if err != nil {
log.Errorf("Error polling PPS Sink: %v", err)
continue
} else if eventTime.IsZero() {
continue
}
log.Debugf("PPS event at %+v", eventTime.UnixNano())
if err := phc.PPSClockSync(pi, ppsSource, eventTime, dstDevice); err != nil {
log.Errorf("Error syncing PHC: %v", err)
}
}
return nil
}

func getPPSSourceFromPath(srcDevicePath string, pinIndex uint) (*phc.PPSSource, error) {
srcDeviceFile, err := os.Open(srcDevicePath)
if err != nil {
return nil, fmt.Errorf("error opening source device: %w", err)
}
ppsSource, err := phc.ActivatePPSSource(phc.FromFile(srcDeviceFile), pinIndex)
if err != nil {
return nil, fmt.Errorf("error activating PPS Source for PHC: %w", err)
}

return ppsSource, nil
}

// phcDeviceFromName returns a PHC device from a device name, which can be either an interface name or a PHC device path
func phcDeviceFromName(dstDeviceName string) (*phc.Device, error) {
devicePath, err := phc.IfaceToPHCDevice(dstDeviceName)
if err != nil {
log.Infof("Provided device name is not an interface, assuming it is a PHC device path")
devicePath = dstDeviceName
}
// need RW permissions to issue CLOCK_ADJTIME on the device
dstFile, err := os.OpenFile(devicePath, os.O_RDWR, 0)
if err != nil {
return nil, fmt.Errorf("error opening destination device: %w", err)
}
dstDevice := phc.FromFile(dstFile)
return dstDevice, nil
}

var ts2phcCmd = &cobra.Command{
Use: "ts2phc",
Short: "Sync PHC with external timestamps",
Run: func(_ *cobra.Command, _ []string) {
ConfigureVerbosity()
if err := ts2phcRun(srcDeviceTS2PHCFlag, dstDeviceTS2PHCFlag, intervalTS2PHCFlag, firstStepTS2PHCFlag, srcPinTS2PHCFlag); err != nil {
log.Fatal(err)
}
},
}
27 changes: 27 additions & 0 deletions phc/device.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ var iocPinSetfunc2 = ioctl.IOW(ptpClkMagic, 16, unsafe.Sizeof(rawPinDesc{}))
// ioctlPTPPeroutRequest2 is an IOCTL req corresponding to PTP_PEROUT_REQUEST2 in linux/ptp_clock.h
var ioctlPTPPeroutRequest2 = ioctl.IOW(ptpClkMagic, 12, unsafe.Sizeof(PTPPeroutRequest{}))

// ioctlExtTTSRequest2 is an IOCTL req corresponding to PTP_EXTTS_REQUEST2 in linux/ptp_clock.h
var ioctlExtTTSRequest2 = ioctl.IOW(ptpClkMagic, 11, unsafe.Sizeof(PTPExtTTSRequest{}))

// Ifreq is the request we send with SIOCETHTOOL IOCTL
// as per Linux kernel's include/uapi/linux/if.h
type Ifreq struct {
Expand Down Expand Up @@ -136,6 +139,30 @@ type PTPPeroutRequest struct {
On PTPClockTime // "On" time of the signal. Must be lower than the period. Valid only if (flags & PTP_PEROUT_DUTY_CYCLE) is set.
}

// 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
}

// 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. */
}

// PTPClockTime as defined in linux/ptp_clock.h
type PTPClockTime struct {
Sec int64 /* seconds */
Expand Down
8 changes: 6 additions & 2 deletions phc/helper_386.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ import (
"golang.org/x/sys/unix"
)

func timeToTimespec(time time.Time) (ts unix.Timespec) {
return unix.Timespec{Sec: int32(time.Unix()), Nsec: int32(time.Nanosecond())}
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))
}
8 changes: 6 additions & 2 deletions phc/helper_64bit.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ import (
"golang.org/x/sys/unix"
)

func timeToTimespec(time time.Time) (ts unix.Timespec) {
return unix.Timespec{Sec: time.Unix(), Nsec: int64(time.Nanosecond())}
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))
}
9 changes: 9 additions & 0 deletions phc/phc.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"fmt"
"os"
"strings"
"syscall"
"time"
"unsafe"

Expand Down Expand Up @@ -274,6 +275,10 @@ 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) }

Expand All @@ -282,3 +287,7 @@ func (dev *Device) AdjFreq(freqPPB float64) error { return clockAdjFreq(dev, fre

// Step steps the PHC clock by given duration
func (dev *Device) Step(step time.Duration) error { return clockStep(dev, step) }

func (dev *Device) Read(buffer []byte) (int, error) {
return syscall.Read(int(dev.Fd()), buffer)
}
Loading

0 comments on commit 930eefa

Please sign in to comment.