Skip to content

Commit

Permalink
time: switch to use upstreamed unix.Ptp… (facebook#416)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
yarikk authored and facebook-github-bot committed Oct 22, 2024
1 parent b6e94e1 commit 6496b5d
Show file tree
Hide file tree
Showing 13 changed files with 325 additions and 554 deletions.
74 changes: 64 additions & 10 deletions cmd/ptpcheck/cmd/pins.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,17 @@ 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"
)

// flags
var devPath string
var pinName string
var pinFunc phc.PinFunc
var pinFunc PinFunc
var setMode bool

func init() {
Expand Down Expand Up @@ -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
}
217 changes: 89 additions & 128 deletions phc/device.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
32 changes: 0 additions & 32 deletions phc/device_test.go

This file was deleted.

Loading

0 comments on commit 6496b5d

Please sign in to comment.