Skip to content

Commit

Permalink
Adds PPS Source Activate function (#394)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: #394

WHAT?

implements ts2phc_phc_pps_source_activate from ts2phc_phc_pps_source.c

WHY?

Remove dependency on linuxptp by rewriting ts2phc.c (T196935923)

Considerations:

Why no tests?
  - Tests are added in a seperate diff D62447639

Why are index, channel, etc... not arguments of ActivatePPSSource?
  - They will ilkely become so in a future diff, when this is used as a part of the entire ts2phc flow.

Reviewed By: leoleovich

Differential Revision: D62022960

fbshipit-source-id: 9cc5fc6d169c882982f9c3b67f8af532328f2d08
  • Loading branch information
crmdias authored and facebook-github-bot committed Sep 12, 2024
1 parent 218b63a commit dd62cd4
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 14 deletions.
29 changes: 29 additions & 0 deletions phc/device.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
const (
ptpMaxSamples = 25
ptpClkMagic = '='
nsPerSec = int32(1000000000)
)

// ioctlPTPSysOffsetExtended is an IOCTL to get extended offset
Expand All @@ -46,6 +47,12 @@ var iocPinGetfunc = ioctl.IOWR(ptpClkMagic, 6, unsafe.Sizeof(rawPinDesc{}))
// iocPinSetfunc is an IOCTL req corresponding to PTP_PIN_SETFUNC in linux/ptp_clock.h
var iocPinSetfunc = ioctl.IOW(ptpClkMagic, 7, unsafe.Sizeof(rawPinDesc{}))

// iocPinSetfunc is an IOCTL req corresponding to PTP_PIN_SETFUNC2 in linux/ptp_clock.h
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{}))

// 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 @@ -114,6 +121,28 @@ type rawPinDesc struct {
Rsv [5]uint32 // Reserved for future use.
}

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

// PTPClockTime as defined in linux/ptp_clock.h
type PTPClockTime struct {
Sec int64 /* seconds */
NSec uint32 /* nanoseconds */
Reserved uint32
}

// PTPClockCaps as defined in linux/ptp_clock.h
type PTPClockCaps struct {
MaxAdj int32 /* Maximum frequency adjustment in parts per billon. */
Expand Down
2 changes: 2 additions & 0 deletions phc/device_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,7 @@ func TestDeviceFromIfaceNotFound(t *testing.T) {
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))
}
83 changes: 69 additions & 14 deletions phc/phc.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,25 @@ import (
"time"
"unsafe"

log "github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
)

// DefaultMaxClockFreqPPB value came from linuxptp project (clockadj.c)
const DefaultMaxClockFreqPPB = 500000.0

// PTPClockTime as defined in linux/ptp_clock.h
type PTPClockTime struct {
Sec int64 /* seconds */
NSec uint32 /* nanoseconds */
Reserved uint32
}
// PPS related constants
const (
ptpPeroutDutyCycle = (1 << 1)
ptpPeroutPhase = (1 << 2)
defaultTs2PhcChannel = uint32(0)
defaultTs2PhcIndex = uint32(0)
defaultPulseWidth = int32(500000000)
// should default to 0 if config specified. Otherwise -1 (ignore phase)
defaultPeroutPhase = int32(-1) //nolint:all
// ppsStartDelay is the delay in seconds before the first PPS signal is sent
ppsStartDelay = 2
)

