Skip to content

Commit

Permalink
Implement GetPPSTimestamp (#396)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: #396

WHAT?

Implement function to get time from activated PPS source

WHY?

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

Considerations:

  - Why so many tests for seemingly trivial things?
    - Type conversions, state logic and magic math is very error prone
  - Why no helper_darwin?
    - phc package has a hard dependency on sys/unix

Differential Revision: D63091044
  • Loading branch information
crmdias authored and facebook-github-bot committed Sep 24, 2024
1 parent 4d61e17 commit 6d400a5
Show file tree
Hide file tree
Showing 5 changed files with 344 additions and 75 deletions.
108 changes: 108 additions & 0 deletions phc/devicecontroller_mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 29 additions & 0 deletions phc/helper_386.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//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(time time.Time) (ts unix.Timespec) {
return unix.Timespec{Sec: int32(time.Unix()), Nsec: int32(time.Nanosecond())}
}
29 changes: 29 additions & 0 deletions phc/helper_64bit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//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(time time.Time) (ts unix.Timespec) {
return unix.Timespec{Sec: time.Unix(), Nsec: int64(time.Nanosecond())}
}
67 changes: 63 additions & 4 deletions phc/phc.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,23 @@ func Time(iface string, method TimeMethod) (time.Time, error) {
}
}

// PPSSource represents a PPS source
type PPSSource struct {
PHCDevice DeviceController
state PPSSourceState
peroutPhase int
}

// PPSSourceState represents the state of a PPS source
type PPSSourceState int

const (
// UnknownStatus is the initial state of a PPS source, which means PPS may or may not be configured
UnknownStatus PPSSourceState = iota
// PPSSet means the underlying device is activated as a PPS source
PPSSet
)

// Device represents a PHC device
type Device os.File

Expand Down Expand Up @@ -306,7 +323,7 @@ func (dev *Device) AdjFreq(freqPPB float64) error { return clockAdjFreq(dev, fre
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 DeviceController) error {
func ActivatePPSSource(dev DeviceController) (*PPSSource, error) {
// Initialize the PTPPeroutRequest struct
peroutRequest := PTPPeroutRequest{}

Expand All @@ -319,7 +336,7 @@ func ActivatePPSSource(dev DeviceController) error {

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

// Set the index and period
Expand All @@ -343,8 +360,50 @@ func ActivatePPSSource(dev DeviceController) error {
log.Debugf("retrying PTP_PEROUT_REQUEST2 with DUTY_CYCLE flag unset for backwards compatibility")
peroutRequest.Flags &^= ptpPeroutDutyCycle
err = dev.setPTPPerout(peroutRequest)
return err

if err != nil {
return nil, err
}
}

return nil
return &PPSSource{PHCDevice: dev, state: PPSSet}, nil
}

// Timestamp returns the timestamp of the last PPS output edge from the given PPS source
// A Pointer is returned to avoid additional memory allocation
func (ppsSource *PPSSource) Timestamp() (*time.Time, error) {
if ppsSource.state != PPSSet {
return nil, fmt.Errorf("PPS source not set")
}

currTime, err := ppsSource.PHCDevice.Time()

if err != nil {
return nil, fmt.Errorf("error getting time (clock_gettime) on %s", ppsSource.PHCDevice.File().Name())
}

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

/*
* As long as the kernel doesn't support a proper API for reporting
* back a precise perout timestamp, we have to assume that the current
* time on the PPS source is still within +/- half a second of the last
* perout output edge, and hence, we can deduce the current second
* (nanossecond is omitted) of this edge at the emitter based on the
* emitter's current time. We support only PHC sources, so we can ignore
* the NMEA source edge case described in ts2phc.c
*/
//nolint:govet
if int64(sourceTs.Nsec) > int64(nsPerSec/2) {

Check failure on line 400 in phc/phc.go

View workflow job for this annotation

GitHub Actions / lint

unnecessary conversion (unconvert)
sourceTs.Sec++
sourceTs.Nsec = 0
}
//nolint:govet
currTime = time.Unix(int64(sourceTs.Sec), int64(sourceTs.Nsec))

Check failure on line 405 in phc/phc.go

View workflow job for this annotation

GitHub Actions / lint

unnecessary conversion (unconvert)
currTime = currTime.Add(time.Duration(ppsSource.peroutPhase))

return &currTime, nil
}
Loading

0 comments on commit 6d400a5

Please sign in to comment.