// Time returns PTPClockTime as time.Time
func (t PTPClockTime) Time() time.Time {
Expand Down Expand Up @@ -169,11 +176,11 @@ func (dev *Device) Time() (time.Time, error) {
return time.Unix(ts.Unix()), nil
}

// similar to unix.ioctlPtr()
func (dev *Device) ioctlPtr(req uintptr, arg unsafe.Pointer) (err error) {
// 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 = errno // translate here if needed
err = fmt.Errorf("errno %w during IOCTL %d on FD %s", errno, req, dev.File().Name())
}
return err
}
Expand All @@ -199,7 +206,7 @@ func (dev *Device) readSysoffExtended(nsamples int) (*PTPSysOffsetExtended, erro
res := &PTPSysOffsetExtended{
NSamples: uint32(nsamples),
}
err := dev.ioctlPtr(ioctlPTPSysOffsetExtended, unsafe.Pointer(res))
err := dev.ioctl(ioctlPTPSysOffsetExtended, unsafe.Pointer(res))
if err != nil {
return nil, fmt.Errorf("failed PTP_SYS_OFFSET_EXTENDED: %w", err)
}
Expand All @@ -208,7 +215,7 @@ func (dev *Device) readSysoffExtended(nsamples int) (*PTPSysOffsetExtended, erro

func (dev *Device) readSysoffPrecise() (*PTPSysOffsetPrecise, error) {
res := &PTPSysOffsetPrecise{}
if err := dev.ioctlPtr(ioctlPTPSysOffsetPrecise, unsafe.Pointer(res)); err != nil {
if err := dev.ioctl(ioctlPTPSysOffsetPrecise, unsafe.Pointer(res)); err != nil {
return nil, fmt.Errorf("failed PTP_SYS_OFFSET_PRECISE: %w", err)
}
return res, nil
Expand All @@ -217,7 +224,7 @@ func (dev *Device) readSysoffPrecise() (*PTPSysOffsetPrecise, error) {
// readCaps reads PTP capabilities using ioctl
func (dev *Device) readCaps() (*PTPClockCaps, error) {
caps := &PTPClockCaps{}
if err := dev.ioctlPtr(ioctlPTPClockGetcaps, unsafe.Pointer(caps)); err != nil {
if err := dev.ioctl(ioctlPTPClockGetcaps, unsafe.Pointer(caps)); err != nil {
return nil, fmt.Errorf("clock didn't respond properly: %w", err)
}
return caps, nil
Expand All @@ -228,7 +235,7 @@ func (dev *Device) readPinDesc(index int, desc *PinDesc) error {
var raw rawPinDesc

raw.Index = uint32(index)
if err := dev.ioctlPtr(iocPinGetfunc, unsafe.Pointer(&raw)); err != nil {
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[:])
Expand All @@ -246,7 +253,7 @@ func (dev *Device) setPinFunc(index uint, pf PinFunc, ch uint) error {
raw.Index = uint32(index) //#nosec G115
raw.Func = uint32(pf) //#nosec G115
raw.Chan = uint32(ch) //#nosec G115
if err := dev.ioctlPtr(iocPinSetfunc, unsafe.Pointer(&raw)); err != nil {
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
Expand Down Expand Up @@ -285,3 +292,51 @@ 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) }

// ActivatePPSSource configures the PHC device to be a PPS timestamp source
func ActivatePPSSource(dev Device) error {
// Initialize the PTPPeroutRequest struct
peroutRequest := PTPPeroutRequest{}

err := dev.ioctl(iocPinSetfunc2, unsafe.Pointer(&rawPinDesc{
Index: defaultTs2PhcIndex,
Chan: defaultTs2PhcChannel,
Func: uint32(PinFuncPerOut),
}))

if err != nil {
log.Errorf("Failed to set PPS Perout on pin index %d, channel %d, PHC %s. Error: %s. Continuing bravely on...",
defaultTs2PhcIndex, defaultTs2PhcChannel, dev.File().Name(), err)
}

ts, err := dev.Time()
if err != nil {
return fmt.Errorf("failed (clock_gettime) on %s", dev.File().Name())
}

// Set the index and period
peroutRequest.Index = defaultTs2PhcIndex
peroutRequest.Period = PTPClockTime{Sec: 1, NSec: 0}

// Set flags and pulse width
pulsewidth := defaultPulseWidth

// TODO: skip this block if pulsewidth > 0 once pulsewidth is configurable
peroutRequest.Flags |= ptpPeroutDutyCycle
peroutRequest.On = PTPClockTime{Sec: int64(pulsewidth / nsPerSec), NSec: uint32(pulsewidth % nsPerSec)}

// Set phase or start time
// TODO: reintroduce peroutPhase != -1 condition once peroutPhase is configurable
peroutRequest.StartOrPhase = PTPClockTime{Sec: int64(ts.Second() + ppsStartDelay), NSec: 0}

err = dev.ioctl(ioctlPTPPeroutRequest2, unsafe.Pointer(&peroutRequest))

if err != nil {
log.Debugf("retrying PTP_PEROUT_REQUEST2 with DUTY_CYCLE flag unset for backwards compatibility")
peroutRequest.Flags &^= ptpPeroutDutyCycle
err = dev.ioctl(ioctlPTPPeroutRequest2, unsafe.Pointer(&peroutRequest))
return err
}

return nil
}

0 comments on commit dd62cd4

Please sign in to comment.